Refresh Application Folders Count Values Task

I followed this tutorial.

So in the left pane I have various folders which show a table row count.

How would I go about creating a task which refreshes the counts on them? The timer seems too slow and I want to refresh the counts after certain events.

Refreshing on an event is possible, but it depends on where your event is raised. If it originates from a UI screen and you have only one web client block, you can use UiEvents as described in the docs. If the event is from middleware or you have multiple web blocks and you want to notify all of them, use the Global Events add-on.

Thanks Konstantin, it looks like that is what I need. But struggling to get it to work.

I am now receiving the events in the web module from the core entity listeners. Now how do I get the folder app counts to refresh? What I am trying is:

  @Component("myapp_ApplicationFoldersUpdateBean")
public class ApplicationFoldersUpdateBean {

@Inject
private DataManager dataManager;

@Inject
private GlobalConfig globalConfig;

@Inject
private TrustedClientService trustedClientService;

@Inject
private WebAuthConfig webAuthConfig;

@Inject
private GlobalEventsService globalEventsService;

@Inject
private Events events;

@Inject
private FoldersService foldersService;

@Inject
private ScreenProfilerService screenProfilerService;

private static final Logger log = LoggerFactory.getLogger(ErrorEvent.class);

@EventListener
public void onBeanNotificationEvent(ErrorEvent event) {
    log.info("Received {AppFolder}", event);
    UserSession systemSession;
    try {
        systemSession = trustedClientService.getSystemSession(webAuthConfig.getTrustedClientPassword());
    } catch (LoginException e) {
        throw new RuntimeException(e);
    }
    AppContext.withSecurityContext(new SecurityContext(systemSession), () -> {
        foldersService.reloadAppFolders(foldersService.loadAppFolders());
        log.info("Reloaded Folders", event);
        //foldersService.loadAppFolders();
    });
}
}

I am trying to refresh the count values on my app folders by sending an event from the core module:
Untitled

Daryn,

You are invoking a service, but it doesn’t help to refresh the UI component. In fact, the service is used by the component, and you need to invoke refresh on the component.

First, get the main window:

TopLevelWindow mainWindow = AppUI.getCurrent().getTopLevelWindow();

The component is of type FoldersPane and by default has foldersPane id:

FoldersPane foldersPane = (FoldersPane) mainWindow.getComponentNN("foldersPane");

Unfortunately, the public API of the component provides only the refreshFolders() method which refreshes the whole content, which is excessive for your task. So better obtain the underlying implementation and invoke the same method which is normally invoked by the timer:

CubaFoldersPane cubaFoldersPane = foldersPane.unwrap(CubaFoldersPane.class);
cubaFoldersPane.reloadAppFolders();

Thanks Konstantin,

It’s sort of working now. It seems to work correctly if invoked from the “OnAfterInsert” listener but not on the “OnAfterDelete” listener for some reason.

Here’s what I have:

Listener:

@Inject
private Events events;


@Override
public void onAfterInsert(ErrorLog entity, Connection connection) {
    System.out.println("after Insert");
    events.publish(new ErrorEvent(entity, "ErrorEvent"));

}


@Override
public void onAfterDelete(ErrorLog entity, Connection connection) {
    System.out.println("after delete");
    events.publish(new ErrorEvent(entity, "ErrorEvent"));
}


@Override
public void onAfterUpdate(ErrorLog entity, Connection connection) {
    System.out.println("after update");
    events.publish(new ErrorEvent(entity, "ErrorEvent"));
}

In my ApplicationFoldersUpdateBean I have put:

   @EventListener
    public void onBeanNotificationEvent(ErrorEvent event) {
        log.info("Application Folders Update Bean", event);
        UserSession systemSession;
        try {
            systemSession = trustedClientService.getSystemSession(webAuthConfig.getTrustedClientPassword());
        } catch (LoginException e) {
            throw new RuntimeException(e);
        }
        AppContext.withSecurityContext(new SecurityContext(systemSession), () -> {
            Window.TopLevelWindow mainWindow = AppUI.getCurrent().getTopLevelWindow();
            FoldersPane foldersPane = (FoldersPane) mainWindow.getComponentNN("foldersPane");
            CubaFoldersPane cubaFoldersPane = foldersPane.unwrap(CubaFoldersPane.class);
            cubaFoldersPane.reloadAppFolders();
            log.info("Application Folders Update Bean Finished", event);
        });
    }

