貌似jquery.mobile和backbones的一些实践网上有很多了,我就分享下最近做的jquery.mobile的应用,中间mvc和数据、逻辑隔离部分的设计和代码。
首先就是“单入口模式”——这种设计无处不在啊。
在jquery.mobile的设计里,有很多page切换的钩子方法,具体可以参考官方文档demos/docs/api/events.html里的列表。
因为我做的应用大多是和和传统的request那种模式差不多的,就是在page渲染之前,先对page的dom做一次数据组装,所以用到较多的是pagebeforecreate这个方法,上文说的“单入口”就是这个方法做入口,然后派发,比作DispatchController。
贴下代码:
// mobile page 工具类,获取dom元素的值,跳转事件的过滤等 // need pageData对象,本类的实例对象 // x.js page.data.js var PageUtils = { initBindLl: ['pagebeforechange', 'pagebeforecreate', 'db'], initApp: function(){ var _pageBase = $("div[data-role='page'],div[data-role='dialog']"); if(PageUtils.initBindLl.contains('pagebeforechange')){ _pageBase.live("pagebeforechange", function(e, data){ // while loading a page by URL if(typeof data.toPage === "string"){ var currentPage = IPage.parsePage(document.location.href); var toPage = IPage.parsePage(data.toPage); var pageName = IPage.parsePageName(data.toPage); var pageObj = PageUtils.pages[pageName]; if(pageObj){ pageObj.setPageInfo(toPage, currentPage); var flag = pageObj.canAccess(); if(flag === false){ e.preventDefault(); } } }else if(typeof data.toPage === "object"){ // load page dom } }); } if(PageUtils.initBindLl.contains('pagebeforecreate')){ _pageBase.live('pagebeforecreate', function(e){ var currentPage = IPage.parsePage(document.location.href); var toPage = IPage.parsePage(this.baseURI); var pageName = IPage.parsePageName(this.baseURI); var pageObj = PageUtils.pages[pageName]; if(pageObj){ pageObj.setPageInfo(toPage, currentPage, this); pageObj.initDomEntry(); } }); } if(PageUtils.initBindLl.contains('db')){ // page db initialize PageDb.initTables(); } }, // application pages pages: { pageTaskLl: new PageTaskLl(), pageOne: new PageOne, // add your pages here dump: '' }, dump: '' };
其中抽象了一组application pages,具体实现如下,先定义个接口
/** * Begin class defination Ipage : mobile page 抽象类,一个页面的接口方法 */ var IPage = Base.extend({ // mobile.path.parseUrl : page info info: null, from: null, // 对dom page的引用 _page: null, constructor: function(){}, setPageInfo: function(i, f, pageDom){ this.info = i; this.from = f; if(pageDom) this._page = $(pageDom); }, // 页面dom初始化 initDomEntry: function(){ this.initDomBefore(); this.initDom(); }, initDomBefore: function(){ // append parameter var search = this.info.search; this.dom('#surveyNav a, a.urlPend').each(function(){ var srcUrl = $(this).attr('href'); $(this).attr('href', srcUrl + search); }); }, // need to overwrite 模板方法 initDom: function(){}, params: function(){ return IPage.parseSearch(this.info.search); }, canAccess: function(){return true;}, dom: function(selecter){ return this._page.find(selecter); }, dump : '' },{ // 辅助方法 // 获取url参数键值对,不包含'?' parseSearch: function(query){ var data = {}; if(query){ _.each(query.substring(1).split('&'), function(one){ var arr = one.split('='); if(arr.length == 2) data[arr[0]] = arr[1]; }); } return data; }, // 获取url参数键值对,包含'?' parseSearchByUrl: function(url){ return this.parseSearch(url.substring(url.indexOf('?'))); }, parsePageName: function(url){ var filename = IPage.parsePage(url).filename; return filename.substring(0, filename.lastIndexOf('.')); }, parsePage: function(url){ return $.mobile.path.parseUrl(url); }, // 一些通用的业务逻辑相关的方法 commonFn: { }, // 一些通用的dom生成的方法 commonDomFn: { }, dump : '' });
这样,一个mobile页面就对应一个IPage子类的实例对象,举例:
/** * Begin class defination PageTaskLl * 任务列表 */ var PageTaskLl = IPage.extend({ // 页面dom初始化 initDom: function(){ var _this = this; var _navLl = _this.dom('#navSub button'); _navLl.click(function(e){ // 分页显示,默认显示第一页,一页10条记录 _this.taskLlRefresh($(this).attr('val'), 1, 10); PageTheme.toggleBtnClass($(this), 'c', 'b'); PageTheme.toggleBtnClass(_navLl.not(this), 'b', 'c'); }); // 根据参数显示已完成还是未完成,默认是所有 var params = this.params(); var type = params.type || 'all'; _navLl.filter('[val={0}]'.format(type)).trigger('click'); }, taskLlRefresh: function(type, cp, npp){ var _ul = this.dom('ul[data-role="listview"]'); _ul.empty(); var _this = this; // 异步的 this.getCaseLl(type, cp, npp, function(item){ _ul.append(_this.domFn.getTaskLi(item)); }, function(pager){ _ul.listview('refresh'); // 分页导航 var _piDiv = _this.dom("#taskLl"); _piDiv.html(pager.getHtml2()); _piDiv.find("a.piLink").click(function(){ var cpTarget = parseInt($(this).text()); _this.taskLlRefresh(type, cpTarget, npp); }); }); }, // **** **** **** **** **** **** **** **** // **** **** **** **** **** **** **** **** // 数据库相关操作 // 获取案件列表 getCaseLl: function(type, cp, npp, fn, callback){ var sql = 'select * from t_claim_case where 1 = 1'; var args = []; if(type && 'all' != type){ sql += ' and type = ?'; args.push(type); } XDB.piAndMap(sql, args, cp, npp, fn, callback); }, // **** **** **** **** **** **** **** **** // **** **** **** **** **** **** **** **** // dom相关操作 domFn: { // 任务列表 // 建议使用easyTemplate做html代码生成,比较简洁明了 getTaskLi: function(item){ var fnDd = XHtml.createFn('dd'); var tpl2 = fnDd( XHtml.tag('报案号:' + item.caseNo + '(' + item.type + ')', 'h3') + '<br />' + XHtml.tag('车牌号:' + item.vehicleNo + ' ' + '联系人:' + item.concatName, 'p')) + fnDd('联系人手机:' + item.concatPhone) + fnDd('报案时间:' + item.claimDate.format()); var tpl = XHtml.alink(tpl2, 'pageCaseBase.gy?caseNo={0}'.format(item.caseNo)); var imgInner = '<img src="{0}mb/claim/img/case.png" />'.format(X.appPath); return XHtml.tag(imgInner + tpl, 'li'); } }, dump : '' },{ dump : '' });
页面效果截图如下:
PS,通过这个设计(一个页面对应一个js文件),如果js的oo方面设计合理些,业务逻辑不太复杂的话,可以做出来比较复杂的页面(大量的对话框,关联菜单,过滤表格,事件处理等)。
希望这些实践能为感兴趣的筒子们有所帮助。