Submission metadata modal to refresh submission workflow page upon clicking on form save button

Submission and Publication Metadata modal gives the possibility to update submission’s metadata and save the changes. However, upon saving the data, the bnackground page, which shows the submission workflow in tabs, does not update automatically. Even when closing the modal, the page won’t update. i’m looking for a way to update the background page.

I found a way that invokes $json->setEvent(‘redirectRequested’, ‘’); however I don’t like this because this requires a full browser page reload and will also not let time for the user to read any visual notification sent by the modal.

The handler saveForm for this form is located in lib/pkp/controllers/modals/submissionMetadata/PublicationEntryHandler.inc.php

Thank you,

Sébastien Nadeau

Hi @Generalelektrix,

This is a known shortcoming with the OJS Javascript toolkit – there’s no way for elements to communicate with each other. @NateWr has been thinking about this for a while and gradually shifting parts of the front-end toolset over to vue.js, and could probably give you some guidance on future approaches.

Regards,
Alec Smecher
Public Knowledge Project Team

Hi @Generalelektrix,

The short answer is that a full page refresh is probably the easiest, most reliable way to achieve what you want at the moment. However, I’ll walk you through a few more details in case you want to go further with this.

First, as Alec mentioned, we’re in a long refactoring process in which we’re moving more of the client-side code into Vue.js. In the meantime, we have some glue to stick the pieces together, but it will take some work.

Second, we have a global event router since v3.1 which you can use to communicate between components that are not in a parent-child relationship. The event router is an instance of a Vue component in which you have access to the events api.

The event bus can be accessed globally in JavaScript at pkp.eventBus. So you can subscribe to any event with:

pkp.eventBus.$on('myEvent', function (passedData) {
  console.log(data)
})

You can then trigger that event at any time with:

pkp.eventBus.$emit('myEvent', anyData);

When using the older JS framework, we pass events from the server to the client-side JS using the JSONMessage method you discovered:

$json->setEvent(‘redirectRequested’, '');

That only works for events that “bubble up” in a parent-child relationship. There’s another helper method you can use to trigger an event directly on the global event bus:

$json->setGlobalEvent('myEvent', $myNewData);

Warning: this only works on the existing form components that receive a JSONMessage response. As we transition to Vue.js for new components (submissions list, reviewer selection list), we will deprecate this method of passing events from the server.

So, to wrap all of that up, you could do something like the following:

// PHP
// Send a global event when the metadata is updated
// Use a custom namespace for the event so you never conflict with
// events in the core application. I'll use your initials, sn.
$json->setGlobalEvent('sn.submissionUpdated', ['title' => $newTitle]);
// JS
// Subscribe to that event and pass a function
// to be called when the event fires. In this case, we'll look for the
// submission title and update it.
if (typeof pkp !== 'undefined') {
  pkp.eventBus.$on('sn.submissionUpdated', function(data) {
    // Use a jQuery selector to pick out the title element and 
    // change the text. (Note: this is not the correct selector.)
    $('.submission_title').text(data.title);
  });
}

This will all be a little bit fragile, since it requires editing a core file for the PHP event and the title selector could change without warning. But it’s the only way to accomplish what you want for now.

In the last few versions we have been introducing a few new components built on the REST API and Vue.js (submissions list, reviewer selection list). We’ll continue building out more of the UI in this way. Eventually, the workflow will come under a state management library (Vuex) and it will be easier to manage these updates in the browser. By that point, we’ll also have better documentation in place for subscribing to events so that customizations like these can be more dependable.

Hi Nate,

Thank you for the reply. With this method I have been able to update some of the submission Metadatas without reloading the entire page.

event_bus_update_component

However, I would love to be able to also update the Participants list too. Is there a way to call the component’s handler and tell it to reload?

Sébastien

You’ve got a couple options here, depending on whether you prefer to customize the core application code or keep your customizations in a separate plugin.

Method 1: customize the core application code
The downside here is that when you update the application, you’ll have to manually handle your customizations to replace them after updates.

In the StageParticipantGridHandler.js component, you will find an event binding in the constructor:

You can also bind to global events like this:

this.bindGlobal('sn.submissionUpdated', function(data) {
  this.refreshGridHandler();
});

Method 2: From a third-party JS file
The downside to this method is that you have to dig pretty deep into the structure of our handlers and this could change between versions without warning.

The participants grid will load with an id like component-grid-users-stageparticipant-stageparticipantgrid-XXXXXXXXX. You can use an attribute selector to find the element using jQuery like this:

$('[id^="component-grid-users-stageparticipant-stageparticipantgrid"]');

(Warning: this will only work when there is a single grid with this ID part on the page. That works for now because we load the tabs dynamically, but since a grid is a reusable component, there’s always the risk that it will be used twice on one page.)

Next, there is a helper function that exists to retrieve the handler that is attached to any dom element. So we can combine these like this:

var $participantsGrid = $('[id^="component-grid-users-stageparticipant-stageparticipantgrid"]');
var $participantsGridHandler = $.pkp.classes.Handler.getHandler($participantsGrid);

And finally, we can put this all together inside of the global event callback from above:

if (typeof pkp !== 'undefined') {
  pkp.eventBus.$on('sn.submissionUpdated', function(data) {
    // Use a jQuery selector to pick out the title element and 
    // change the text. (Note: this is not the correct selector.)
    $('.submission_title').text(data.title);

    // Reload the participants grid
    var $participantsGrid = $('[id^="component-grid-users-stageparticipant-stageparticipantgrid"]');
    var $participantsGridHandler = $.pkp.classes.Handler.getHandler($participantsGrid);
    $participantsGridsHandler.refreshGridHandler();
  });
}

:crossed_fingers: And hopefully that will do it!

Thank you again Nate. You first solution is exactly what I was looking for. I will keep in mind the possibility to implement solution #2 because it seems to give a lot of flexibility!

1 Like

I need to reload submission/review page after unassigning a reviewer.

Can I do that with $json->setEvent() or setGlobalEvent() method, and how?

As Nate explainded, I just bound a Global event to refresh the grid by adding these lines to lib/pkp/js/controllers/grid/users/stageParticipant/StageParticipantGridHandler.js:

	// Refresh this grid upon submission metadata update
	this.bindGlobal('ul.submissionUpdated', function(data) {
		this.refreshGridHandler();
	});

I guess you can set a Global event in the controller when unassigning a reviewer like this:

		// Pass a global event to indicate a reviewer was unassigned
		$json->setGlobalEvent('reviewerUnassigned', array(
			'submissions' => array($submission->getId()),
		));

And then catch it with bindGlobal, but you’ll need to find where exactly to set the event and where to bind it.

Okay, some time has passed since this conversation and here we are with the same thing. Is there now any better/faster/more stable way to refresh another grid? Or is it still like that? I have two grids on the same page and I need to reload one of them when something was changed on another one and of course I don’t want to reload the page. @NateWr are you able to answer that?

You will need to use the global event bus. This the only way for two grids to talk to each other. You can fire a global even from the backend by using the technique described in this comment but you will need to listen for the event in the specific grid handler’s JavaScript.

1 Like