Override Edit button action

Hi @vsposato5370,

Above logic is working great for Edit Action but can I implement same on Create Button Action?
In this case I want to make “Type” field as dropdown and while selecting any record from “Type” field, “Order” field should automatic Increase in numbers(Unique Service).

Capture

@saurabh60292,
I don’t think I understand what you are trying to accomplish. The screen you show looks like an EditScreen which means the CreateButton wouldn’t exist in that context.

 To answer your question, yes you can modify the CreateAction the same way you did with the EditAction with either BeforeActionPerformHandler or AfterActionPerformHandler. It just depends on where the logic you want to happen needs to occur in the Lifecycle.

Vincent

@vsposato5370,

sorry If I did not make it clear understanding.

Actually this (below) is my browse screen:

Capture1

and this is my edit screen:

Capture

And I want the “Type” field in Edit screen to look like dropdown which should contain “Type” record from the browse screen.

And after selecting any record from “Type” field in Edit screen, The “Order” field should auto increment.

@saurabh60292,
So is the Type field an Entity? Normally, if this is an association or composition then the field would automatically become the dropdown. I don’t know if your project is proprietary or not, but it might help if I can see the bigger picture with an example project or the real project - either one. I just don’t seem to be grasping what you are attempting to do.

Vincent

no its not an Entity, as you can see in the below image from the Data Model, “Type” field is of string type.
Capture

Even I tried it to change into Association but I got an error , see below:

error2

But I got this error.

error1

I dont Know how can I make this “type” field as dropdown in a proper manner.

Below are Datasource from browse and Edit screen:

BROWSE DATASOURCE:
browseDs

EDIT DATASOURCE:
EditDs

What I would suggest, is make an Entity named something like UndInsType and have it just have an ID and a String name. You then associate it to that spot in the CorpRTSUndIns entity and allow it to lookup and open in the field definition in the parent. You can pre-populate this entity with the majority of the options you expect, but if someone needs to add one then they do the magnifying glass and Create on the lookup screen. This ensures that similar things are identified as such, and it will prevent typos and the like from messing anything up.

The other option you have, and I don’t know that I would recommend it, would be to build a method that pulls a listing of distinct Types that have been used before and make that the optionsDatasource for the Type field and manually configure that field as a SuggestionField (or whichever of those fields makes the most sense) with the result saved to the underlying entity type. There are definitely some pitfalls here, and I have not done this so I would have to let the larger CUBA folks help with this path.

I think those are the 2 options for that. As far as incrementing the Order when they select a Type, that should happen on save - so you could use a PreCommit handler in the Controller of your edit screen to increment the value if necessary but only on Commit of a new entity.

Hope this helps!

Thank You for your suggestion. Lookup field issue is ok now.But how can I make listener so that it can change the “Order” field auto increment? Below previous code I used in edit screen if you remember. I think this code need to be change in order to make listener.

@Named("corpRTSUndInsesTable.edit")
private EditAction corpRTSUndInsesEdit;

@Override
public void init(Map<String, Object> params) {
        this.corpRTSUndInsesEdit.setBeforeActionPerformedHandler(() -> {
            CorpRTSUndIns grsIndusIns = corpRTSUndInsesTable.getSingleSelected();

            if (grsIndusIns != null) {
                CorpRTSUndIns newGrsIndusIns = metadata.create(CorpRTSUndIns.class);
                newGrsIndusIns.setOrder((int) unService.getNextNumber(grsIndusIns.getType()));

                openEditor(newGrsIndusIns, WindowManager.OpenType.THIS_TAB).addCloseWithCommitListener(() -> corpRTSUndInsesDs.refresh());
            } else {
                showNotification("Please select a row to update", NotificationType.HUMANIZED);
                
            }
             return false; // This makes the action not continue
        });
}

When should the Order number be incremented? Do you want it to increment at the start or at the end of the process? If you do it at the start, then you have decrement the value if you don’t end up saving. Where would it make sense for your application?

Whenever the user pick any value from the lookupfield (“Type Field”), the Order number should automatically generate. Also in case if user dont save the record it should remain constant and do not increment.

So, I think the easiest way to do this - and again I am not 100% sure I understand the full use case - would be to add a value change listener to the LookupField. You can increment the order on that listener event. Here is a stab at it…

@Named("fieldGroup.type") // This is the name of the FieldGroup that holds your fields and the id of the underlying field.
protected LookupField typeLookupField;

