Because MW speeds the page load along by late-loading as much of the JS and CSS as possible, and I'm going along with that design, we load most of the WW javascript code at the end of the page load. As a consequence, if you click a WW link, say to create or destroy a background job, if the page hasn't finished loading you get the no-javascript fallback implementation of the link: it takes you away to a ManageProject page that does the operation before it loads.
I'd like to put in a minimal bit of JS code to intercept those clicks and make them do the intended thing, which is almost surely to do the operation inline just as they do after the page is done loading.
Anonymous
I started work on an implementation where it slaps a spinner on the link when you click it, and puts it on a queue to get dealt with later when the code arrives.
But then I was thinking, if I click a link while the page is loading, it'll often be because I want to do the operation now, without waiting. So now I'm thinking about an implementation that starts loading the necessary code right away, regardless of whether it slows down the overall page load. To do this I guess I would package up the minimal code you need to create and send a WW API request, so I could load and use that when there's a click. The code that's needed to deal with the result of the API request, on the other hand, I could load after sending the request...
So minimally I need:
ww.injectSpinner : immediately - load before the HTML, because spinner needs to appear with no delay
ww.api : load quickly when link is clicked
ww.confirm : load quickly when link is clicked (if needed).
ww.notify : load after API request is sent
Or if api and confirm (and whatever apparatus they need) are small enough I could just top-load them along with injectSpinner.
It's nice to set up the links after they load, with jquery, because you can have various data in the function scope. But that's hopeless for this, because we need to not have a race condition when a link is created and doesn't yet have the right behavior. So I think the only way is to ship each <a> tag in the html with an onclick="doSomething()" attribute whose doSomething() string is as compact as possible.
Also we want the top-loaded JS resources to be as compact as possible, which means not defining 27 different onclick functions for all the different kinds of links.
I think the only sensible design is onclick="wwlink({action:'ww-create-background-job',project='Sandbox',target='test-mk.out'})". Even though it's not very compact and in some cases providing the full API data like that will be redundant, as it is stored nearby in the DOM and could be retrieved from there (I do it that way now). In other words, this will provide a good response when the page hasn't fully loaded, at the cost of the time it takes to fully load.
This also presumes that wwlink will be able to infer the confirmation, success and failure messages to use for each action, and whatever else it needs along the way. I think that can be done.
Side note: Do I want the API action names to be shorter?
Weird thought: Each of these links needs to also have a non-JS target, which is its href attribute, something like href='//lalashan.mcmaster.ca/theobio/whatever/index.php?title=Special:ManageProject&project=Sandbox&ww-action=create-background-job&action-target=test-mk.out'. All the info is there redundantly. Would it be too horrible to have wwlink() infer the info from that href string? I could impose more regularity on the form of those URL parameters.
This goes back to the complications I encountered in [#434] ("Keep AJAX DRY"). For one thing, there are clicks, "remove/remake" for example, that don't correspond to a single API action. I guess if I had to I could make them correspond, or use a framework that allows a way to make exceptions.
Related
Bugs: #434
Maybe I can require that all parameters to WWAction have names starting with 'ww-', and no others do? Or rather, no others in URLs that might include 'ww-action'. I think there will be some others in ImportProjectFiles that don't matter.
That will make it simple to parse out into an API call. I can just write onclick="wwlink()" if it's sufficient to infer the API call from the href, and onclick="wwlink({action:'whatever',whatever:'whatever else'})" if it isn't (or even onclick="wwlink({action='ww-remove-project-file',...});wwlink({action='ww-get-project-file',make=1,...})").
It's unfortunate that sharing $wwConfirmationsForApiActions between the PHP and JS seems to require passing it to the JS as a config variable, which puts it all in the top of the HTML. This design here seems like it'll also necessitate a $wwSuccessMessagesForApiActions, $wwFailureMessagesForApiActions, and maybe even other things? Most pages won't use these and if they do they won't use much of them, so I'd rather not front-load them all for every page!
Maybe I can standardize the messages' names and pass them only using the messages infrastructure? That would let me wrap them into ResourceLoader packages that are only loaded when needed. I might be able to only pass a short list of $wwActionsThatNeedConfirmation or something at the top.
Note before I get too busy with this, I should double-check that config variables can't be late-loaded :).
I can use mw.message('xyz').exists() to do confirm/alert behavior just by defining certain messages or leaving them undefined. The problem is that they have particular arguments: mw.message('wwb-confirm-destroy-background-job', jobid) vs. mw.message('wwb-confirm-create-background-job', projectname, target). I am frustrated.
I would really like to have a DRY design, and since the confirmation/success/failure messages for all these actions are used in both PHP and JS, I want to define them in PHP and pass the data to JS without duplicating it. I can pass the messages, e.g.
but how to tell it to pass the filename and project name as arguments without writing a PHP function and a JS function?
Well, that's what $wwConfirmationsForApiActions does. But unless I do something weird like requiring them to have the same parameters, I'll have to provide a similar structure for success and failure messages.
What if I ignore the standard $1 and $2 structure and put things like $target and $project into the message string???? I think that's a bad idea, though I do like it...
Or I could write some weird file that generates both JS and PHP code???
Well, I think I'm just going to have 3 arrays and put them in the page header for now.
But I can use smart defaults and remove redundant information.
For action 'ww-X', do a confirmation if message 'ww-X-confirm-message' exists. If so, put a list of arguments to that message in $wwApiMessages (note shortened name).
Success and failure messages for 'ww-X' are 'ww-X-success' and 'ww-X-failure'. Fall back to default messages if those messages don't exist.
So $wwApiMessages['ww-X'] may contain a 'args' key, defining args to be used in confirmation/success/failure messages alike, or a 'confirmation-args', etc.
For the record: message 'ww-X-confirm-button' also needs to exist if confirming (the title for the "yes" button. The "no" button will always be message 'ww-cancel'.).
Cool, $wwApiMessages is working and is pretty minimal. Leaving it at page top seems okay to me.
The staged loading seems fine:
The background-job handling JS is separated out into modules stored in the Background/ directory of the source code. They hook in so that they get loaded with the above modules at the appropriate stages.
Right now I have the ww-create-background-job and ww-destroy-background-job actions working in the new system, and the automatic ww-list-background-job update. Actually, only some of the create-background-job interfaces.
So I need to:
Then I'll be done with this redesign, and I can go back to making all the other WW actions have JS links.
Also:
Done, all the above. Ready to deploy to lalashan, I think, when I'm not horizontally bound.
It's a bit intricate, but also nice and clean. I converted the kill and merge actions from WWAction to Api with ajax links in just a few minutes, because the framework is now there to do pretty much all of it automatically. This is quite promising for the next step, which is to convert all the non-background-job actions, such as sync-source-files, etc.
The only real exception to the cleanliness is how I did the 'background make' form on the MP page. There's some trick to handling buttons with names in English that get translated, but I haven't used it, whatever it is, I used a nasty hack instead. To convert the regular make button to ajax as well as the background one I'll have to revisit this and do it right.
BTW the mw.hook() apparatus is really clean and easy to use - I rewrote the reload-list-of-background-jobs code with it and it immediately worked without any debugging whatsoever!
Oh man, I tried to deploy that and it caused all kinds of trouble.
Merge conflicts in the live code, causing whitescreens, and version incompatibility in the PHP and JS causing breakage when the code was in fact running.
I've backed off to an older revision of the WW code. I moved the working copy aside for the time being. Some fixes I made in the live WW code over the last few days are probably reverted, as they're only in the moved-aside code. I'll fix that ASAP.
These changes to the API/WWAction design need to be worked over in MW 1.19 and 1.21, as there are compatibility issues involving WW code that uses MW features that were added since then.
Fixes I made in the live code are back in the live code now.
Meanwhile, back on my laptop:
Problems in 1.19
OK, fixed those, now seems pretty good.
Problems in 1.21 or 1.22, now that I've fixed the above?
I think it's ready for deploy now, but again I'm going to sleep. Later...
The page load is kind of quick when the wiki's on my laptop, so I'm looking forward to trying out the while-page's-loading part when it's on lalashan and I'll be able to see if it makes a difference.
These new JS features are deployed on lalashan now. Seems to be working well on both 1.21 and 1.19.
Unfortunately :) the response time is good from where I am at UCB, so no word on how well it works when pages are slow...
Seems like sometimes clicks while the page is loading do the non-JS behavior. What's up with that? And how can I troubleshoot it?
Followup: made a link for testing on a sandbox page: it calls preventDefault() to stop the browser from going to the link destination, console.log() to record that it happened, and alert() to make it obvious.
It looks like clicks get lost if they happen while the page is doing the blocking loads in the head element? They don't browse away to the link target though, they just don't do anything at all.
I notice that the .js file where wwlink() is defined is loaded from load.php, so while it's a blocking load at the beginning of the page, it's not inline in the page source, and I can imagine clicks being possible while waiting for that resource to arrive? I thought it had to be received before any html could be displayed, but it doesn't seem so from the tests I was doing yesterday. I could look into that more. Ideally I'd like to put the minimal wwlink() code right into the page's head element.
well I tried some different loads from a lalashan wiki and I didn't get it to do the wrong thing.
Still not finding anything useful in the search engines. Maybe I'll have to wait for a reproducible example of bad behavior. Maybe I'll work on the question of what happens when you middle-click a link, and see if anything emerges.