Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Page life cycle with pagecontainer: Reasons to keep 'pageshow' event #7283

Closed
wauwau0977 opened this Issue · 29 comments

8 participants

@wauwau0977

This is not a bug request, i would like to raise an design / architectureal issue. Starting in JQM 1.4 onwards, page life cycle events (e.g. pageshow) are deprecated in favour of 'pagecontainershow'. Please consider the case below, where i think it's not a nice replacement.

We use JQM in a rather big project with several developers, where we try to split up the app in functional "modules". For this we also use separated files to give the app a certain structure. It was very convenient to have a way to implement the page specific handlers on the page itself for actions like templating and custom JavaScript preparations, etc...

Now, with the container i still can do that, but i must control the logic centrally and then dispatch again to the page module... Architectural wise, that's a strange approach...

I would love to see again a way to bind "life cycle" logic to a page again. Since programmers will work on pages as their logical work unit and they want to control the life cycle on that logical work unit...

Cheers,
Patrick

@Palestinian

I have made this post explaining page cycle of a page in jQM 1.4. Unfortunately, between all events, only two events can be attached/delegated to page div pagebeforecreate and pagecreate.

Initializing plugins such as Google Maps won't function as they should because page div is still hidden and dimensions are unknown. They should be initialized on pagecontainershow only.

Using if statement or switch/case to identify on which page pagecontainershow emits, isn't an ultimate solution.

I second @wauwau0977 on this issue. Please, we want the old events back.

Thanks!

@simsam7

I support @wauwau0977 and @Palestinian on this, the new 'pagecontainershow' does not really encapsulate one of the elements of jQM that I have always seen to be a core feature, namely to handle page cycle events with relative ease and clarity. The current solution would make me rather use another framework for more complex scenarios, since it architecturally inhibits a more modular approach.

@livewire1407

I agree as well, I feel as if jQM 1.4 took a step backwards in this area, requiring much more code and adding much more confusion to things that should be fairly "simple"

@jtara

You can do this yourself, but why should you have to write a work-around for something so fundamental and useful?

It is useful to fire events on the objects that have the greatest need to know about them. Most of these events concern (primarily) the page that is involved. It is almost always page-related code that is interested in pagebeforeshow, pageshow, pageinit, etc. There are limited instances where this might be of interest globally, and in those few cases, you can allow the event to bubble to the document and catch it there.

An "if" statement in a document-level callback is not a good alternative. It means that code that applies to a specific page (or class of pages) has to be dealt-with at the document level. It makes it difficult to write modular code. Given the level of sophistication of the average jQuery Mobile developer (low - not meant to insult anybody, just an observation...) this is going to create tons of awful spaghetti-code.

This seems an issue of purity of concept winning over ease of use. The end effect is it appears the developers want to make it difficult to use. Makes no sense.

Work-around is to catch these events on $(document) and reflect those that are applicable (e.g. when the page actually exists) to the page. But you'll have to invent new names because the old events have only been deprecated, not removed.

So, now everybody is going to be writing their own code (with their own bugs) and inventing their own names for these events.

Why not do this in a standardized way and make it an option?

This is somewhat less efficient than the old way, because now the document will get an event, and then the user's own code will reflect it down to the page, from which it will then (potentially) bubble back up to the document. (Which is another reason why the reflected events have to have a different name!)

@arschmitz
Owner

Hi just want every one to know we are listening to you and reading these comments. I am currently working on a solution i hope will please everyone. I will post more details shortly once a few things are finished up and the changes will be coming in 1.4.3

Thank you for your patience on this.

@jtara

To clarify, I'm going to post one of our typical .js files (a short one). I believe that this is typical of the type of code that wawau0977 is talking about when he speaks of "modules". I suspect this is a pretty common design pattern for jQuery Mobile JS code.

Of note is the caching of various in-page selectors on pageinit. The new system makes writing this kind of code impossible without reflection of events down to the pages.

Edit: OK this isn't really a great example. If we want, say, to do something on pageshow, that would be done inside this closure as well. I'll edit-in an empty example below:

/*jslint browser: true, devel: true, vars: true, unparam: true, white: true, indent: 2 */
/*global jQuery */
(function photoEditListSelfEx($, window, document, undefined) {  // Ignore JSLint error on undefined
  'use strict';

  // Note that edit_list.js now handles most of photo_edit's JS needs.
  // Only need new Photo and new Drawing code
  $(document).on('pageinit', '.page_edit_photo_list', function() {
    var $page = $(this);
    var auditID = $page.data('audit-id').toString();
    var $list = $page.find('.one_to_many_item_list');
    var $newPhotoBtn = $list.find('.nav_to_new_photo');
    var $newDrawingBtn = $list.find('.nav_to_new_drawing');

    $page.on('pagebeforeshow', function() {
      // Do somewhere here before the page is shown. Typically, populate something,
      // hide/unhide something, clear "active" classes, etc. etc.
    });

    $newPhotoBtn.on('vclick click', function(event) {
      event.stopPropagation();
      event.preventDefault();
      if (event.type === 'click') { return; }
      var $a = $(this);
      var $btn = $a.closest('.ui-btn');
      $btn.addClass('ui-btn-active');
      $.post( '/app/Photo/new_photo', {id: auditID} );
     });

    $newDrawingBtn.on('vclick click', function(event) {
      event.stopPropagation();
      event.preventDefault();
      if (event.type === 'click') { return; }
      var $a = $(this);
      var $btn = $a.closest('.ui-btn');
      $btn.addClass('ui-btn-active');
      $.post( '/app/Photo/new_drawing', {id: auditID} );
     });

  });

}(window.jQuery, window, document));
@arschmitz
Owner

@jtara thank you for your example however pageinit is an event issued by the page widget it is only being deprecated because it is no longer needed. It's original purpose was it provided a particular timing needed for some widget auto enhance code. However this was due to our old event based autoinit which we have since replaced with a non event based solution which allows for better timing so no the page widgets built in create event can be used and init will be removed. If you have a use case where pagecreate does not properly replace pageinit please open a separate issue for this.

However this issue is about page events vs pagecontainer events which is a very different issue.

Note: looking briefly at your code the only thing you need to do is change pageinit to pagecreate in your first binding.

@jtara

^ Yes, I understand pagecreate is roughly equivalent to old pageinit.

pagecreate is still fired on the page, not the container? (Yes, I see that the page widget now has only two events - pagebeforecreate and pagecreate...)

But what about pagebeforeshow (or equivalent?) How can I get a callback on the equivalent of pagebeforeshow and package it as above? This will still work only because pagebeforeshow is currently only deprecated. But it is slated for removal in 1.5.

OK, I guess I can listen at the document level within my module, and test with an if statement. Was a lot neater to just bind to pagebeforeshow, pageshow, pagehide etc. within the pageinit (or pagecreate) callback.

This means if you have a bunch of modules, you will have a bunch of document-level callbacks needlessly listening to stuff they don't need to listen to, testing with an if statement, and then deciding "nope, not interested in this, this isn't me!"

Is this inefficient? Probably not very. Should be roughly the same as what is already going on when you delegate to document. But you'll note that my code does't delegate to document. It binds to page (for pagebeforeshow in the example.) And is listening on document and if-testing inefficient vs that binding? Probably only slightly so. But it's more code than was previously needed, and is a messy solution for the developer.

Can we please have something in the documentation similar to this for 1.4?:

http://bradbroulik.blogspot.com/2011/12/jquery-mobile-events-diagram.html

@Palestinian

@arschmitz thank you, we look forward to seeing page events revived.

@wauwau0977

@arschmitz, @jtara, @Palestinian, @simsam7, @livewire1407: These are great news. What a great promise and what a quick reaction time... Big thank you :-)

@Palestinian

@wauwau0977 Yup, great news from a great team.

@simsam7

Thanks @arschmitz and the jQM team for responding to feedback like this & also thanks to @Palestinian and @wauwau0977 for pushing for it in the way that you have! :-)

@arschmitz
Owner

Ok sorry to keep every one waiting but wanted to make sure a few things were ironed out before saying anything. First i would like to say thank you for all the feedback we do take this and the use cases you present seriously.

We however will not be keeping the old page events these were fundamentally flawed because page events should be issued by the page widget but most of these events the page widget is oblivious of. Previously these events were emitted from our navigation system so they had no real paradigm to follow. However in 1.4 we created a page container widget which now handles all the navigation. The pagecontainer widget is now the home of these events and widget have very specific rules which they follow for emitted events they are always emitted on the actual element the widget is initialized on, they always use the name of the widget as a prefix. The page container widget is the logic place of these events because it is where the pages are controlled and it is what is performing all of the actions in the described by the events.

However the pagecontainer events were not implemented very well and this needs to be fixed. The first this we are going to do to fix this situation is all pagecontainer events will now contain a toPage and prevPage property on the ui object passed to the event handler so it will always be possible to know what two page you are dealing with prevPage will always be a jQuery object containing the current page ( except on initial page load where it will be undefined ) toPage will initially be a string containing either the string passed to the change method or the href on the link clicked and will switch to a jQuery object once the page is loaded ( except when it is the initial page load or when a jQuery object or DOM node is passed to change in which it will contain those )