@Override
public void init(Map<String, Object> params) {
      this.typeLookupField..addValueChangeListener(e -> {
           CorpRTSUndIns grsIndusIns = getItem();
           grsIndusIns.setOrder((int) unService.getNextNumber(grsIndusIns.getType()));
      });
}

You will probably still need to do the preClose listener to decrement the service if it is canceled. I hope this helps.

1 Like

Perfect, Your suggestions are really working for me :slight_smile:
Now I am able to auto generate everything perfectly with below code:

@Inject
private UniqueNumbersService unService;

@Named("fieldGroup.typeId") 
protected WebPickerField typeIdLookupField;

@Override
public void init(Map<String, Object> params) {
      this.typeIdLookupField.addValueChangeListener(e -> {
           CorpRTSUndIns corpRTSUndIns = getItem();
           corpRTSUndIns.setType(corpRTSUndIns.getTypeId().getRestictionType());
           corpRTSUndIns.setOrder((int) unService.getNextNumber(corpRTSUndIns.getTypeId().getRestictionType().replaceAll(" ", "").replaceAll("/", "")));
      });
}

As you can see in below screenshot, I am generating two fields based on “TypeId” field which is my WebPickerField.

RestType

Now only challenge is how to preClose listener to decrement the service if it is canceled.
I have seen this topic Entity Listener but couldn’t reach on any conclusion how to do it. Sorry for loose my hand on java.

Thank you again!

Regards,
Saurabh

I actually provided this code in my earlier responses.

@Override
    protected boolean preClose(String actionId)
    {
        if ("windowClose".equalsIgnoreCase(actionId))
        {
            unService.setCurrentNumber = unService.getCurrentNumber - 1;
        }

        return Boolean.TRUE;
    }

This goes into your EditController. It’s not a listener - it’s a lifecycle event that every screen goes through. You want to check that the window is not closing as part of a commit but rather is a straight close - which is what the If statement does. You return true so that the close continues.

so where this code fits in my controller. I tried below one but its saying

setCurrentNumber cannot be resolved or is not a field

code:

	@Inject
	private UniqueNumbersService unService;
	
	@Named("fieldGroup.typeId") 
	protected WebPickerField typeIdLookupField;

	@Override
	public void init(Map<String, Object> params) {
	      this.typeIdLookupField.addValueChangeListener(e -> {
	           CorpRTSUndIns corpRTSUndIns = getItem();
	           corpRTSUndIns.setType(corpRTSUndIns.getTypeId().getRestictionType());
	           corpRTSUndIns.setOrder((int) unService.getNextNumber(corpRTSUndIns.getTypeId().getRestictionType().replaceAll(" ", "").replaceAll("/", "")));
	          
	      });
	
	}
	
	@Override
    protected boolean preClose(String actionId)
    {
        if ("windowClose".equalsIgnoreCase(actionId))
        {
            unService.setCurrentNumber = unService.getCurrentNumber - 1;
        }

        return Boolean.TRUE;
    }

Ok, 2 things are wrong here. First, you probably should use the UniqueNumbersAPI rather than UniqueNumbersService, so that you have access to the method I am referencing. The other thing is, I apparently had a slip up in my code, so here is what it will look like after the change.

@Inject
	private UniqueNumbersAPI unService;
	
	@Named("fieldGroup.typeId") 
	protected WebPickerField typeIdLookupField;

	@Override
	public void init(Map<String, Object> params) {
	      this.typeIdLookupField.addValueChangeListener(e -> {
	           CorpRTSUndIns corpRTSUndIns = getItem();
	           corpRTSUndIns.setType(corpRTSUndIns.getTypeId().getRestictionType());
	           corpRTSUndIns.setOrder((int) unService.getNextNumber(corpRTSUndIns.getTypeId().getRestictionType().replaceAll(" ", "").replaceAll("/", "")));
	          
	      });
	
	}
	
	@Override
    protected boolean preClose(String actionId)
    {
        if ("windowClose".equalsIgnoreCase(actionId))
        {
            unService.setCurrentNumber(unService.getCurrentNumber - 1);
        }

        return Boolean.TRUE;
    }

setCurrentNumber isn’t a property, but a method - so it should have been called as such - my bad! See if that works for you.

While importing import com.haulmont.cuba.core.app.UniqueNumbersAPI i am getting below error:

The import com.haulmont.cuba.core.app.UniqueNumbersAPI cannot be resolved

