diff --git a/demos/jqm-contents.php b/demos/jqm-contents.php index 08181c0d17f..7f6ae13dc2c 100644 --- a/demos/jqm-contents.php +++ b/demos/jqm-contents.php @@ -136,6 +136,7 @@
  • Linking pages
  • PHP redirect demo
  • Hash processing demo
  • +
  • Page Navigation Events
  • diff --git a/demos/page-events/alertevents-2.php b/demos/page-events/alertevents-2.php new file mode 100644 index 00000000000..2cd1dad42e5 --- /dev/null +++ b/demos/page-events/alertevents-2.php @@ -0,0 +1,59 @@ + + + + + + Page Events - jQuery Mobile Demos + + + + + + + + + + + +
    + +
    +

    jQuery Mobile

    +

    Demos

    + Menu + Search +
    + +
    + +

    Navigate between pages and open and close panel and popup widgets to see which events fire and the data these hold

    + Go To Page 1 + Go To Page 3 + +
    + + + + + + + +
    + + + diff --git a/demos/page-events/alertevents-3.php b/demos/page-events/alertevents-3.php new file mode 100644 index 00000000000..278812d4fa8 --- /dev/null +++ b/demos/page-events/alertevents-3.php @@ -0,0 +1,60 @@ + + + + + + Page Events - jQuery Mobile Demos + + + + + + + + + + + +
    + +
    +

    jQuery Mobile

    +

    Demos

    + Menu + Search +
    + +
    + +

    navigate between pages and open and close panel and popup widgets to see which events fire and their data

    + Go To Page 1 + Go To Page 2 + Open Popup + +
    + + + + + + + +
    + + + diff --git a/demos/page-events/alertevents.php b/demos/page-events/alertevents.php new file mode 100644 index 00000000000..8ee2e9c5c89 --- /dev/null +++ b/demos/page-events/alertevents.php @@ -0,0 +1,68 @@ + + + + + + Page Events - jQuery Mobile Demos + + + + + + + + + + + +
    + +
    +

    jQuery Mobile

    +

    Demos

    + Menu + Search +
    + +
    + +

    navigate between pages and open and close panel and popup widgets to see which events fire and their data

    + Go To Page 2 + Go To Page 3 + Go To Page that does not exist + Open Popup + + + +
    + + + + + + + +
    + + + diff --git a/demos/page-events/docs.php b/demos/page-events/docs.php new file mode 100644 index 00000000000..0fe8dacca40 --- /dev/null +++ b/demos/page-events/docs.php @@ -0,0 +1,8 @@ + diff --git a/demos/page-events/index.php b/demos/page-events/index.php new file mode 100644 index 00000000000..9bca9add3a9 --- /dev/null +++ b/demos/page-events/index.php @@ -0,0 +1,53 @@ + + + + + + Page Events - jQuery Mobile Demos + + + + + + + + + +
    + +
    +

    jQuery Mobile

    +

    Demos

    + Menu + Search +
    + +
    + +

    Page Events Demo

    + +

    This demo illustrates the different page events that fire during a page's life-cycle and the elements on which they are triggered.

    + +

    Event Debugging

    + +

    This demo uses the jquery-mobile-event-debugger plugin from arschmitz available at https://github.com/arschmitz/jquery-mobile-event-debugger. + This tool allows jQuery Mobile events to be logged or alerted as they happen for inspection and are agumented with descriptions from the API docs.

    + + View Demo + View Demo With Alerts + +
    + + + + + + + +
    + + + diff --git a/demos/page-events/logevents-2.php b/demos/page-events/logevents-2.php new file mode 100644 index 00000000000..ee97d92ddab --- /dev/null +++ b/demos/page-events/logevents-2.php @@ -0,0 +1,59 @@ + + + + + + Page Events - jQuery Mobile Demos + + + + + + + + + + + +
    + +
    +

    jQuery Mobile

    +

    Demos

    + Menu + Search +
    + +
    + +

    Navigate between pages and open and close panel and popup widgets to see which events fire and their data.

    + Go To Page 1 + Go To Page 3 + Open Popup + +
    + + + + + + + +
    + + + diff --git a/demos/page-events/logevents-3.php b/demos/page-events/logevents-3.php new file mode 100644 index 00000000000..c0b6189a056 --- /dev/null +++ b/demos/page-events/logevents-3.php @@ -0,0 +1,59 @@ + + + + + + Page Events - jQuery Mobile Demos + + + + + + + + + + + +
    + +
    +

    jQuery Mobile

    +

    Demos

    + Menu + Search +
    + +
    + +

    Navigate between pages and open and close panel and popup widgets to see which events fire and their data.

    + Go To Page 1 + Go To Page 2 + Open Popup + +
    + + + + + + + +
    + + + diff --git a/demos/page-events/logevents.php b/demos/page-events/logevents.php new file mode 100644 index 00000000000..301e66e6714 --- /dev/null +++ b/demos/page-events/logevents.php @@ -0,0 +1,67 @@ + + + + + + Page Events - jQuery Mobile Demos + + + + + + + + + + + +
    + +
    +

    jQuery Mobile

    +

    Demos

    + Menu + Search +
    + +
    + +

    Navigate between pages and open and close panel and popup widgets to see which events fire and their data.

    + Go To Page 2 + Go To Page 3 + Go To Page that does not exist + Open Popup + + + +
    + + + + + + + +
    + + + diff --git a/demos/selectmenu-custom-filter/index.php b/demos/selectmenu-custom-filter/index.php index 71f9f9f7362..86fd4be6403 100644 --- a/demos/selectmenu-custom-filter/index.php +++ b/demos/selectmenu-custom-filter/index.php @@ -37,35 +37,48 @@ } // Instantiate a filterable widget on the newly created listview and indicate that the - // generated input is to be used for the filtering. + // generated input form element is to be used for the filtering. list.filterable({ input: input, children: "> li:not(:jqmData(placeholder='true'))" }); }) - // The custom select list may show up as either a popup or a dialog, depending how much + // The custom select list may show up as either a popup or a dialog, depending on how much // vertical room there is on the screen. If it shows up as a dialog, then the form containing // the filter input field must be transferred to the dialog so that the user can continue to // use it for filtering list items. - .on( "pagebeforeshow", "#filter-menu-dialog,#title-filter-menu-dialog", function( event ) { - var dialog = $( event.target ) - listview = dialog.find( "ul" ), - form = listview.jqmData( "filter-form" ); + .on( "pagecontainerbeforeshow", function( event, data ) { + var listview, form, + id = data.toPage && data.toPage.attr( "id" ); + + if ( !( id === "filter-menu-dialog" || id === "title-filter-menu-dialog" ) ) { + return; + } + + listview = data.toPage.find( "ul" ); + form = listview.jqmData( "filter-form" ); // Attach a reference to the listview as a data item to the dialog, because during the - // pagehide handler below the selectmenu widget will already have returned the listview - // to the popup, so we won't be able to find it inside the dialog with a selector. - dialog.jqmData( "listview", listview ); + // pagecontainerhide handler below the selectmenu widget will already have returned the + // listview to the popup, so we won't be able to find it inside the dialog with a selector. + data.toPage.jqmData( "listview", listview ); // Place the form before the listview in the dialog. listview.before( form ); }) // After the dialog is closed, the form containing the filter input is returned to the popup. - .on( "pagehide", "#filter-menu-dialog,#title-filter-menu-dialog", function( event ) { - var listview = $( event.target ).jqmData( "listview" ), - form = listview.jqmData( "filter-form" ); + .on( "pagecontainerhide", function( event, data ) { + var listview, form, + id = data.toPage && data.toPage.attr( "id" ); + + if ( !( id === "filter-menu-dialog" || id === "title-filter-menu-dialog" ) ) { + return; + } + + listview = data.toPage.jqmData( "listview" ), + form = listview.jqmData( "filter-form" ); // Put the form back in the popup. It goes ahead of the listview. listview.before( form ); diff --git a/js/widgets/pagecontainer.js b/js/widgets/pagecontainer.js index df094d646dc..962262ee800 100644 --- a/js/widgets/pagecontainer.js +++ b/js/widgets/pagecontainer.js @@ -27,6 +27,7 @@ define( [ initSelector: false, _create: function() { + this._trigger( "beforecreate" ); this.setLastScrollEnabled = true; this._on( this.window, { @@ -479,7 +480,7 @@ define( [ ( page || this.element ).trigger( deprecatedEvent, data ); // use the widget trigger method for the new content* event - this.element.trigger( newEvent, data ); + this._trigger( name, newEvent, data ); return { deprecatedEvent: deprecatedEvent, @@ -532,11 +533,13 @@ define( [ triggerData.content = content; + triggerData.toPage = content; + // If the default behavior is prevented, stop here! // Note that it is the responsibility of the listener/handler // that called preventDefault(), to resolve/reject the // deferred object within the triggerData. - if ( !this._trigger( "load", undefined, triggerData ) ) { + if ( this._triggerWithDeprecated( "load" ).event.isDefaultPrevented() ) { return; } @@ -559,11 +562,6 @@ define( [ this._hideLoading(); } - // BEGIN DEPRECATED --------------------------------------------------- - // Let listeners know the content loaded successfully. - this.element.trigger( "pageload" ); - // END DEPRECATED ----------------------------------------------------- - deferred.resolve( absUrl, settings, content ); }, this); }, @@ -664,6 +662,8 @@ define( [ triggerData = { url: url, absUrl: absUrl, + toPage: url, + prevPage: options.fromPage, dataUrl: dataUrl, deferred: deferred, options: settings @@ -704,7 +704,7 @@ define( [ success: this._loadSuccess( absUrl, triggerData, settings, deferred ), error: this._loadError( absUrl, triggerData, settings, deferred ) }); - + return deferred.promise(); }, @@ -764,11 +764,21 @@ define( [ //trigger before show/hide events // TODO deprecate nextPage in favor of next - this._triggerWithDeprecated( prefix + "hide", { nextPage: to, samePage: samePage }, from ); + this._triggerWithDeprecated( prefix + "hide", { + + // Deprecated in 1.4 remove in 1.5 + nextPage: to, + toPage: to, + prevPage: from, + samePage: samePage + }, from ); } // TODO deprecate prevPage in favor of previous - this._triggerWithDeprecated( prefix + "show", { prevPage: from || $( "" ) }, to ); + this._triggerWithDeprecated( prefix + "show", { + prevPage: from || $( "" ), + toPage: to + }, to ); }, // TODO make private once change has been defined in the widget @@ -788,14 +798,14 @@ define( [ promise = ( new TransitionHandler( transition, reverse, to, from ) ).transition(); + promise.done( $.proxy( function() { + this._triggerCssTransitionEvents( to, from ); + }, this )); + // TODO temporary accomodation of argument deferred promise.done(function() { deferred.resolve.apply( deferred, arguments ); }); - - promise.done($.proxy(function() { - this._triggerCssTransitionEvents( to, from ); - }, this)); }, _releaseTransitionLock: function() { @@ -840,9 +850,13 @@ define( [ }, _triggerPageBeforeChange: function( to, triggerData, settings ) { - var pbcEvent = new $.Event( "pagebeforechange" ); + var returnEvents; - $.extend(triggerData, { toPage: to, options: settings }); + triggerData.prevPage = this.activePage; + $.extend( triggerData, { + toPage: to, + options: settings + }); // NOTE: preserve the original target as the dataUrl value will be // simplified eg, removing ui-state, and removing query params from @@ -859,10 +873,11 @@ define( [ } // Let listeners know we're about to change the current page. - this.element.trigger( pbcEvent, triggerData ); + returnEvents = this._triggerWithDeprecated( "beforechange", triggerData ); // If the default behavior is prevented, stop here! - if ( pbcEvent.isDefaultPrevented() ) { + if ( returnEvents.event.isDefaultPrevented() || + returnEvents.deprecatedEvent.isDefaultPrevented() ) { return false; } @@ -935,6 +950,7 @@ define( [ return; } + triggerData.prevPage = settings.fromPage; // if the (content|page)beforetransition default is prevented return early // Note, we have to check for both the deprecated and new events beforeTransition = this._triggerWithDeprecated( "beforetransition", triggerData ); @@ -990,7 +1006,7 @@ define( [ isPageTransitioning = false; this._triggerWithDeprecated( "transition", triggerData ); - this.element.trigger( "pagechange", triggerData ); + this._triggerWithDeprecated( "change", triggerData ); // Even if there is no page change to be done, we should keep the // urlHistory in sync with the hash changes @@ -1157,8 +1173,8 @@ define( [ } this._releaseTransitionLock(); - this.element.trigger( "pagechange", triggerData ); this._triggerWithDeprecated( "transition", triggerData ); + this._triggerWithDeprecated( "change", triggerData ); }, this)); }, diff --git a/tests/integration/pagecontainer/other-page.html b/tests/integration/pagecontainer/other-page.html new file mode 100644 index 00000000000..1d858ce7cbd --- /dev/null +++ b/tests/integration/pagecontainer/other-page.html @@ -0,0 +1,11 @@ + + + + + +
    +

    Other Page

    +

    This is the other page.

    +
    + + diff --git a/tests/integration/pagecontainer/page-event-sequence-tests.html b/tests/integration/pagecontainer/page-event-sequence-tests.html new file mode 100644 index 00000000000..a91d4e42a11 --- /dev/null +++ b/tests/integration/pagecontainer/page-event-sequence-tests.html @@ -0,0 +1,40 @@ + + + + + + jQuery Mobile Pagecontainer Event Sequence Test Suite + + + + + + + + + + + + + + + + +
    + +
    +

    Start page

    +
    + Go to other page + Go to non-existent page +
    +
    + + diff --git a/tests/integration/pagecontainer/page_event_sequence_core.js b/tests/integration/pagecontainer/page_event_sequence_core.js new file mode 100644 index 00000000000..eb739c30cda --- /dev/null +++ b/tests/integration/pagecontainer/page_event_sequence_core.js @@ -0,0 +1,236 @@ +( function() { + +var eventSequence, + eventsList = [ + + // Deprecated as of 1.4.x + "pagebeforechange", + "pagebeforeload", + "pageload", + "pageloadfailed", + "pagebeforehide", + "pagebeforeshow", + "pagehide", + "pageshow", + "pagechange", + "pageinit", + + // Valid as of 1.4.x + "pagecontainerbeforechange", + "pagecontainerbeforeload", + "pagecontainerload", + "pagecontainerloadfailed", + "pagebeforecreate", + "pagecreate", + "pagecontainerbeforetransition", + "pagecontainerbeforehide", + "pagecontainerbeforeshow", + "pagecontainerhide", + "pagecontainershow", + "pagecontainertransition", + "pagecontainerchange" + ].join( " " ), + dataItem = function( item ) { + return ( item ? + ( item.jquery ? + item.attr( "id" ) : + $.type( item ) === "string" ? + item : + "unknown" ) : + undefined ); + }, + recordEvent = function( event, data ) { + eventSequence.push({ + type: event.type, + target: event.target.getAttribute( "id" ), + data: { + prevPage: data && dataItem( data.prevPage ), + nextPage: data && dataItem( data.nextPage ), + toPage: data && dataItem( data.toPage ) + } + }); + }; + +module( "Page event sequence tests", { + setup: function() { + eventSequence = []; + + $( document ).on( eventsList, recordEvent ); + }, + teardown: function() { + $( document ).off( eventsList, recordEvent ); + } +}); + +function makeOtherPageUrl( filename ) { + var path = $.mobile.path, + parsedUrl = path.parseLocation(); + + return path.getLocation( $.extend( parsedUrl, { + filename: filename, + pathname: parsedUrl.directory + filename, + hash: "", + search: "" + })); +} + +asyncTest( "Event sequence during navigation to another page", function() { + expect( 1 ); + + var otherPageUrl = makeOtherPageUrl( "other-page.html" ), + expectedEventSequence = [ + + // Deprecated as of 1.4.0 + { type: "pagebeforechange", target: "the-body", + data: { prevPage: "start-page", nextPage: undefined, toPage: otherPageUrl } }, + + // Valid + { type: "pagecontainerbeforechange", target: "the-body", + data: { prevPage: "start-page", nextPage: undefined, toPage: otherPageUrl } }, + + // Deprecated as of 1.4.0 + { type: "pagebeforeload", target: "the-body", + data: { prevPage: "start-page", nextPage: undefined, toPage: otherPageUrl } }, + + // Valid + { type: "pagecontainerbeforeload", target: "the-body", + data: { prevPage: "start-page", nextPage: undefined, toPage: otherPageUrl } }, + + // Deprecated as of 1.4.0 + { type: "pageload", target: "the-body", + data: { prevPage: undefined, nextPage: undefined, toPage: undefined } }, + + // Valid + { type: "pagecontainerload", target: "the-body", + data: { prevPage: undefined, nextPage: undefined, toPage: undefined } }, + + // Valid - page widget events + { type: "pagebeforecreate", target: "other-page", + data: { prevPage: undefined, nextPage: undefined, toPage: undefined } }, + { type: "pagecreate", target: "other-page", + data: { prevPage: undefined, nextPage: undefined, toPage: undefined } }, + + // Deprecated as of 1.4.0 + { type: "pageinit", target: "other-page", + data: { prevPage: undefined, nextPage: undefined, toPage: undefined } }, + + // Deprecated as of 1.4.0 + { type: "pagebeforechange", target: "the-body", + data: { prevPage: "start-page", nextPage: undefined, toPage: "other-page" } }, + + // Valid + { type: "pagecontainerbeforechange", target: "the-body", + data: { prevPage: "start-page", nextPage: undefined, toPage: "other-page" } }, + + // Valid + { type: "pagecontainerbeforetransition", target: "the-body", + data: { prevPage: "start-page", nextPage: undefined, toPage: "other-page" } }, + + // Deprecated as of 1.4.0 + { type: "pagebeforehide", target: "start-page", + data: { prevPage: "start-page", nextPage: "other-page", toPage: "other-page" } }, + + // Valid, but nextPage is deprecated as of 1.4.0 + { type: "pagecontainerbeforehide", target: "the-body", + data: { prevPage: "start-page", nextPage: "other-page", toPage: "other-page" } }, + + // Deprecated as of 1.4.0 + { type: "pagebeforeshow", target: "other-page", + data: { prevPage: "start-page", nextPage: undefined, toPage: "other-page" } }, + + // Valid + { type: "pagecontainerbeforeshow", target: "the-body", + data: { prevPage: "start-page", nextPage: undefined, toPage: "other-page" } }, + + // Deprecated as of 1.4.0 + { type: "pagehide", target: "start-page", + data: { prevPage: "start-page", nextPage: "other-page", toPage: "other-page" } }, + + // Valid, but nextPage is deprecated as of 1.4.0 + { type: "pagecontainerhide", target: "the-body", + data: { prevPage: "start-page", nextPage: "other-page", toPage: "other-page" } }, + + // Deprecated as of 1.4.0 + { type: "pageshow", target: "other-page", + data: { prevPage: "start-page", nextPage: undefined, toPage: "other-page" } }, + + // Valid + { type: "pagecontainershow", target: "the-body", + data: { prevPage: "start-page", nextPage: undefined, toPage: "other-page" } }, + + // Valid + { type: "pagecontainertransition", target: "the-body", + data: { prevPage: "start-page", nextPage: undefined, toPage: "other-page" } }, + + // Deprecated as of 1.4.0 + { type: "pagechange", target: "the-body", + data: { prevPage: "start-page", nextPage: undefined, toPage: "other-page" } }, + + // Valid + { type: "pagecontainerchange", target: "the-body", + data: { prevPage: "start-page", nextPage: undefined, toPage: "other-page" } } + ]; + + $.testHelper.pageSequence([ + function() { + $( "#go-to-other-page" ).click(); + }, + function() { + deepEqual( eventSequence, expectedEventSequence, "Event sequence as expected" ); + $( ":mobile-pagecontainer" ).pagecontainer( "back" ); + }, + function() { + start(); + } + ]); +}); + +asyncTest( "Event sequence during page load failure", function() { + expect( 1 ); + + var otherPageUrl = makeOtherPageUrl( "page-does-not-exist.html" ), + expectedEventSequence = [ + + // Deprecated as of 1.4.0 + { type: "pagebeforechange", target: "the-body", + data: { prevPage: "start-page", nextPage: undefined, toPage: otherPageUrl } }, + + // Valid + { type: "pagecontainerbeforechange", target: "the-body", + data: { prevPage: "start-page", nextPage: undefined, toPage: otherPageUrl } }, + + // Deprecated as of 1.4.0 + { type: "pagebeforeload", target: "the-body", + data: { prevPage: "start-page", nextPage: undefined, toPage: otherPageUrl } }, + + // Valid + { type: "pagecontainerbeforeload", target: "the-body", + data: { prevPage: "start-page", nextPage: undefined, toPage: otherPageUrl } }, + + // Deprecated as of 1.4.0 + { type: "pageloadfailed", target: "the-body", + data: { prevPage: "start-page", nextPage: undefined, toPage: otherPageUrl } }, + + // Valid + { type: "pagecontainerloadfailed", target: "the-body", + data: { prevPage: "start-page", nextPage: undefined, toPage: otherPageUrl } } + ]; + + $.testHelper.detailedEventCascade([ + function() { + $( "#go-to-nonexistent-page" ).click(); + }, + { + pagecontainerloadfailed: { + src: $( ":mobile-pagecontainer" ), + event: "pagecontainerloadfailed.eventSequenceDuringPageLoadFailure1" + } + }, + function() { + deepEqual( eventSequence, expectedEventSequence, "Event sequence as expected" ); + start(); + } + ]); +}); + +})();