Editor screens opening multiple tabs

When opening an editor screen that is already open, it opens a new tab instead of directing to the already open tab. For example, opening a customer, going back to the customer list and opening the same customer editor would result in 2 tabs.

Hello!

This is a standard behavior of screens. But you can close opened screen manually, see:

Where would we put this? The issue happens across all of our screens, we are not using breadcrumbs.

Before opening an editor screen. For instance, we can subscribe to ActionPerformedEvent and close opened screen before showing a new one.

<groupTable id="ordersTable"
            ...>
    <actions>
        <action id="create" type="create"/>
        <action id="edit" type="edit"/>
        <action id="remove" type="remove"/>
    </actions>
    ...
</groupTable>
Screen controller code
@Autowired
private ScreenBuilders screenBuilders;
@Autowired
private GroupTable<Order> ordersTable;
@Autowired
private Screens screens;
@Autowired
private WindowConfig windowConfig;
@Autowired
private Metadata metadata;

@Subscribe("ordersTable.edit")
public void onOrdersTableEdit(Action.ActionPerformedEvent event) {
    MetaClass metaClass = metadata.getClass(Order.class);
    closeScreen(windowConfig.getEditorScreenId(metaClass));

    screenBuilders.editor(ordersTable)
            .withOpenMode(OpenMode.NEW_TAB)
            .show();
}

protected void closeScreen(String screenId) {
    Collection<Screens.WindowStack> windowStacks = screens.getOpenedScreens().getWorkAreaStacks();
    for (Screens.WindowStack windowStack : windowStacks) {
        Collection<Screen> screens = windowStack.getBreadcrumbs();
        for (Screen screen : screens) {
            if (screenId.equals(screen.getId())) {
                screen.close(StandardOutcome.CLOSE);
                return;
            }
        }
    }
}

To avoid duplication code in every browse screen, you can extend Create and Edit actions and close opened screen before action execution. These actions will be available in the screen descriptor. For instance, custom edit action:

Custom EditAction
@StudioAction(
        target = "io.jmix.ui.component.ListComponent",
        description = "Edits an entity instance using its editor screen",
        availableInScreenWizard = true)
@ActionType(AppEditAction.ID)
public class AppEditAction<E> extends EditAction<E> {

    public static final String ID = "app_edit";

    @Autowired
    private WindowConfig windowConfig;
    @Autowired
    private Metadata metadata;

    public AppEditAction() {
        super(ID);
    }

    public AppEditAction(String id) {
        super(id);
    }

    @Override
    public void execute() {
        if (target == null) {
            throw new IllegalStateException("EditAction target is not set");
        }

        if (!(target.getItems() instanceof EntityDataUnit)) {
            throw new IllegalStateException("EditAction target dataSource is null or does not implement EntityDataUnit");
        }

        String screenId = windowConfig.getEditorScreenId(metadata.getClass(Order.class));
        closeScreen(screenId);

        super.execute();
    }

    protected void closeScreen(String screenId) {
        Frame frame = target.getFrame();
        if (frame == null) {
            throw new IllegalStateException("Component is not attached to the frame");
        }
        Screens screens = UiControllerUtils.getScreenContext(frame.getFrameOwner()).getScreens();
        Collection<Screens.WindowStack> windowStacks = screens.getOpenedScreens().getWorkAreaStacks();

        for (Screens.WindowStack windowStack : windowStacks) {
            Collection<Screen> windowStackScreens = windowStack.getBreadcrumbs();
            for (Screen screen : windowStackScreens) {
                if (screenId.equals(screen.getId())) {
                    screen.close(StandardOutcome.CLOSE);
                    return;
                }
            }
        }
    }
}

And XML descriptor:

<groupTable id="ordersTable"
            ...>
    <actions>
        <action id="create" type="create"/>
        <action id="edit" type="app_edit">
            <properties>
                <property name="openMode" value="NEW_TAB"/>
            </properties>
        </action>
        <action id="remove" type="remove"/>
    </actions>
    ...
</groupTable>
1 Like

Hello Roman,

In our project we had a similar problem, and we solved it by overriding the isSameScreen() method. The behavior is almost as we expected, but there is a problem that I see in your solution, too: we always reopen an opened editor screen instead of just activating it, which means that any unsaved data is either lost or committed to the database. Is there any way not to reopen but simply to activate the screen?

Hello Vadim,

to just select screen in MainTabSheet you should get currently opened screens (WindowStacks) to filter editor you need and select it. For instance:

@Autowired
private Screens screens;

@Subscribe("someBtn")
public void onSomeBtnClick(Button.ClickEvent event) {
    Collection<Screens.WindowStack> workAreaStacks = screens.getOpenedScreens().getWorkAreaStacks();
    workAreaStacks.stream()
            .filter(stack ->
                    stack.getBreadcrumbs().stream()
                            .anyMatch(screen -> "MyEntity.edit".equals(screen.getId()))) // or you can get screen instance and compare it here
            .findFirst()
            .ifPresent(Screens.WindowStack::select);
}
2 Likes

Thank you Roman, I think that’s exactly what I was looking for!