However it only prints the first log “Application Folders Update Bean”. It does not print the second one “Application Folders Update Finished”. It refreshes the folders when invoked by onAfterInsert but not when invoked by onAfterDelete (at least there is a significant delay on after delete).

Seems a little buggy…

Actually…

It is working, but the values do not refresh instantly unless I e.g. manually resize the folders pane etc.

Do I need to add another step? e.g. do I need to redraw the folders pane somehow or something? How would I refresh the folders pane to show the updated values?

Try to deliver the event right to the main window: implement GlobalUiEvent in your event, extend the main window and add event listener to it. You don’t need any authentication then, also FoldersPane can be obtained right from this.

Thanks Konstantin,

That does not seem to work either… Now I am not receiving the events at all.

What I have:

Event Entity:

public class UpdateApplicationEvents extends GlobalApplicationEvent implements GlobalUiEvent {

private String message;

/**
 * Create a new ApplicationEvent.
 *
 * @param source the object on which the event initially occurred (never {@code null})
 */
public UpdateApplicationEvents(Object source, String message) {

    super(source);
    this.message = message;
}

public String getMessage() {
    return message;
}

@Override
public String toString() {
    return "ApplicationFolderEvent{" +
            "message='" + message + '\'' +
            ", source=" + source +
            '}';
}

}

ErrorEntity Listener:

    @Component("daryn_ErrorLogListener")
public class ErrorLogListener implements AfterInsertEntityListener<ErrorLog>, AfterDeleteEntityListener<ErrorLog>, AfterUpdateEntityListener<ErrorLog> {

    @Inject
    private Events events;

    @Inject
    private UserSessionSource userSessionSource;

    @Override
    public void onAfterInsert(ErrorLog entity, Connection connection) {
        events.publish(new UpdateApplicationEvents(entity, "Error Created Event"));
    }

    @Override
    public void onAfterDelete(ErrorLog entity, Connection connection) {
        events.publish(new UpdateApplicationEvents(entity, "Error Delete Event"));
    }

    @Override
    public void onAfterUpdate(ErrorLog entity, Connection connection) {
        events.publish(new UpdateApplicationEvents(entity, "Error Update Event"));
    }
}

And extending AppMainWIndow

public class ApplicationFoldersUpdateBean extends AppMainWindow {

@Inject
private Logger log;

@EventListener
public void updateApplicationEvents(UpdateApplicationEvents event) {
    System.out.println("got event extension");
    log.info(event.getSource().getClass() + " " + event.getMessage());
    FoldersPane foldersPane = this.getFoldersPane();
    CubaFoldersPane cubaFoldersPane = foldersPane.unwrap(CubaFoldersPane.class);

    cubaFoldersPane.reloadAppFolders();
    cubaFoldersPane.refreshFolders();
    cubaFoldersPane.asyncReloadAppFolders();
    System.out.println("after update");
}

}

I also tried putting it into ExtAppMainWindow and doesn’t receive events either:

