/*
|
* 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> </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.$);
|