same as this topic UniqueNumber API exception where cuba guy suggested to use uniqueNumbersServices instead of API

Ok, well you could implement your own service that basically calls their service. If you use the Studio to create a new service called CustomUniqueNumbersService. Something like this:

Service interface

/**
 * Custom Service Wrapper for UniqueNumbersAPI that provides sequences of unique numbers based on database sequences.
 *
 * @author Vincent 
 */
public interface CustomUniqueNumbersService
{
    /**
     * Service name.
     */
    String NAME = "This will autogenerate during the creation";

    /**
     * Returns the next sequence value.
     *
     * @param domain sequence identifier
     *
     * @return next value
     */
    long getNextNumber(String domain);

    /**
     * Returns the current value of the sequence. For some implementations
     * {@link #getNextNumber(String)} must be called at least once beforehand.
     *
     * @param domain sequence identifier
     *
     * @return current value
     */
    long getCurrentNumber(String domain);

    /**
     * Set current value for the sequence.
     * Next {@link #getCurrentNumber(String)} invocation will return {@code value}
     * Next {@link #getNextNumber(String)} invocation will return {@code value + increment}
     *
     * @param domain sequence identifier
     * @param value  value
     */
    void setCurrentNumber(String domain, long value);

    /**
     * Removes sequence with specified identifier
     *
     * @param domain sequence identifier
     *
     * @throws java.lang.IllegalStateException if sequence does not exist
     */
    void deleteSequence(String domain);
}

Bean

package com.rx30.ems.basicworkflowmanager.service;

import javax.inject.Inject;

import com.haulmont.cuba.core.app.UniqueNumbersAPI;
import org.springframework.stereotype.Service;

/**
 * Custom Service Wrapper for UniqueNumbersAPI that provides sequences of unique numbers based on database sequences.
 *
 * @author Vincent
 */
@Service(CustomUniqueNumbersService.NAME)
public class CustomUniqueNumbersServiceBean implements CustomUniqueNumbersService
{

    @Inject
    protected UniqueNumbersAPI uniqueNumbersAPI;

    /**
     * Returns the next sequence value.
     *
     * @param domain sequence identifier
     *
     * @return next value
     */
    @Override
    public long getNextNumber(String domain)
    {
        return this.uniqueNumbersAPI.getNextNumber(domain);
    }

    /**
     * Returns the current value of the sequence. For some implementations
     * {@link #getNextNumber(String)} must be called at least once beforehand.
     *
     * @param domain sequence identifier
     *
     * @return current value
     */
    @Override
    public long getCurrentNumber(String domain)
    {
        return this.getCurrentNumber(domain);
    }

    /**
     * Set current value for the sequence.
     * Next {@link #getCurrentNumber(String)} invocation will return {@code value}
     * Next {@link #getNextNumber(String)} invocation will return {@code value + increment}
     *
     * @param domain sequence identifier
     * @param value  value
     */
    @Override
    public void setCurrentNumber(String domain, long value)
    {
        this.uniqueNumbersAPI.setCurrentNumber(domain, value);
    }

    /**
     * Removes sequence with specified identifier
     *
     * @param domain sequence identifier
     *
     * @throws IllegalStateException if sequence does not exist
     */
    @Override
    public void deleteSequence(String domain)
    {
        this.uniqueNumbersAPI.deleteSequence(domain);
    }
}

You can use this to do your unique numbers directly in the controller. Now, this may be considered bad form by some because you are bringing code that was not intended to be used outside of the core module to the web module - but you have to make the decision of whether it is bad for you. It should theoretically solve your problem, but just be aware there could be unintended consequences.

The other option would be to build a service for this specific entity that handles just this increment / decrement process and not wrap the whole service.

1 Like

Thank You very much :slight_smile: I hope this may end up in a working phase.

Well, if not, we are here to help - just send a note back and we will take it from there. Good luck!

Hi everyone,

Does anyone knows what happens in concurrent setup? Is it possible that on “Cancel” action setting the sequence number to the previous one lead to collision because other user created a new record?

Thanks for the help.

Sammy

Hi @sambill,

Reverting sequence is not the best way, as it could happen that this sequence has been triggered concurrently by another user. There is only one recommendation, pool a value from the sequence in EntityChangedEvent (EntityChangedEvent - CUBA Platform. Developer’s Manual) at the before commit phase.

Regards,
Aleksey