Files
2015-08-20 19:06:32 +08:00

2117 lines
70 KiB
JavaScript

/*
* 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('<i class="icon '+icon+'"></i>');
}
}
/**
* 构造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('<input style="display: none;" name="'+name+'" value="'+$el.hasClass('active')+'"/>');
}
$el.append('<div class="toggle-handle"></div>');
$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 = $('<input type="number" name="test" value="'+$range.val()+'"/>');
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<min?min:value);
$range.val(value);
$input.val(value);
})
}
/**
* 构造progress组件
*/
var _init_progress = function(el){
var $el = $(el),$bar;
var progress = parseFloat($el.data('progress'))+'%';
var title = $el.data('title') || '';
$bar = $el.find('div.bar');
if($bar.length == 0){
$bar = $('<div class="bar"></div>').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 = $('<span class="count '+className+'">'+count+'</span>').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('<i class="icon checkbox-'+value+'"></i>');
$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 = $('<div id="section_container_mask"></div>').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('<div class="aside-close icon close"></div>');
}
//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 = '<div class="back-mask"><div class="icon '+icon+'"></div><div>'+title+'</div></div>';
$(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 : '<a href="#">{value}</a>',
success : '<a href="#"><i class="icon checkmark-circle"></i>{value}</a>',
error : '<a href="#"><i class="icon cancel-circle"></i>{value}</a></div>',
info : '<a href="#"><i class="icon info-2"></i>{value}</a>'
}
var _init = function(){
//全局只有一个实例
$('body').append('<div id="jingle_toast"></div>');
_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<seg.length;i++){
if(!seg[i])continue;
s = seg[i].split('=');
param[s[0]] = s[1];
}
}
return {
hash : hash,
tag : tag,
query : query,
param : param
}
}
/**
* 格式化date
* @param date
* @param format
*/
var formatDate = function(date,format){
var o =
{
"M+" : date.getMonth()+1, //month
"d+" : date.getDate(), //day
"h+" : date.getHours(), //hour
"m+" : date.getMinutes(), //minute
"s+" : date.getSeconds(), //second
"q+" : Math.floor((date.getMonth()+3)/3), //quarter
"S" : date.getMilliseconds() //millisecond
}
if(/(y+)/.test(format))
format=format.replace(RegExp.$1,(date.getFullYear()+"").substr(4 - RegExp.$1.length));
for(var k in o)
if(new RegExp("("+ k +")").test(format))
format = format.replace(RegExp.$1,RegExp.$1.length==1 ? o[k] : ("00"+ o[k]).substr((""+ o[k]).length));
return format;
}
return {
parseHash : parseHash,
formatDate : formatDate
}
})(J.$);
/**
* 欢迎界面,可以制作酷炫吊炸天的欢迎界面哦
* @module Welcome
*/
J.Welcome = (function($){
/**
* 显示欢迎界面
*/
var showWelcome = function(){
$.ajax({
url : J.settings.basePagePath+'welcome.html',
timeout : 5000,
async : false,
success : function(html){
//添加到dom树中
$('body').append(html);
new J.Slider({
selector : '#jingle_welcome',
onAfterSlide : J.settings.welcomeSlideChange
});
}
})
}
/**
* 关闭欢迎界面
*/
var hideWelcome = function(){
J.anim('#jingle_welcome','slideLeftOut',function(){
$(this).remove();
window.localStorage.setItem('hasShowWelcome',true);
})
}
return {
show : showWelcome,
hide : hideWelcome
}
})(J.$);
(function($){
/*
* alias func
* 简化一些常用方法的写法
** /
/**
* 完善zepto的动画函数,让参数变为可选
*/
J.anim = function(el,animName,duration,ease,callback){
var d, e,c;
var len = arguments.length;
for(var i = 2;i<len;i++){
var a = arguments[i];
var t = $.type(a);
t == 'number'?(d=a):(t=='string'?(e=a):(t=='function')?(c=a):null);
}
$(el).animate(animName,d|| J.settings.transitionTime,e||J.settings.transitionTimingFunc,c);
}
/**
* 显示loading框
* @param text
*/
J.showMask = function(text){
J.Popup.loading(text);
}
/**
* 关闭loading框
*/
J.hideMask = function(){
J.Popup.close(true);
}
/**
* 显示消息
* @param text
* @param type toast|success|error|info
* @param duration 持续时间,为0则不自动关闭
*/
J.showToast = function(text,type,duration){
type = type || 'toast';
J.Toast.show(type,text,duration);
}
/**
* 关闭消息提示
*/
J.hideToast = function(){
J.Toast.hide();
}
J.alert = function(title,content){
J.Popup.alert(title,content);
}
J.confirm = function(title,content,okCall,cancelCall){
J.Popup.confirm(title,content,okCall,cancelCall);
}
/**
* 弹出窗口
* @param options
*/
J.popup = function(options){
J.Popup.show(options);
}
/**
* 关闭窗口
*/
J.closePopup = function(){
J.Popup.close();
}
/**
* 带箭头的弹出框
* @param html [可选]
* @param pos [可选] 位置
* @param arrowDirection [可选] 箭头方向
* @param onShow [可选] 显示之前执行
*/
J.popover = function(html,pos,arrowDirection,onShow){
J.Popup.popover(html,pos,arrowDirection,onShow);
}
/**
* 自动渲染模板并填充到页面
* @param containerSelector 欲填充的容器
* @param templateId 模板ID
* @param data 数据源
* @param type [可选] add|replace
*/
J.tmpl = function(containerSelector,templateId,data,type){
J.Template.render(containerSelector,templateId,data,type);
}
})(J.$);
/**
* 弹出框组件
*/
J.Popup = (function($){
var _popup,_mask,transition,clickMask2close,
POSITION = {
'top':{
top:0,
left:0,
right:0
},
'top-second':{
top:'44px',
left:0,
right:0
},
'center':{
top:'50%',
left:'5%',
right:'5%',
'border-radius' : '3px'
},
'bottom' : {
bottom:0,
left:0,
right:0
},
'bottom-second':{
bottom : '51px',
left:0,
right:0
}
},
ANIM = {
top : ['slideDownIn','slideUpOut'],
bottom : ['slideUpIn','slideDownOut'],
defaultAnim : ['bounceIn','bounceOut']
},
TEMPLATE = {
alert : '<div class="popup-title">{title}</div><div class="popup-content">{content}</div><div id="popup_btn_container"><a data-target="closePopup" data-icon="checkmark">{ok}</a></div>',
confirm : '<div class="popup-title">{title}</div><div class="popup-content">{content}</div><div id="popup_btn_container"><a class="cancel" data-icon="close">{cancel}</a><a data-icon="checkmark">{ok}</a></div>',
loading : '<i class="icon spinner"></i><p>{title}</p>'
};
/**
* 全局只有一个popup实例
* @private
*/
var _init = function(){
$('body').append('<div id="jingle_popup"></div><div id="jingle_popup_mask"></div>');
_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 += '<div id="tag_close_popup" data-target="closePopup" class="icon cancel-circle"></div>';
}
//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 = '<div class="actionsheet">';
$.each(buttons,function(i,n){
markup += '<button style="background-color: '+ n.color +' !important;">'+ n.text +'</button>';
});
markup += '<button class="alizarin">取消</button>';
markup += '</div>';
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 += '<div class="jingle-calendar">';
html += _renderNav(currentYear,currentMonth);
html += _renderHead();
html += '<div class="jingle-calendar-body">';
html += _renderBody(currentDate);
html += '</div></div>'
$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 = '<div class="jingle-calendar-nav">';
html += '<div> <i class="icon previous" data-year='+year+'></i><span>'+year+'</span><i class="icon next" data-year='+year+'></i></div>';
html += '<div ><i class="icon previous" data-month='+month+'></i> <span>'+_this.settings.months[month]+'</span><i class="icon next" data-month='+month+'></i></div>';
html += '</div>';
return html;
}
var _renderHead = function(){
var html = '<table><thead><tr>';
for(var i = 0; i < 7; i++){
html += '<th>'+_this.settings.days[i]+'</th>';
}
html += '</tr></thead></table>'
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 += '<table><tbody>';
for(var i = 0; i < rows; i++){
html += '<tr>';
for(var j = 0; j < 7; j++){
html += _renderDay(beginDate,currentMonth);
beginDate.setDate(beginDate.getDate() + 1);
}
html += '</tr>';
}
html += '</tbody></table>';
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 ? '<td>&nbsp;</td>' : '<td data-selected="selected" class="'+classname+ '" data-date= '+dateStr+'>'+dayStr+'</td>';
}
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 = oldDate<newDate ? 'slideLeftRound' : 'slideRightRound';
$yearText.text(date.getFullYear());
$monthText.text(this.settings.months[date.getMonth()]);
var newbody = _renderBody(date);
J.anim($calendarBody,transition,function(){
$calendarBody.html(newbody);
});
}
_init();
}
/**
* 字符串转化为日期对象,只支持yyyy-MM-dd 和 yyyy/MM/dd
* @param date
* @return {*}
*/
calendar.prototype.parse = function(date){
var dateRE = /^(\d{4})(?:\-|\/)(\d{1,2})(?:\-|\/)(\d{1,2})$/;
return dateRE.test(date) ? new Date(parseInt(RegExp.$1, 10), parseInt(RegExp.$2, 10) - 1, parseInt(RegExp.$3, 10)) : null;
}
/**
* 格式化日期 yyyy-MM-dd
* @return {String}
*/
calendar.prototype.format = function(date){
var y = date.getFullYear(),m = date.getMonth()+1,d = date.getDate();
m = (m<10)?('0'+m):m;
d = (d<10)?('0'+d):d;
return y + '-' + m + '-' + d;
}
J.Calendar = calendar;
})(J.$);
/**
* 哥屋恩动组件(iscroll)
*/
;(function($){
var scrollCache = {},index = 1;
J.Scroll = function(selector,opts){
var scroll,scrollId,$el = $(selector),
options = {
hScroll : false,
bounce : false,
lockDirection : true,
useTransform: true,
useTransition: false,
checkDOMChanges: false,
onBeforeScrollStart: function (e) {
var target = e.target;
while (target.nodeType != 1) target = target.parentNode;
if (target.tagName != 'SELECT' && target.tagName != 'INPUT' && target.tagName != 'TEXTAREA')
e.preventDefault();
}
};
scrollId = $el.data('_jscroll_');
//滚动组件使用频繁,缓存起来节省开销
if(scrollId && scrollCache[scrollId]){
scroll = scrollCache[scrollId];
$.extend(scroll.scroller.options,opts)
scroll.scroller.refresh();
return scroll;
}else{
scrollId = '_jscroll_'+index++;
$el.data('_jscroll_',scrollId);
$.extend(options,opts);
scroller = new iScroll($el[0],options);
return scrollCache[scrollId] = {
scroller : scroller,
destroy : function(){
scroller.destroy();
delete scrollCache[scrollId];
}
};
};
}
})(J.$);
/**
* 幻灯片组件
*/
;(function($){
function slider(selector,showDots){
var afterSlide = function(){},
beforeSlide = function(){return true},
gestureStarted = false,
index = 0,
speed = 200,
wrapper,
dots,
container,
slides,
slideNum,
slideWidth,
deltaX,
autoPlay
interval = 0;
var _this = this;
if($.isPlainObject(selector)){
wrapper = $(selector.selector);
showDots = selector.showDots;
beforeSlide = selector.onBeforeSlide || beforeSlide;
afterSlide = selector.onAfterSlide || afterSlide;
autoPlay = selector.autoPlay;
interval = selector.interval || 3000;
}else{
wrapper = $(selector);
}
/**
* 初始化容器大小
*/
var _init = function() {
container = wrapper.children().first();
slides = container.children();
slideNum = slides.length;
slideWidth = wrapper.offset().width;
container.css('width',slideNum * slideWidth);
slides.css({
'width':slideWidth,
'float':'left'
}).show();
if(showDots == undefined)showDots = true;
showDots && _initDots();
_slide(0, 0);
afterSlide(0);
autoPlay && _autoPlay();
};
var _autoPlay = function(){
setTimeout(function(){
if(index == slideNum - 1){
_slide(0);
}else{
_this.next();
}
_autoPlay();
},interval);
}
var _initDots = function(){
dots = wrapper.find('.dots');
if(dots.length>0){
dots.show();
}else{
var dotsWidth = slideNum*30+20+2;
var html = '<div class="dots"><ul>';
for(var i=0;i<slideNum;i++){
html +='<li index="'+i+'"';
if(i == 0){
html += 'class="active"';
}
html += '><a href="#"></a></li>';
}
html += '</ul></div>';
wrapper.append(html);
dots = wrapper.find('.dots');
dots.children().css('width',dotsWidth+'px');
dots.find('li').on('tap',function(){
var index = $(this).attr('index');
_slide(parseInt(index), speed);
})
}
}
/**
* 滑动到指定卡片
* @param i
* @param duration
* @private
*/
var _slide = function(i, duration) {
duration = duration || speed;
container.css({
'-webkit-transition-duration':duration + 'ms',
'-webkit-transform':'translate3D(' + -(i * slideWidth) + 'px,0,0)'
});
if(index != i){
index = i;
if(dots) $(dots.find('li').get(index)).addClass('active').siblings().removeClass('active');
afterSlide(index);
}
};
/**
* 绑定滑动事件
*/
var _bindEvents = function() {
container.on('touchstart',_touchStart,false);
container.on('touchmove',_touchMove,false);
container.on('touchend',_touchEnd,false);
};
var _touchStart = function(event) {
var e = event.touches[0];
start = {
pageX: e.pageX,
pageY: e.pageY,
time: Number(new Date())
};
isScrolling = undefined;
deltaX = 0;
container[0].style.webkitTransitionDuration = 0;
gestureStarted = true;
};
var _touchMove = function(event) {
if(!gestureStarted)return;
var e = event.touches[0];
deltaX = e.pageX - start.pageX;
if ( typeof isScrolling == 'undefined') {
//根据X、Y轴的偏移量判断用户的意图是左右滑动还是上下滑动
isScrolling = Math.abs(deltaX) < Math.abs(e.pageY - start.pageY)
}
if (!isScrolling) {
event.preventDefault();
//判定是否达到了边界即第一个右滑、最后一个左滑
var isPastBounds = !index && deltaX > 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 = '<div class="refresh-container"><span class="refresh-icon icon '+opts.onPullIcon
+'"></span><span class="refresh-label">'
+opts.pullText+'</span>'
+(opts.refreshTip?'<div class="refresh-tip">'+opts.refreshTip+'</div>':'')+'</div>';
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.$);