public class ExtAppMainWindow extends AppMainWindow {

@EventListener
public void updateApplicationEvents(UpdateApplicationEvents event) {
    System.out.println("got event extension");
    FoldersPane foldersPane = this.getFoldersPane();
    CubaFoldersPane cubaFoldersPane = foldersPane.unwrap(CubaFoldersPane.class);
    cubaFoldersPane.reloadAppFolders();
    cubaFoldersPane.refreshFolders();
    cubaFoldersPane.asyncReloadAppFolders();
}

Thought I would note, I am trying to receive the event in [app-web].

If I have no application folders the following works and will print the “after” println statement (but not the log statement).

If I have any application folders the println statement doesn’t print.

@Component("ecosmart_ApplicationFoldersUpdateBean")
public class ApplicationFoldersUpdateBean {

@Inject
private Logger log;

@Inject
private Events events;

@Inject
private TrustedClientService trustedClientService;

@Inject
private WebAuthConfig webAuthConfig;

@EventListener
public void updateApplicationEvents(UpdateApplicationEvents event) {
    log.info("APplication Folders update bean");
    UserSession systemSession;
    try {
        systemSession = trustedClientService.getSystemSession(webAuthConfig.getTrustedClientPassword());
    } catch (LoginException e) {
        throw new RuntimeException(e);
    }
    AppContext.withSecurityContext(new SecurityContext(systemSession), () -> {
        Window.TopLevelWindow mainWindow = AppUI.getCurrent().getTopLevelWindow();
        FoldersPane foldersPane = (FoldersPane) mainWindow.getComponentNN("foldersPane");
        CubaFoldersPane cubaFoldersPane = foldersPane.unwrap(CubaFoldersPane.class);
        cubaFoldersPane.reloadAppFolders();
        //cubaFoldersPane.refreshFolders();
        //cubaFoldersPane.asyncReloadAppFolders();
        log.info("after update bean");
    });
    System.out.println("After");
}
}

There seems to be a bug somewhere in the AppContext.withSecurityContext(new SecurityContext(systemSession), () -> { thread but there is no error message or anything posted so I cannot figure out what it is…

I thought I would note I am trying to receive this in the [app-web] module from the [core] module listeners.

Actually nevermind, I have managed to fix the issue.

I think my spring configuration was old.

Also, there may have been errors on my main window so after recreating it it worked.

Pretty cool!

Here is a demo project of this working in case anyone is interested.

GlobalEventsTester.zip (478.3 KB)

You will need to use Postgres to init the tables.

2 Likes

For those who may be interested, let me add that when listening to a GlobalUiEvent, you don’t need any additional authentication:

@EventListener
public void updateApplicationEvents(UiNotificationEvent event) {
    log.info("Main window event received");
    FoldersPane foldersPane = this.getFoldersPane();
    CubaFoldersPane cubaFoldersPane = foldersPane.unwrap(CubaFoldersPane.class);
    cubaFoldersPane.reloadAppFolders();
    showNotification("Message recevied from "+event.getSource() + " , folder count updated");
}

This is how actually Daryn’s example works.

By the way… @Konstantin

Would you happen to know how to:

  1. Change the folder icon depending on whether there are results in each folder count. e.g. I was thinking about putting green and red “lights” as the folder icons.
  2. Change the folders panel icon (arrow) color if any of the folders have a count. e.g. have it green if no count, red if count.
  3. Change the name of “Application Folders” to something else.

Just thought that would be a nice addition.

question

For folders, it’s possible by setting a value to the style variable in Count script. See the docs.

For the other elements, you can try to set an appropriate style to the FoldersPane component when reloading folders, and write CSS that will use this style for for the component elements.

Oh wow, that’s pretty cool. I never noticed that was there in the docs.

So I have
.c-folders-pane .v-tree-node.red {
font-weight: bold;;
color: red;
}

.c-folders-pane .v-tree-node.green {
font-weight: bold;;
color: limegreen;
}

And I can get the text red/green. But do you know how to override the folder icon?

Hello @daryn

This task is little bit tricky but still solvable. We will place icons into ::before elements that contain tree arrow icons.

First of all we should disable default rotation transformation for ::before elements in tree nodes to be able to display icons in one line:

.v-tree-node-expanded > .v-tree-node-caption > div::before {
  -webkit-transform: none;
  -moz-transform: none;
  -ms-transform: none;
  -o-transform: none;
  transform: none;
}

Then we have to find correct combinations of space glyphs that will divide tree arrows and item icons to avoid icon glitches. I’ve found that we can use "\2003" "\2009" combination for collapsed and "\2003" for expanded items. This difference exists because the arrow icon takes more space for expanded item than for collapsed.

After spaces we can put our icon. I’ve chosen the cog (or gear) icon. Its code is "\f0c1". And the last thing is to increase a width of ::before elements.

Final style:

.v-tree-node-expanded > .v-tree-node-caption > div::before {
  // disable transformation to display icons in a row
  -webkit-transform: none;
  -moz-transform: none;
  -ms-transform: none;
  -o-transform: none;
  transform: none;
}

.v-tree-node.cog > .v-tree-node-caption > div::before {
  // the hardest moment - combine space characters to avoid icon glitches
  // values: arrow, two spaces, FA link icon
  content: "\f0da" "\2003" "\2009" "\f0c1";
  // override default width to place both icons
  width: $v-unit-size + $v-unit-size / 6;
}

.v-tree-node-expanded.cog > .v-tree-node-caption > div::before {
  // the same moment with spaces
  // values: arrow, one space, FA link icon
  content: "\f0d7" "\2003" "\f0c1";
  // the same
  width: $v-unit-size + $v-unit-size / 6;
}

Small demo:

app_folders_icon

Few useful links:

Regards,
Daniil

2 Likes

Hi @tsarev

Thank you for your reply. However that does not seem to modify anything for me and I’m not sure you understood the question? I am trying to change the icon depending on the search result. So I have .c-folders-pane .v-tree-node.red if there are results > 0 from the query and .c-folders-pane .v-tree-node.green if there are no results.

What I have tryed in my scss:

.c-folders-pane .v-tree-node.red {
font-weight: bold;;
color: red;
  }

  .c-folders-pane .v-tree-node.green {
color: #32a73a; 
  }

  .v-tree-node-expanded > .v-tree-node-caption > div::before {
-webkit-transform: none;
-moz-transform: none;
-ms-transform: none;
-o-transform: none;
transform: none;
  }

  .v-tree-node.cog > .v-tree-node-caption > div::before{
// the hardest moment - combine space characters to avoid icon glitches
// values: arrow, two spaces, FA link icon
content: "\f0da" "\2003" "\2009" "\f0c1";
// override default width to place both icons
width: $v-unit-size + $v-unit-size / 6;
  }

  .v-tree-node-expanded.cog > .v-tree-node-caption > div::before {
// the same moment with spaces
// values: arrow, one space, FA link icon
content: "\f0d7" "\2003" "\f0c1";
// the same
width: $v-unit-size + $v-unit-size / 6;
  }

And my script:

def em = persistence.getEntityManager()
def q = em.createQuery('select count(o) from daryn$ErrorLog o')
def count = q.getSingleResult()
style = count > 0 ? 'red' : 'green'
return count

The font colors are changing based on .c-folders-pane .v-tree-node.red and .c-folders-pane .v-tree-node.green but the icons have not changed with your suggestion. I would rather have a red/green icon than change the color of the text (see my pic in my previous post).

Maybe the problem is that I don’t need the tree functionality in my app folders?

And one other thing. How can I remove the selected table row styling? I don’t want it to change when a folder is selected (i.e. the background and font colors to change)

Hello @daryn

I’ve prepared more complicated example. If you do not need tree functionality, it is easier to change icons:

.v-tree-node.red > .v-tree-node-caption > div::before {
  // circle glyph
  content: "\f111";
  // style dependent color
  color: red;
  font-family: "FontAwesome";
  visibility: visible;
}

.v-tree-node.green > .v-tree-node-caption > div::before {
  content: "\f111";
  color: green;
  font-family: "FontAwesome";
  visibility: visible;
}

And to disable “selected” style you can use the following:

.v-tree-node-selected {
  color: $v-font-color;

  &::after {
    background: none transparent;
  }
}

Result:

image

Demo project:

af-icons.zip (78.4 KB)

Regards,
Daniil

Awesome! Thank you! :slight_smile: