/* * Jingle v0.4 Copyright (c) 2013 shixy, http://shixy.github.io/Jingle/ * Released under MIT license * walker.shixy@gmail.com */ var Jingle = J = { version : '0.41', $ : window.Zepto, //参数设置 settings : { //single 单页面工程 muti 多页面工程 appType : 'single', //page默认动画效果 transitionType : 'slide', //自定义动画时的默认动画时间(非page转场动画时间) transitionTime : 250, //自定义动画时的默认动画函数(非page转场动画函数) transitionTimingFunc : 'ease-in', //toast 持续时间,默认为3s toastDuration : 3000, //是否显示欢迎界面 showWelcome : false, //欢迎界面卡片切换时的执行函数,可以制作酷帅吊炸天的欢迎界面 welcomeSlideChange : null, //加载page模板时,是否显示遮罩 showPageLoading : false, //page模板默认的相对位置,主要用于开发hybrid应用,实现page的自动装载 basePagePath : 'html/', basePageSuffix : '.html', //page模板的远程路径{#id: href,#id: href} remotePage:{} }, //手机或者平板 mode : window.innerWidth < 800 ? "phone" : "tablet", hasTouch : 'ontouchstart' in window, //是否启动完成 launchCompleted : false, //是否有打开的侧边菜单 hasMenuOpen : false, //是否有打开的弹出框 hasPopupOpen : false, isWebApp : location.protocol == 'http:', /** * 启动Jingle * @param opts {object} */ launch : function(opts){ $.extend(this.settings,opts); var hasShowWelcome = window.localStorage.getItem('hasShowWelcome'); if(!hasShowWelcome && this.settings.showWelcome){ this.Welcome.show(); } this.Element.init(); this.Element.initControlGroup(); this.Router.init(); this.Menu.init(); } }; /** * 初始化页面组件元素 */ J.Element = (function($){ var SELECTOR = { 'icon' : '[data-icon]', 'scroll' : '[data-scroll="true"]', 'toggle' : '.toggle', 'range' : '[data-rangeinput]', 'progress' : '[data-progress]', 'count' : '[data-count]', 'checkbox' : '[data-checkbox]' } /** * 初始化容器内组件 * @param {String} 父元素的css选择器 * @param {Object} 父元素或者父元素的zepto实例 */ var init = function(selector){ if(!selector){ //iscroll 必须在元素可见的情况下才能初始化 $(document).on('articleshow','article',function(){ J.Element.scroll(this); }); }; var $el = $(selector || 'body'); if($el.length == 0)return; $.map(_getMatchElements($el,SELECTOR.icon),_init_icon); $.map(_getMatchElements($el,SELECTOR.toggle),_init_toggle); $.map(_getMatchElements($el,SELECTOR.range),_init_range); $.map(_getMatchElements($el,SELECTOR.progress),_init_progress); $.map(_getMatchElements($el,SELECTOR.count),_init_badge); $.map(_getMatchElements($el,SELECTOR.checkbox),_init_checkbox); $el = null; } /** * 初始化按钮组(绑定事件) */ var initControlGroup = function(){ $(document).on('tap','ul.control-group li',function(){ var $this = $(this); if($this.hasClass('active'))return; $this.addClass('active').siblings('.active').removeClass('active').parent().trigger('change',[$this]); }); } /** * 自身与子集相结合 */ var _getMatchElements = function($el,selector){ return $el.find(selector).add($el.filter(selector)); } /** * 初始化iscroll组件或容器内iscroll组件 */ var initScroll = function(selector){ $.map(_getMatchElements($(selector),SELECTOR.scroll),function(el){J.Scroll(el);}); } /** * 构造icon组件 */ var _init_icon = function(el){ var $el = $(el),$icon=$el.children('i.icon'),icon = $el.data('icon'); if($icon.length > 0){//已经初始化,就更新icon $icon.attr('class','icon '+icon); }else{ $el.prepend(''); } } /** * 构造toggle切换组件 */ var _init_toggle = function(el){ var $el = $(el); if($el.find('div.toggle-handle').length>0){//已经初始化 return; } var name = $el.attr('name'); //添加隐藏域,方便获取值 if(name){ $el.append(''); } $el.append('
'); $el.tap(function(){ var $t = $(this),v = !$t.hasClass('active'); $t.toggleClass('active').trigger('toggle',[v]);//定义toggle事件 $t.find('input').val(v); }) } /** * 构造range滑块组件 */ var _init_range = function(el){ var $el = $(el),$input; var $range = $('input[type="range"]',el); var align = $el.data('rangeinput'); var input = $(''); if(align == 'left'){ $input = input.prependTo($el); }else{ $input = input.appendTo($el); } var max = parseInt($range.attr('max'),10); var min = parseInt($range.attr('min'),10); $range.change(function(){ $input.val($range.val()); }); $input.on('input',function(){ var value = parseInt($input.val(),10); value = value>max?max:(value').appendTo($el); } $bar.width(progress).text(title+progress); if(progress == '100%'){ $bar.css('border-radius','10px'); } } /** * 构造badge组件 */ var _init_badge = function(el){ var $el = $(el),$count = $el.find('span.count'),count = parseInt($el.data('count')), orient = $el.data('orient'), className = (orient == 'left')?'left':''; if($count.length>0){ $count.text(count).show();//更新数字 }else{ $count = $(''+count+'').appendTo($el); } if(count == 0){ $count.hide(); } } var _init_checkbox = function(el){ var $el = $(el); var value = $el.data('checkbox'); if($el.find('i.icon').length>0){ return; } $el.prepend(''); $el.on('tap',function(){ var status = ($el.data('checkbox') == 'checked') ? 'unchecked':'checked'; $el.data('checkbox',status).find('i.icon').attr('class','icon checkbox-'+status); //自定义change事件 $el.trigger('change'); }); } return { init : init, initControlGroup : initControlGroup, icon : _init_icon, toggle : _init_toggle, progress : _init_progress, range : _init_range, badge : _init_badge, scroll : initScroll } })(J.$); /** * 侧边菜单 */ J.Menu = (function($){ var $asideContainer,$sectionContainer,$sectionMask; var init = function(){ $asideContainer = $('#aside_container'); $sectionContainer = $('#section_container'); $sectionMask = $('
').appendTo('#section_container'); //添加各种关闭事件 $sectionMask.on('tap',hideMenu); $asideContainer.on('swipeRight','aside',function(){ if($(this).data('position') == 'right'){ hideMenu(); } }); $asideContainer.on('swipeLeft','aside',function(){ if($(this).data('position') != 'right'){ hideMenu(); } }); $asideContainer.on('tap','.aside-close',hideMenu); } /** * 打开侧边菜单 * @param selector css选择器或element实例 */ var showMenu = function(selector){ var $aside = $(selector).addClass('active'), transition = $aside.data('transition'),// push overlay reveal position = $aside.data('position') || 'left', showClose = $aside.data('show-close'), width = $aside.width(), translateX = position == 'left'?width+'px':'-'+width+'px'; if(showClose && $aside.find('div.aside-close').length == 0){ $aside.append('
'); } //aside中可能需要scroll组件 J.Element.scroll($aside); if(transition == 'overlay'){ J.anim($aside,{translateX : '0%'}); }else if(transition == 'reveal'){ J.anim($sectionContainer,{translateX : translateX}); }else{//默认为push J.anim($aside,{translateX : '0%'}); J.anim($sectionContainer,{translateX : translateX}); } $('#section_container_mask').show(); J.hasMenuOpen = true; } /** * 关闭侧边菜单 * @param duration {int} 动画持续时间 * @param callback 动画完毕回调函数 */ var hideMenu = function(duration,callback){ var $aside = $('#aside_container aside.active'), transition = $aside.data('transition'),// push overlay reveal position = $aside.data('position') || 'left', translateX = position == 'left'?'-100%':'100%'; var _finishTransition = function(){ $aside.removeClass('active'); J.hasMenuOpen = false; callback && callback.call(this); }; if(transition == 'overlay'){ J.anim($aside,{translateX : translateX},duration,_finishTransition); }else if(transition == 'reveal'){ J.anim($sectionContainer,{translateX : '0'},duration,_finishTransition); }else{//默认为push J.anim($aside,{translateX : translateX},duration); J.anim($sectionContainer,{translateX : '0'},duration,_finishTransition); } $('#section_container_mask').hide(); } return { init : init, show : showMenu, hide : hideMenu } })(J.$); /** * section 页面远程加载 */ J.Page = (function($){ var _formatHash = function(hash){ return hash.indexOf('#') == 0 ? hash.substr(1) : hash; } /** * 加载section模板 * @param {string} hash信息 * @param {string} url参数 */ var loadSectionTpl = function(hash,callback){ var param = {},query,replaceSection = false; if($.type(hash) == 'object'){ param = hash.param; query = hash.query; hash = hash.tag; } var q = $(hash).data('query'); //已经存在则直接跳转到对应的页面 if($(hash).length == 1){ if(q == query){ callback(); return; }else{ replaceSection = true; } } var id = _formatHash(hash); //当前dom中不存在,需要从服务端加载 var url = J.settings.remotePage[hash]; //检查remotePage中是否有配置,没有则自动从basePagePath中装载模板 url || (url = J.settings.basePagePath+id+J.settings.basePageSuffix); J.settings.showPageLoading && J.showMask(); loadContent(url,param,function(html){ J.settings.showPageLoading && J.hideMask(); //添加到dom树中 $(hash).remove(); var $h = $(html); $('#section_container').append($h); if(replaceSection){ $h.addClass('active'); } //触发pageload事件 $h.trigger('pageload').data('query',query); //构造组件 J.Element.init(hash); callback(); $h = null; }); } var loadSectionRemote = function(url,section){ var param = J.Util.parseHash(window.location.hash).param; loadContent(url,param,function(html){ $(section).html(html); J.Element.init(section); }); } /** * 加载文档片段 * @param url */ var loadContent = function(url,param,callback){ return $.ajax({ url : url, timeout : 20000, data : param, success : function(html){ callback && callback(html); } }); } return { load : loadSectionTpl, loadSection : loadSectionRemote, loadContent : loadContent } })(J.$); /** * 路由控制器 */ J.Router = (function($){ var _history = []; /** * 初始化events、state */ var init = function(){ $(window).on('popstate', _popstateHandler); //阻止含data-target或者href以'#'开头的的a元素的默认行为 $(document).on('click','a',function(e){ var target = $(this).data('target'), href = $(this).attr('href'); if(!href || href.match(/^#/) || target){ e.preventDefault(); return false; } }); $(document).on('tap','a[data-target]',_targetHandler); _initIndex(); } //处理app页面初始化 var _initIndex = function(){ var targetHash = location.hash; //取页面中第一个section作为app的起始页 var $section = $('#section_container section').first(); var indexHash = '#'+$section.attr('id'); _add2History(indexHash,true); if(targetHash != '' && targetHash != indexHash){ _showSection(targetHash);//跳转到指定的页面 }else{ $section.trigger('pageinit').trigger('pageshow').data('init',true).find('article.active').trigger('articleshow'); } } /** * 处理浏览器的后退事件 * 前进事件不做处理 * @private */ var _popstateHandler = function(e){ if(e.state && e.state.hash){ var hash = e.state.hash; if(_history[1] && hash === _history[1].hash){//存在历史记录,证明是后退事件 J.hasMenuOpen && J.Menu.hide();//关闭当前页面的菜单 J.hasPopupOpen && J.Popup.close();//关闭当前页面的弹出窗口 back(); }else{//其他认为是非法后退或者前进 return; } }else{ return; } } var _targetHandler = function(){ var _this = $(this), target = _this.attr('data-target'), href = _this.attr('href'); switch(target){ case 'section' : if(J.settings.appType == 'single'){ _showSection(href); } break; case 'article' : _showArticle(href,_this); break; case 'menu' : _toggleMenu(href); break; case 'back' : window.history.go(-1); break; } } /** * 跳转到新页面 * @param hash 新page的'#id' */ var _showSection = function(hash){ if(J.hasMenuOpen){//关闭菜单后再转场 J.Menu.hide(200,function(){ _showSection(hash); }); return; } //读取hash信息 var hashObj = J.Util.parseHash(hash); var current = _history[0]; //同一个页面,则不重新加载 if(current.hash === hashObj.hash){ return; } //加载模板 J.Page.load(hashObj,function(){ var sameSection = (current.tag == hashObj.tag); if(sameSection){//相同页面,触发相关事件 $(current.tag).trigger('pageshow').find('article.active').trigger('articlehide'); }else{//不同卡片页跳转动画 _changePage(current.tag,hashObj.tag); } _add2History(hash,sameSection); }); } /** * 后退 */ var back = function(){ if(J.settings.appType == 'single'){ _changePage(_history.shift().tag,_history[0].tag,true) } } var _changePage = function(current,target,isBack){ J.Transition.run(current,target,isBack); } /** * 缓存访问记录 */ var _add2History = function(hash,noState){ var hashObj = J.Util.parseHash(hash); if(noState){//不添加浏览器历史记录 _history.shift(hashObj); window.history.replaceState(hashObj,'',hash); }else{ window.history.pushState(hashObj,'',hash); } _history.unshift(hashObj); } /** * 激活href对应的article * @param href #id * @param el 当前锚点 */ var _showArticle = function(href,el){ var article = $(href); if(article.hasClass('active'))return; el.addClass('active').siblings('.active').removeClass('active'); var activeArticle = article.addClass('active').siblings('.active').removeClass('active'); article.trigger('articleshow'); activeArticle.trigger('articlehide'); } var _toggleMenu = function(hash){ J.hasMenuOpen?J.Menu.hide():J.Menu.show(hash); } return { init : init, goTo : _showSection, showArticle : _showArticle, back : back } })(J.$); /** * 对zeptojs的ajax进行封装,实现离线访问 * 推荐纯数据的ajax请求调用本方法,其他的依旧使用zeptojs自己的ajax * @Deprecated 用J.Cache代替 */ J.Service = (function($){ var UNPOST_KEY = 'JINGLE_POST_DATA', GET_KEY_PREFIX = 'JINGLE_GET_'; var ajax = function(options){ if(options.type == 'post'){ _doPost(options); }else{ _doGet(options); } } var _doPost = function(options){ if(J.offline){//离线模式,将数据存到本地,连线时进行提交 _setUnPostData(options.url,options.data); options.success('数据已存至本地'); }else{//在线模式,直接提交 $.ajax(options); } } var _doGet = function(options){ var key = options.url +JSON.stringify(options.data); if(J.offline){//离线模式,直接从本地读取 var result = _getCache(key); if(result){ options.success(result.data,key,result.cacheTime); }else{//未缓存该数据 options.success(result); } }else{//在线模式,将数据保存到本地 var callback = options.success; options.success = function(result){ _saveData2local(key,result); callback(result,key); } $.ajax(options); } } /** * 获取本地已缓存的数据 * @private */ var _getCache = function(key){ return JSON.parse(window.localStorage.getItem(GET_KEY_PREFIX+key)); } /** * 缓存数据到本地 * @private */ var _saveData2local = function(key,result){ var data = { data : result, cacheTime : new Date() } window.localStorage.setItem(GET_KEY_PREFIX+key,JSON.stringify(data)); } /** * 将post的数据保存至本地 * @param url * @param result * @private */ var _setUnPostData = function(url,result){ var data = getUnPostData(); data = data || {}; data[url] = { data : result, createdTime : new Date() } window.localStorage.setItem(UNPOST_KEY,JSON.stringify(data)); } /** * 获取尚未同步的post数据 * @param url 没有就返回所有未同步的数据 */ var getUnPostData = function(url){ var data = JSON.parse(window.localStorage.getItem(UNPOST_KEY)); return (data && url ) ? data[url] : data; } /** * 移除未同步的数据 * @param url 没有就移除所有未同步的数据 */ var removeUnPostData = function(url){ if(url){ var data = getUnPostData(); delete data[url]; window.localStorage.setItem(UNPOST_KEY,JSON.stringify(data)); }else{ window.localStorage.removeItem(UNPOST_KEY); } } /** * 同步本地缓存的post数据 * @param url */ var syncPostData = function(url,success,error){ var unPostData = getUnPostData(url).data; $.ajax({ url : url, contentType:'application/json', data : unPostData, type : 'post', success : function(){ success(url); }, error : function(){ error(url); } }) } /** * 同步所有的数据 * @param callback */ var syncAllPostData = function(success,error){ var unPostData = getUnPostData(); for(var url in unPostData){ syncPostData(url,success,error); } removeUnPostData(); } //copy from zepto function parseArguments(url, data, success, dataType) { var hasData = !$.isFunction(data) return { url: url, data: hasData ? data : undefined, success: !hasData ? data : $.isFunction(success) ? success : undefined, dataType: hasData ? dataType || success : success } } var get = function(url, data, success, dataType){ return ajax(parseArguments.apply(null, arguments)) } var post = function(url, data, success, dataType){ var options = parseArguments.apply(null, arguments) options.type = 'POST' return ajax(options) } var getJSON = function(url, data, success){ var options = parseArguments.apply(null, arguments); options.dataType = 'json' return ajax(options) } var clear = function(){ var storage = window.localStorage; var keys = []; for(var i = 0; i< storage.length; i++){ var key = storage.key(i); key.indexOf(GET_KEY_PREFIX) == 0 && keys.push(key); } for(var i = 0; i < keys.length; i++){ storage.removeItem(keys[i]); } storage.removeItem(UNPOST_KEY); } return { ajax : ajax, get : get, post : post, getJSON : getJSON, getUnPostData : getUnPostData, removeUnPostData : removeUnPostData, syncPostData : syncPostData, syncAllPostData : syncAllPostData, getCacheData : _getCache, saveCacheData : _saveData2local, clear : clear } })(J.$); /** * 提供一些简单的模板,及artTemplate的渲染 */ J.Template = (function($){ /** * 背景模板 * @param el selector * @param title 显示文本 * @param icon 图标 */ var background = function(el,title,icon){ var markup = '
'+title+'
'; $(el).html(markup); } /** * 无记录背景模板 * @param el */ var no_result = function(el){ background(el,'没有找到相关数据','drawer'); } /** * 加载等待背景模板 * @param el */ var loading = function(el){ background(el,'加载中...','cloud-download'); } /** * 借助artTemplate模板来渲染页面 * @param containerSelector 目标容器 * @param templateId artTemplate模板ID * @param data 模板数据 * @param type replace|add 渲染好的文档片段是替换还是添加到目标容器中 */ var render = function(containerSelector,templateId,data,type){ var el = $(containerSelector), type = type || 'replace';//replace add if($.type(data) == 'array' && data.length == 0 ){ no_result(el); }else{ var html = template(templateId,data); if(type == 'replace'){ el.html(html); }else{ el.append(html); } J.Element.init(html); } } return { render : render, background : background, loading : loading, no_result : no_result } })(J.$); /** * 消息组件 */ J.Toast = (function($){ var toast_type = 'toast',_toast,timer, //定义模板 TEMPLATE = { toast : '{value}', success : '{value}', error : '{value}', info : '{value}' } var _init = function(){ //全局只有一个实例 $('body').append('
'); _toast = $('#jingle_toast'); _subscribeCloseTag(); } /** * 关闭消息提示 */ var hide = function(){ J.anim(_toast,'scaleOut',function(){ _toast.hide(); _toast.empty(); }); } /** * 显示消息提示 * @param type 类型 toast|success|error|info 空格 + class name 可以实现自定义样式 * @param text 文字内容 * @param duration 持续时间 为0则不自动关闭,默认为3000ms */ var show = function(type,text,duration){ if(timer) clearTimeout(timer); var classname = type.split(/\s/); toast_type = classname[0]; _toast.attr('class',type).html(TEMPLATE[toast_type].replace('{value}',text)).show(); J.anim(_toast,'scaleIn'); if(duration !== 0){//为0 不自动关闭 timer = setTimeout(hide,duration || J.settings.toastDuration); } } var _subscribeCloseTag = function(){ _toast.on('tap','[data-target="close"]',function(){ hide(); }) } _init(); return { show : show, hide : hide } })(J.$); /** * page转场动画 * 可自定义css动画 */ J.Transition = (function($){ var isBack,$current,$target,transitionName, animationClass = { //[[currentOut,targetIn],[currentOut,targetIn]] slide : [['slideLeftOut','slideLeftIn'],['slideRightOut','slideRightIn']], cover : [['','slideLeftIn'],['slideRightOut','']], slideUp : [['','slideUpIn'],['slideDownOut','']], slideDown : [['','slideDownIn'],['slideUpOut','']], popup : [['','scaleIn'],['scaleOut','']] }; var _doTransition = function(){ //触发 beforepagehide 事件 $current.trigger('beforepagehide',[isBack]); //触发 beforepageshow 事件 $target.trigger('beforepageshow',[isBack]); var c_class = transitionName[0]||'empty' ,t_class = transitionName[1]||'empty'; $current.bind('webkitAnimationEnd.jingle', _finishTransition).addClass('anim '+ c_class); $target.addClass('anim animating '+ t_class); } var _finishTransition = function() { $current.off('webkitAnimationEnd.jingle'); $target.off('webkitAnimationEnd.jingle'); //reset class $current.attr('class',''); $target.attr('class','active'); //add custom events !$target.data('init') && $target.trigger('pageinit').data('init',true); !$current.data('init') && $current.trigger('pageinit').data('init',true); //触发pagehide事件 $current.trigger('pagehide',[isBack]); //触发pageshow事件 $target.trigger('pageshow',[isBack]); $current.find('article.active').trigger('articlehide'); $target.find('article.active').trigger('articleshow'); $current = $target = null;//释放 } /** * 执行转场动画,动画类型取决于目标page上动画配置(返回时取决于当前page) * @param current 当前page * @param target 目标page * @param back 是否为后退 */ var run = function(current,target,back){ //关闭键盘 $(':focus').trigger('blur'); isBack = back; $current = $(current); $target = $(target); var type = isBack?$current.attr('data-transition'):$target.attr('data-transition'); type = type|| J.settings.transitionType; //后退时取相反的动画效果组 transitionName = isBack ? animationClass[type][1] : animationClass[type][0]; _doTransition(); } /** * 添加自定义转场动画效果 * @param name 动画名称 * @param currentOut 正常情况下当前页面退去的动画class * @param targetIn 正常情况下目标页面进入的动画class * @param backCurrentOut 后退情况下当前页面退去的动画class * @param backCurrentIn 后退情况下目标页面进入的动画class */ var addAnimation = function(name,currentOut,targetIn,backCurrentOut,backCurrentIn){ if(animationClass[name]){ console.error('该转场动画已经存在,请检查你自定义的动画名称(名称不能重复)'); return; } animationClass[name] = [[currentOut,targetIn],[backCurrentOut,backCurrentIn]]; } return { run : run, add : addAnimation } })(J.$); /** * 常用工具类 */ J.Util = (function($){ var parseHash = function(hash){ var tag,query,param={}; var arr = hash.split('?'); tag = arr[0]; if(arr.length>1){ var seg,s; query = arr[1]; seg = query.split('&'); for(var i=0;i{title}', confirm : '', loading : '

{title}

' }; /** * 全局只有一个popup实例 * @private */ var _init = function(){ $('body').append('
'); _mask = $('#jingle_popup_mask'); _popup = $('#jingle_popup'); _subscribeEvents(); } var show = function(options){ var settings = { height : undefined,//高度 width : undefined,//宽度 opacity : 0.3,//透明度 url : null,//远程加载url tplId : null,//加载模板ID tplData : null,//模板数据,配合tplId使用 html : '',//popup内容 pos : 'center',//位置 {@String top|top-second|center|bottom|bottom-second} {@object css样式} clickMask2Close : true,// 是否点击外层遮罩关闭popup showCloseBtn : true,// 是否显示关闭按钮 arrowDirection : undefined,//popover的箭头指向 animation : true,//是否显示动画 timingFunc : 'linear', duration : 200,//动画执行时间 onShow : undefined //@event 在popup内容加载完毕,动画开始前触发 } $.extend(settings,options); clickMask2close = settings.clickMask2Close; _mask.css('opacity',settings.opacity); //rest position and class _popup.attr({'style':'','class':''}); settings.width && _popup.width(settings.width); settings.height && _popup.height(settings.height); var pos_type = $.type(settings.pos); if(pos_type == 'object'){// style _popup.css(settings.pos); transition = ANIM['defaultAnim']; }else if(pos_type == 'string'){ if(POSITION[settings.pos]){ //已经默认的样式 _popup.css(POSITION[settings.pos]) var trans_key = settings.pos.indexOf('top')>-1?'top':(settings.pos.indexOf('bottom')>-1?'bottom':'defaultAnim'); transition = ANIM[trans_key]; }else{// pos 为 class _popup.addClass(settings.pos); transition = ANIM['defaultAnim']; } }else{ console.error('错误的参数!'); return; } _mask.show(); var html; if(settings.html){ html = settings.html; }else if(settings.url){//远程加载 html = J.Page.loadContent(settings.url); }else if(settings.tplId){//加载模板 html = template(settings.tplId,settings.tplData) } //是否显示关闭按钮 if(settings.showCloseBtn){ html += '
'; } //popover 箭头方向 if(settings.arrowDirection){ _popup.addClass('arrow '+settings.arrowDirection); _popup.css('padding','8px'); if(settings.arrowDirection=='top'||settings.arrowDirection=='bottom'){ transition = ANIM[settings.arrowDirection]; } } _popup.html(html).show(); J.Element.init(_popup); //执行onShow事件,可以动态添加内容 settings.onShow && settings.onShow.call(_popup); //显示获取容器高度,调整至垂直居中 if(settings.pos == 'center'){ var height = _popup.height(); _popup.css('margin-top','-'+height/2+'px') } if(settings.animation){ J.anim(_popup,transition[0],settings.duration,settings.timingFunc); } J.hasPopupOpen = true; } /** * 关闭弹出框 * @param noTransition 立即关闭,无动画 */ var hide = function(noTransition){ _mask.hide(); if(transition && !noTransition){ J.anim(_popup,transition[1],200,function(){ _popup.hide().empty(); J.hasPopupOpen = false; }); }else{ _popup.hide().empty(); J.hasPopupOpen = false; } } var _subscribeEvents = function(){ _mask.on('tap',function(){ clickMask2close && hide(); }); _popup.on('tap','[data-target="closePopup"]',function(){hide();}); } /** * alert组件 * @param title 标题 * @param content 内容 */ var alert = function(title,content,btnName){ var markup = TEMPLATE.alert.replace('{title}',title).replace('{content}',content).replace('{ok}',btnName || '确定'); show({ html : markup, pos : 'center', clickMask2Close : false, showCloseBtn : false }); } /** * confirm 组件 * @param title 标题 * @param content 内容 * @param okCall 确定按钮handler * @param cancelCall 取消按钮handler */ var confirm = function(title,content,okCall,cancelCall){ var markup = TEMPLATE.confirm.replace('{title}',title).replace('{content}',content).replace('{cancel}','取消').replace('{ok}','确定'); show({ html : markup, pos : 'center', clickMask2Close : false, showCloseBtn : false }); $('#popup_btn_container [data-icon="checkmark"]').tap(function(){ hide(); okCall.call(this); }); $('#popup_btn_container [data-icon="close"]').tap(function(){ hide(); cancelCall.call(this); }); } /** * 带箭头的弹出框 * @param html 弹出框内容 * @param pos 位置 * @param arrow_direction 箭头方向 * @param onShow onShow事件 */ var popover = function(html,pos,arrow_direction,onShow){ show({ html : html, pos : pos, showCloseBtn : false, arrowDirection : arrow_direction, onShow : onShow }); } /** * loading组件 * @param text 文本,默认为“加载中...” */ var loading = function(text){ var markup = TEMPLATE.loading.replace('{title}',text||'加载中...'); show({ html : markup, pos : 'loading', opacity :.1, animation : true, clickMask2Close : false }); } /** * actionsheet组件 * @param buttons 按钮集合 * [{color:'red',text:'btn',handler:function(){}},{color:'red',text:'btn',handler:function(){}}] */ var actionsheet = function(buttons){ var markup = '
'; $.each(buttons,function(i,n){ markup += ''; }); markup += ''; markup += '
'; show({ html : markup, pos : 'bottom', showCloseBtn : false, onShow : function(){ $(this).find('button').each(function(i,button){ $(button).on('tap',function(){ if(buttons[i] && buttons[i].handler){ buttons[i].handler.call(button); } hide(); }); }); } }); } _init(); return { show : show, close : hide, alert : alert, confirm : confirm, popover : popover, loading : loading, actionsheet : actionsheet } })(J.$); /** * 高亮组件 * 在zepto的tap事件里注入了一个延时器,来实现点击态 */ J.Selected = (function($){ var DELAY = 100,SELECTOR='[data-selected]'; var _trigger = $.fn.trigger; $.fn.trigger = function (event) { var $this = $(this), args = arguments, classname; if (event === 'tap' || event.type === 'tap') { var match = $this.closest(SELECTOR).get(0); if(match){ match = $(match); classname = match.data('selected'); match.addClass(classname); setTimeout(function () { match.removeClass(classname); _trigger.apply($this, args); $this = match = null; }, DELAY); return this; } } _trigger.apply($this, args); return this; } })(J.$); /** * 数据缓存 * todo 对数据进行加密 */ J.Cache = (function($){ var UNPOST_KEY = '_J_P_', GET_KEY_PREFIX = '_J_'; /** * 缓存从服务端获取的数据 * @param key * @param value */ var save = function(key,value){ var data = { data : value, cacheTime : new Date() } window.localStorage.setItem(GET_KEY_PREFIX+key,JSON.stringify(data)); } /** * 获取本地已缓存的数据 */ var get = function(key){ return JSON.parse(window.localStorage.getItem(GET_KEY_PREFIX+key)); } /** * 缓存本地待提交到服务端的数据(离线操作) * @param url * @param result */ var savePost = function(url,result){ var data = getPost(); data = data || {}; data[url] = { data : result, createdTime : new Date() } window.localStorage.setItem(UNPOST_KEY,JSON.stringify(data)); } /** * 获取本地尚未提交到服务端的缓存数据 * @param url 没有就返回所有未同步的数据 */ var getPost = function(url){ var data = JSON.parse(window.localStorage.getItem(UNPOST_KEY)); return (data && url ) ? data[url] : data; } /** * 移除未提交的待提交到服务端的缓存数据 * @param url 没有就移除所有未提交的数据 */ var removePost = function(url){ if(url){ var data = getPost(); delete data[url]; window.localStorage.setItem(UNPOST_KEY,JSON.stringify(data)); }else{ window.localStorage.removeItem(UNPOST_KEY); } } /** * 同步本地未提交的数据到服务端 * * @param url 没有就同步所有未提交的数据 */ var syncPost = function(url,success,error){ var dataLen,index = 0; if($.type(url) == 'string'){ dataLen = 1; sync(url); }else{ var postData = getPost(); if(!postData)return; dataLen = postData.length; for(var url in postData){ sync(url); } } function sync(url){ var data = getPost(url).data; $.ajax({ url : url, contentType:'application/json', data : data, type : 'post', success : function(){ index++; removePost(url); if(index == dataLen)success(url); }, error : function(){ error(url); } }) } } /** * 清空本地缓存 */ var clear = function(){ var storage = window.localStorage; for(var key in storage){ if(key.indexOf(GET_KEY_PREFIX) == 0){ storage.removeItem(key); } } storage.removeItem(UNPOST_KEY); } return { get : get, save : save, getPost : getPost, savePost : savePost, removePost : removePost, syncPost : syncPost, clear : clear } })(J.$); ;(function($){ /** * 日历组件 * @param selector selector * @param options 配置参数 */ var calendar = function(selector,options){ var defaults = { months : ["01月", "02月", "03月", "04月", "05月", "06月", "07月", "08月", "09月", "10月", "11月", "12月"], days : ["日", "一", "二", "三", "四", "五", "六"], swipeable : true,//是否可通过手指滑动 date : new Date(),//日历当前日期 onRenderDay : undefined,//渲染单元格时的事件 onSelect : undefined //选中日期时的事件 }, _this = this, $el = $(selector), $yearText, $monthText, $calendarBody, currentDate,currentYear,currentMonth; var _init = function(){ _this.settings = $.extend({},defaults,options); currentYear = _this.settings.date.getFullYear(); currentMonth = _this.settings.date.getMonth(); currentDate = new Date(currentYear,currentMonth,_this.settings.date.getDate()); _render(); _subscribeEvents(); } /** * 获取月份第一天是星期几[0-6] */ var _fisrtDayOfMonth = function(date){ return ( new Date(date.getFullYear(), date.getMonth(), 1) ).getDay(); } /** * 获取月份总天数[1-31] */ var _daysInMonth = function(date){ return ( new Date(date.getFullYear(),date.getMonth()+1,0) ).getDate(); } /** * 渲染日历 * @private */ var _render = function(){ var html = ''; html += '
'; html += _renderNav(currentYear,currentMonth); html += _renderHead(); html += '
'; html += _renderBody(currentDate); html += '
' $el.html(html); var $span = $el.find('span'); $yearText = $span.eq(0); $monthText = $span.eq(1); $calendarBody = $el.find('.jingle-calendar-body'); } var _renderNav = function(year,month){ var html = '
'; html += '
'+year+'
'; html += '
'+_this.settings.months[month]+'
'; html += '
'; return html; } var _renderHead = function(){ var html = ''; for(var i = 0; i < 7; i++){ html += ''; } html += '
'+_this.settings.days[i]+'
' return html; } var _renderBody = function(date){ var firstDay = _fisrtDayOfMonth(date), days = _daysInMonth(date), rows = Math.ceil((firstDay+days) / 7), beginDate, html = ''; currentYear = date.getFullYear(); currentMonth = date.getMonth(); beginDate = new Date(currentYear,currentMonth,1-firstDay);//日历开始的日期 html += ''; for(var i = 0; i < rows; i++){ html += ''; for(var j = 0; j < 7; j++){ html += _renderDay(beginDate,currentMonth); beginDate.setDate(beginDate.getDate() + 1); } html += ''; } html += '
'; return html; } var _renderDay = function(date,month){ var otherMonth = (date.getMonth() !== month); var dateStr = _this.format(date); var classname = (_this.format(_this.settings.date) == dateStr) ? 'active':''; var dayStr = date.getDate(); if(_this.settings.onRenderDay){ dayStr = _this.settings.onRenderDay.call(null,dayStr,dateStr); } return otherMonth ? ' ' : ''+dayStr+''; } var _subscribeEvents = function(){ var $target,$ctarget; $el.on('tap',function(e){ $target = $(e.target); if($target.is('[data-year].next')){ //后一年 currentDate.setFullYear(currentDate.getFullYear()+1); _this.refresh(currentDate); }else if($target.is('[data-year].previous')){ //前一年 currentDate.setFullYear(currentDate.getFullYear()-1); _this.refresh(currentDate); }else if($target.is('[data-month].next')){ //后一月 currentDate.setMonth(currentDate.getMonth()+1); _this.refresh(currentDate); }else if($target.is('[data-month].previous')){ //前一月 currentDate.setMonth(currentDate.getMonth()-1); _this.refresh(currentDate); } $ctarget = $target.closest('td'); if(!$target.is('td') && $ctarget.length > 0){ $target = $ctarget; } if($target.is('td')){ var dateStr = $target.data('date'); if(dateStr && _this.settings.onSelect){ _this.settings.onSelect.call(_this,dateStr) } } }); $el.on('swipeLeft',function(){ currentDate.setMonth(currentDate.getMonth()+1); _this.refresh(currentDate); }); $el.on('swipeRight',function(){ currentDate.setMonth(currentDate.getMonth()-1); _this.refresh(currentDate); }) } /** * 刷新日历为指定日期 * @param date 指定日期 */ this.refresh = function(date){ var oldDate = new Date(currentYear,currentMonth,1), newDate = new Date(date.getFullYear(),date.getMonth(),1), transition = undefined,$table; if(oldDate.getTime() == newDate.getTime())return; transition = oldDate0){ dots.show(); }else{ var dotsWidth = slideNum*30+20+2; var html = '
    '; for(var i=0;i 0 || index == slideNum - 1 && deltaX < 0; if(isPastBounds)return; var pos = (deltaX - index * slideWidth); container[0].style.webkitTransform = 'translate3D('+pos+'px,0,0)'; } }; var _touchEnd = function(e) { //判定是否跳转到下一个卡片 //滑动时间小于250ms或者滑动X轴的距离大于屏幕宽度的1/3,则直接跳转到下一个卡片 var isValidSlide = (Number(new Date()) - start.time < 250 && Math.abs(deltaX) > 20) || Math.abs(deltaX) > slideWidth/3; //判定是否达到了边界即第一个右滑、最后一个左滑 var isPastBounds = !index && deltaX > 0 || index == slideNum - 1 && deltaX < 0; if (!isScrolling) { if(beforeSlide(index,deltaX)){ _slide( index + ( isValidSlide && !isPastBounds ? (deltaX < 0 ? 1 : -1) : 0 ), speed ); }else{ _slide(index); } } gestureStarted = false; }; _init(); _bindEvents(); this.refresh = function(){ container.attr('style',''); _init(); }; this.prev = function() { if (index) _slide(index-1, speed); }; this.next = function() { if(index < slideNum-1){ _slide(index+1, speed); } }; this.index = function(i) { _slide(i); }; } J.Slider = slider; })(J.$); /** * 上拉/下拉组件 */ ;(function($){ var refreshCache = {},index = 1; function Refresh(selector,type,callback){ var iscroll, scroller,refreshEl,iconEl,labelEl,topOffset,isPullDown, options = { selector : undefined, type : 'pullDown',//pullDown|pullUp 默认为pullDown minPullHeight : 10,//拉动的像素相对值,超过才会翻转 pullText: "下拉刷新...", releaseText: "松开立即刷新...", refreshText: "刷新中...", refreshTip : false,//下拉时显示的文本,比如:最后更新时间:2013-.... onPullIcon : 'arrow-down-2', onReleaseIcon : 'icon-reverse', onRefreshIcon : 'spinner', callback : undefined }; //装载配置 if(typeof selector === 'object'){ $.extend(options,selector); }else{ options.selector = selector; options.type = type; options.callback = callback; if(type === 'pullUp'){ $.extend(options,{ pullText: "上拉加载更多...", releaseText: "松开开立即加载...", refreshText: "加载中...", onPullIcon : 'arrow-up-3' }) } } isPullDown = options.type === 'pullDown' ? true : false; /** * 初始化dom节点 * @param opts * @private */ var _init = function(opts){ scroller = $(opts.selector).children()[0]; var refreshTpl = '
    ' +opts.pullText+'' +(opts.refreshTip?'
    '+opts.refreshTip+'
    ':'')+'
    '; if(isPullDown){ refreshEl = $(refreshTpl).prependTo(scroller); }else{ refreshEl = $(refreshTpl).appendTo(scroller); } topOffset = refreshEl.height(); iconEl = refreshEl.find('.refresh-icon'); labelEl = refreshEl.find('.refresh-label'); } /** * 构造iscroll组件,并绑定滑动事件 * @param opts * @private */ var _excuteScroll = function(opts){ return J.Scroll(opts.selector,{ topOffset:isPullDown?topOffset:0, bounce : true, onScrollMove : function(){ if (this.y > opts.minPullHeight && isPullDown && !iconEl.hasClass(opts.onReleaseIcon)) { iconEl.addClass(opts.onReleaseIcon); labelEl.html(opts.releaseText); this.minScrollY = 0; } else if (this.y < opts.minPullHeight && isPullDown && iconEl.hasClass(opts.onReleaseIcon)) { iconEl.removeClass(opts.onReleaseIcon); labelEl.html(opts.pullText); this.minScrollY = -topOffset; }else if (this.y < (this.maxScrollY - opts.minPullHeight) && !isPullDown && !iconEl.hasClass(opts.onReleaseIcon)) { iconEl.addClass(opts.onReleaseIcon); labelEl.html(opts.releaseText); this.maxScrollY = this.maxScrollY; } else if (this.y > (this.maxScrollY + opts.minPullHeight) && !isPullDown && iconEl.hasClass(opts.onReleaseIcon)) { iconEl.removeClass(opts.onReleaseIcon); labelEl.html(opts.pullText); this.maxScrollY = topOffset; } }, onScrollEnd : function(){ if(iconEl.hasClass(opts.onReleaseIcon)){ iconEl.removeClass(opts.onReleaseIcon).removeClass(opts.onPullIcon).addClass(opts.onRefreshIcon); labelEl.html(opts.refreshText); var _this = this; setTimeout(function(){//解决在chrome下onRefresh的时候文本无法更改的问题。奇怪的问题! opts.callback.call(_this); },1); } }, onRefresh: function () { iconEl.removeClass(opts.onRefreshIcon).addClass(opts.onPullIcon); labelEl.html(opts.pullText); } }); } //run _init(options); iscroll = _excuteScroll(options); return iscroll; } /** * 刷新组件 * @param selector selector * @param type 类型 pullDown(下拉) pullUp(上拉) * @param callback 回调函数 */ J.Refresh = function(selector,type,callback){ var el,jRefreshId; if(selector.selector){ el = $(selector.selector) }else{ el = $(selector); } jRefreshId = el.data('_jrefresh_'); //因上拉下拉可能会使用的比较频繁,故缓存起来节省开销,亦可防止重复绑定 if(jRefreshId && refreshCache[jRefreshId]){ return refreshCache[jRefreshId]; }else{ jRefreshId = '_jrefresh_'+index++; el.data('_jrefresh_',jRefreshId); var refresh = new Refresh(selector,type,callback); return refreshCache[jRefreshId] = { scroller : refresh.scroller, destroy : function(){ delete refreshCache[jRefreshId]; refresh.scroller.destroy(); $('.refresh-container',selector).remove(); } }; } } })(J.$);