The next problem we will fix is that only some of the page events had a corresponding pagecontainer replacement which we feel is inconsistent and confusing so now all page events ( except those actually issued by the page widget ex: pagecreate, pagebeforecreate, pageinit ) will now have a corresponding pagecontianer replacement.

you can see a table of our plans here https://docs.google.com/spreadsheets/d/10FBrCSUFCmLIbJlODJCXvLNmKSUqQesWIX41H26YWsc/edit?usp=drive_web

With these all you will need to attach your events just as before you will just need either an if or a switch or similar to check if the event is for one of the pages your looking for.

Additionally i have created 2 tools to to help with using the new events.

First is a pair of jQuery plugins called onPage these allow you to attach events the same way you did in 1.3 with one minor change instead of doing something like $( document ).on( "pageshow", "#page-id", function(){}); with on page you will just do $(document).onPage( "show", "#page-id", function(){}); so you basically just move the word page from the event name to the method name. This also has the additional benefit that internally it sets these via the option in the pagecontainer widget. This means events bound this way are called directly and there are no actual event handlers ever bound. This leads to a performance gain if you bind a lot of page events. offPage works the same way except to remove event handlers. you can see more info on this here https://github.com/arschmitz/jquery-mobile-onpage its not usable yet as it depends on related changes in the pagecontainer events in mobile but you can check it out and get an idea and feel free to file issues.

The second tool is a plugin to help in debugging events you can enable it for groups of events and it will log or alert all of those events along with their target and their ui object and the description of the event from the api docs. This tool is tied directly to the api docs and will always stay up to date and has an option to turn on or off deprecated events. you can see this tool here https://github.com/arschmitz/jquery-mobile-event-debugger. in addition we are working on demos showing all of these new events their usage as well as demos for the plugins to help with converting.

Finally page events will remain for 1.5 to give additional time to switch but all updates I mention here will be in 1.4.3

I know this is was long but i wanted to fully explain the changes we are planning so we could get everyones opinion.

@Palestinian

@arschmitz Thank you for sharing those handy plugins. Could you please tell us why jQM team has decided to deprecate "Page Events" and replace them with "Page Container"? Is it performance issue? I assume that using if or switch/case would affect performance negatively. Please correct me if I am mistaken. Thank you once again.

@jtara

The event debugger is nice. It's nice some effort is being put into cleaning-up the documentation.

That said, this is not what I was hoping for.

"With these all you will need to attach your events just as before you will just need either an if or a switch or similar to check if the event is for one of the pages your looking for."

That's what I'm trying to avoid. OK, next?

$(document).onPage( "show", "#page-id", function(){});

I don't find that particularly useful, either. It's equally inconvenient as the first solution. The sensible thing here is to have events fired on the page, not it's container.

The new way is clean and pure, to be sure. The old way was better for programmers. Silly me, I like programming stuff to be better for programmers...

A plugin to bring back the old 1.3 events would be fairly trivial. Just catch these events on the page container and reflect them to the page. Unfortunately, this means a name change for all of the events to maintain compatibility until the old events are finally removed.

Actually, it would be better to just rely on the existing support for the old events until they are removed. The plugin should backfill the new events that have been added in 1.4. Test for existence of the old events, and if not present (1.5 or whenever...) then also reflect the corresponding page container events to the old event names (on the page object).

And that's my plan. This "solution" is not useful to me. A plugin providing the old events (as well as new ones fired on page) allows me to rescue existing code, and, as well, continue to work with page-level events (Imagine that! Events that concern pages fired on pages! What will they think of next? Donuts with holes?), which are much more convenient to work with.

@livewire1407

+1 for the above post.

At this point, I just wish the pagecontainer events were delegate-able.

