-
Notifications
You must be signed in to change notification settings - Fork 454
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Routing events between handlers in the JS framework #2163
Comments
…lished or unpublished
… are published or unpublished
This option was related to the old jQueryUI dialogs.
… are published or unpublished
…lished or unpublished
This option was related to the old jQueryUI dialogs.
Previously, a user tab handler performed a partial tab refresh to refresh the user grid when a user merge occurred. This causes problems for the handler event deregistration process I'm working on implementing. This commit refactors away from that approach in favor of a modal approach. It also fixed pkp#2171 in the process.
See comment in pkp-lib commit 6ed3c58bb6aaf0030fe1f7883b4e98ca9fee325f
Thanks, @NateWr, good reading. Do you remember when you first started working with the PKP JS code that you proposed a change to the handler lifecycle? IIRC you wanted static calling or something. Would that be a useful thing to revisit in mangling an observer pattern into our DOM usage? |
I dug up my old doc on that when I was looking into this. I'm not sure it would solve the problems we're facing in this case, although I think questions about how we initialize components will keep coming up if we start to introduce parts of an existing framework. I've come up with an ad-hoc way to get a sort-of registry of handlers. Basically, we have every handler crawl up the DOM hierarchy and register itself with the first handler it finds. So each handler gets a reference to handlers attached to elements within the DOM. This works because in the majority of cases we do full-handler refreshes all at once. So when refreshing, a handler can loop through it's child handlers and de-register event listeners. There are a few cases where we do partial refreshes and I'm working on those at the moment. Some I'm refactoring away. Others (like replacing single grid elements) I'll probably write something special to handle those cases. But it's a pretty small group of cases (maybe half a dozen) which I've been able to identify. I'm hoping this will be pretty reliable, though I'll have to see once it's in place if we end up with memory leaks. |
@NateWr, thanks, that sounds like a decent short-term plan. |
…lers. This commit implements a pattern for binding global events on handlers. Each handler registers itself with any parent handlers, so that when a handler is refreshed, event listeners bound to any child handlers are also de-registered. It does this by routing HTML replace functions through wrappers attached to the JS Handler object, which unbind listeners before performing the HTML replacement. ~ This refactor is still partial. The information center , tab handlers and modal handlers have been updated, but instances of HTML replacement that doesn't properly unbind events still exist. These will be refactored in subsequent commits.
…lished or unpublished
This option was related to the old jQueryUI dialogs.
Previously, a user tab handler performed a partial tab refresh to refresh the user grid when a user merge occurred. This causes problems for the handler event deregistration process I'm working on implementing. This commit refactors away from that approach in favor of a modal approach. It also fixed pkp#2171 in the process.
…lers. This commit implements a pattern for binding global events on handlers. Each handler registers itself with any parent handlers, so that when a handler is refreshed, event listeners bound to any child handlers are also de-registered. It does this by routing HTML replace functions through wrappers attached to the JS Handler object, which unbind listeners before performing the HTML replacement. ~ This refactor is still partial. The information center , tab handlers and modal handlers have been updated, but instances of HTML replacement that doesn't properly unbind events still exist. These will be refactored in subsequent commits.
… are published or unpublished
See comment in pkp-lib commit 6ed3c58bb6aaf0030fe1f7883b4e98ca9fee325f
…s not found by apiKey setting pkp#2163
- Moves coverImage props into OJS service classes. - Delivers full URLs to cover images.
@kaschioudi I think I've worked out the test issues. Do you have time to code review the PRs in this comment? #2163 (comment) |
@NateWr Everything looks fine to me 👍 |
pkp/pkp-lib#2163 Integrate with API changes from entity property serv…
#2163 Implement entity property list for service classes
pkp/pkp-lib#2163 Implement entity property list for service classes
pkp/pkp-lib#2163 Implement entity property list for service classes
🎉 Really happy to have this merged in. Ready to start build more API stuff. :) |
pkp/pkp-lib#2163 Add series title to monograph query builder
@NateWr and @kaschioudi, is this one ready to be closed now that the OMP warning fix has been merged? |
💥 |
…ents. ReviewRound and ReviewAssigment objects get defined statuses. This commit extends those statuses, makes sure they're recalculated at the appropriate times, and moves that calculation into the objects instead of grids. IN the process, it removes the all-reviews-are-in task. (This will get surfaced as a review round status in the submissions list.) It also removes the now obsolete replaced state for ReviewAssignments. Whenever a ReviewAssignment is updated, the ReviewRound status should get updated automatically. So the _updateReviewRoundStatus() helper method in PKPReviewGridHandler has been removed. (A similarly named method in EditorDecisions is still in play, as it manually overrides the status when an EditorDecision is invoked.) Also fixes a couple ofbugs with the reviewer grid and round status not updating in response to actions.
pkp/pkp-lib#2163 Implement entity property list for service classes
This issue is a placeholder to track some work/experimentation to improve the way that our JS handlers can communicate between themselves. The suggested approach is to have a kind of global event router which is based on the observer pattern common in several JS frameworks.
I'd like to use this to document some of the lessons learned about how events in the JS Handlers (eg - UI "widgets) work.
Overview
As a general rule, events that are executed on a Handler do not bubble up like normal JS events. They're captured in an effort to ensure that Handlers remain encapsulated.
However, in some cases we have needed Handlers to talk to each other. This is done by white-listing certain events to be "published", using the
publishEvent
,trigger
andtriggerPublicEvent_
methods. These published events are triggered on the Handler's parent element in the DOM, from which they can bubble up.This event is captured by a parent DOM element which has a handler attached, typically a complex Handler like a grid or the
SiteHandler
.Because modals exist outside the normal DOM hierarchy, bubbling doesn't really get the job done. To accomodate this, there's a
eventBridge
option which can be passed to Handlers. This is used by modals to redirect events to theLinkAction
which opened the modal.The
notifyUser
event also gets it's own routing. Several handlers deliberately bubble the event up to the SiteHandler, which then makes a request to the server to decide what notifications to display.So we have two ways for Handlers which are separate in the DOM (ie - not nested) to communicate with each other: an event that bubbles to
SiteHandler
and is then re-routed bySiteHandler
, or an event bridge that reaches out to another element with a jQuery selector.The maintenance problem
This architecture has worked to keep the Handlers highly re-usable. But it means that in cases where we do need Handlers to interact, including in cases where bubbling may not be sufficient, we have written a lot of code to reach out from one Handler to another.
The result has been that the connections between coupled Handlers tend to be ad-hoc, written into whatever method of the Handler happens to be doing that job right then. As a result, it's hard to have a clear sense of the dependencies between Handlers where they do exist, and to guard against breaking these when refactoring.
The (first) proposed solution
I looked into addressing this with the observer pattern, something I was familiar with from the Backbone.js framework. It would also give us an opportunity to start piecemeal refactoring towards a more common approach to JS-heavy applications.
The blocking problem with implementing the observer pattern
The observer pattern can’t be implemented because we lack a central registry of handlers. A handler which updates its content will remove large chunks of the DOM without tracking which handlers were caught up in the destruction. We will have references to listeners that are no longer present in the DOM (the lapsed listener problem).
The usual way to resolve this is with a central registry of handlers which deregister their events when removed (the dispose pattern). But because a handler doesn’t know what handlers are caught up in it’s refresh operations, it can’t trigger deregistration on embedded handlers (eg - a
LinkActionHandler
that's part of aGridHandler
).A new proposal
Without a significant refactor, we can’t have truly decoupled handlers. Instead, I’d suggest using a modified observer pattern in which the relationships between events and listeners are stored explicitly in the observer’s code. So handlers would not subscribe to events on the observer. Instead, the observer would have explicit instructions for passing specific events to specific handlers.
The typical observer pattern looks like this:
The Observer can be completely ignorant of the events that are passing through it. The modified observer pattern I’m proposing would nest lookup logic in the Observer.
The Observer would not store references to handlers. Instead, it would look up any specific handlers by their associated DOM elements, performing a fresh lookup each time an event was triggered.
This would provide decoupling of a sort, though the coupling would still have to be specified in a central Observer. It’s only marginally better than the current practice of triggering an event from one handler on another handler’s DOM element directly.
But is it really an improvement?
Relying on DOM lookups is not something I feel good about, to be honest. All this pattern really does is restructure the hacky DOM-based lookups away from the Handler into a central location (example). I think it will help (a little) with the maintenance issue by exposing these relationships more clearly. But the same could be done in theory with the existing
eventBridge
technique.I don't see a way around these DOM lookup approaches without a pretty significant refactor of our Handlers. I've been wracking my brain but I haven't come up with anything.
One very simple refactor that I have made which I think is genuinely useful is that, on the PHP side, the
JSONMessage
object now supports multiple events. So you can register more than one event to return in a JSON response. Example: when publishing an issue you can return thedataChanged
event that the grid is programmed to respond to, as well as aissuePublished
event that can be broadcast separately.The global event router, combined with multiple events in a
JSONMessage
, could be a good pattern to adopt more broadly (triggering notifications, tasks, etc from the PHP side). But I'm not sure it gets us very far down the road I originally had in mind of an event-driven UI with stronger decoupling of Handlers.Examples
pkp-lib: https://github.com/NateWr/pkp-lib/commits/i2163_event_router
ojs: https://github.com/NateWr/ojs/tree/i2163_event_router
The text was updated successfully, but these errors were encountered: