javascript - jQuery Mobile: dokument klart vs. sid-händelser
Jag använder jQuery Mobile, och jag har problem med att förstå skillnaderna mellan klassisk document ready och jQuery Mobile sida-händelser.
Vad är den verkliga skillnaden?
Varför ska
<!-- language: lang-js --> $(document).ready() { });vara bättre än
$(document).on('pageinit') { });Vad är ordningen på sidahändelser när du går från en sida till en annan?
Hur kan jag skicka data från en sida till en annan och är det möjligt att komma åt data från föregående sida?
Svar #1
jQuery Mobile 1.4 Uppdatering:
Min ursprungliga artikel var avsedd för det gamla sättet att hantera sidor, i princip allt före jQuery Mobile 1.4. Det gamla sättet att hantera är nu föråldrat och kommer att vara aktivt fram till (inklusive) jQuery Mobile 1.5, så du kan fortfarande använda allt som nämns nedan, åtminstone fram till nästa år och jQuery Mobile 1.6.
Gamla händelser, inklusive pageinit, finns inte längre, de ersätts med pagecontainer-widgeten. Pageinit är helt borttagen och du kan istället använda pagecreate, den händelsen är densamma och kommer inte att ändras.
Om du är intresserad av ett nytt sätt att hantera sidohändelser, ta en titt här, i annat fall kan du fortsätta med den här artikeln. Du bör läsa detta svar även om du använder jQuery Mobile 1.4 +, det går bortom sidohändelser så du kommer troligen att hitta mycket användbar information.
Äldre innehåll:
Denna artikel kan också hittas som en del av min blogg HÄR.
$ (document).on('pageinit') vs $ (document).ready()
Det första du lär dig i jQuery är att anropa kod inuti $ (document).ready()-funktionen så att allt körs så snart DOM är laddad. Men i jQuery Mobile används Ajax för att ladda innehållet på varje sida i DOM när du navigerar. På grund av detta kommer $ (document).ready() att triggas innan din första sida har laddats, och all kod som är avsedd för sidmanipulation kommer att köras efter en siduppdatering. Detta kan vara en mycket subtil bugg. På vissa system kan det verka som att det fungerar bra, men på andra kan det orsaka oförutsägbara, svårrepeterade konstigheter.
Klassisk jQuery-syntax:
$(document).ready(function() {
});
För att lösa detta problem (och tro mig, detta är ett problem) skapade jQuery Mobile-utvecklare sid-händelser. Kort sagt är sid-händelser händelser som triggas vid en viss punkt i sidans körning. En av dessa sid-händelser är en pageinit-händelse och vi kan använda den på detta sätt:
$(document).on('pageinit', function() {
});
Vi kan gå ännu längre och använda en sid-id istället för dokumentväljare. Låt oss säga att vi har en jQuery Mobile-sida med ett id index:
<div data-role="page" id="index">
<div data-theme="a" data-role="header">
<h3>
First Page
</h3>
<a href="#second" class="ui-btn-right">Next</a>
</div>
<div data-role="content">
<a href="#" data-role="button" id="test-button">Test button</a>
</div>
<div data-theme="a" data-role="footer" data-position="fixed">
</div>
</div>
För att köra kod som endast kommer att vara tillgänglig på indexsidan kan vi använda denna syntax:
$('#index').on('pageinit', function() {
});
Pageinit-händelsen kommer att köras varje gång sidan håller på att laddas och visas för första gången. Den kommer inte att trigga igen om inte sidan manuellt uppdateras eller om Ajax-sidans laddning är avstängd. Om du vill att koden ska köras varje gång du besöker en sida är det bättre att använda pagebeforeshow-händelsen.
Här är ett fungerande exempel: http://jsfiddle.net/Gajotres/Q3Usv/ för att visa detta problem.
Några fler anteckningar om denna fråga. Oavsett om du använder 1 HTML med flera sidor eller flera HTML-filer paradigm, rekommenderas det att separera all din anpassade JavaScript-sidahantering till en enda separat JavaScript-fil. Detta kommer inte att göra din kod bättre, men du kommer att få en mycket bättre översikt över koden, särskilt när du skapar en jQuery Mobile-applikation.
Det finns också en annan speciell jQuery Mobile-händelse och den kallas mobileinit. När jQuery Mobile startar, utlöser den en mobileinit-händelse på dokumentobjektet. För att åsidosätta standardinställningar, bind dem till mobileinit. Ett bra exempel på mobileinit-användning är att stänga av Ajax-sidoladdning, eller ändra standardbeteendet för Ajax-laddare.
$(document).on("mobileinit", function(){
//apply overrides here
});
Sidans händelser övergångsordning
Först kan alla händelser hittas här: http://api.jquerymobile.com/category/events/
Låt oss säga att vi har en sida A och en sida B, detta är en avlastnings/laddningsordning:
sida B - händelse pagebeforecreate
sida B - händelse pagecreate
sida B - händelse pageinit
sida A - händelse pagebeforehide
sida A - händelse pageremove
sida A - händelse pagehide
sida B - händelse pagebeforeshow
sida B - händelse pageshow
För bättre förståelse av sidans händelser, läs detta:
pagebeforeload,pageloadochpageloadfailedutlöses när en extern sida laddaspagebeforechange,pagechangeochpagechangefailedär sidändringshändelser. Dessa händelser utlöses när en användare navigerar mellan sidor i applikationerna.pagebeforeshow,pagebeforehide,pageshowochpagehideär sidövergångshändelser. Dessa händelser utlöses före, under och efter en övergång och är namngivna.pagebeforecreate,pagecreateochpageinitär för sidinitiering.pageremovekan utlösas och hanteras när en sida tas bort från DOM
Sida som laddar jsFiddle-exempel: http://jsfiddle.net/Gajotres/QGnft/
Om AJAX inte är aktiverat kan vissa händelser kanske inte utlösas.
Förhindra sidövergång
Om av någon anledning sidövergång måste förhindras under vissa villkor kan det göras med denna kod:
$(document).on('pagebeforechange', function(e, data){
var to = data.toPage,
from = data.options.fromPage;
if (typeof to === 'string') {
var u = $.mobile.path.parseUrl(to);
to = u.hash || '#' + u.pathname.substring(1);
if (from) from = '#' + from.attr('id');
if (from === '#index' && to === '#second') {
alert('Can not transition from #index to #second!');
e.preventDefault();
e.stopPropagation();
// remove active status on a button, if transition was triggered with a button
$.mobile.activePage.find('.ui-btn-active').removeClass('ui-btn-active ui-focus ui-btn');;
}
}
});
Detta exempel kommer att fungera i alla fall eftersom det kommer att utlösas vid början av varje sidövergång och det som är mest viktigt är att det kommer att förhindra sidbyte innan sidövergången kan inträffa.
Här är ett fungerande exempel:
Förhindra flera händelsebindningar/utlösningar
jQuery Mobile fungerar på ett annat sätt än klassiska webbaserade applikationer. Beroende på hur du lyckats binda dina händelser kommer det varje gång du besöker någon sida att binda händelser om och om igen. Detta är inte ett fel, det är helt enkelt hur jQuery Mobile hanterar sina sidor. Till exempel, ta en titt på denna kodsnutt:
$(document).on('pagebeforeshow','#index' ,function(e,data){
$(document).on('click', '#test-button',function(e) {
alert('Button click');
});
});
Fungerande jsFiddle-exempel: http://jsfiddle.net/Gajotres/CCfL4/
Varje gång du besöker sidan #index kommer klickhändelsen att kopplas till knappen #test-button. Testa det genom att gå från sida 1 till sida 2 och tillbaka flera gånger. Det finns några sätt att förhindra detta problem:
Lösning 1
Bästa lösningen skulle vara att använda pageinit för att binda händelser. Om du tittar på den officiella dokumentationen kommer du att se att pageinit bara triggas EN gång, precis som document ready, så det finns inget sätt att händelserna bindas igen. Detta är den bästa lösningen eftersom du inte har bearbetningsoverhead som när man tar bort händelser med off-metoden.
Fungerande jsFiddle-exempel: http://jsfiddle.net/Gajotres/AAFH8/
Denna fungerande lösning är gjord på basis av ett tidigare problematiskt exempel.
Lösning 2
Ta bort händelsen innan du binder den:
$(document).on('pagebeforeshow', '#index', function(){
$(document).off('click', '#test-button').on('click', '#test-button',function(e) {
alert('Button click');
});
});
Fungerande jsFiddle-exempel: http://jsfiddle.net/Gajotres/K8YmG/
Lösning 3
Använd en jQuery Filter-selektor, som detta:
$('#carousel div:Event(!click)').each(function(){
//If click is not bind to #carousel div do something
});
Eftersom eventfilter inte är en del av det officiella jQuery-ramverket kan det hittas här: http://www.codenothing.com/archives/2009/event-filter/
Kort sagt, om hastighet är din huvudsakliga oro så är Lösning 2 mycket bättre än Lösning 1.
Lösning 4
En ny, förmodligen den lättaste av dem alla.
$(document).on('pagebeforeshow', '#index', function(){
$(document).on('click', '#test-button',function(e) {
if(e.handled !== true) // This will prevent event triggering more than once
{
alert('Clicked');
e.handled = true;
}
});
});
Fungerande jsFiddle-exempel: http://jsfiddle.net/Gajotres/Yerv9/
Tack till sholsinger för denna lösning: http://sholsinger.com/archive/2011/08/prevent-jquery-live-handlers-from-firing-multiple-times/
pageChange-händelse konstigheter - triggas två gånger
Ibland kan pagechange-händelsen utlösas två gånger och det har inget att göra med problemet som nämndes tidigare.
Anledningen till att pagebeforechange-händelsen inträffar två gånger beror på det rekursiva anropet i changePage när toPage inte är ett DOM-objekt som har förstärkts av jQuery. Denna rekursion är farlig, eftersom utvecklaren tillåts att ändra toPage inom händelsen. Om utvecklaren konsekvent sätter toPage till en sträng inom pagebeforechange-händelsehanteraren, oavsett om det var ett objekt eller inte, kommer en oändlig rekursiv loop att uppstå. Pageload-händelsen skickar den nya sidan som page-egenskapen i data-objektet (detta bör läggas till i dokumentationen, det är för närvarande inte listat). Pageload-händelsen kan därför användas för att komma åt den laddade sidan.
I få ord händer detta eftersom du skickar ytterligare parametrar genom pageChange.
Exempel :
<a data-role="button" data-icon="arrow-r" data-iconpos="right" href="#care-plan-view?id=9e273f31-2672-47fd-9baa-6c35f093a800&name=Sat"><h3>Sat</h3></a>
För att åtgärda detta problem, använd valfri sid-händelse som listas i Sidornas händelseövergångsordning.
Sidbytestider
Som nämnts, när du byter från en jQuery Mobile-sida till en annan, vanligtvis antingen genom att klicka på en länk till en annan jQuery Mobile-sida som redan finns i DOM, eller genom att manuellt kalla på $.mobile.changePage, sker flera händelser och efterföljande åtgärder. På en övergripande nivå sker följande åtgärder:
- En sidbytesprocess påbörjas
- En ny sida har laddats
- Innehållet för den sidan är "förbättrat" (stylat)
- En övergång (slide/pop/etc) från den befintliga sidan till den nya sidan sker
Detta är ett genomsnittligt sidövergångsbenchmark:
Sidladdning och bearbetning: 3 ms
Sidförbättring: 45 ms
Övergång: 604 ms
Total tid: 670 ms
*Dessa värden är i millisekunder.
Så som du kan se äter en övergångshändelse nästan 90 % av exekveringstiden.
Hantering av data/parametrar mellan sidövergångar
Det är möjligt att skicka en parameter/parametrar från en sida till en annan under sidövergång. Det kan göras på några olika sätt.
Referens: https://stackoverflow.com/a/13932240/1848600
Lösning 1:
Du kan skicka värden med changePage:
$.mobile.changePage('page2.html', { dataUrl : "page2.html?paremeter=123", data : { 'paremeter' : '123' }, reloadPage : true, changeHash : true });
Och läs dem så här:
$(document).on('pagebeforeshow', "#index", function (event, data) {
var parameters = $(this).data("url").split("?")[1];;
parameter = parameters.replace("parameter=","");
alert(parameter);
});
Exempel :
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="widdiv=device-widdiv, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<title>
</title>
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.2.0/jquery.mobile-1.2.0.min.css" />
<script src="http://www.dragan-gaic.info/js/jquery-1.8.2.min.js">
</script>
<script src="http://code.jquery.com/mobile/1.2.0/jquery.mobile-1.2.0.min.js"></script>
<script>
$(document).on('pagebeforeshow', "#index",function () {
$(document).on('click', "#changePage",function () {
$.mobile.changePage('second.html', { dataUrl : "second.html?paremeter=123", data : { 'paremeter' : '123' }, reloadPage : false, changeHash : true });
});
});
$(document).on('pagebeforeshow', "#second",function () {
var parameters = $(this).data("url").split("?")[1];;
parameter = parameters.replace("parameter=","");
alert(parameter);
});
</script>
</head>
<body>
<!-- Home -->
<div data-role="page" id="index">
<div data-role="header">
<h3>
First Page
</h3>
</div>
<div data-role="content">
<a data-role="button" id="changePage">Test</a>
</div> <!--content-->
</div><!--page-->
</body>
</html>second.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="widdiv=device-widdiv, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<title>
</title>
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.2.0/jquery.mobile-1.2.0.min.css" />
<script src="http://www.dragan-gaic.info/js/jquery-1.8.2.min.js">
</script>
<script src="http://code.jquery.com/mobile/1.2.0/jquery.mobile-1.2.0.min.js"></script>
</head>
<body>
<!-- Home -->
<div data-role="page" id="second">
<div data-role="header">
<h3>
Second Page
</h3>
</div>
<div data-role="content">
</div> <!--content-->
</div><!--page-->
</body>
</html>Lösning 2:
Eller så kan du skapa ett beständigt JavaScript-objekt för lagringsändamål. Så länge Ajax används för sidladdning (och sidan inte laddas om på något sätt) kommer det objektet att förbli aktivt.
var storeObject = {
firstname : '',
lastname : ''
}
Exempel: http://jsfiddle.net/Gajotres/9KKbx/
Lösning 3:
Du kan också komma åt data från föregående sida på detta sätt:
$(document).on('pagebeforeshow', '#index',function (e, data) {
alert(data.prevPage.attr('id'));
});
prevPage-objektet innehåller en komplett föregående sida.
Lösning 4:
Som en sista lösning har vi en smart HTML-implementering av localStorage. Den fungerar endast med HTML5-webbläsare (inklusive Android- och iOS-webbläsare) men all lagrad data är beständig genom siduppdatering.
if(typeof(Storage)!=="undefined") {
localStorage.firstname="Dragan";
localStorage.lastname="Gaic";
}
Exempel: http://jsfiddle.net/Gajotres/J9NTr/
Förmodligen den bästa lösningen men den kommer att misslyckas i vissa versioner av iOS 5.X. Det är ett välkänt fel.
Använd inte .live()/.bind()/.delegate()
Jag glömde nämna (och tack Andleer för att du påminde mig) använd on/off för att binda/ta bort händelser, live/die och bind/unbind är föråldrade.
.live()-metoden i jQuery sågs som en gudagåva när den introducerades i API:et i version 1.3. I en typisk jQuery-app kan det förekomma mycket DOM-manipulation och det kan bli mycket tröttsamt att koppla och koppla bort händelser allteftersom element kommer och går. .live()-metoden gjorde det möjligt att koppla en händelse för appens liv baserat på dess selektor. Bra, eller hur? Fel, .live()-metoden är extremt långsam. .live()-metoden kopplar faktiskt sina händelser till dokumentobjektet, vilket innebär att händelsen måste bubbla upp från elementet som genererade händelsen tills det når dokumentet. Detta kan vara otroligt tidskrävande.
Det är nu föråldrat. Personerna i jQuery-teamet rekommenderar inte längre dess användning och det gör inte jag heller. Även om det kan vara tidskrävande att koppla och koppla bort händelser, kommer din kod att vara mycket snabbare utan .live()-metoden än med den.
Istället för .live() bör du använda .on(). .on() är ungefär 2-3 gånger snabbare än .live(). Ta en titt på detta benchmark för händelsebindning: http://jsperf.com/jquery-live-vs-delegate-vs-on/34, allt blir tydligt därifrån.
Jämförelseanalys:
Det finns ett utmärkt skript gjort för jQuery Mobile-sideshändelsebenchmarking. Det kan hittas här: https://github.com/jquery/jquery-mobile/blob/master/tools/page-change-time.js. Men innan du gör något med det rekommenderar jag att du tar bort dess alert-notifieringssystem (varje “change page” kommer att visa dig dessa data genom att stoppa appen) och ändrar det till console.log-funktionen.
I grunden kommer detta skript att logga alla dina sida-händelser och om du läser denna artikel noggrant (beskrivningar av sida-händelser) kommer du att veta hur mycket tid jQm spenderade på sidförbättringar, sidövergångar ...
Slutanteckningar
Alltid, och jag menar alltid, läs officiell jQuery Mobile-dokumentation. Den kommer vanligtvis att ge dig den information du behöver, och till skillnad från en del annan dokumentation är denna ganska bra, med tillräckliga förklaringar och kodexempel.
Ändringar :
- 30.01.2013 - Lade till en ny metod för att förhindra flera händelseutlösningar
- 31.01.2013 - Lade till en bättre förtydligande för kapitel Data/Parameters manipulation mellan sidövergångar
- 03.02.2013 - Lagt till nytt innehåll/exempel i kapitlet Data/Parameters manipulation mellan sidövergångar
- 22.05.2013 - Lagt till en lösning för att förhindra sidövergång/förändring och lagt till länkar till den officiella dokumentationen för sidans händelser API
- 18.05.2013 - Lade till en annan lösning mot flera händelsebindningar
Svar #2
Vissa av er kanske tycker att detta är användbart. Kopiera bara och klistra in det på din sida så får du en sekvens i vilken händelser utlöses i Chrome-konsolen (Ctrl + Shift + I).
$(document).on('pagebeforecreate',function(){console.log('pagebeforecreate');});
$(document).on('pagecreate',function(){console.log('pagecreate');});
$(document).on('pageinit',function(){console.log('pageinit');});
$(document).on('pagebeforehide',function(){console.log('pagebeforehide');});
$(document).on('pagebeforeshow',function(){console.log('pagebeforeshow');});
$(document).on('pageremove',function(){console.log('pageremove');});
$(document).on('pageshow',function(){console.log('pageshow');});
$(document).on('pagehide',function(){console.log('pagehide');});
$(window).load(function () {console.log("window loaded");});
$(window).unload(function () {console.log("window unloaded");});
$(function () {console.log('document ready');});
Du kommer inte att se unload i konsolen eftersom det utlöses när sidan håller på att laddas om (när du lämnar sidan). Använd det så här:
$(window).unload(function () { debugger; console.log("window unloaded");});
Och du kommer att se vad jag menar.