If I could do $(document).on('pagecontainershow', '#onePage", function(event, ui) {

instead of $(document).on('pagecontainershow', function(event, ui) { if($.mobile.activePage[0].id == '#onePage'){

I'd consider that a win at this point (which is sad). Realistically if my projects weren't so involved, I'd switch from jQuery Mobile to something else, after hearing this "resolution".

I've been working with jQuery Mobile since alpha (I did a research project on it while it was in alpha when I was in college), and with the 1.4 changes I just can barely stand it now.

@simsam7
@ottoville

With pagecreate event it was possible to lauch event when jqm page loaded the first page.
On new pagecontainer wigder the pagecreate event does not launch on first page, but only when navigation occurs after first page load.
Is there some way to launch pagecontainer events on first page?

@arschmitz
Owner

@ottoville if you look at what i posted you will see that pagecreate and pagebeforecreate are both page widget events and are not changing and are not deprecated in fact as long as page is a widget its not even possible to remove the pagecreate event as it is issued as part of the widget factory.

@Palestinian switch and if statements have in all reality no perf impact please see http://jsperf.com/switch-and-if-perf while the switch and if are of course slower all three tests are in the range of 500 MILLION operations per second I can 100% promise this will not impact your application. In fact delegated events in the way page events were generally bound to before have to perform this same logic under the hood anyway. In a delegated event you listen to the event on the parent then using an if statement it is checked if the target matches the page you bound to.

@simsam7 @jtara i fail to see how the onPage solution differs from from what we originally had with page events you bind to them in the same way the ONLY difference is you move the word page from the event name to the method name??

@ottoville

What about pagebeforeshow event, docs say that it is deprecated. That event is also triggered on initial load but new pagecontainerbeforeshow does not trigger on initial load.

@ottoville

There are some bug with these new events, please have a look this jsfiddle
http://jsfiddle.net/2gMh2/1/

It is possible to register event listeners only after DOM has been fully loaded. I dont see any reason for that, it is against event listening model.
At some point when jQm load, it clear all event listeners from page container.

@ottoville

@Palestinian
Not it does not, see the jsfiddle I made from previous post.
The old event used to fire on initial load, but not the new one.

@Palestinian

@ottoville Attach pagecontainer events listeners to $(document) or wrap them in pagecreate.

http://jsfiddle.net/Palestinian/2gMh2/2/

@Palestinian

@arschmitz Once again, thanks for the clarification. Btw, I have tested the onPage plugin, but couldn't get to work, could please provide us with a working example?

@arschmitz
Owner

@Palestinian No problem we want this to work and be as convenient as possible however we also have to make an api that makes sense. the onPage plugin depends on changes to JQM that are currently only in a branch this branch includes demos on use of both the onPage plugin and the event debugger once this branch is ready I will post a link here. This should be pretty soon finishing this and getting 1.4.3 out is my current top priority.

@ottoville

It is weird that it is not possible to attach pagecontainerbeforeshow listener on document.body. As the documentation states, the page container === body element, so I dont see why $(document) does the job but $(document.body) does not.

The old beforeshow event triggered on page, so it was possible to have reference to page by using 'this' or evt.target. The new pagecontainerbeforeshow triggers on page container. I cannot see reference to page itself in event attributes. It is possible to use $(this).pagecontainer( "getActivePage" )[0], but that does not work on initial load.

So how to have reference on page that triggered pagecontainerbeforeshow, when having a initial load?

@arschmitz
Owner

@ottoville please read the updates above we are planning on making #7283 (comment) you will reference the to and from page via the ui param passed to the callback ui.toPage and ui.prevPage also if you use the onPage plugin it will keep the this context as the page just as it was in 1.3.x

as far as the body element you should be able to bind to the body for these events just fine if you can produce an example showing other wise in jsbin please open an issue for it. Remember if you bind in this way too soon the body may not exist yet, and since you are not delegating your handler you wont get the event. If you do it in document ready you wont get the initial one because you are binding too late.

@jaspermdegroot jaspermdegroot added this to the 1.4.3 milestone
@arschmitz arschmitz referenced this issue from a commit
@arschmitz arschmitz Pagecontainer: make all page events go through triggerWithDeprecated
To make sure all former page events have a page container counterpart,
and that this counterpart has both a toPage and prevPage prop on the ui
object
Also add a demo of how to debug pagecontainer events

Fixes gh-7063
Fixes gh-7283
Fixes gh-7176
Closes gh-7285
5319adc
@arschmitz arschmitz closed this issue from a commit
@arschmitz arschmitz Pagecontainer: make all page events go through triggerWithDeprecated
To make sure all former page events have a page container counterpart,
and that this counterpart has both a toPage and prevPage prop on the ui
object
Also add a demo of how to debug pagecontainer events

Fixes gh-7063
Fixes gh-7283
Fixes gh-7176
Closes gh-7285
3d4d7dd
@arschmitz arschmitz closed this in 3d4d7dd
@arschmitz arschmitz referenced this issue from a commit
@arschmitz arschmitz Pagecontainer: make all page events go through triggerWithDeprecated
To make sure all former page events have a page container counterpart,
and that this counterpart has both a toPage and prevPage prop on the ui
object
Also add a demo of how to debug pagecontainer events

Fixes gh-7063
Fixes gh-7283
Fixes gh-7176
Closes gh-7285

(cherry picked from commit 3d4d7dd)
40b626e
@agcolom agcolom referenced this issue from a commit in agcolom/jquery-mobile
@arschmitz arschmitz Pagecontainer: make all page events go through triggerWithDeprecated
To make sure all former page events have a page container counterpart,
and that this counterpart has both a toPage and prevPage prop on the ui
object
Also add a demo of how to debug pagecontainer events

Fixes gh-7063
Fixes gh-7283
Fixes gh-7176
Closes gh-7285
d76c5c9
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.