jQuery Mobile 1.4 更新:
我的原始文章是针对旧的页面处理方式,基本上是 jQuery Mobile 1.4 之前的所有内容。旧的处理方式现已弃用,它将一直有效到(包括)jQuery Mobile 1.5,因此您仍然可以使用下面提到的所有内容,至少在明年和 jQuery Mobile 1.6 之前。
旧的事件,包括 pageinit 不再存在,它们被 pagecontainer 小部件取代。 Pageinit 已完全删除,您可以改用 pagecreate,该事件保持不变,不会更改。
如果您对页面事件处理的新方式感兴趣,请查看 here,在任何其他情况下,请随时继续阅读本文。即使您使用的是 jQuery Mobile 1.4 +,您也应该阅读此答案,它超越了页面事件,因此您可能会发现很多有用的信息。
旧内容:
这篇文章也可以在我的博客 HERE 中找到。
$(document).on('pageinit') 与 $(document).ready()
您在 jQuery 中学习的第一件事是调用 $(document).ready() 函数内的代码,以便在加载 DOM 后立即执行所有操作。然而,在 jQuery Mobile 中,Ajax 用于在您导航时将每个页面的内容加载到 DOM 中。因此,$(document).ready() 将在您的第一个页面加载之前触发,并且用于页面操作的每个代码都将在页面刷新后执行。这可能是一个非常微妙的错误。在某些系统上,它可能看起来工作正常,但在其他系统上,它可能会导致出现不稳定、难以重复的怪异现象。
经典的 jQuery 语法:
$(document).ready(function() {
});
为了解决这个问题(相信我,这是个问题)jQuery Mobile 开发人员创建了页面事件。简而言之,页面事件是在页面执行的特定点触发的事件。其中一个页面事件是 pageinit 事件,我们可以这样使用它:
$(document).on('pageinit', function() {
});
我们可以更进一步,使用页面 id 代替文档选择器。假设我们有一个 id 为 index 的 jQuery Mobile 页面:
<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>
要执行仅可用于索引页面的代码,我们可以使用以下语法:
$('#index').on('pageinit', function() {
});
Pageinit 事件将在每次页面即将被加载并首次显示时执行。除非手动刷新页面或关闭 Ajax 页面加载,否则它不会再次触发。如果您希望每次访问页面时都执行代码,最好使用 pagebeforeshow 事件。
这是一个工作示例:http://jsfiddle.net/Gajotres/Q3Usv/ 来演示这个问题。
关于这个问题的更多注释。无论您是使用 1 html 多页面还是多个 HTML 文件范例,都建议您将所有自定义 JavaScript 页面处理分离到一个单独的 JavaScript 文件中。这将使您的代码变得更好,但您将拥有更好的代码概览,尤其是在创建 jQuery Mobile 应用程序时。
还有另一个特殊的 jQuery Mobile 事件,它被称为 mobileinit。当 jQuery Mobile 启动时,它会在文档对象上触发 mobileinit 事件。要覆盖默认设置,请将它们绑定到 mobileinit。 mobileinit 用法的一个很好的例子是关闭 Ajax 页面加载,或更改默认 Ajax 加载器行为。
$(document).on("mobileinit", function(){
//apply overrides here
});
页面事件转换顺序
首先可以在这里找到所有事件:http://api.jquerymobile.com/category/events/
假设我们有一个页面 A 和一个页面 B,这是一个卸载/加载顺序:
页面 B - 事件 pagebeforecreate
页面 B - 事件 pagecreate
页面 B - 事件 pageinit
页面 A - 事件 pagebeforehide
页面 A - 事件 pageremove
页面 A - 事件 pagehide
页面 B - 事件 pagebeforeshow
页面 B - 事件 pageshow
为了更好地理解页面事件,请阅读以下内容:
-
pagebeforeload、pageload 和 pageloadfailed 在加载外部页面时触发
-
pagebeforechange、pagechange 和 pagechangefailed 是页面更改事件。当用户在应用程序的页面之间导航时会触发这些事件。
-
pagebeforeshow、pagebeforehide、pageshow 和 pagehide 是页面转换事件。这些事件在转换之前、期间和之后触发并命名。
-
pagebeforecreate、pagecreate 和 pageinit 用于页面初始化。
-
pageremove 可以在从 DOM 中删除页面时触发并处理
页面加载jsFiddle示例:http://jsfiddle.net/Gajotres/QGnft/
如果未启用 AJAX,某些事件可能不会触发。
防止页面转换
如果由于某种原因需要在某些情况下阻止页面转换,可以使用以下代码完成:
$(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');;
}
}
});
此示例在任何情况下都有效,因为它会在每次页面转换的请求时触发,最重要的是它会在页面转换发生之前阻止页面更改。
这是一个工作示例:
防止多个事件绑定/触发
jQuery Mobile 的工作方式与经典网络应用程序不同。根据您每次访问某个页面时如何设法绑定事件,它会一遍又一遍地绑定事件。这不是错误,它只是 jQuery Mobile 处理其页面的方式。比如看一下这段代码sn -p:
$(document).on('pagebeforeshow','#index' ,function(e,data){
$(document).on('click', '#test-button',function(e) {
alert('Button click');
});
});
工作 jsFiddle 示例:http://jsfiddle.net/Gajotres/CCfL4/
每次访问页面#index点击事件都会绑定到按钮#test-button。通过从第 1 页移动到第 2 页并返回几次来测试它。有几种方法可以防止此问题:
解决方案 1
最好的解决方案是使用 pageinit 来绑定事件。如果您查看官方文档,您会发现 pageinit 只会触发一次,就像文档准备好一样,因此无法再次绑定事件。这是最好的解决方案,因为您没有像使用 off 方法删除事件那样的处理开销。
工作 jsFiddle 示例:http://jsfiddle.net/Gajotres/AAFH8/
这个可行的解决方案是基于之前有问题的示例。
解决方案 2
在绑定之前移除事件:
$(document).on('pagebeforeshow', '#index', function(){
$(document).off('click', '#test-button').on('click', '#test-button',function(e) {
alert('Button click');
});
});
工作 jsFiddle 示例:http://jsfiddle.net/Gajotres/K8YmG/
解决方案 3
使用 jQuery 过滤器选择器,如下所示:
$('#carousel div:Event(!click)').each(function(){
//If click is not bind to #carousel div do something
});
因为事件过滤器不是官方 jQuery 框架的一部分,所以可以在这里找到:http://www.codenothing.com/archives/2009/event-filter/
简而言之,如果您主要关心的是速度,那么解决方案 2 比解决方案 1 好得多。
解决方案 4
一个新的,可能是其中最简单的一个。
$(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;
}
});
});
工作 jsFiddle 示例:http://jsfiddle.net/Gajotres/Yerv9/
此解决方案的 Tnx 至 sholsinger:http://sholsinger.com/archive/2011/08/prevent-jquery-live-handlers-from-firing-multiple-times/
pageChange 事件怪癖 - 触发两次
有时pagechange事件会触发两次,和前面提到的问题没有任何关系。
pagebeforechange 事件发生两次的原因是由于在 toPage 不是 jQuery 增强的 DOM 对象时 changePage 中的递归调用。这种递归是危险的,因为允许开发人员在事件中更改 toPage。如果开发人员始终将 toPage 设置为字符串,则在 pagebeforechange 事件处理程序中,无论它是否是对象,都会导致无限递归循环。 pageload 事件将新页面作为数据对象的页面属性传递(这应该添加到文档中,当前未列出)。因此,pageload 事件可用于访问加载的页面。
简而言之,这是因为您正在通过 pageChange 发送其他参数。
例子:
<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>
要解决此问题,请使用页面事件转换顺序中列出的任何页面事件。
换页次数
如前所述,当您从一个 jQuery Mobile 页面更改为另一个页面时,通常是通过单击指向 DOM 中已经存在的另一个 jQuery Mobile 页面的链接,或者通过手动调用 $.mobile.changePage、几个事件和后续动作发生。在较高级别上会发生以下操作:
- 页面更改过程开始
- 新页面已加载
- 该页面的内容已“增强”(样式化)
- 发生从现有页面到新页面的转换(幻灯片/弹出/等)
这是一个平均页面转换基准:
页面加载和处理:3 ms
页面增强:45 毫秒
转换:604 毫秒
总时间:670 毫秒
*这些值以毫秒为单位。
如您所见,转换事件占用了将近 90% 的执行时间。
页面转换之间的数据/参数操作
在页面转换期间可以将参数从一个页面发送到另一个页面。可以通过几种方式完成。
参考:https://stackoverflow.com/a/13932240/1848600
解决方案 1:
您可以使用 changePage 传递值:
$.mobile.changePage('page2.html', { dataUrl : "page2.html?paremeter=123", data : { 'paremeter' : '123' }, reloadPage : true, changeHash : true });
然后像这样阅读它们:
$(document).on('pagebeforeshow', "#index", function (event, data) {
var parameters = $(this).data("url").split("?")[1];;
parameter = parameters.replace("parameter=","");
alert(parameter);
});
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>
解决方案 2:
或者您可以创建一个持久的 JavaScript 对象用于存储目的。只要 Ajax 用于页面加载(并且页面不会以任何方式重新加载),该对象就会保持活动状态。
var storeObject = {
firstname : '',
lastname : ''
}
示例:http://jsfiddle.net/Gajotres/9KKbx/
解决方案 3:
您还可以像这样访问上一页的数据:
$(document).on('pagebeforeshow', '#index',function (e, data) {
alert(data.prevPage.attr('id'));
});
prevPage 对象保存完整的上一页。
解决方案 4:
作为最后一个解决方案,我们有一个漂亮的本地存储 HTML 实现。它仅适用于 HTML5 浏览器(包括 Android 和 iOS 浏览器),但所有存储的数据通过页面刷新保持不变。
if(typeof(Storage)!=="undefined") {
localStorage.firstname="Dragan";
localStorage.lastname="Gaic";
}
示例:http://jsfiddle.net/Gajotres/J9NTr/
可能是最佳解决方案,但在某些版本的 iOS 5.X 中会失败。这是一个众所周知的错误。
不要使用.live() / .bind() / .delegate()
我忘了提及(tnx andleer 提醒我)使用 on/off 进行事件绑定/解除绑定,live/die 和 bind/unbind 已弃用。
jQuery 的 .live() 方法在 1.3 版本中被引入 API 时被视为天赐之物。在一个典型的 jQuery 应用程序中,可能会有大量的 DOM 操作,并且随着元素的来来去去,挂钩和解除挂钩会变得非常乏味。 .live() 方法可以根据其选择器在应用程序的生命周期内挂钩事件。很棒吧?错了,.live() 方法非常慢。 .live() 方法实际上将其事件挂钩到文档对象,这意味着事件必须从生成事件的元素开始冒泡,直到到达文档。这可能非常耗时。
现在已弃用。 jQuery 团队的人不再推荐使用它,我也不推荐。即使挂钩和取消挂钩事件可能很乏味,但如果没有 .live() 方法,您的代码会比使用它快得多。
您应该使用 .on() 而不是 .live()。 .on() 比 .live() 快大约 2-3 倍。看看这个事件绑定基准:http://jsperf.com/jquery-live-vs-delegate-vs-on/34,一切都会从那里一目了然。
基准测试:
有一个用于 jQuery Mobile 页面事件基准测试的出色脚本。可以在这里找到:https://github.com/jquery/jquery-mobile/blob/master/tools/page-change-time.js。但在你对它做任何事情之前,我建议你删除它的 alert 通知系统(每个“更改页面”都会通过停止应用程序向你显示这些数据)并将其更改为 console.log 函数。
基本上这个脚本会记录你所有的页面事件,如果你仔细阅读这篇文章(页面事件描述)你就会知道 jQm 花了多少时间在页面增强、页面转换......
最后的笔记
总是,我的意思是总是阅读官方 jQuery Mobile 文档。它通常会为您提供所需的信息,并且与其他一些文档不同,此文档相当不错,有足够的解释和代码示例。
变化:
- 30.01.2013 - 添加了防止多事件触发的新方法
- 31.01.2013 - 为章节页面转换之间的数据/参数操作添加了更好的说明
- 03.02.2013 - 在页面转换之间的数据/参数操作一章中添加了新的内容/示例
- 22.05.2013 - 添加了防止页面转换/更改的解决方案,并添加了指向官方页面事件 API 文档的链接
- 18.05.2013 - 添加了另一个针对多事件绑定的解决方案