Closed NateWr closed 7 years ago
Adding another PR to this to update the help guide for the new list panels: https://github.com/pkp/ojs-user-guide/pull/13
@NateWr, here are some JS linting/build fixes to pull into your branches:
https://github.com/asmecher/pkp-lib/commit/48c86e48c275755283bac6d1d76b9e7aa9fdebaa https://github.com/asmecher/ojs/commit/8e90e9456e409da0aa12e47f0c8a9b63f2ca88f6
These will get it past the current build stage where it's failing, to a new and exciting stage where the failures are different. Does this help? I didn't want to put too much time into this in case you were struggling with something different.
(Those commits are on a throwaway branch, but they'll be around for at least a few days.)
@asmecher, you're actually on an old abandoned branch. Sorry, it's my fault for leaving the branch around and not cleaning it up properly.
All the latest work is in i2163_js_with_api
. I've got all those linting issues you identified cleaned up. I only have a few remaining which I can't work out:
Some undefined globals: https://travis-ci.org/pkp/ojs/jobs/243265753#L731-L739
Some type notation issues: https://travis-ci.org/pkp/ojs/jobs/243265753#L839-L849
(To avoid further confusion, there's also a branch called i2163_tests_prep
. Ignore that one altogether, it's just me doing some stuff until the FCGI testing changes gets merged.)
Try this, @NateWr. It's not beautiful but it can be shown the door as soon as we do away with our current closure compiler/jslint toolset, which won't be a moment too soon. https://github.com/asmecher/pkp-lib/commit/b4f7c7e5662e7ed4a0593546de3c0727537d2319
(edit: does it show that I've been wrestling with Crockford on a Friday evening after a long week?)
:tada: That got the JS tests passing!
Woo! There is no emoticon that adequately expresses both festiveness and grave injury.
Tests have passed for OJS!
https://github.com/pkp/ojs/pull/1337 https://github.com/pkp/pkp-lib/pull/2399
@asmecher, these tests include the commit you recommended we cherry-pick in to fix the FCGI issues, and I've also disabled the MySQL tests in travis. Can you do code review and merge if/when you see fit?
I'll work on getting the OMP tests going now.
Huge thanks, @NateWr and @kaschioudi. Still some things to work on -- in particular removing OJS-specific stuff from the pkp-lib part, and some yak-shaving. @NateWr, before you leave, could you spend a few minutes with @kaschioudi to hash over the remaining work so that this doesn't stall more than necessary while you're gone?
I've already got family in town so won't be able to dip into code much. Next week I may not have internet access at all.
I'll try to comment on a couple of the big ones right now. Otherwise, tag me explicitly if you need input on something to move forward.
@asmecher, @kaschioudi Tests are passing for OJS and OMP. Here are the PRs for final review/merge:
https://github.com/pkp/pkp-lib/pull/2399 https://github.com/pkp/ojs/pull/1337 https://github.com/pkp/omp/pull/412
All right, it's merged! @kaschioudi, there are some warnings I'm seeing:
PHP Notice: Undefined index: collation in /home/asmecher/git/ojs/lib/pkp/lib/vendor/illuminate/database/Illuminate/Database/Connectors/MySqlConnector.php on line 22
[26-Jul-2017 22:31:22 UTC] PHP Stack trace:
[26-Jul-2017 22:31:22 UTC] PHP 1. {main}() /home/asmecher/git/ojs/index.php:0
[26-Jul-2017 22:31:22 UTC] PHP 2. PKPApplication->execute() /home/asmecher/git/ojs/index.php:68
[26-Jul-2017 22:31:22 UTC] PHP 3. Dispatcher->dispatch() /home/asmecher/git/ojs/lib/pkp/classes/core/PKPApplication.inc.php:239
[26-Jul-2017 22:31:22 UTC] PHP 4. APIRouter->route() /home/asmecher/git/ojs/lib/pkp/classes/core/Dispatcher.inc.php:134
[26-Jul-2017 22:31:22 UTC] PHP 5. Slim\App->run() /home/asmecher/git/ojs/lib/pkp/classes/core/APIRouter.inc.php:110
[26-Jul-2017 22:31:22 UTC] PHP 6. Slim\App->process() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/App.php:295
[26-Jul-2017 22:31:22 UTC] PHP 7. Slim\App->callMiddlewareStack() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/App.php:370
[26-Jul-2017 22:31:22 UTC] PHP 8. Slim\App->Slim\{closure}() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/MiddlewareAwareTrait.php:122
[26-Jul-2017 22:31:22 UTC] PHP 9. call_user_func:{/home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/MiddlewareAwareTrait.php:73}() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/MiddlewareAwareTrait.php:73
[26-Jul-2017 22:31:22 UTC] PHP 10. Slim\DeferredCallable->__invoke() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/MiddlewareAwareTrait.php:73
[26-Jul-2017 22:31:22 UTC] PHP 11. call_user_func_array:{/home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/DeferredCallable.php:43}() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/DeferredCallable.php:43
[26-Jul-2017 22:31:22 UTC] PHP 12. APIHandler->{closure:/home/asmecher/git/ojs/lib/pkp/classes/handler/APIHandler.inc.php:68-87}() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/DeferredCallable.php:43
[26-Jul-2017 22:31:22 UTC] PHP 13. Slim\App->Slim\{closure}() /home/asmecher/git/ojs/lib/pkp/classes/handler/APIHandler.inc.php:86
[26-Jul-2017 22:31:22 UTC] PHP 14. call_user_func:{/home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/MiddlewareAwareTrait.php:73}() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/MiddlewareAwareTrait.php:73
[26-Jul-2017 22:31:22 UTC] PHP 15. Slim\DeferredCallable->__invoke() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/MiddlewareAwareTrait.php:73
[26-Jul-2017 22:31:22 UTC] PHP 16. call_user_func_array:{/home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/DeferredCallable.php:43}() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/DeferredCallable.php:43
[26-Jul-2017 22:31:22 UTC] PHP 17. APIHandler->{closure:/home/asmecher/git/ojs/lib/pkp/classes/handler/APIHandler.inc.php:51-65}() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/DeferredCallable.php:43
[26-Jul-2017 22:31:22 UTC] PHP 18. Slim\App->Slim\{closure}() /home/asmecher/git/ojs/lib/pkp/classes/handler/APIHandler.inc.php:64
[26-Jul-2017 22:31:22 UTC] PHP 19. call_user_func:{/home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/MiddlewareAwareTrait.php:73}() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/MiddlewareAwareTrait.php:73
[26-Jul-2017 22:31:22 UTC] PHP 20. Slim\DeferredCallable->__invoke() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/MiddlewareAwareTrait.php:73
[26-Jul-2017 22:31:22 UTC] PHP 21. call_user_func_array:{/home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/DeferredCallable.php:43}() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/DeferredCallable.php:43
[26-Jul-2017 22:31:22 UTC] PHP 22. ApiAuthorizationMiddleware->__invoke() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/DeferredCallable.php:43
[26-Jul-2017 22:31:22 UTC] PHP 23. Slim\App->__invoke() /home/asmecher/git/ojs/lib/pkp/classes/security/authorization/internal/ApiAuthorizationMiddleware.inc.php:74
[26-Jul-2017 22:31:22 UTC] PHP 24. Slim\Route->run() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/App.php:476
[26-Jul-2017 22:31:22 UTC] PHP 25. Slim\Route->callMiddlewareStack() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/Route.php:316
[26-Jul-2017 22:31:22 UTC] PHP 26. Slim\Route->__invoke() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/MiddlewareAwareTrait.php:122
[26-Jul-2017 22:31:22 UTC] PHP 27. Slim\Handlers\Strategies\RequestResponse->__invoke() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/Route.php:344
[26-Jul-2017 22:31:22 UTC] PHP 28. call_user_func:{/home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/Handlers/Strategies/RequestResponse.php:41}() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/Handlers/Strategies/RequestResponse.php:41
[26-Jul-2017 22:31:22 UTC] PHP 29. PKPBackendSubmissionsHandler->getSubmissions() /home/asmecher/git/ojs/lib/pkp/lib/vendor/slim/slim/Slim/Handlers/Strategies/RequestResponse.php:41
[26-Jul-2017 22:31:22 UTC] PHP 30. PKP\Services\PKPSubmissionService->getSubmissionList() /home/asmecher/git/ojs/lib/pkp/api/v1/_submissions/PKPBackendSubmissionsHandler.inc.php:137
[26-Jul-2017 22:31:22 UTC] PHP 31. PKP\Services\QueryBuilders\PKPSubmissionListQueryBuilder->get() /home/asmecher/git/ojs/lib/pkp/classes/services/PKPSubmissionService.inc.php:71
[26-Jul-2017 22:31:22 UTC] PHP 32. Illuminate\Database\Capsule\Manager::table() /home/asmecher/git/ojs/lib/pkp/classes/services/queryBuilders/PKPSubmissionListQueryBuilder.inc.php:134
[26-Jul-2017 22:31:22 UTC] PHP 33. Illuminate\Database\Capsule\Manager::connection() /home/asmecher/git/ojs/lib/pkp/lib/vendor/illuminate/database/Illuminate/Database/Capsule/Manager.php:110
[26-Jul-2017 22:31:22 UTC] PHP 34. Illuminate\Database\Capsule\Manager->getConnection() /home/asmecher/git/ojs/lib/pkp/lib/vendor/illuminate/database/Illuminate/Database/Capsule/Manager.php:98
[26-Jul-2017 22:31:22 UTC] PHP 35. Illuminate\Database\DatabaseManager->connection() /home/asmecher/git/ojs/lib/pkp/lib/vendor/illuminate/database/Illuminate/Database/Capsule/Manager.php:132
[26-Jul-2017 22:31:22 UTC] PHP 36. Illuminate\Database\DatabaseManager->makeConnection() /home/asmecher/git/ojs/lib/pkp/lib/vendor/illuminate/database/Illuminate/Database/DatabaseManager.php:63
[26-Jul-2017 22:31:22 UTC] PHP 37. Illuminate\Database\Connectors\ConnectionFactory->make() /home/asmecher/git/ojs/lib/pkp/lib/vendor/illuminate/database/Illuminate/Database/DatabaseManager.php:127
[26-Jul-2017 22:31:22 UTC] PHP 38. Illuminate\Database\Connectors\ConnectionFactory->createSingleConnection() /home/asmecher/git/ojs/lib/pkp/lib/vendor/illuminate/database/Illuminate/Database/Connectors/ConnectionFactory.php:47
[26-Jul-2017 22:31:22 UTC] PHP 39. Illuminate\Database\Connectors\MySqlConnector->connect() /home/asmecher/git/ojs/lib/pkp/lib/vendor/illuminate/database/Illuminate/Database/Connectors/ConnectionFactory.php:59
Also:
[26-Jul-2017 23:00:53 UTC] PHP Notice: Undefined index: section_title in /home/asmecher/git/ojs/classes/article/ArticleDAO.inc.php on line 81
[26-Jul-2017 23:00:53 UTC] PHP Stack trace:
[26-Jul-2017 23:00:53 UTC] PHP 1. {main}() /home/asmecher/git/ojs/index.php:0
[26-Jul-2017 23:00:53 UTC] PHP 2. PKPApplication->execute() /home/asmecher/git/ojs/index.php:68
[26-Jul-2017 23:00:53 UTC] PHP 3. Dispatcher->dispatch() /home/asmecher/git/ojs/lib/pkp/classes/core/PKPApplication.inc.php:239
[26-Jul-2017 23:00:53 UTC] PHP 4. PKPPageRouter->route() /home/asmecher/git/ojs/lib/pkp/classes/core/Dispatcher.inc.php:134
[26-Jul-2017 23:00:53 UTC] PHP 5. PKPRouter->_authorizeInitializeAndCallRequest() /home/asmecher/git/ojs/lib/pkp/classes/core/PKPPageRouter.inc.php:233
[26-Jul-2017 23:00:53 UTC] PHP 6. call_user_func:{/home/asmecher/git/ojs/lib/pkp/classes/core/PKPRouter.inc.php:372}() /home/asmecher/git/ojs/lib/pkp/classes/core/PKPRouter.inc.php:372
[26-Jul-2017 23:00:53 UTC] PHP 7. DashboardHandler->index() /home/asmecher/git/ojs/lib/pkp/classes/core/PKPRouter.inc.php:372
[26-Jul-2017 23:00:53 UTC] PHP 8. SubmissionsListHandler->getConfig() /home/asmecher/git/ojs/lib/pkp/pages/dashboard/DashboardHandler.inc.php:62
[26-Jul-2017 23:00:53 UTC] PHP 9. SubmissionsListHandler->getItems() /home/asmecher/git/ojs/lib/pkp/controllers/list/submissions/SubmissionsListHandler.inc.php:70
[26-Jul-2017 23:00:53 UTC] PHP 10. PKP\Services\PKPSubmissionService->getSubmissionList() /home/asmecher/git/ojs/lib/pkp/controllers/list/submissions/SubmissionsListHandler.inc.php:159
[26-Jul-2017 23:00:53 UTC] PHP 11. DAOResultFactory->toArray() /home/asmecher/git/ojs/lib/pkp/classes/services/PKPSubmissionService.inc.php:81
[26-Jul-2017 23:00:53 UTC] PHP 12. DAOResultFactory->next() /home/asmecher/git/ojs/lib/pkp/classes/db/DAOResultFactory.inc.php:218
[26-Jul-2017 23:00:53 UTC] PHP 13. ArticleDAO->_fromRow() /home/asmecher/git/ojs/lib/pkp/classes/db/DAOResultFactory.inc.php:104
[26-Jul-2017 23:00:53 UTC] PHP Notice: Undefined index: section_abbrev in /home/asmecher/git/ojs/classes/article/ArticleDAO.inc.php on line 82
[26-Jul-2017 23:00:53 UTC] PHP Stack trace:
[26-Jul-2017 23:00:53 UTC] PHP 1. {main}() /home/asmecher/git/ojs/index.php:0
[26-Jul-2017 23:00:53 UTC] PHP 2. PKPApplication->execute() /home/asmecher/git/ojs/index.php:68
[26-Jul-2017 23:00:53 UTC] PHP 3. Dispatcher->dispatch() /home/asmecher/git/ojs/lib/pkp/classes/core/PKPApplication.inc.php:239
[26-Jul-2017 23:00:53 UTC] PHP 4. PKPPageRouter->route() /home/asmecher/git/ojs/lib/pkp/classes/core/Dispatcher.inc.php:134
[26-Jul-2017 23:00:53 UTC] PHP 5. PKPRouter->_authorizeInitializeAndCallRequest() /home/asmecher/git/ojs/lib/pkp/classes/core/PKPPageRouter.inc.php:233
[26-Jul-2017 23:00:53 UTC] PHP 6. call_user_func:{/home/asmecher/git/ojs/lib/pkp/classes/core/PKPRouter.inc.php:372}() /home/asmecher/git/ojs/lib/pkp/classes/core/PKPRouter.inc.php:372
[26-Jul-2017 23:00:53 UTC] PHP 7. DashboardHandler->index() /home/asmecher/git/ojs/lib/pkp/classes/core/PKPRouter.inc.php:372
[26-Jul-2017 23:00:53 UTC] PHP 8. SubmissionsListHandler->getConfig() /home/asmecher/git/ojs/lib/pkp/pages/dashboard/DashboardHandler.inc.php:62
[26-Jul-2017 23:00:53 UTC] PHP 9. SubmissionsListHandler->getItems() /home/asmecher/git/ojs/lib/pkp/controllers/list/submissions/SubmissionsListHandler.inc.php:70
[26-Jul-2017 23:00:53 UTC] PHP 10. PKP\Services\PKPSubmissionService->getSubmissionList() /home/asmecher/git/ojs/lib/pkp/controllers/list/submissions/SubmissionsListHandler.inc.php:159
[26-Jul-2017 23:00:53 UTC] PHP 11. DAOResultFactory->toArray() /home/asmecher/git/ojs/lib/pkp/classes/services/PKPSubmissionService.inc.php:81
[26-Jul-2017 23:00:53 UTC] PHP 12. DAOResultFactory->next() /home/asmecher/git/ojs/lib/pkp/classes/db/DAOResultFactory.inc.php:218
[26-Jul-2017 23:00:53 UTC] PHP 13. ArticleDAO->_fromRow() /home/asmecher/git/ojs/lib/pkp/classes/db/DAOResultFactory.inc.php:104
@asmecher : the first warning is caused by the absence of collation index in config array. We could add a default collation. Would 'collation' => 'utf8_general_ci'
be a good default value?
@kaschioudi, yes, I think so.
@asmecher and the second notice addressed here: https://github.com/pkp/ojs/pull/1471
@kaschioudi can you take a look at the updated docs in this PR and merge if it looks alright?
@kaschioudi I've pushed up the changes I've been working on the past couple days. I just cherry-picked your commit into my branch, but I've also rebased on latest master
so you might want to just work off my branch as a base.
https://github.com/NateWr/pkp-lib/tree/i2163_api_prop_list https://github.com/NateWr/ojs/tree/i2163_api_prop_list https://github.com/NateWr/omp/tree/i2163_api_prop_list https://github.com/NateWr/ui-library/tree/i2163_api_prop_list
The entity property pattern looks really nice. I've brought the API in line with the documentation (at least what's promised for 3.1), making a few changes to the documentation in places and adding some of the missing stuff. Here are some of the changes to notice:
_href
from objects that have no API endpoint._submissions
endpoint is now using the entity property approach. There's still some duplicated code, but I hope to be able to get the lists running on submissions
before 3.1 or shortly after.Submission::getProperties::fullProperties
gives a good hint of what the hook is for).Here are my TODO notes. Maybe we can split this work up between us a bit:
_href
s returned are compatible with disable_path_info
.http
URL used in _href
s, _parent
and coverImageUrl
.args
that we're passing into getSummaryProperties
and getFullProperties
. I think request
is always required. And I've made it so it works without slimRequest
in all cases. But I think there are a couple examples where other things are required (like the galley expects a parent
).ui-library
commits to finish.PKPAuthorService
and AuthorService
? I'm not sure if we'll use it but don't care if we leave it in for now./submissions
, passing orderBy=title&orderDirection=ASC
doesn't seem to work.(edit - I've updated with a link to the ui-library
changes for OJS. Going to check on OMP now.)
(edit 2 - I've added an OMP branch and a few more commits that should get everything separate but still working.)
@NateWr
Do we need PKPAuthorService and AuthorService? I'm not sure if we'll use it but don't care if we leave it in for now.
Since authorString
and shortAuthorString
values are pulled directly from Submission
data object, I don't think there's a need for PKPAuthorService
and AuthorService
. We could remove them.
In /submissions, passing orderBy=title&orderDirection=ASC doesn't seem to work.
I can't reproduce this. I just tested and it seems to be working fine. Could you confirm? If no please describe your use case.
It'd be good to find a way to document the required args that we're passing into getSummaryProperties and getFullProperties. I think request is always required. And I've made it so it works without slimRequest in all cases. But I think there are a couple examples where other things are required (like the galley expects a parent).
We could document the expected fields for each service getSummaryProperties
/getFullProperties
function implementation phpdoc. Something like below:
/**
* function description
*
* @param $args array extra arguments
* $args['request'] PKPRequest
* $args['slimRequest'] SlimRequest
*/
What do you think?
@NateWr : am I missing something or it's not longer possible to download files through the api?
@asmecher @NateWr
I have looked into an approach to modify the Authorization framework so that error codes are also associated with corresponding error messages when access is denied. This way the api will be capable of returning correct error codes (400, 403 or 404) instead of always returning 404.
My initial approach was to add a helper method, say mapErrorMessageToErrorCode()
, to ApiAuthorizationMiddleware
so that whenever authorization fails it's possible to figure out an error code based on the error message. But when I look closer to the policies, let's say SubmissionRequiredPolicy
, the error message user.authorization.invalidSubmission
could be returned for multiples reasons. For instance
The other approach is to modify policies base classe by introducing a new $_errorCode
property and modify all the constructors to support an additional $errorCode
parameter along with $errorMessage
. After more digging I realized there is no single base class which policies extend. Some policies extend AuthorizationPolicy
whilst others extend PolicySet
. I thought about using traits
(for code reuse) but at least PHP 5.4 is required (http://php.net/manual/en/language.oop5.traits.php).
Even if we don't mind the code duplication that is required by the latter approach's implementation, we still have to browse through most effect()
methods to set appropriate error codes (just like in the initial approach) when multiple rules lead to AUTHORIZATION_DENY
.
(I hope these last paragraphs are clear enough. these things are not simple to explain in text)
Based on the above, I suggest we delay this task until we refresh the authorization framework.
Based on the above, I suggest we delay this task until we refresh the authorization framework.
I agree. Thanks for the overview. That makes sense. We'll live with the error codes as they are in 3.1 and flag it as an area for improvement.
We could document the expected fields for each service getSummaryProperties/getFullProperties function implementation phpdoc. Something like below:
I like it. Have you made any commits yet or am I safe to continue pushing to the branches above?
am I missing something or it's not longer possible to download files through the api?
We do provide the public URL for galleys. But we'll need a proper mechanism for files in the API. I just thought it'd be a post-3.1 thing.
In /submissions, passing orderBy=title&orderDirection=ASC doesn't seem to work.
I can't reproduce this. I just tested and it seems to be working fine. Could you confirm? If no please describe your use case.
You're right. I couldn't reproduce this.
@NateWr I added this fix. Please cherry-pick and add it to your branch. https://github.com/kaschioudi/pkp-lib/commit/92bddcb0614f480a12fb78a644b211137460568b thanks.
Cheers! I've got it in now.
@kaschioudi I think I've worked out the test issues. Do you have time to code review the PRs in this comment? https://github.com/pkp/pkp-lib/issues/2163#issuecomment-337226135
@NateWr Everything looks fine to me :thumbsup:
:tada: Really happy to have this merged in. Ready to start build more API stuff. :)
@NateWr and @kaschioudi, is this one ready to be closed now that the OMP warning fix has been merged?
:boom:
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