补充一些文档里没有的。
官方案例里,就是pullrefresh_main.html和pullrefresh_sub.html这两个文件。
在pullrefresh_main.html中:
- if(mui.os.ios){
- contentWebview.evalJS("mui('#pullrefresh').pullRefresh().scrollTo(0,0,100)");
- }else{
- contentWebview.evalJS('mui.scrollTo(0, 100)');
- }
其中mui('#pullrefresh')获得了子页面中id为pullrefresh的div的对象,这段代码的目的是让让滚动条在顶部。如官方文档所言,Android是用Webview实现,iOS是用H5(div)实现。下面这个代码是针对Android使用HTML5+实现的。在iOS中,用div模拟scroll,因此scrollTo方法属于pullRefresh这个方法返回的js对象。而对于Android而言,使用的是H5+扩展,scrollTo直接就是Webkit方法。
在pushfresh_sub.html中调用了pullRefresh().pullupLoading()的,目的是为了初始化(首次加载)表格:
- if (mui.os.plus) {
- mui.plusReady(function() {
- setTimeout(function() {
- mui('#pullrefresh').pullRefresh().pullupLoading();
- }, 1000);
- });
- } else {
- mui.ready(function() {
- mui('#pullrefresh').pullRefresh().pullupLoading();
- });
- }
接下来我们分析代码是如何实现上述目的的。
首先我们来分析mui('#pullrefresh').pullRefresh()是怎么回事?
在mui.js中包含pullRefresh代码主要作用是初始化PullRefresh对象,但是针对iOS和Android是不同的,前者是js对象,后者是H5+对象。
H5版本的pullRefresh方法如下:
- $.fn.pullRefresh = function(options) {
- if (this.length === 1) {
- var self = this[0];
- var pullRefreshApi = null;
- options = options || {};
- var id = self.getAttribute('data-pullrefresh');
- if (!id) {
- id = ++$.uuid;
- $.data[id] = pullRefreshApi = new PullRefresh(self, options);
- self.setAttribute('data-pullrefresh', id);
- } else {
- pullRefreshApi = $.data[id];
- }
- if (options.up && options.up.auto) { //如果设置了auto,则自动上拉一次,在官方例子中,没有设置auto,因此在sub.html中主动调用了一次
- pullRefreshApi.pullupLoading();
- }
- return pullRefreshApi;
- }
- };
注意:后面的PlusPullRefresh是绑定在Webview整个dom的扩展,然后$.fn.pullRefresh对于全局有效(jquery的语法规则),因此 $container.pullrefresh就是调用的$.fn.pullRefresh
其中核心是:
1、返回了PullRefresh或PlusPullRefresh对象
2、如果up设置了auto属性为true,则自动调用up对应的上拉加载函数加载一起数据。
然后我们来看看pullRefresh如何调用的pullupLoading
我们可以看到,实际调用pullupLoading是通过pullRefreshApi的,而后者其实是pullupRefresh中创建的PullupRefresh对象的引用,该对象有个pullupLoading()方法,其将在mui.init中声明的options作为参数传到内部,new PullRefresh(self, options);。
- function($, window, document, undefined) {
- var CLASS_VISIBILITY = 'mui-visibility';
- var CLASS_HIDDEN = 'mui-hidden';
- var PullRefresh = $.Scroll.extend($.extend({
- handleEvent: function(e) {
- this._super(e);
- ........
- pullupLoading: function(callback, x, time) {
- x = x || 0;
- this.scrollTo(x, this.maxScrollY, time, this.options.bounceEasing);
- if (this.loading) {
- return;
- }
- this._initPullupRefresh();
- this._setCaption(this.options.up.contentrefresh);
- this.indicators.map(function(indicator) {
- indicator.fade(0);
- });
- this.loading = true;
- callback = callback || this.options.up.callback;
- callback && callback.call(this);
- },
从callback= callback || this.options.up.callback;可以看出pullupLoading方法中调用了up.callback,callback.call(this)。换言之,mui('#pullrefresh').pullRefresh().pullupLoading();等于是调用了 pullupRefresh //上拉加载具体业务实现 方法。
那为什么this.options.up.callback对应的就是pullupFresh呢?
那么pullupLoading是如何调用up对应pullupRefresh自定义方法的?
简而言之,是因为在初始化时,将pullupRefresh方法赋值给了up的callback方法。如下代码所示。
- mui.init({
- pullRefresh: {
- container: '#pullrefresh',
- down: {
- callback: pulldownRefresh //下拉刷新具体业务实现
- },
- up: {
- contentrefresh: '正在加载...',
- callback: pullupRefresh //上拉加载具体业务实现
- }
- }
- });
那么这个mui.init调用的pullRefresh具体代码是如何进行参数传递的呢?
- **
- * mui.init pulldownRefresh
- * @param {type} $
- * @returns {undefined}
- */
- (function($) {
- $.registerInit({
- name: 'pullrefresh',
- index: 1000,
- handle: function() {
- <span style="color: #ff0000;">var options = $.options;</span>
- var pullRefreshOptions = options.pullRefresh || {};
- var hasPulldown = pullRefreshOptions.down && pullRefreshOptions.down.hasOwnProperty('callback');
- var hasPullup = pullRefreshOptions.up && pullRefreshOptions.up.hasOwnProperty('callback');
- if (hasPulldown || hasPullup) {
- var container = pullRefreshOptions.container;
- if (container) {
- var $container = $(container);
- if ($container.length === 1) {
- if ($.os.plus && $.os.android) { //android 5+
- $.plusReady(function() {
- var webview = plus.webview.currentWebview();
- if (hasPullup) {
- //当前页面初始化pullup
- var upOptions = {};
- upOptions.up = pullRefreshOptions.up;
- upOptions.webviewId = webview.id || webview.getURL();
- $container.pullRefresh(upOptions);
- }
- if (hasPulldown) {
- var parent = webview.parent();
- var id = webview.id || webview.getURL();
- if (parent) {
- if (!hasPullup) { //如果没有上拉加载,需要手动初始化一个默认的pullRefresh,以便当前页面容器可以调用endPulldownToRefresh等方法
- $container.pullRefresh({
- webviewId: id
- });
- }
- var downOptions = {
- webviewId: id
- };
- downOptions.down = $.extend({}, pullRefreshOptions.down);
- downOptions.down.callback = '_CALLBACK';
- //父页面初始化pulldown
- parent.evalJS("mui&&mui(document.querySelector('.mui-content')).pullRefresh('" + JSON.stringify(downOptions) + "')");
- }
- }
- });
- } else {
- $container.pullRefresh(pullRefreshOptions);
- }
- }
- }
- }
- }
- });
- })(mui);
- /**
其中$container是从mui.init中的pullRefresh初始化的参数中来的:
- var container = pullRefreshOptions.container;
就是containers等于id为pullreresh的那个div。
这段代码的核心是:
1、 通过调用$container.pullRefresh(pullRefreshOptions);, 将sub.html中配置的container,up,down这些options都传给了PullRefresh对象。
2、也是通过上述代码,初始化了PullRefresh对象。
总结:
1、在mui.init中通过调用pullRefresh将container,up,down等参数传入初始化了PullRefresh对象(针对iOS使用H5),或PlusPullRefresh对象(针对Android使用HTML5+)
2、在mui('#pullrefresh').pullRefresh().pullupLoading();中完成了表格初始化加载。调用了up对应的pullupRefresh方法。
3、当下拉刷新时,调用down对应的方法pulldownRefresh;当上拉加载时,调用up调用的方法pullupRefresh;
4、在pullupRefresh和pullupRefresh中利用ajax实现远程数据加载。
5、在mui.init中初始化pullRefresh时,可以通过参数来设置显示的话术,不写的话默认也有话术,见源代码。
- up:{contentrefresh:"正在加载...",//可选,正在加载状态时,上拉加载控件上显示的标题内容
- contentnomore:'没有更多数据了',//可选,请求完毕若没有更多数据时显示的提醒内容;
==================================================================================================================
var count = 0; /** * 上拉加载具体业务实现 */ function pullupRefresh() { setTimeout(function() { mui('#pullrefresh').pullRefresh().endPullupToRefresh((++count > 2)); //参数为true代表没有更多数据了。 var table = document.body.querySelector('.mui-table-view'); var cells = document.body.querySelectorAll('.mui-table-view-cell'); for (var i = cells.length, len = i + 20; i < len; i++) { var li = document.createElement('li'); li.className = 'mui-table-view-cell'; li.innerHTML = '<a class="mui-navigate-right">Item ' + (i + 1) + '</a>'; table.appendChild(li); } }, 1500); }
下面分析一下上拉加载的代码,同时它也是表格初始化的代码。
mui('#pullrefresh').pullRefresh().endPullupToRefresh((++count > 2)); //参数为true代表没有更多数据了。
这句代码用于显示没有更多数据这句话,并结束上拉。由于官方案例主要用于模拟,因此它的终止条件是上拉2次,但是初始化表格还算一次,因此count最终等于3而终止上拉。
如果是真实项目,应该是先用Ajax从远程或storage从本地加载数据,应该从Server返回一个属性标识是否后续还有数据。
同时还需要考虑首次加载和上拉加载的参数传递。
假设列表是按照item的id排序(假设id是自增序列),首次加载从max(item_id)返回20条数据,这样上拉加载时就告知server当前列表底部最后一条数据的id,然后server返回比该id更小的20条数据。当下拉刷新时,向服务器请求id大于列表顶端的item。
rest api:上拉加载 1、/list/load 加载最新的20条,select * from list orderby id limit 20
2、/list/load?startId={列表底部item的id} select * from list where id<{列表底部item的id} limit 20
如果返回的结果小于20,那说明已经到底儿了,返回给客户端告知isEnd:ture
下拉刷新 :/list/load?endId={列表顶部item的id} select * from list where id>{列表顶部item的id} orderby id limit 20
如果新的数据大于20,也不可能一次都拉过来,上述sql返回离现在最近的20条,余下的可以采用循环拉取方式,比如发现select回来加了limi限制的数据和select count(*) from list where id>{列表顶部item的id} 相比更少,可以再次发起请求拉取,/list/load?endId={本次下拉刷新操作前列表顶部item的id} & startId={上次取回的数据最小的id}, select * from list where id>{本次下拉刷新操作前列表顶部item的id} and id<{上次取回的数据最小的id} orderby id limit 2,然后拿回来的数据再次与select count(*) from list where id>{本次下拉刷新操作前列表顶部item的id} and id<{上次取回的数据最小的id}比如果还是小,那么告知客户端继续拉取。
另一种方式是将之前的数据清掉,保证可以触发上拉加载事件(不知道MUI是否支持)
其实如果量不大,就一次性都拉过来也可以,也就是去掉limit 20的限制。
返回的结果集json:
var result={"list":[
{"item_id":1,"item_content":"Hello cat"}, {"item_id":2,"item_content":"Hello dog"}
],"isEnd":true}