更新前端文件

This commit is contained in:
2019-07-06 16:59:35 +08:00
parent 777b452685
commit 79615defdb
1758 changed files with 315372 additions and 12014 deletions

View File

@@ -25,7 +25,8 @@ class Config extends Admin{
*/
public function group(ConfigModel $config){
if ($this->request->isAjax()) {
# code...
$this->data['code'] = 1;
return $this->data;
}else{
$this->data['id'] = $this->request->param('id', 1);
$res = $config->where(array('status' => 1, 'group' => $this->data['id']))->field('id,name,title,extra,value,remark,type')->order('sort')->select();

View File

@@ -26,6 +26,11 @@ class Index extends Admin{
* @title 更新缓存
*/
public function clear(){
if ($this->request->isAjax()) {
# code...
}else{
return $this->data;
}
}
/**

View File

@@ -8,5 +8,5 @@ return [
// Session初始化
\think\middleware\SessionInit::class,
// 页面Trace调试
\think\middleware\TraceDebug::class,
// \think\middleware\TraceDebug::class,
];

View File

@@ -26,7 +26,9 @@ class Admin {
}
if ($request->isAjax()) {
if (isset($this->data['config'])) {
unset($this->data['config']);
}
return json($this->data);
} else {
return $response->data($this->fetch());
@@ -47,8 +49,7 @@ class Admin {
'__public__' => '/static/admin',
),
);
$data = isset($this->data['data']) ? $this->data['data'] : [];
$data['config'] = isset($this->data['config']) ? $this->data['config'] : $data;
return View::config($config)->assign($data)->fetch($template);
return View::config($config)->assign($this->data)->fetch($template);
}
}

View File

@@ -116,7 +116,7 @@
</ul>
</li>
<li class="visible-lg">
<a href="#" class="btn" onclick="helpIntro();">
<a href="#" class="btn hopscotch">
<i class="fa fa-question-circle"></i>
操作指南
</a>
@@ -168,19 +168,19 @@
</div>
<div class="row">
<div class="col-lg-12">
{block name="body"}
<div class="main-box clearfix">
<header class="main-box-header clearfix">
<div class="pull-left">
<h2>{$meta|default='新功能'}</h2>
</div>
<div class="pull-right">
{block name="toolbar"}{/block}
</div>
</header>
<div class="main-box-body clearfix">
{block name="body"}{/block}
</div>
</div>
{/block}
</div>
</div>
</div>
@@ -194,13 +194,11 @@
</div>
{include file="admin/setting"}
<script src="__js__/skin-changer.js"></script>
<script src="__static__/libs/jquery/jquery.min.js"></script>
<script src="__static__/libs/bootstrap/js/bootstrap.min.js"></script>
<script src="__static__/libs/nanoscroller/jquery.nanoscroller.min.js"></script>
<script type="text/javascript" src="__static__/js/jquery.slimscroll.min.js"></script>
<script src="__js__/app.js"></script>
<script type="text/javascript" src="/static/js/vue.js"></script>
<script type="text/javascript" src="/static/js/axios.min.js"></script>
{block name="script"}{/block}
<script type="text/javascript" src="__static__/libs/requirejs/require.js" data-main="__js__/main.js?v={$system_version|default='4.0.0'}"></script>
{block name="script"}
<script type="text/javascript">
var isLoadModule = false;
</script>
{/block}
</body>
</html>

View File

@@ -1,15 +1,23 @@
{extend name="admin/base"/}
{block name="toolbar"}
<a href="{:url('Config/index')}" class="btn btn-primary">
{block name="body"}
<div class="main-box clearfix">
<header class="main-box-header clearfix">
<div class="pull-left">
<h2>{$meta|default='新功能'}</h2>
</div>
<div class="pull-right">
<a href="{:url('Config/index')}" class="btn btn-primary">
<i class="fa fa-list"></i>
配置列表
</a>
<a href="{:url('Config/add')}" class="btn btn-danger">
</a>
<a href="{:url('Config/add')}" class="btn btn-danger">
<i class="fa fa-list"></i>
添加配置
</a>
{/block}
{block name="body"}
</a>
</div>
</header>
<div class="main-box-body clearfix">
<div class="tabs-wrapper">
<ul class="nav nav-tabs">
{volist name="config['config_group_list']" id="item"}
@@ -72,19 +80,11 @@
</div>
</div>
</div>
</div>
</div>
{/block}
{block name="script"}
<script type="text/javascript">
var vm = new Vue({
el:".vue-main",
data:{},
created(){
$.ajax({
success: function(res){
console.log(res)
}
})
}
})
var isLoadModule = true;
</script>
{/block}

View File

@@ -1,15 +1,10 @@
{extend name="admin/base"/}
{block name="body"}
<div class="main-box clearfix">
<header class="main-box-header clearfix">
<div class="pull-left">
<h2>清除缓存</h2>
</div>
<div class="pull-right">
</div>
</header>
<div class="main-box-body clearfix">
</div>
</div>
{/block}
{block name="script"}
<script type="text/javascript">
var isLoadModule = true;
</script>
{/block}

View File

@@ -64,7 +64,7 @@
</div>
<script src="__static__/libs/jquery/jquery.min.js"></script>
<script src="__static__/libs/layui/layui.all.js"></script>
<script src="__static__/libs/layer/layer.js"></script>
<script type="text/javascript">
$(function () {
//表单提交

View File

@@ -0,0 +1,290 @@
define(['jquery', 'hopscotch'], function($, hopscotch){
var Backend = {
writeStorage: function(storage, key, value) {
if (storage) {
try {
localStorage.setItem(key, value);
} catch (e) {
console.log(e);
}
}
},
helpIntro: function(){
var placementRight = 'right';
var placementLeft = 'left';
if ($('body').hasClass('rtl')) {
placementRight = 'left';
placementLeft = 'right';
}
// Define the tour!
var tour = {
id: "Cube-intro",
steps: [
{
target: 'make-small-nav',
title: "设置小菜单按钮",
content: "点击小菜单可以把左侧菜单变成小菜单,增大右侧操作区域!",
placement: "bottom",
zindex: 999,
xOffset: -8
},
{
target: 'config-tool-options',
title: "后台配置工具",
content: "配置后台主题色彩,定制头部、左侧菜单以及底部信息",
placement: placementLeft,
zindex: 999,
fixedElement: true,
xOffset: -55
},
{
target: 'sidebar-nav',
title: "左侧导航区域",
content: "左侧功能导航区域。",
placement: placementRight
}
],
showPrevButton: true
};
console.log(hopscotch);
// Start the tour!
hopscotch.startTour(tour);
},
init: function () {
$('.hopscotch').on('click', function(){
Backend.helpIntro()
})
var storage, fail, uid;
try {
uid = new Date;
(storage = window.localStorage).setItem(uid, uid);
fail = storage.getItem(uid) != uid;
storage.removeItem(uid);
fail && (storage = false);
} catch (e) {}
if (storage) {
try {
var usedSkin = localStorage.getItem('config-skin');
if (usedSkin != '') {
$('#skin-colors .skin-changer').removeClass('active');
$('#skin-colors .skin-changer[data-skin="' + usedSkin + '"]').addClass('active');
}
var fixedHeader = localStorage.getItem('config-fixed-header');
if (fixedHeader == 'fixed-header') {
$('body').addClass(fixedHeader);
$('#config-fixed-header').prop('checked', true);
}
var fixedFooter = localStorage.getItem('config-fixed-footer');
if (fixedFooter == 'fixed-footer') {
$('body').addClass(fixedFooter);
$('#config-fixed-footer').prop('checked', true);
}
var boxedLayout = localStorage.getItem('config-boxed-layout');
if (boxedLayout == 'boxed-layout') {
$('body').addClass(boxedLayout);
$('#config-boxed-layout').prop('checked', true);
}
var rtlLayout = localStorage.getItem('config-rtl-layout');
if (rtlLayout == 'rtl') {
$('body').addClass(rtlLayout);
$('#config-rtl-layout').prop('checked', true);
}
var fixedLeftmenu = localStorage.getItem('config-fixed-leftmenu');
if (fixedLeftmenu == 'fixed-leftmenu') {
$('body').addClass(fixedLeftmenu);
$('#config-fixed-sidebar').prop('checked', true);
if ($('#page-wrapper').hasClass('nav-small')) {
$('#page-wrapper').removeClass('nav-small');
}
$('.fixed-leftmenu #col-left').nanoScroller({
alwaysVisible: true,
iOSNativeScrolling: false,
preventPageScrolling: true,
contentClass: 'col-left-nano-content'
});
}
} catch (e) {
console.log(e);
}
}
$('#config-tool-cog').on('click', function() {
$('#config-tool').toggleClass('closed');
});
$('#config-fixed-header').on('change', function() {
var fixedHeader = '';
if ($(this).is(':checked')) {
$('body').addClass('fixed-header');
fixedHeader = 'fixed-header';
} else {
$('body').removeClass('fixed-header');
if ($('#config-fixed-sidebar').is(':checked')) {
$('#config-fixed-sidebar').prop('checked', false);
$('#config-fixed-sidebar').trigger('change');
location.reload();
}
}
Backend.writeStorage(storage, 'config-fixed-header', fixedHeader);
});
$('#config-fixed-footer').on('change', function() {
var fixedFooter = '';
if ($(this).is(':checked')) {
$('body').addClass('fixed-footer');
fixedFooter = 'fixed-footer';
} else {
$('body').removeClass('fixed-footer');
}
Backend.writeStorage(storage, 'config-fixed-footer', fixedFooter);
});
$('#config-boxed-layout').on('change', function() {
var boxedLayout = '';
if ($(this).is(':checked')) {
$('body').addClass('boxed-layout');
boxedLayout = 'boxed-layout';
} else {
$('body').removeClass('boxed-layout');
}
Backend.writeStorage(storage, 'config-boxed-layout', boxedLayout);
});
$('#config-rtl-layout').on('change', function() {
var rtlLayout = '';
if ($(this).is(':checked')) {
rtlLayout = 'rtl';
} else {}
Backend.writeStorage(storage, 'config-rtl-layout', rtlLayout);
location.reload();
});
$('#config-fixed-sidebar').on('change', function() {
var fixedSidebar = '';
if ($(this).is(':checked')) {
if (!$('#config-fixed-header').is(':checked')) {
$('#config-fixed-header').prop('checked', true);
$('#config-fixed-header').trigger('change');
}
if ($('#page-wrapper').hasClass('nav-small')) {
$('#page-wrapper').removeClass('nav-small');
}
$('body').addClass('fixed-leftmenu');
fixedSidebar = 'fixed-leftmenu';
$('.fixed-leftmenu #col-left').nanoScroller({
alwaysVisible: true,
iOSNativeScrolling: false,
preventPageScrolling: true,
contentClass: 'col-left-nano-content'
});
Backend.writeStorage(storage, 'config-fixed-leftmenu', fixedSidebar);
} else {
$('body').removeClass('fixed-leftmenu');
Backend.writeStorage(storage, 'config-fixed-leftmenu', fixedSidebar);
location.reload();
}
});
if (!storage) {
$('#config-fixed-header').prop('checked', false);
$('#config-fixed-footer').prop('checked', false);
$('#config-fixed-sidebar').prop('checked', false);
$('#config-boxed-layout').prop('checked', false);
$('#config-rtl-layout').prop('checked', false);
}
$('#skin-colors .skin-changer').on('click', function() {
$('body').removeClassPrefix('theme-');
$('body').addClass($(this).data('skin'));
$('#skin-colors .skin-changer').removeClass('active');
$(this).addClass('active');
Backend.writeStorage(storage, 'config-skin', $(this).data('skin'));
});
setTimeout(function() {
$('#content-wrapper > .row').css({
opacity: 1
});
}, 200);
$('#sidebar-nav,#nav-col-submenu').on('click', '.dropdown-toggle', function(e) {
e.preventDefault();
var $item = $(this).parent();
if (!$item.hasClass('open')) {
$item.parent().find('.open .submenu').slideUp('fast');
$item.parent().find('.open').toggleClass('open');
}
$item.toggleClass('open');
if ($item.hasClass('open')) {
$item.children('.submenu').slideDown('fast');
} else {
$item.children('.submenu').slideUp('fast');
}
});
$('body').on('mouseenter', '#page-wrapper.nav-small #sidebar-nav .dropdown-toggle', function(e) {
if ($(document).width() >= 992) {
var $item = $(this).parent();
if ($('body').hasClass('fixed-leftmenu')) {
var topPosition = $item.position().top;
if ((topPosition + 4 * $(this).outerHeight()) >= $(window).height()) {
topPosition -= 6 * $(this).outerHeight();
}
$('#nav-col-submenu').html($item.children('.submenu').clone());
$('#nav-col-submenu > .submenu').css({
'top': topPosition
});
}
$item.addClass('open');
$item.children('.submenu').slideDown('fast');
}
});
$('body').on('mouseleave', '#page-wrapper.nav-small #sidebar-nav > .nav-pills > li', function(e) {
if ($(document).width() >= 992) {
var $item = $(this);
if ($item.hasClass('open')) {
$item.find('.open .submenu').slideUp('fast');
$item.find('.open').removeClass('open');
$item.children('.submenu').slideUp('fast');
}
$item.removeClass('open');
}
});
$('body').on('mouseenter', '#page-wrapper.nav-small #sidebar-nav a:not(.dropdown-toggle)', function(e) {
if ($('body').hasClass('fixed-leftmenu')) {
$('#nav-col-submenu').html('');
}
});
$('body').on('mouseleave', '#page-wrapper.nav-small #nav-col', function(e) {
if ($('body').hasClass('fixed-leftmenu')) {
$('#nav-col-submenu').html('');
}
});
$('#make-small-nav').click(function(e) {
$('#page-wrapper').toggleClass('nav-small');
});
$('.mobile-search').click(function(e) {
e.preventDefault();
$('.mobile-search').addClass('active');
$('.mobile-search form input.form-control').focus();
});
$(document).mouseup(function(e) {
var container = $('.mobile-search');
if (!container.is(e.target) && container.has(e.target).length === 0) {
container.removeClass('active');
}
});
$('.fixed-leftmenu #col-left').nanoScroller({
alwaysVisible: false,
iOSNativeScrolling: false,
preventPageScrolling: true,
contentClass: 'col-left-nano-content'
});
$("[data-toggle='tooltip']").each(function(index, el) {
$(el).tooltip({
placement: $(this).data("placement") || 'top'
});
});
}
}
Backend.init(); //默认初始化执行的代码
return Backend;
})

View File

@@ -1,10 +1,174 @@
require.config({
paths:{
'vue':'/static/js/vue.js',
"jquery":'/static/libs/jquery/jquery.min.js'
urlArgs: "v=" + '4.0.0',
packages: [
{
name: 'moment',
location: '/static/libs/moment',
main: 'moment'
}
],
include: ['jquery', 'drop', 'drag', 'form'],
paths:{
'backend': '../admin/js/backend',
'form': '/static/js/require-form',
'table': '/static/js/require-table',
'upload': '/static/js/require-upload',
'validator': '/static/js/require-validator',
'drag': 'jquery.drag.min',
'drop': 'jquery.drop.min',
'echarts': 'echarts.min',
'echarts-theme': 'echarts-theme',
"vue":'/static/libs/vue/dist/vue.min',
"jquery":'/static/libs/jquery/jquery.min',
"nanoscroller":"/static/libs/nanoscroller/jquery.nanoscroller.min",
"slimscroll":"/static/js/jquery.slimscroll.min",
"bootstrap":"/static/libs/bootstrap/js/bootstrap.min",
'bootstrap-table-commonsearch': 'bootstrap-table-commonsearch',
'bootstrap-table-template': 'bootstrap-table-template',
//
// 以下的包从bower的libs目录加载
'jquery': '/static/libs/jquery/dist/jquery.min',
'bootstrap': '/static/libs/bootstrap/dist/js/bootstrap.min',
'bootstrap-datetimepicker': '/static/libs/eonasdan-bootstrap-datetimepicker/build/js/bootstrap-datetimepicker.min',
'bootstrap-daterangepicker': '/static/libs/bootstrap-daterangepicker/daterangepicker',
'bootstrap-select': '/static/libs/bootstrap-select/dist/js/bootstrap-select.min',
'bootstrap-select-lang': '/static/libs/bootstrap-select/dist/js/i18n/defaults-zh_CN',
'bootstrap-table': '/static/libs/bootstrap-table/dist/bootstrap-table.min',
'bootstrap-table-export': '/static/libs/bootstrap-table/dist/extensions/export/bootstrap-table-export.min',
'bootstrap-table-mobile': '/static/libs/bootstrap-table/dist/extensions/mobile/bootstrap-table-mobile',
'bootstrap-table-lang': '/static/libs/bootstrap-table/dist/locale/bootstrap-table-zh-CN',
'bootstrap-slider': '/static/libs/bootstrap-slider/bootstrap-slider',
'tableexport': '/static/libs/tableExport.jquery.plugin/tableExport.min',
'dragsort': '/static/libs/fastadmin-dragsort/jquery.dragsort',
'sortable': '/static/libs/Sortable/Sortable.min',
'addtabs': '/static/libs/fastadmin-addtabs/jquery.addtabs',
'slimscroll': '/static/libs/jquery-slimscroll/jquery.slimscroll',
'validator-core': '/static/libs/nice-validator/dist/jquery.validator',
'validator-lang': '/static/libs/nice-validator/dist/local/zh-CN',
'plupload': '/static/libs/plupload/js/plupload.min',
'toastr': '/static/libs/toastr/toastr',
'jstree': '/static/libs/jstree/dist/jstree.min',
'layer': '/static/libs/fastadmin-layer/dist/layer',
'cookie': '/static/libs/jquery.cookie/jquery.cookie',
'cxselect': '/static/libs/fastadmin-cxselect/js/jquery.cxselect',
'template': '/static/libs/art-template/dist/template-native',
'selectpage': '/static/libs/fastadmin-selectpage/selectpage',
'citypicker': '/static/libs/fastadmin-citypicker/dist/js/city-picker.min',
'citypicker-data': '/static/libs/fastadmin-citypicker/dist/js/city-picker.data',
},
shim:{
"nanoscroller":['jquery'],
"slimscroll":{
deps: ['jquery'],
exports: '$.fn.extend'
},
'bootstrap': ['jquery'],
'bootstrap-table': {
deps: [
'bootstrap',
// 'css!/static/libs/bootstrap-table/dist/bootstrap-table.min.css'
],
exports: '$.fn.bootstrapTable'
},
'bootstrap-table-lang': {
deps: ['bootstrap-table'],
exports: '$.fn.bootstrapTable.defaults'
},
'bootstrap-table-export': {
deps: ['bootstrap-table', 'tableexport'],
exports: '$.fn.bootstrapTable.defaults'
},
'bootstrap-table-mobile': {
deps: ['bootstrap-table'],
exports: '$.fn.bootstrapTable.defaults'
},
'bootstrap-table-advancedsearch': {
deps: ['bootstrap-table'],
exports: '$.fn.bootstrapTable.defaults'
},
'bootstrap-table-commonsearch': {
deps: ['bootstrap-table'],
exports: '$.fn.bootstrapTable.defaults'
},
'bootstrap-table-template': {
deps: ['bootstrap-table', 'template'],
exports: '$.fn.bootstrapTable.defaults'
},
'tableexport': {
deps: ['jquery'],
exports: '$.fn.extend'
},
'bootstrap-datetimepicker': [
'moment/locale/zh-cn.js',
// 'css!/static/libs/eonasdan-bootstrap-datetimepicker/build/css/bootstrap-datetimepicker.min.css',
],
// 'bootstrap-select': ['css!/static/libs/bootstrap-select/dist/css/bootstrap-select.min.css',],
'bootstrap-select-lang': ['bootstrap-select'],
// 'toastr': ['css!/static/libs/toastr/toastr.min.css'],
'jstree': ['css!/static/libs/jstree/dist/themes/default/style.css',],
'plupload': {
deps: ['/static/libs/plupload/js/moxie.min.js'],
exports: "plupload"
},
// 'layer': ['css!/static/libs/fastadmin-layer/dist/theme/default/layer.css'],
// 'validator-core': ['css!/static/libs/nice-validator/dist/jquery.validator.css'],
'validator-lang': ['validator-core'],
// 'selectpage': ['css!/static/libs/fastadmin-selectpage/selectpage.css'],
'citypicker': ['citypicker-data', 'css!/static/libs/fastadmin-citypicker/dist/css/city-picker.css']
},
baseUrl:"/static/js/",
map: {
'*': {
'css': '/static/libs/require-css/css.min.js'
}
},
//baseUrl: '/static/admin/js/backend/',
waitSeconds: 30,
charset: 'utf-8' // 文件编码
});
require(['vue', 'jquery'], function(Vue, $){
require(['jquery', 'nanoscroller', 'bootstrap'], function($){
$(function($) {
require(['sent'], function(Sent){
require(['backend', 'backend-init', 'addons'], function (Backend, undefined, Addons) {
// 避免目录冲突
require.config({baseUrl: '/static/admin/js/module/'});
var current_url = (window.location.pathname).split('/');
if (typeof(isLoadModule) != "undefined" && isLoadModule) {
require([current_url[2]], function(Controller){
var action = current_url[3].split('.');
if (Controller.hasOwnProperty(action[0])) {
Controller[action[0]]();
} else {
if (Controller.hasOwnProperty("_empty")) {
Controller._empty();
}
}
}, function (e) {
console.error(e); // 这里可捕获模块加载的错误
})
}
})
})
$.fn.removeClassPrefix = function(prefix) {
this.each(function(i, el) {
var classes = el.className.split(" ").filter(function(c) {
return c.lastIndexOf(prefix, 0) !== 0;
});
el.className = classes.join(" ");
});
return this;
};
});
function setContentBody() {
header_height = $('header#header-navbar').height();
if ($(window).height() - header_height - 50 > $('#content-wrapper').height()) {
$('#content-wrapper').height($(window).height() - header_height - 50);
}
}
})

View File

@@ -0,0 +1,8 @@
define([], function(){
var controller = {
group: function(){
}
}
return controller;
})

View File

@@ -0,0 +1,9 @@
define(['vue'],function(Vue){
var controller = {
clear: function(){
var vm = new Vue();
}
}
return controller;
})

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,179 @@
(function(context,namespace){var Hopscotch,HopscotchBubble,HopscotchCalloutManager,HopscotchI18N,customI18N,customRenderer,customEscape,templateToUse='bubble_default',Sizzle=window.Sizzle||null,utils,callbacks,helpers,winLoadHandler,defaultOpts,winHopscotch=context[namespace],undefinedStr='undefined',waitingToStart=false,hasJquery=(typeof window.jQuery!==undefinedStr),hasSessionStorage=false,isStorageWritable=false,document=window.document;try{if(typeof window.sessionStorage!==undefinedStr){hasSessionStorage=true;sessionStorage.setItem('hopscotch.test.storage','ok');sessionStorage.removeItem('hopscotch.test.storage');isStorageWritable=true;}}catch(err){}
defaultOpts={smoothScroll:true,scrollDuration:1000,scrollTopMargin:200,showCloseButton:true,showPrevButton:false,showNextButton:true,bubbleWidth:280,bubblePadding:15,arrowWidth:20,skipIfNoElement:true,cookieName:'hopscotch.tour.state'};if(winHopscotch){return;}
if(!Array.isArray){Array.isArray=function(obj){return Object.prototype.toString.call(obj)==='[object Array]';};}
winLoadHandler=function(){if(waitingToStart){winHopscotch.startTour();}};utils={addClass:function(domEl,classToAdd){var domClasses,classToAddArr,setClass,i,len;if(!domEl.className){domEl.className=classToAdd;}
else{classToAddArr=classToAdd.split(/\s+/);domClasses=' '+ domEl.className+' ';for(i=0,len=classToAddArr.length;i<len;++i){if(domClasses.indexOf(' '+ classToAddArr[i]+' ')<0){domClasses+=classToAddArr[i]+' ';}}
domEl.className=domClasses.replace(/^\s+|\s+$/g,'');}},removeClass:function(domEl,classToRemove){var domClasses,classToRemoveArr,currClass,i,len;classToRemoveArr=classToRemove.split(/\s+/);domClasses=' '+ domEl.className+' ';for(i=0,len=classToRemoveArr.length;i<len;++i){domClasses=domClasses.replace(' '+ classToRemoveArr[i]+' ',' ');}
domEl.className=domClasses.replace(/^\s+|\s+$/g,'');},hasClass:function(domEl,classToCheck){var classes;if(!domEl.className){return false;}
classes=' '+ domEl.className+' ';return(classes.indexOf(' '+ classToCheck+' ')!==-1);},getPixelValue:function(val){var valType=typeof val;if(valType==='number'){return val;}
if(valType==='string'){return parseInt(val,10);}
return 0;},valOrDefault:function(val,valDefault){return typeof val!==undefinedStr?val:valDefault;},invokeCallbackArrayHelper:function(arr){var fn;if(Array.isArray(arr)){fn=helpers[arr[0]];if(typeof fn==='function'){return fn.apply(this,arr.slice(1));}}},invokeCallbackArray:function(arr){var i,len;if(Array.isArray(arr)){if(typeof arr[0]==='string'){return utils.invokeCallbackArrayHelper(arr);}
else{for(i=0,len=arr.length;i<len;++i){utils.invokeCallback(arr[i]);}}}},invokeCallback:function(cb){if(typeof cb==='function'){return cb();}
if(typeof cb==='string'&&helpers[cb]){return helpers[cb]();}
else{return utils.invokeCallbackArray(cb);}},invokeEventCallbacks:function(evtType,stepCb){var cbArr=callbacks[evtType],callback,fn,i,len;if(stepCb){return this.invokeCallback(stepCb);}
for(i=0,len=cbArr.length;i<len;++i){this.invokeCallback(cbArr[i].cb);}},getScrollTop:function(){var scrollTop;if(typeof window.pageYOffset!==undefinedStr){scrollTop=window.pageYOffset;}
else{scrollTop=document.documentElement.scrollTop;}
return scrollTop;},getScrollLeft:function(){var scrollLeft;if(typeof window.pageXOffset!==undefinedStr){scrollLeft=window.pageXOffset;}
else{scrollLeft=document.documentElement.scrollLeft;}
return scrollLeft;},getWindowHeight:function(){return window.innerHeight||document.documentElement.clientHeight;},getWindowWidth:function(){return window.innerWidth||document.documentElement.clientWidth;},addEvtListener:function(el,evtName,fn){return el.addEventListener?el.addEventListener(evtName,fn,false):el.attachEvent('on'+ evtName,fn);},removeEvtListener:function(el,evtName,fn){return el.removeEventListener?el.removeEventListener(evtName,fn,false):el.detachEvent('on'+ evtName,fn);},documentIsReady:function(){return document.readyState==='complete'||document.readyState==='interactive';},evtPreventDefault:function(evt){if(evt.preventDefault){evt.preventDefault();}
else if(event){event.returnValue=false;}},extend:function(obj1,obj2){var prop;for(prop in obj2){if(obj2.hasOwnProperty(prop)){obj1[prop]=obj2[prop];}}},getStepTargetHelper:function(target){var result=document.getElementById(target);if(result){return result;}
if(hasJquery){result=jQuery(target);return result.length?result[0]:null;}
if(Sizzle){result=new Sizzle(target);return result.length?result[0]:null;}
if(document.querySelector){try{return document.querySelector(target);}catch(err){}}
if(/^#[a-zA-Z][\w-_:.]*$/.test(target)){return document.getElementById(target.substring(1));}
return null;},getStepTarget:function(step){var queriedTarget;if(!step||!step.target){return null;}
if(typeof step.target==='string'){return utils.getStepTargetHelper(step.target);}
else if(Array.isArray(step.target)){var i,len;for(i=0,len=step.target.length;i<len;i++){if(typeof step.target[i]==='string'){queriedTarget=utils.getStepTargetHelper(step.target[i]);if(queriedTarget){return queriedTarget;}}}
return null;}
return step.target;},getI18NString:function(key){return customI18N[key]||HopscotchI18N[key];},setState:function(name,value,days){var expires='',date;if(hasSessionStorage&&isStorageWritable){try{sessionStorage.setItem(name,value);}
catch(err){isStorageWritable=false;this.setState(name,value,days);}}
else{if(hasSessionStorage){sessionStorage.removeItem(name);}
if(days){date=new Date();date.setTime(date.getTime()+(days*24*60*60*1000));expires='; expires='+date.toGMTString();}
document.cookie=name+'='+value+expires+'; path=/';}},getState:function(name){var nameEQ=name+'=',ca=document.cookie.split(';'),i,c,state;if(hasSessionStorage){state=sessionStorage.getItem(name);if(state){return state;}}
for(i=0;i<ca.length;i++){c=ca[i];while(c.charAt(0)===' '){c=c.substring(1,c.length);}
if(c.indexOf(nameEQ)===0){state=c.substring(nameEQ.length,c.length);break;}}
return state;},clearState:function(name){if(hasSessionStorage){sessionStorage.removeItem(name);}
else{this.setState(name,'',-1);}}};utils.addEvtListener(window,'load',winLoadHandler);callbacks={next:[],prev:[],start:[],end:[],show:[],error:[],close:[]};helpers={};HopscotchI18N={stepNums:null,nextBtn:'Next',prevBtn:'Back',doneBtn:'Done',skipBtn:'Skip',closeTooltip:'Close'};customI18N={};HopscotchBubble=function(opt){this.init(opt);};HopscotchBubble.prototype={isShowing:false,currStep:undefined,setPosition:function(step){var bubbleBoundingHeight,bubbleBoundingWidth,boundingRect,top,left,arrowOffset,targetEl=utils.getStepTarget(step),el=this.element,arrowEl=this.arrowEl;bubbleBoundingWidth=el.offsetWidth;bubbleBoundingHeight=el.offsetHeight;utils.removeClass(el,'fade-in-down fade-in-up fade-in-left fade-in-right');if(!step.placement&&step.orientation){step.placement=step.orientation;}
boundingRect=targetEl.getBoundingClientRect();if(step.placement==='top'){top=(boundingRect.top- bubbleBoundingHeight)- this.opt.arrowWidth;left=boundingRect.left;}
else if(step.placement==='bottom'){top=boundingRect.bottom+ this.opt.arrowWidth;left=boundingRect.left;}
else if(step.placement==='left'){top=boundingRect.top;left=boundingRect.left- bubbleBoundingWidth- this.opt.arrowWidth;}
else if(step.placement==='right'){top=boundingRect.top;left=boundingRect.right+ this.opt.arrowWidth;}
else{throw'Bubble placement failed because step.placement is invalid or undefined!';}
if(step.arrowOffset!=='center'){arrowOffset=utils.getPixelValue(step.arrowOffset);}
else{arrowOffset=step.arrowOffset;}
if(!arrowOffset){arrowEl.style.top='';arrowEl.style.left='';}
else if(step.placement==='top'||step.placement==='bottom'){arrowEl.style.top='';if(arrowOffset==='center'){arrowEl.style.left=Math.floor((bubbleBoundingWidth/2)- arrowEl.offsetWidth/2)+'px';}
else{arrowEl.style.left=arrowOffset+'px';}}
else if(step.placement==='left'||step.placement==='right'){arrowEl.style.left='';if(arrowOffset==='center'){arrowEl.style.top=Math.floor((bubbleBoundingHeight/2)- arrowEl.offsetHeight/2)+'px';}
else{arrowEl.style.top=arrowOffset+'px';}}
if(step.xOffset==='center'){left=(boundingRect.left+ targetEl.offsetWidth/2)-(bubbleBoundingWidth/2);}
else{left+=utils.getPixelValue(step.xOffset);}
if(step.yOffset==='center'){top=(boundingRect.top+ targetEl.offsetHeight/2)-(bubbleBoundingHeight/2);}
else{top+=utils.getPixelValue(step.yOffset);}
if(!step.fixedElement){top+=utils.getScrollTop();left+=utils.getScrollLeft();}
el.style.position=(step.fixedElement?'fixed':'absolute');el.style.top=top+'px';el.style.left=left+'px';},render:function(step,idx,callback){var el=this.element,tourSpecificRenderer,customTourData,unsafe,currTour,totalSteps,nextBtnText,isLast,opts;if(step){this.currStep=step;}
else if(this.currStep){step=this.currStep;}
if(this.opt.isTourBubble){currTour=winHopscotch.getCurrTour();if(currTour){customTourData=currTour.customData;tourSpecificRenderer=currTour.customRenderer;unsafe=currTour.unsafe;if(Array.isArray(currTour.steps)){totalSteps=currTour.steps.length;isLast=(idx===totalSteps- 1);}}}else{customTourData=step.customData;tourSpecificRenderer=step.customRenderer;unsafe=step.unsafe;}
if(isLast){nextBtnText=utils.getI18NString('doneBtn');}else if(step.showSkip){nextBtnText=utils.getI18NString('skipBtn');}else{nextBtnText=utils.getI18NString('nextBtn');}
if(!step.placement&&step.orientation){step.placement=step.orientation;}
this.placement=step.placement;opts={i18n:{prevBtn:utils.getI18NString('prevBtn'),nextBtn:nextBtnText,closeTooltip:utils.getI18NString('closeTooltip'),stepNum:this._getStepI18nNum(idx),},buttons:{showPrev:(utils.valOrDefault(step.showPrevButton,this.opt.showPrevButton)&&(idx>0)),showNext:utils.valOrDefault(step.showNextButton,this.opt.showNextButton),showCTA:utils.valOrDefault((step.showCTAButton&&step.ctaLabel),false),ctaLabel:step.ctaLabel,showClose:utils.valOrDefault(this.opt.showCloseButton,true)},step:{num:idx,isLast:utils.valOrDefault(isLast,false),title:(step.title||''),content:(step.content||''),placement:step.placement,padding:utils.valOrDefault(step.padding,this.opt.bubblePadding),width:utils.getPixelValue(step.width)||this.opt.bubbleWidth,customData:(step.customData||{})},tour:{isTour:this.opt.isTourBubble,numSteps:totalSteps,unsafe:utils.valOrDefault(unsafe,false),customData:(customTourData||{})}};if(typeof tourSpecificRenderer==='function'){el.innerHTML=tourSpecificRenderer(opts);}
else if(typeof tourSpecificRenderer==='string'){if(!hopscotch.templates||(typeof hopscotch.templates[tourSpecificRenderer]!=='function')){throw'Bubble rendering failed - template "'+ tourSpecificRenderer+'" is not a function.';}
el.innerHTML=hopscotch.templates[tourSpecificRenderer](opts);}
else if(customRenderer){el.innerHTML=customRenderer(opts);}
else{if(!hopscotch.templates||(typeof hopscotch.templates[templateToUse]!=='function')){throw'Bubble rendering failed - template "'+ templateToUse+'" is not a function.';}
el.innerHTML=hopscotch.templates[templateToUse](opts);}
children=el.children;numChildren=children.length;for(i=0;i<numChildren;i++){node=children[i];if(utils.hasClass(node,'hopscotch-arrow')){this.arrowEl=node;}}
el.style.zIndex=(typeof step.zindex==='number')?step.zindex:'auto';this._setArrow(step.placement);this.hide(false);this.setPosition(step);if(callback){callback(!step.fixedElement);}
return this;},_getStepI18nNum:function(idx){var stepNumI18N=utils.getI18NString('stepNums');if(stepNumI18N&&idx<stepNumI18N.length){idx=stepNumI18N[idx];}
else{idx=idx+ 1;}
return idx;},_setArrow:function(orientation){utils.removeClass(this.arrowEl,'down up right left');if(orientation==='top'){utils.addClass(this.arrowEl,'down');}
else if(orientation==='bottom'){utils.addClass(this.arrowEl,'up');}
else if(orientation==='left'){utils.addClass(this.arrowEl,'right');}
else if(orientation==='right'){utils.addClass(this.arrowEl,'left');}},_getArrowDirection:function(){if(this.placement==='top'){return'down';}
if(this.placement==='bottom'){return'up';}
if(this.placement==='left'){return'right';}
if(this.placement==='right'){return'left';}},show:function(){var self=this,fadeClass='fade-in-'+ this._getArrowDirection(),fadeDur=1000;utils.removeClass(this.element,'hide');utils.addClass(this.element,fadeClass);setTimeout(function(){utils.removeClass(self.element,'invisible');},50);setTimeout(function(){utils.removeClass(self.element,fadeClass);},fadeDur);this.isShowing=true;return this;},hide:function(remove){var el=this.element;remove=utils.valOrDefault(remove,true);el.style.top='';el.style.left='';if(remove){utils.addClass(el,'hide');utils.removeClass(el,'invisible');}
else{utils.removeClass(el,'hide');utils.addClass(el,'invisible');}
utils.removeClass(el,'animate fade-in-up fade-in-down fade-in-right fade-in-left');this.isShowing=false;return this;},destroy:function(){var el=this.element;if(el){el.parentNode.removeChild(el);}
utils.removeEvtListener(el,'click',this.clickCb);},_handleBubbleClick:function(evt){var action;evt=evt||window.event;var targetElement=evt.target||evt.srcElement;function findMatchRecur(el){if(el===evt.currentTarget){return null;}
if(utils.hasClass(el,'hopscotch-cta')){return'cta';}
if(utils.hasClass(el,'hopscotch-next')){return'next';}
if(utils.hasClass(el,'hopscotch-prev')){return'prev';}
if(utils.hasClass(el,'hopscotch-close')){return'close';}
return findMatchRecur(el.parentElement);}
action=findMatchRecur(targetElement);if(action==='cta'){if(!this.opt.isTourBubble){winHopscotch.getCalloutManager().removeCallout(this.currStep.id);}
if(this.currStep.onCTA){utils.invokeCallback(this.currStep.onCTA);}}
else if(action==='next'){winHopscotch.nextStep(true);}
else if(action==='prev'){winHopscotch.prevStep(true);}
else if(action==='close'){if(this.opt.isTourBubble){var currStepNum=winHopscotch.getCurrStepNum(),currTour=winHopscotch.getCurrTour(),doEndCallback=(currStepNum===currTour.steps.length-1);utils.invokeEventCallbacks('close');winHopscotch.endTour(true,doEndCallback);}else{if(this.opt.onClose){utils.invokeCallback(this.opt.onClose);}
if(this.opt.id&&!this.opt.isTourBubble){winHopscotch.getCalloutManager().removeCallout(this.opt.id);}
else{this.destroy();}}
utils.evtPreventDefault(evt);}},init:function(initOpt){var el=document.createElement('div'),self=this,resizeCooldown=false,onWinResize,appendToBody,children,numChildren,node,i,opt;this.element=el;opt={showPrevButton:defaultOpts.showPrevButton,showNextButton:defaultOpts.showNextButton,bubbleWidth:defaultOpts.bubbleWidth,bubblePadding:defaultOpts.bubblePadding,arrowWidth:defaultOpts.arrowWidth,showNumber:true,isTourBubble:true};initOpt=(typeof initOpt===undefinedStr?{}:initOpt);utils.extend(opt,initOpt);this.opt=opt;el.className='hopscotch-bubble animated';if(!opt.isTourBubble){utils.addClass(el,'hopscotch-callout no-number');}
onWinResize=function(){if(resizeCooldown||!self.isShowing){return;}
resizeCooldown=true;setTimeout(function(){self.setPosition(self.currStep);resizeCooldown=false;},100);};utils.addEvtListener(window,'resize',onWinResize);this.clickCb=function(evt){self._handleBubbleClick(evt);};utils.addEvtListener(el,'click',this.clickCb);this.hide();if(utils.documentIsReady()){document.body.appendChild(el);}
else{if(document.addEventListener){appendToBody=function(){document.removeEventListener('DOMContentLoaded',appendToBody);window.removeEventListener('load',appendToBody);document.body.appendChild(el);};document.addEventListener('DOMContentLoaded',appendToBody,false);}
else{appendToBody=function(){if(document.readyState==='complete'){document.detachEvent('onreadystatechange',appendToBody);window.detachEvent('onload',appendToBody);document.body.appendChild(el);}};document.attachEvent('onreadystatechange',appendToBody);}
utils.addEvtListener(window,'load',appendToBody);}}};HopscotchCalloutManager=function(){var callouts={};this.createCallout=function(opt){var callout;if(opt.id){if(callouts[opt.id]){throw'Callout by that id already exists. Please choose a unique id.';}
opt.showNextButton=opt.showPrevButton=false;opt.isTourBubble=false;callout=new HopscotchBubble(opt);callouts[opt.id]=callout;if(opt.target){callout.render(opt,null,function(){callout.show();});}}
else{throw'Must specify a callout id.';}
return callout;};this.getCallout=function(id){return callouts[id];};this.removeAllCallouts=function(){var calloutId,callout;for(calloutId in callouts){if(callouts.hasOwnProperty(calloutId)){this.removeCallout(calloutId);}}};this.removeCallout=function(id){var callout=callouts[id];callouts[id]=null;if(!callout){return;}
callout.destroy();};};Hopscotch=function(initOptions){var self=this,bubble,calloutMgr,opt,currTour,currStepNum,cookieTourId,cookieTourStep,_configure,getBubble=function(setOptions){if(!bubble){bubble=new HopscotchBubble(opt);}
if(setOptions){utils.extend(bubble.opt,{bubblePadding:getOption('bubblePadding'),bubbleWidth:getOption('bubbleWidth'),showNextButton:getOption('showNextButton'),showPrevButton:getOption('showPrevButton'),showCloseButton:getOption('showCloseButton'),arrowWidth:getOption('arrowWidth')});}
return bubble;},getOption=function(name){if(typeof opt==='undefined'){return defaultOpts[name];}
return utils.valOrDefault(opt[name],defaultOpts[name]);},getCurrStep=function(){var step;if(currStepNum<0||currStepNum>=currTour.steps.length){step=null;}
else{step=currTour.steps[currStepNum];}
return step;},targetClickNextFn=function(){self.nextStep();},adjustWindowScroll=function(cb){var bubble=getBubble(),bubbleEl=bubble.element,bubbleTop=utils.getPixelValue(bubbleEl.style.top),bubbleBottom=bubbleTop+ utils.getPixelValue(bubbleEl.offsetHeight),targetEl=utils.getStepTarget(getCurrStep()),targetBounds=targetEl.getBoundingClientRect(),targetElTop=targetBounds.top+ utils.getScrollTop(),targetElBottom=targetBounds.bottom+ utils.getScrollTop(),targetTop=(bubbleTop<targetElTop)?bubbleTop:targetElTop,targetBottom=(bubbleBottom>targetElBottom)?bubbleBottom:targetElBottom,windowTop=utils.getScrollTop(),windowBottom=windowTop+ utils.getWindowHeight(),scrollToVal=targetTop- getOption('scrollTopMargin'),scrollEl,yuiAnim,yuiEase,direction,scrollIncr,scrollTimeout,scrollTimeoutFn;if(targetTop>=windowTop&&(targetTop<=windowTop+ getOption('scrollTopMargin')||targetBottom<=windowBottom)){if(cb){cb();}}
else if(!getOption('smoothScroll')){window.scrollTo(0,scrollToVal);if(cb){cb();}}
else{if(typeof YAHOO!==undefinedStr&&typeof YAHOO.env!==undefinedStr&&typeof YAHOO.env.ua!==undefinedStr&&typeof YAHOO.util!==undefinedStr&&typeof YAHOO.util.Scroll!==undefinedStr){scrollEl=YAHOO.env.ua.webkit?document.body:document.documentElement;yuiEase=YAHOO.util.Easing?YAHOO.util.Easing.easeOut:undefined;yuiAnim=new YAHOO.util.Scroll(scrollEl,{scroll:{to:[0,scrollToVal]}},getOption('scrollDuration')/1000, yuiEase);
yuiAnim.onComplete.subscribe(cb);yuiAnim.animate();}
else if(hasJquery){jQuery('body, html').animate({scrollTop:scrollToVal},getOption('scrollDuration'),cb);}
else{if(scrollToVal<0){scrollToVal=0;}
direction=(windowTop>targetTop)?-1:1;scrollIncr=Math.abs(windowTop- scrollToVal)/ (getOption('scrollDuration')/10);
scrollTimeoutFn=function(){var scrollTop=utils.getScrollTop(),scrollTarget=scrollTop+(direction*scrollIncr);if((direction>0&&scrollTarget>=scrollToVal)||(direction<0&&scrollTarget<=scrollToVal)){scrollTarget=scrollToVal;if(cb){cb();}
window.scrollTo(0,scrollTarget);return;}
window.scrollTo(0,scrollTarget);if(utils.getScrollTop()===scrollTop){if(cb){cb();}
return;}
setTimeout(scrollTimeoutFn,10);};scrollTimeoutFn();}}},goToStepWithTarget=function(direction,cb){var target,step,goToStepFn;if(currStepNum+ direction>=0&&currStepNum+ direction<currTour.steps.length){currStepNum+=direction;step=getCurrStep();goToStepFn=function(){target=utils.getStepTarget(step);if(target){cb(currStepNum);}
else{utils.invokeEventCallbacks('error');goToStepWithTarget(direction,cb);}};if(step.delay){setTimeout(goToStepFn,step.delay);}
else{goToStepFn();}}
else{cb(-1);}},changeStep=function(doCallbacks,direction){var bubble=getBubble(),self=this,step,origStep,wasMultiPage,changeStepCb;bubble.hide();doCallbacks=utils.valOrDefault(doCallbacks,true);step=getCurrStep();origStep=step;if(direction>0){wasMultiPage=origStep.multipage;}
else{wasMultiPage=(currStepNum>0&&currTour.steps[currStepNum-1].multipage);}
changeStepCb=function(stepNum){var doShowFollowingStep;if(stepNum===-1){return this.endTour(true);}
if(doCallbacks){if(direction>0){doShowFollowingStep=utils.invokeEventCallbacks('next',origStep.onNext);}
else{doShowFollowingStep=utils.invokeEventCallbacks('prev',origStep.onPrev);}}
if(stepNum!==currStepNum){return;}
if(wasMultiPage){utils.setState(getOption('cookieName'),currTour.id+':'+ currStepNum,1);return;}
doShowFollowingStep=utils.valOrDefault(doShowFollowingStep,true);if(doShowFollowingStep){this.showStep(stepNum);}
else{this.endTour(false);}};if(!wasMultiPage&&getOption('skipIfNoElement')){goToStepWithTarget(direction,function(stepNum){changeStepCb.call(self,stepNum);});}
else if(currStepNum+ direction>=0&&currStepNum+ direction<currTour.steps.length){currStepNum+=direction;step=getCurrStep();if(!utils.getStepTarget(step)&&!wasMultiPage){utils.invokeEventCallbacks('error');return this.endTour(true,false);}
changeStepCb.call(this,currStepNum);}
return this;},loadTour=function(tour){var tmpOpt={},prop,tourState,tourPair;for(prop in tour){if(tour.hasOwnProperty(prop)&&prop!=='id'&&prop!=='steps'){tmpOpt[prop]=tour[prop];}}
_configure.call(this,tmpOpt,true);tourState=utils.getState(getOption('cookieName'));if(tourState){tourPair=tourState.split(':');cookieTourId=tourPair[0];cookieTourStep=tourPair[1];cookieTourStep=parseInt(cookieTourStep,10);}
return this;},findStartingStep=function(startStepNum,cb){var step,target,stepNum;currStepNum=startStepNum||0;step=getCurrStep();target=utils.getStepTarget(step);if(target){cb(currStepNum);return;}
if(!target){utils.invokeEventCallbacks('error');if(getOption('skipIfNoElement')){goToStepWithTarget(1,cb);return;}
else{currStepNum=-1;cb(currStepNum);}}},showStepHelper=function(stepNum){var step=currTour.steps[stepNum],tourSteps=currTour.steps,numTourSteps=tourSteps.length,cookieVal=currTour.id+':'+ stepNum,bubble=getBubble(),targetEl=utils.getStepTarget(step),isLast,showBubble;showBubble=function(){bubble.show();utils.invokeEventCallbacks('show',step.onShow);};currStepNum=stepNum;bubble.hide(false);isLast=(stepNum===numTourSteps- 1);bubble.render(step,stepNum,function(adjustScroll){if(adjustScroll){adjustWindowScroll(showBubble);}
else{showBubble();}
if(step.nextOnTargetClick){utils.addEvtListener(targetEl,'click',targetClickNextFn);}});utils.setState(getOption('cookieName'),cookieVal,1);},init=function(initOptions){if(initOptions){this.configure(initOptions);}};this.getCalloutManager=function(){if(typeof calloutMgr===undefinedStr){calloutMgr=new HopscotchCalloutManager();}
return calloutMgr;};this.startTour=function(tour,stepNum){var bubble,currStepNum,self=this;if(!currTour){currTour=tour;loadTour.call(this,tour);}
if(typeof stepNum!==undefinedStr){if(stepNum>=currTour.steps.length){throw'Specified step number out of bounds.';}
currStepNum=stepNum;}
if(!utils.documentIsReady()){waitingToStart=true;return this;}
if(typeof currStepNum==="undefined"&&currTour.id===cookieTourId&&typeof cookieTourStep!==undefinedStr){currStepNum=cookieTourStep;}
else if(!currStepNum){currStepNum=0;}
findStartingStep(currStepNum,function(stepNum){var target=(stepNum!==-1)&&utils.getStepTarget(currTour.steps[stepNum]);if(!target){self.endTour(false,false);return;}
utils.invokeEventCallbacks('start');bubble=getBubble();bubble.hide(false);self.isActive=true;if(!utils.getStepTarget(getCurrStep())){utils.invokeEventCallbacks('error');if(getOption('skipIfNoElement')){self.nextStep(false);}}
else{self.showStep(stepNum);}});return this;};this.showStep=function(stepNum){var step=currTour.steps[stepNum];if(step.delay){setTimeout(function(){showStepHelper(stepNum);},step.delay);}
else{showStepHelper(stepNum);}
return this;};this.prevStep=function(doCallbacks){changeStep.call(this,doCallbacks,-1);return this;};this.nextStep=function(doCallbacks){var step=getCurrStep(),targetEl=utils.getStepTarget(step);if(step.nextOnTargetClick){utils.removeEvtListener(targetEl,'click',targetClickNextFn);}
changeStep.call(this,doCallbacks,1);return this;};this.endTour=function(clearState,doCallbacks){var bubble=getBubble();clearState=utils.valOrDefault(clearState,true);doCallbacks=utils.valOrDefault(doCallbacks,true);currStepNum=0;cookieTourStep=undefined;bubble.hide();if(clearState){utils.clearState(getOption('cookieName'));}
if(this.isActive){this.isActive=false;if(currTour&&doCallbacks){utils.invokeEventCallbacks('end');}}
this.removeCallbacks(null,true);this.resetDefaultOptions();currTour=null;return this;};this.getCurrTour=function(){return currTour;};this.getCurrTarget=function(){return utils.getStepTarget(getCurrStep());};this.getCurrStepNum=function(){return currStepNum;};this.refreshBubblePosition=function(){bubble.setPosition(getCurrStep());return this;};this.listen=function(evtType,cb,isTourCb){if(evtType){callbacks[evtType].push({cb:cb,fromTour:isTourCb});}
return this;};this.unlisten=function(evtType,cb){var evtCallbacks=callbacks[evtType],i,len;for(i=0,len=evtCallbacks.length;i<len;++i){if(evtCallbacks[i]===cb){evtCallbacks.splice(i,1);}}
return this;};this.removeCallbacks=function(evtName,tourOnly){var cbArr,i,len,evt;for(evt in callbacks){if(!evtName||evtName===evt){if(tourOnly){cbArr=callbacks[evt];for(i=0,len=cbArr.length;i<len;++i){if(cbArr[i].fromTour){cbArr.splice(i--,1);--len;}}}
else{callbacks[evt]=[];}}}
return this;};this.registerHelper=function(id,fn){if(typeof id==='string'&&typeof fn==='function'){helpers[id]=fn;}};this.unregisterHelper=function(id){helpers[id]=null;};this.invokeHelper=function(id){var args=[],i,len;for(i=1,len=arguments.length;i<len;++i){args.push(arguments[i]);}
if(helpers[id]){helpers[id].call(null,args);}};this.setCookieName=function(name){opt.cookieName=name;return this;};this.resetDefaultOptions=function(){opt={};return this;};this.resetDefaultI18N=function(){customI18N={};return this;};this.getState=function(){return utils.getState(getOption('cookieName'));};_configure=function(options,isTourOptions){var bubble,events=['next','prev','start','end','show','error','close'],eventPropName,callbackProp,i,len;if(!opt){this.resetDefaultOptions();}
utils.extend(opt,options);if(options){utils.extend(customI18N,options.i18n);}
for(i=0,len=events.length;i<len;++i){eventPropName='on'+ events[i].charAt(0).toUpperCase()+ events[i].substring(1);if(options[eventPropName]){this.listen(events[i],options[eventPropName],isTourOptions);}}
bubble=getBubble(true);return this;};this.configure=function(options){return _configure.call(this,options,false);};this.setRenderer=function(render){var typeOfRender=typeof render;if(typeOfRender==='string'){templateToUse=render;customRenderer=undefined;}
else if(typeOfRender==='function'){customRenderer=render;}
return this;};this.setEscaper=function(esc){if(typeof esc==='function'){customEscape=esc;}
return this;};init.call(this,initOptions);};winHopscotch=new Hopscotch();context[namespace]=winHopscotch;(function(){var _={};_.escape=function(str){if(customEscape){return customEscape(str);}
if(str==null)return'';return(''+ str).replace(new RegExp('[&<>"\']','g'),function(match){if(match=='&'){return'&amp;'}
if(match=='<'){return'&lt;'}
if(match=='>'){return'&gt;'}
if(match=='"'){return'&quot;'}
if(match=="'"){return'&#x27;'}});}
this["hopscotch"]=this["hopscotch"]||{};this["hopscotch"]["templates"]=this["hopscotch"]["templates"]||{};this["hopscotch"]["templates"]["bubble_default"]=function(obj){obj||(obj={});var __t,__p='',__e=_.escape,__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}
with(obj){function optEscape(str,unsafe){if(unsafe){return _.escape(str);}
return str;};__p+='\n<div class="hopscotch-bubble-container" style="width: '+
((__t=(step.width))==null?'':__t)+'px; padding: '+
((__t=(step.padding))==null?'':__t)+'px;">\n ';if(tour.isTour){;__p+='<span class="hopscotch-bubble-number">'+
((__t=(i18n.stepNum))==null?'':__t)+'</span>';};__p+='\n <div class="hopscotch-bubble-content">\n ';if(step.title!==''){;__p+='<h3 class="hopscotch-title">'+
((__t=(optEscape(step.title,tour.unsafe)))==null?'':__t)+'</h3>';};__p+='\n ';if(step.content!==''){;__p+='<div class="hopscotch-content">'+
((__t=(optEscape(step.content,tour.unsafe)))==null?'':__t)+'</div>';};__p+='\n </div>\n <div class="hopscotch-actions">\n ';if(buttons.showPrev){;__p+='<button class="btn btn-default hopscotch-prev">'+
((__t=(i18n.prevBtn))==null?'':__t)+'</button>';};__p+='\n ';if(buttons.showCTA){;__p+='<button class="btn btn-primary hopscotch-cta">'+
((__t=(buttons.ctaLabel))==null?'':__t)+'</button>';};__p+='\n ';if(buttons.showNext){;__p+='<button class="btn btn-primary hopscotch-next">'+
((__t=(i18n.nextBtn))==null?'':__t)+'</button>';};__p+='\n </div>\n ';if(buttons.showClose){;__p+='<a title="'+
((__t=(i18n.closeTooltip))==null?'':__t)+'" href="#" class="hopscotch-bubble-close hopscotch-close"><i class="fa fa-times"></i></a>';};__p+='\n</div>\n<div class="hopscotch-bubble-arrow-container hopscotch-arrow">\n <div class="hopscotch-bubble-arrow-border"></div>\n <div class="hopscotch-bubble-arrow"></div>\n</div>';}
return __p};}());}(window,'hopscotch'));

6
public/static/js/jquery.drag.min.js vendored Normal file

File diff suppressed because one or more lines are too long

6
public/static/js/jquery.drop.min.js vendored Normal file
View File

@@ -0,0 +1,6 @@
/*!
* jquery.event.drop - v 2.2
* Copyright (c) 2010 Three Dub Media - http://threedubmedia.com
* Open Source MIT License - http://threedubmedia.com/code/license
*/
;(function(d){d.fn.drop=function(i,e,h){var g=typeof i=="string"?i:"",f=d.isFunction(i)?i:d.isFunction(e)?e:null;if(g.indexOf("drop")!==0){g="drop"+g}h=(i==f?e:h)||{};return f?this.bind(g,h,f):this.trigger(g)};d.drop=function(e){e=e||{};b.multi=e.multi===true?Infinity:e.multi===false?1:!isNaN(e.multi)?e.multi:b.multi;b.delay=e.delay||b.delay;b.tolerance=d.isFunction(e.tolerance)?e.tolerance:e.tolerance===null?null:b.tolerance;b.mode=e.mode||b.mode||"intersect"};var c=d.event,a=c.special,b=d.event.special.drop={multi:1,delay:20,mode:"overlap",targets:[],datakey:"dropdata",noBubble:true,add:function(f){var e=d.data(this,b.datakey);e.related+=1},remove:function(){d.data(this,b.datakey).related-=1},setup:function(){if(d.data(this,b.datakey)){return}var e={related:0,active:[],anyactive:0,winner:0,location:{}};d.data(this,b.datakey,e);b.targets.push(this);return false},teardown:function(){var f=d.data(this,b.datakey)||{};if(f.related){return}d.removeData(this,b.datakey);var e=this;b.targets=d.grep(b.targets,function(g){return(g!==e)})},handler:function(g,e){var f,h;if(!e){return}switch(g.type){case"mousedown":case"touchstart":h=d(b.targets);if(typeof e.drop=="string"){h=h.filter(e.drop)}h.each(function(){var i=d.data(this,b.datakey);i.active=[];i.anyactive=0;i.winner=0});e.droppable=h;a.drag.hijack(g,"dropinit",e);break;case"mousemove":case"touchmove":b.event=g;if(!b.timer){b.tolerate(e)}break;case"mouseup":case"touchend":b.timer=clearTimeout(b.timer);if(e.propagates){a.drag.hijack(g,"drop",e);a.drag.hijack(g,"dropend",e)}break}},locate:function(k,h){var l=d.data(k,b.datakey),g=d(k),i=g.offset()||{},e=g.outerHeight(),j=g.outerWidth(),f={elem:k,width:j,height:e,top:i.top,left:i.left,right:i.left+j,bottom:i.top+e};if(l){l.location=f;l.index=h;l.elem=k}return f},contains:function(e,f){return((f[0]||f.left)>=e.left&&(f[0]||f.right)<=e.right&&(f[1]||f.top)>=e.top&&(f[1]||f.bottom)<=e.bottom)},modes:{intersect:function(f,e,g){return this.contains(g,[f.pageX,f.pageY])?1000000000:this.modes.overlap.apply(this,arguments)},overlap:function(f,e,g){return Math.max(0,Math.min(g.bottom,e.bottom)-Math.max(g.top,e.top))*Math.max(0,Math.min(g.right,e.right)-Math.max(g.left,e.left))},fit:function(f,e,g){return this.contains(g,e)?1:0},middle:function(f,e,g){return this.contains(g,[e.left+e.width*0.5,e.top+e.height*0.5])?1:0}},sort:function(f,e){return(e.winner-f.winner)||(f.index-e.index)},tolerate:function(q){var k,e,n,j,l,m,g,p=0,f,h=q.interactions.length,r=[b.event.pageX,b.event.pageY],o=b.tolerance||b.modes[b.mode];do{if(f=q.interactions[p]){if(!f){return}f.drop=[];l=[];m=f.droppable.length;if(o){n=b.locate(f.proxy)}k=0;do{if(g=f.droppable[k]){j=d.data(g,b.datakey);e=j.location;if(!e){continue}j.winner=o?o.call(b,b.event,n,e):b.contains(e,r)?1:0;l.push(j)}}while(++k<m);l.sort(b.sort);k=0;do{if(j=l[k]){if(j.winner&&f.drop.length<b.multi){if(!j.active[p]&&!j.anyactive){if(a.drag.hijack(b.event,"dropstart",q,p,j.elem)[0]!==false){j.active[p]=1;j.anyactive+=1}else{j.winner=0}}if(j.winner){f.drop.push(j.elem)}}else{if(j.active[p]&&j.anyactive==1){a.drag.hijack(b.event,"dropend",q,p,j.elem);j.active[p]=0;j.anyactive-=1}}}}while(++k<m)}}while(++p<h);if(b.last&&r[0]==b.last.pageX&&r[1]==b.last.pageY){delete b.timer}else{b.timer=setTimeout(function(){b.tolerate(q)},b.delay)}b.last=b.event}};a.dropinit=a.dropstart=a.dropend=b})(jQuery);

View File

@@ -0,0 +1,3 @@
define(['validator-core', 'validator-lang'], function (Validator, undefined) {
return Validator;
});

File diff suppressed because one or more lines are too long

11
public/static/js/sent.js Normal file
View File

@@ -0,0 +1,11 @@
define(['jquery'], function($){
var Sent = {
init: function () {
}
}
Sent.init(); //默认初始化执行的代码
return Sent;
})

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,39 @@
{
"name": "Sortable",
"main": [
"Sortable.js"
],
"homepage": "http://rubaxa.github.io/Sortable/",
"authors": [
"RubaXa <ibnRubaXa@gmail.com>"
],
"description": "Minimalist library for reorderable drag-and-drop lists on modern browsers and touch devices. No jQuery.",
"keywords": [
"sortable",
"reorder",
"list",
"html5",
"drag",
"and",
"drop",
"dnd",
"web-components"
],
"license": "MIT",
"ignore": [
"node_modules",
"bower_components",
"test",
"tests"
],
"version": "1.5.1",
"_release": "1.5.1",
"_resolution": {
"type": "version",
"tag": "1.5.1",
"commit": "c2d48c160cf33d44d167e1b282af103b5bed0edb"
},
"_source": "https://github.com/RubaXa/Sortable.git",
"_target": "~1.5.0",
"_originalSource": "Sortable"
}

View File

@@ -0,0 +1,12 @@
# editorconfig.org
root = true
[*]
indent_style = tab
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

View File

@@ -0,0 +1,5 @@
node_modules
mock.png
.*.sw*
.build*
jquery.fn.*

View File

@@ -0,0 +1,24 @@
{
"strict": true,
"newcap": false,
"node": true,
"expr": true,
"supernew": true,
"laxbreak": true,
"white": true,
"globals": {
"define": true,
"test": true,
"expect": true,
"module": true,
"asyncTest": true,
"start": true,
"ok": true,
"equal": true,
"notEqual": true,
"deepEqual": true,
"window": true,
"document": true,
"performance": true
}
}

View File

@@ -0,0 +1,23 @@
# Contribution Guidelines
### Issue
1. Try [dev](https://github.com/RubaXa/Sortable/tree/dev/)-branch, perhaps the problem has been solved;
2. [Use the search](https://github.com/RubaXa/Sortable/search?type=Issues&q=problem), maybe already have an answer;
3. If not found, create example on [jsbin.com (draft)](http://jsbin.com/zunibaxada/1/edit?html,js,output) and describe the problem.
---
### Pull Request
1. Before PR run `grunt`;
2. Only into [dev](https://github.com/RubaXa/Sortable/tree/dev/)-branch.
### Setup
Pieced together from [gruntjs](http://gruntjs.com/getting-started)
1. Fork repo on [github](https://github.com)
2. Clone locally
3. from local repro ```npm install```
4. Install grunt-cli globally ```sudo -H npm install -g grunt-cli```

View File

@@ -0,0 +1,88 @@
module.exports = function (grunt) {
'use strict';
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
version: {
js: {
src: ['<%= pkg.exportName %>.js', '*.json']
},
cdn: {
options: {
prefix: '(cdnjs\\.cloudflare\\.com\\/ajax\\/libs\\/Sortable|cdn\\.jsdelivr\\.net\\/sortable)\\/',
replace: '[0-9\\.]+'
},
src: ['README.md']
}
},
jshint: {
all: ['*.js', '!*.min.js'],
options: {
jshintrc: true
}
},
uglify: {
options: {
banner: '/*! <%= pkg.exportName %> <%= pkg.version %> - <%= pkg.license %> | <%= pkg.repository.url %> */\n'
},
dist: {
files: {
'<%= pkg.exportName %>.min.js': ['<%= pkg.exportName %>.js']
}
},
jquery: {
files: {}
}
},
jquery: {}
});
grunt.registerTask('jquery', function (exportName, uglify) {
if (exportName == 'min') {
exportName = null;
uglify = 'min';
}
if (!exportName) {
exportName = 'sortable';
}
var fs = require('fs'),
filename = 'jquery.fn.' + exportName + '.js';
grunt.log.oklns(filename);
fs.writeFileSync(
filename,
(fs.readFileSync('jquery.binding.js') + '')
.replace('$.fn.sortable', '$.fn.' + exportName)
.replace('/* CODE */',
(fs.readFileSync('Sortable.js') + '')
.replace(/^[\s\S]*?function[\s\S]*?(var[\s\S]+)\/\/\s+Export[\s\S]+/, '$1')
)
);
if (uglify) {
var opts = {};
opts['jquery.fn.' + exportName + '.min.js'] = filename;
grunt.config.set('uglify.jquery.files', opts);
grunt.task.run('uglify:jquery');
}
});
grunt.loadNpmTasks('grunt-version');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.registerTask('tests', ['jshint']);
grunt.registerTask('default', ['tests', 'version', 'uglify:dist']);
};

View File

@@ -0,0 +1,17 @@
Before you create a issue, check it:
1. Try [master](https://github.com/RubaXa/Sortable/tree/master/)-branch, perhaps the problem has been solved;
2. [Use the search](https://github.com/RubaXa/Sortable/search?q=problem), maybe already have an answer;
3. If not found, create example on [jsbin.com (draft)](http://jsbin.com/vojixek/edit?html,js,output) and describe the problem.
Bindings:
- Angular
- 2.0+: https://github.com/SortableJS/angular-sortablejs/issues
- legacy: https://github.com/SortableJS/angular-legacy-sortablejs/issues
- React
- ES2015+: https://github.com/SortableJS/react-sortablejs/issues
- mixin: https://github.com/SortableJS/react-mixin-sortablejs/issues
- Polymer: https://github.com/SortableJS/polymer-sortablejs/issues
- Knockout: https://github.com/SortableJS/knockout-sortablejs/issues
- Meteor: https://github.com/SortableJS/meteor-sortablejs/issues

View File

@@ -0,0 +1,640 @@
# Sortable
Sortable is a <s>minimalist</s> JavaScript library for reorderable drag-and-drop lists.
Demo: http://rubaxa.github.io/Sortable/
## Features
* Supports touch devices and [modern](http://caniuse.com/#search=drag) browsers (including IE9)
* Can drag from one list to another or within the same list
* CSS animation when moving items
* Supports drag handles *and selectable text* (better than voidberg's html5sortable)
* Smart auto-scrolling
* Built using native HTML5 drag and drop API
* Supports
* [Meteor](https://github.com/SortableJS/meteor-sortablejs)
* AngularJS
* [2.0+](https://github.com/SortableJS/angular-sortablejs)
* [1.*](https://github.com/SortableJS/angular-legacy-sortablejs)
* React
* [ES2015+](https://github.com/SortableJS/react-sortablejs)
* [Mixin](https://github.com/SortableJS/react-mixin-sortablejs)
* [Knockout](https://github.com/SortableJS/knockout-sortablejs)
* [Polymer](https://github.com/SortableJS/polymer-sortablejs)
* [Vue](https://github.com/SortableJS/Vue.Draggable)
* Supports any CSS library, e.g. [Bootstrap](#bs)
* Simple API
* [CDN](#cdn)
* No jQuery (but there is [support](#jq))
<br/>
### Articles
* [Sortable v1.0 — New capabilities](https://github.com/RubaXa/Sortable/wiki/Sortable-v1.0-—-New-capabilities/) (December 22, 2014)
* [Sorting with the help of HTML5 Drag'n'Drop API](https://github.com/RubaXa/Sortable/wiki/Sorting-with-the-help-of-HTML5-Drag'n'Drop-API/) (December 23, 2013)
<br/>
### Install
Via npm
```bash
$ npm install sortablejs --save
```
Via bower:
```bash
$ bower install --save sortablejs
```
<br/>
### Usage
```html
<ul id="items">
<li>item 1</li>
<li>item 2</li>
<li>item 3</li>
</ul>
```
```js
var el = document.getElementById('items');
var sortable = Sortable.create(el);
```
You can use any element for the list and its elements, not just `ul`/`li`. Here is an [example with `div`s](http://jsbin.com/qumuwe/edit?html,js,output).
---
### Options
```js
var sortable = new Sortable(el, {
group: "name", // or { name: "...", pull: [true, false, clone], put: [true, false, array] }
sort: true, // sorting inside list
delay: 0, // time in milliseconds to define when the sorting should start
disabled: false, // Disables the sortable if set to true.
store: null, // @see Store
animation: 150, // ms, animation speed moving items when sorting, `0` — without animation
handle: ".my-handle", // Drag handle selector within list items
filter: ".ignore-elements", // Selectors that do not lead to dragging (String or Function)
preventOnFilter: true, // Call `event.preventDefault()` when triggered `filter`
draggable: ".item", // Specifies which items inside the element should be draggable
ghostClass: "sortable-ghost", // Class name for the drop placeholder
chosenClass: "sortable-chosen", // Class name for the chosen item
dragClass: "sortable-drag", // Class name for the dragging item
dataIdAttr: 'data-id',
forceFallback: false, // ignore the HTML5 DnD behaviour and force the fallback to kick in
fallbackClass: "sortable-fallback", // Class name for the cloned DOM Element when using forceFallback
fallbackOnBody: false, // Appends the cloned DOM Element into the Document's Body
fallbackTolerance: 0, // Specify in pixels how far the mouse should move before it's considered as a drag.
scroll: true, // or HTMLElement
scrollFn: function(offsetX, offsetY, originalEvent) { ... }, // if you have custom scrollbar scrollFn may be used for autoscrolling
scrollSensitivity: 30, // px, how near the mouse must be to an edge to start scrolling.
scrollSpeed: 10, // px
setData: function (/** DataTransfer */dataTransfer, /** HTMLElement*/dragEl) {
dataTransfer.setData('Text', dragEl.textContent); // `dataTransfer` object of HTML5 DragEvent
},
// Element is chosen
onChoose: function (/**Event*/evt) {
evt.oldIndex; // element index within parent
},
// Element dragging started
onStart: function (/**Event*/evt) {
evt.oldIndex; // element index within parent
},
// Element dragging ended
onEnd: function (/**Event*/evt) {
evt.oldIndex; // element's old index within parent
evt.newIndex; // element's new index within parent
},
// Element is dropped into the list from another list
onAdd: function (/**Event*/evt) {
var itemEl = evt.item; // dragged HTMLElement
evt.from; // previous list
// + indexes from onEnd
},
// Changed sorting within list
onUpdate: function (/**Event*/evt) {
var itemEl = evt.item; // dragged HTMLElement
// + indexes from onEnd
},
// Called by any change to the list (add / update / remove)
onSort: function (/**Event*/evt) {
// same properties as onUpdate
},
// Element is removed from the list into another list
onRemove: function (/**Event*/evt) {
// same properties as onUpdate
},
// Attempt to drag a filtered element
onFilter: function (/**Event*/evt) {
var itemEl = evt.item; // HTMLElement receiving the `mousedown|tapstart` event.
},
// Event when you move an item in the list or between lists
onMove: function (/**Event*/evt, /**Event*/originalEvent) {
// Example: http://jsbin.com/tuyafe/1/edit?js,output
evt.dragged; // dragged HTMLElement
evt.draggedRect; // TextRectangle {left, top, right и bottom}
evt.related; // HTMLElement on which have guided
evt.relatedRect; // TextRectangle
originalEvent.clientY; // mouse position
// return false; — for cancel
},
// Called when creating a clone of element
onClone: function (/**Event*/evt) {
var origEl = evt.item;
var cloneEl = evt.clone;
}
});
```
---
#### `group` option
To drag elements from one list into another, both lists must have the same `group` value.
You can also define whether lists can give away, give and keep a copy (`clone`), and receive elements.
* name: `String` — group name
* pull: `true|false|'clone'|function` — ability to move from the list. `clone` — copy the item, rather than move.
* put: `true|false|["foo", "bar"]|function` — whether elements can be added from other lists, or an array of group names from which elements can be taken.
* revertClone: `boolean` — revert cloned element to initial position after moving to a another list.
Demo:
- http://jsbin.com/naduvo/edit?js,output
- http://jsbin.com/rusuvot/edit?js,output — use of complex logic in the `pull` and` put`
- http://jsbin.com/magogub/edit?js,output — use `revertClone: true`
---
#### `sort` option
Sorting inside list.
Demo: http://jsbin.com/videzob/edit?html,js,output
---
#### `delay` option
Time in milliseconds to define when the sorting should start.
Demo: http://jsbin.com/xizeh/edit?html,js,output
---
#### `disabled` options
Disables the sortable if set to `true`.
Demo: http://jsbin.com/xiloqu/edit?html,js,output
```js
var sortable = Sortable.create(list);
document.getElementById("switcher").onclick = function () {
var state = sortable.option("disabled"); // get
sortable.option("disabled", !state); // set
};
```
---
#### `handle` option
To make list items draggable, Sortable disables text selection by the user.
That's not always desirable. To allow text selection, define a drag handler,
which is an area of every list element that allows it to be dragged around.
Demo: http://jsbin.com/newize/edit?html,js,output
```js
Sortable.create(el, {
handle: ".my-handle"
});
```
```html
<ul>
<li><span class="my-handle">::</span> list item text one
<li><span class="my-handle">::</span> list item text two
</ul>
```
```css
.my-handle {
cursor: move;
cursor: -webkit-grabbing;
}
```
---
#### `filter` option
```js
Sortable.create(list, {
filter: ".js-remove, .js-edit",
onFilter: function (evt) {
var item = evt.item,
ctrl = evt.target;
if (Sortable.utils.is(ctrl, ".js-remove")) { // Click on remove button
item.parentNode.removeChild(item); // remove sortable item
}
else if (Sortable.utils.is(ctrl, ".js-edit")) { // Click on edit link
// ...
}
}
})
```
---
#### `ghostClass` option
Class name for the drop placeholder (default `sortable-ghost`).
Demo: http://jsbin.com/hunifu/4/edit?css,js,output
```css
.ghost {
opacity: 0.4;
}
```
```js
Sortable.create(list, {
ghostClass: "ghost"
});
```
---
#### `chosenClass` option
Class name for the chosen item (default `sortable-chosen`).
Demo: http://jsbin.com/hunifu/3/edit?html,css,js,output
```css
.chosen {
color: #fff;
background-color: #c00;
}
```
```js
Sortable.create(list, {
delay: 500,
chosenClass: "chosen"
});
```
---
#### `forceFallback` option
If set to `true`, the Fallback for non HTML5 Browser will be used, even if we are using an HTML5 Browser.
This gives us the possibility to test the behaviour for older Browsers even in newer Browser, or make the Drag 'n Drop feel more consistent between Desktop , Mobile and old Browsers.
On top of that, the Fallback always generates a copy of that DOM Element and appends the class `fallbackClass` defined in the options. This behaviour controls the look of this 'dragged' Element.
Demo: http://jsbin.com/yacuqib/edit?html,css,js,output
---
#### `fallbackTolerance` option
Emulates the native drag threshold. Specify in pixels how far the mouse should move before it's considered as a drag.
Useful if the items are also clickable like in a list of links.
When the user clicks inside a sortable element, it's not uncommon for your hand to move a little between the time you press and the time you release.
Dragging only starts if you move the pointer past a certain tolerance, so that you don't accidentally start dragging every time you click.
3 to 5 are probably good values.
---
#### `scroll` option
If set to `true`, the page (or sortable-area) scrolls when coming to an edge.
Demo:
- `window`: http://jsbin.com/tutuzeh/edit?html,js,output
- `overflow: hidden`: http://jsbin.com/kolisu/edit?html,js,output
---
#### `scrollFn` option
Defines function that will be used for autoscrolling. el.scrollTop/el.scrollLeft is used by default.
Useful when you have custom scrollbar with dedicated scroll function.
---
#### `scrollSensitivity` option
Defines how near the mouse must be to an edge to start scrolling.
---
#### `scrollSpeed` option
The speed at which the window should scroll once the mouse pointer gets within the `scrollSensitivity` distance.
---
### Event object ([demo](http://jsbin.com/xedusu/edit?js,output))
- to:`HTMLElement` — list, in which moved element.
- from:`HTMLElement` — previous list
- item:`HTMLElement` — dragged element
- clone:`HTMLElement`
- oldIndex:`Number|undefined` — old index within parent
- newIndex:`Number|undefined` — new index within parent
#### `move` event object
- to:`HTMLElement`
- from:`HTMLElement`
- dragged:`HTMLElement`
- draggedRect:` TextRectangle`
- related:`HTMLElement` — element on which have guided
- relatedRect:` TextRectangle`
---
### Method
##### option(name:`String`[, value:`*`]):`*`
Get or set the option.
##### closest(el:`String`[, selector:`HTMLElement`]):`HTMLElement|null`
For each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree.
##### toArray():`String[]`
Serializes the sortable's item `data-id`'s (`dataIdAttr` option) into an array of string.
##### sort(order:`String[]`)
Sorts the elements according to the array.
```js
var order = sortable.toArray();
sortable.sort(order.reverse()); // apply
```
##### save()
Save the current sorting (see [store](#store))
##### destroy()
Removes the sortable functionality completely.
---
<a name="store"></a>
### Store
Saving and restoring of the sort.
```html
<ul>
<li data-id="1">order</li>
<li data-id="2">save</li>
<li data-id="3">restore</li>
</ul>
```
```js
Sortable.create(el, {
group: "localStorage-example",
store: {
/**
* Get the order of elements. Called once during initialization.
* @param {Sortable} sortable
* @returns {Array}
*/
get: function (sortable) {
var order = localStorage.getItem(sortable.options.group.name);
return order ? order.split('|') : [];
},
/**
* Save the order of elements. Called onEnd (when the item is dropped).
* @param {Sortable} sortable
*/
set: function (sortable) {
var order = sortable.toArray();
localStorage.setItem(sortable.options.group.name, order.join('|'));
}
}
})
```
---
<a name="bs"></a>
### Bootstrap
Demo: http://jsbin.com/qumuwe/edit?html,js,output
```html
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css"/>
<!-- Latest Sortable -->
<script src="http://rubaxa.github.io/Sortable/Sortable.js"></script>
<!-- Simple List -->
<ul id="simpleList" class="list-group">
<li class="list-group-item">This is <a href="http://rubaxa.github.io/Sortable/">Sortable</a></li>
<li class="list-group-item">It works with Bootstrap...</li>
<li class="list-group-item">...out of the box.</li>
<li class="list-group-item">It has support for touch devices.</li>
<li class="list-group-item">Just drag some elements around.</li>
</ul>
<script>
// Simple list
Sortable.create(simpleList, { /* options */ });
</script>
```
---
### Static methods & properties
##### Sortable.create(el:`HTMLElement`[, options:`Object`]):`Sortable`
Create new instance.
---
##### Sortable.active:`Sortable`
Link to the active instance.
---
##### Sortable.utils
* on(el`:HTMLElement`, event`:String`, fn`:Function`) — attach an event handler function
* off(el`:HTMLElement`, event`:String`, fn`:Function`) — remove an event handler
* css(el`:HTMLElement`)`:Object` — get the values of all the CSS properties
* css(el`:HTMLElement`, prop`:String`)`:Mixed` — get the value of style properties
* css(el`:HTMLElement`, prop`:String`, value`:String`) — set one CSS properties
* css(el`:HTMLElement`, props`:Object`) — set more CSS properties
* find(ctx`:HTMLElement`, tagName`:String`[, iterator`:Function`])`:Array` — get elements by tag name
* bind(ctx`:Mixed`, fn`:Function`)`:Function` — Takes a function and returns a new one that will always have a particular context
* is(el`:HTMLElement`, selector`:String`)`:Boolean` — check the current matched set of elements against a selector
* closest(el`:HTMLElement`, selector`:String`[, ctx`:HTMLElement`])`:HTMLElement|Null` — for each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree
* clone(el`:HTMLElement`)`:HTMLElement` — create a deep copy of the set of matched elements
* toggleClass(el`:HTMLElement`, name`:String`, state`:Boolean`) — add or remove one classes from each element
---
<a name="cdn"></a>
### CDN
```html
<!-- CDNJS :: Sortable (https://cdnjs.com/) -->
<script src="//cdnjs.cloudflare.com/ajax/libs/Sortable/1.5.1/Sortable.min.js"></script>
<!-- jsDelivr :: Sortable (http://www.jsdelivr.com/) -->
<script src="//cdn.jsdelivr.net/sortable/1.5.1/Sortable.min.js"></script>
<!-- jsDelivr :: Sortable :: Latest (http://www.jsdelivr.com/) -->
<script src="//cdn.jsdelivr.net/sortable/latest/Sortable.min.js"></script>
```
---
<a name="jq"></a>
### jQuery compatibility
To assemble plugin for jQuery, perform the following steps:
```bash
cd Sortable
npm install
grunt jquery
```
Now you can use `jquery.fn.sortable.js`:<br/>
(or `jquery.fn.sortable.min.js` if you run `grunt jquery:min`)
```js
$("#list").sortable({ /* options */ }); // init
$("#list").sortable("widget"); // get Sortable instance
$("#list").sortable("destroy"); // destroy Sortable instance
$("#list").sortable("{method-name}"); // call an instance method
$("#list").sortable("{method-name}", "foo", "bar"); // call an instance method with parameters
```
And `grunt jquery:mySortableFunc``jquery.fn.mySortableFunc.js`
---
### Contributing (Issue/PR)
Please, [read this](CONTRIBUTING.md).
---
## MIT LICENSE
Copyright 2013-2017 Lebedev Konstantin <ibnRubaXa@gmail.com>
http://rubaxa.github.io/Sortable/
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,29 @@
{
"name": "Sortable",
"main": [
"Sortable.js"
],
"homepage": "http://rubaxa.github.io/Sortable/",
"authors": [
"RubaXa <ibnRubaXa@gmail.com>"
],
"description": "Minimalist library for reorderable drag-and-drop lists on modern browsers and touch devices. No jQuery.",
"keywords": [
"sortable",
"reorder",
"list",
"html5",
"drag",
"and",
"drop",
"dnd",
"web-components"
],
"license": "MIT",
"ignore": [
"node_modules",
"bower_components",
"test",
"tests"
]
}

View File

@@ -0,0 +1,32 @@
{
"name": "Sortable",
"main": "Sortable.js",
"version": "1.5.1",
"homepage": "http://rubaxa.github.io/Sortable/",
"repo": "RubaXa/Sortable",
"authors": [
"RubaXa <ibnRubaXa@gmail.com>"
],
"description": "Minimalist library for reorderable drag-and-drop lists on modern browsers and touch devices. No jQuery.",
"keywords": [
"sortable",
"reorder",
"list",
"html5",
"drag",
"and",
"drop",
"dnd"
],
"license": "MIT",
"ignore": [
"node_modules",
"bower_components",
"test",
"tests"
],
"scripts": [
"Sortable.js"
]
}

View File

@@ -0,0 +1,344 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta property="og:image" content="/st/og-image.png"/>
<title>Sortable. No jQuery.</title>
<meta name="keywords" content="sortable, reorder, list, javascript, html5, drag and drop, dnd, animation, groups, angular, ng-sortable, react, mixin, effects, rubaxa"/>
<meta name="description" content="Sortable - is a minimalist JavaScript library for reorderable drag-and-drop lists on modern browsers and touch devices. No jQuery. Supports Meteor, AngularJS, React and any CSS library, e.g. Bootstrap."/>
<meta name="viewport" content="width=device-width, initial-scale=0.5"/>
<link href="//rubaxa.github.io/Ply/ply.css" rel="stylesheet" type="text/css"/>
<link href="//fonts.googleapis.com/css?family=Roboto:300" rel="stylesheet" type="text/css"/>
<link href="st/app.css" rel="stylesheet" type="text/css"/>
</head>
<body>
<a href="https://github.com/RubaXa/Sortable"><img style="position: fixed; top: 0; right: 0; border: 0; z-index: 10000;" src="//s3.amazonaws.com/github/ribbons/forkme_right_orange_ff7600.png" alt="Fork me on GitHub"></a>
<div class="container">
<div style="padding: 80px 150px 0; height: 160px;">
<a class="logo" href="https://github.com/RubaXa/Sortable"><img src="st/logo.png"/></a>
<h1 data-force="40" data-force-y="2.5">The JavaScript library for modern browsers and touch devices. No&nbsp;jQuery.</h1>
</div>
</div>
<!-- Connected lists -->
<div class="container" style="height: 520px">
<div data-force="30" class="layer block" style="left: 14.5%; top: 0; width: 37%">
<div class="layer title">List A</div>
<ul id="foo" class="block__list block__list_words">
<li>бегемот</li>
<li>корм</li>
<li>антон</li>
<li>сало</li>
<li>железосталь</li>
<li>валик</li>
<li>кровать</li>
<li>краб</li>
</ul>
</div>
<div data-force="18" class="layer block" style="left: 58%; top: 143px; width: 40%;">
<div class="layer title">List B</div>
<ul id="bar" class="block__list block__list_tags">
<li>казнить</li>
<li>,</li>
<li>нельзя</li>
<li>помиловать</li>
</ul>
</div>
</div>
<!-- Multi connected lists -->
<a name="m"></a>
<div class="container">
<div id="multi" style="margin-left: 30px">
<div><div data-force="5" class="layer title title_xl">Multi</div></div>
<div class="layer tile" data-force="30">
<div class="tile__name">Group A</div>
<div class="tile__list">
<img src="st/face-01.jpg"/><!--
--><img src="st/face-02.jpg"/><!--
--><img src="st/face-03.jpg"/><!--
--><img src="st/face-04.jpg"/>
</div>
</div>
<div class="layer tile" data-force="25">
<div class="tile__name">Group B</div>
<div class="tile__list">
<img src="st/face-05.jpg"/><!--
--><img src="st/face-06.jpg"/><!--
--><img src="st/face-07.jpg"/>
</div>
</div>
<div class="layer tile" data-force="20">
<div class="tile__name">Group C</div>
<div class="tile__list">
<img src="st/face-08.jpg"/><!--
--><img src="st/face-09.jpg"/>
</div>
</div>
</div>
</div>
<!-- Editable list -->
<a name="e"></a>
<div class="container" style="margin-top: 100px">
<div id="filter" style="margin-left: 30px">
<div><div data-force="5" class="layer title title_xl">Editable list</div></div>
<div style="margin-top: -8px; margin-left: 10px" class="block__list block__list_words">
<ul id="editable">
<li>Оля<i class="js-remove"></i></li>
<li>Владимир<i class="js-remove"></i></li>
<li>Алина<i class="js-remove"></i></li>
</ul>
<button id="addUser">Add</button>
</div>
</div>
</div>
<!-- Advanced connected lists -->
<a name="ag"></a>
<div class="container" style="margin-top: 100px;">
<div id="advanced" style="margin-left: 30px;">
<div><div data-force="5" class="layer title title_xl">Advanced groups</div></div>
<div style="width: 25%; float: left; margin-top: 15px; margin-left: 10px" class="block__list block__list_words">
<div class="block__list-title">pull & put</div>
<ul id="advanced-1">
<li>Meat</li>
<li>Potato</li>
<li>Tea</li>
</ul>
</div>
<div style="width: 25%; float: left; margin-top: 15px; margin-left: 10px" class="block__list block__list_words">
<div class="block__list-title">only pull (clone) no&nbsp;reordering</div>
<ul id="advanced-2">
<li>Sex</li>
<li>Drugs</li>
<li>Rock'n'roll</li>
</ul>
</div>
<div style="width: 25%; float: left; margin-top: 15px; margin-left: 10px" class="block__list block__list_words">
<div class="block__list-title">only put</div>
<ul id="advanced-3">
<li>Money</li>
<li>Force</li>
<li>Agility</li>
</ul>
</div>
<div style="clear: both"></div>
</div>
</div>
<!-- 'handle' option -->
<a name="h"></a>
<div class="container" style="margin-top: 100px;">
<div id="handle" style="margin-left: 30px;">
<div><div data-force="5" class="layer title title_xl">Drag handle and selectable text</div></div>
<div style="width: 30%; margin-left: 10px" class="block__list_words">
<ul id="handle-1">
<li><span class="drag-handle">&#9776;</span>Select text freely</li>
<li><span class="drag-handle">&#9776;</span>Drag my handle</li>
<li><span class="drag-handle">&#9776;</span>Best of both worlds</li>
</ul>
</div>
<div style="clear: both"></div>
</div>
</div>
<!-- Angular -->
<a name="ng"></a>
<div id="todos" ng-app="todoApp" class="container" style="margin-top: 100px">
<div style="margin-left: 30px">
<div><div data-force="5" class="layer title title_xl">AngularJS / ng-sortable</div></div>
<div style="width: 30%; margin-top: -8px; margin-left: 10px; float: left;" class="block__list block__list_words">
<div ng-controller="TodoController">
<span style="padding-left: 20px">{{remaining()}} of {{todos.length}} remaining</span>
[ <a href="" ng-click="archive()">archive</a> ]
<ul ng-sortable="{ group: 'todo', animation: 150 }" class="unstyled">
<li ng-repeat="todo in todos">
<input type="checkbox" ng-model="todo.done">
<span class="done-{{todo.done}}">{{todo.text}}</span>
</li>
</ul>
<form ng-submit="addTodo()" style="padding-left: 20px">
<input type="text" ng-model="todoText" size="30"
placeholder="add new todo here">
</form>
</div>
</div>
<div style="width: 30%; margin-top: -8px; margin-left: 10px; float: left;" class="block__list block__list_words">
<div ng-controller="TodoControllerNext">
<span style="padding-left: 20px">{{remaining()}} of {{todos.length}} remaining</span>
<ul ng-sortable="sortableConfig" class="unstyled">
<li ng-repeat="todo in todos">
<input type="checkbox" ng-model="todo.done">
<span class="done-{{todo.done}}">{{todo.text}}</span>
</li>
</ul>
</div>
</div>
<div style="clear: both"></div>
</div>
</div>
<!-- Code example -->
<a name="c"></a>
<div class="container" style="margin-top: 100px">
<div style="margin-left: 30px">
<div><div class="layer title title_xl">Code example</div></div>
<pre data-force="100" class="layer javascript" style="margin-top: -8px; margin-left: 10px; width: 90%"><code>// Simple list
var list = document.getElementById("my-ui-list");
Sortable.create(list); // That's all.
// Grouping
var foo = document.getElementById("foo");
Sortable.create(foo, { group: "omega" });
var bar = document.getElementById("bar");
Sortable.create(bar, { group: "omega" });
// Or
var container = document.getElementById("multi");
var sort = Sortable.create(container, {
animation: 150, // ms, animation speed moving items when sorting, `0` — without animation
handle: ".tile__title", // Restricts sort start click/touch to the specified element
draggable: ".tile", // Specifies which items inside the element should be sortable
onUpdate: function (evt/**Event*/){
var item = evt.item; // the current dragged HTMLElement
}
});
// ..
sort.destroy();
// Editable list
var editableList = Sortable.create(editable, {
filter: '.js-remove',
onFilter: function (evt) {
var el = editableList.closest(evt.item); // get dragged item
el && el.parentNode.removeChild(el);
}
});
</code></pre>
</div>
<div class="container" style="margin: 100px 0;">
<div style="margin-left: 30px">
<div><div class="layer title title_xl">See also</div></div>
<div id="rubaxa-repos" data-force="100" class="layer" style="margin-top: -8px; margin-left: 10px; width: 90%; background-color: #fff;">Loading&hellip;</div>
<script src="//rubaxa.github.io/repos.js"></script>
</div>
</div>
</div>
<script src="Sortable.js"></script>
<script src="//rubaxa.github.io/Ply/Ply.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular.min.js"></script>
<script src="ng-sortable.js"></script>
<script src="st/app.js"></script>
<!-- highlight.js -->
<style>
/* Tomorrow Theme */
/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
/* Original theme - https://github.com/chriskempson/tomorrow-theme */
/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
.tomorrow-comment, pre .comment, pre .title {
color: #8e908c;
}
.tomorrow-red, pre .variable, pre .attribute, pre .tag, pre .regexp, pre .ruby .constant, pre .xml .tag .title, pre .xml .pi, pre .xml .doctype, pre .html .doctype, pre .css .id, pre .css .class, pre .css .pseudo {
color: #c82829;
}
.tomorrow-orange, pre .number, pre .preprocessor, pre .built_in, pre .literal, pre .params, pre .constant {
color: #f5871f;
}
.tomorrow-yellow, pre .class, pre .ruby .class .title, pre .css .rules .attribute {
color: #eab700;
}
.tomorrow-green, pre .string, pre .value, pre .inheritance, pre .header, pre .ruby .symbol, pre .xml .cdata {
color: #718c00;
}
.tomorrow-aqua, pre .css .hexcolor {
color: #3e999f;
}
.tomorrow-blue, pre .function, pre .python .decorator, pre .python .title, pre .ruby .function .title, pre .ruby .title .keyword, pre .perl .sub, pre .javascript .title, pre .coffeescript .title {
color: #4271ae;
}
.tomorrow-purple, pre .keyword, pre .javascript .function {
color: #8959a8;
}
pre {
border: 0;
background-color: #fff;
}
pre code {
display: block;
color: #4d4d4c;
font-size: 15px;
font-family: Menlo, Monaco, Consolas, monospace;
line-height: 1.5;
padding: 30px;
}
</style>
<script src="//yandex.st/highlightjs/7.5/highlight.min.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-16483888-3', 'rubaxa.github.io');
ga('send', 'pageview');
</script>
</body>
</html>

View File

@@ -0,0 +1,61 @@
/**
* jQuery plugin for Sortable
* @author RubaXa <trash@rubaxa.org>
* @license MIT
*/
(function (factory) {
"use strict";
if (typeof define === "function" && define.amd) {
define(["jquery"], factory);
}
else {
/* jshint sub:true */
factory(jQuery);
}
})(function ($) {
"use strict";
/* CODE */
/**
* jQuery plugin for Sortable
* @param {Object|String} options
* @param {..*} [args]
* @returns {jQuery|*}
*/
$.fn.sortable = function (options) {
var retVal,
args = arguments;
this.each(function () {
var $el = $(this),
sortable = $el.data('sortable');
if (!sortable && (options instanceof Object || !options)) {
sortable = new Sortable(this, options);
$el.data('sortable', sortable);
}
if (sortable) {
if (options === 'widget') {
retVal = sortable;
}
else if (options === 'destroy') {
sortable.destroy();
$el.removeData('sortable');
}
else if (typeof sortable[options] === 'function') {
retVal = sortable[options].apply(sortable, [].slice.call(args, 1));
}
else if (options in sortable.options) {
retVal = sortable.option.apply(sortable, args);
}
}
});
return (retVal === void 0) ? this : retVal;
};
});

View File

@@ -0,0 +1,44 @@
{
"name": "sortablejs",
"exportName": "Sortable",
"version": "1.5.1",
"devDependencies": {
"grunt": "*",
"grunt-version": "*",
"grunt-contrib-jshint": "*",
"grunt-contrib-uglify": "*"
},
"description": "Minimalist JavaScript library for reorderable drag-and-drop lists on modern browsers and touch devices. No jQuery. Supports AngularJS and any CSS library, e.g. Bootstrap.",
"main": "Sortable.js",
"scripts": {
"test": "./node_modules/grunt/bin/grunt",
"prepublish": "./node_modules/grunt/bin/grunt"
},
"repository": {
"type": "git",
"url": "git://github.com/rubaxa/Sortable.git"
},
"files": [
"Sortable.js",
"Sortable.min.js"
],
"keywords": [
"sortable",
"reorder",
"drag",
"meteor",
"angular",
"ng-sortable",
"react",
"mixin"
],
"author": "Konstantin Lebedev <ibnRubaXa@gmail.com>",
"license": "MIT",
"spm": {
"main": "Sortable.js",
"ignore": [
"meteor",
"st"
]
}
}

View File

@@ -0,0 +1,243 @@
html {
background-image: -webkit-linear-gradient(bottom, #F4E2C9 20%, #F4D7C9 100%);
background-image: -ms-linear-gradient(bottom, #F4E2C9 20%, #F4D7C9 100%);
background-image: linear-gradient(to bottom, #F4E2C9 20%, #F4D7C9 100%);
}
html, body {
margin: 0;
padding: 0;
position: relative;
color: #464637;
min-height: 100%;
font-size: 20px;
font-family: 'Roboto', sans-serif;
font-weight: 300;
}
h1 {
color: #FF3F00;
font-size: 20px;
font-family: 'Roboto', sans-serif;
font-weight: 300;
text-align: center;
}
ul {
margin: 0;
padding: 0;
list-style: none;
}
.container {
width: 80%;
margin: auto;
min-width: 1100px;
max-width: 1300px;
position: relative;
}
@media (min-width: 750px) and (max-width: 970px){
.container {
width: 100%;
min-width: 750px;
}
}
.sortable-ghost {
opacity: .2;
}
#foo .sortable-drag {
background: #daf4ff;
}
img {
border: 0;
vertical-align: middle;
}
.logo {
top: 55px;
left: 30px;
position: absolute;
}
.title {
color: #fff;
padding: 3px 10px;
display: inline-block;
position: relative;
background-color: #FF7373;
z-index: 1000;
}
.title_xl {
padding: 3px 15px;
font-size: 40px;
}
.tile {
width: 22%;
min-width: 245px;
color: #FF7270;
padding: 10px 30px;
text-align: center;
margin-top: 15px;
margin-left: 5px;
margin-right: 30px;
background-color: #fff;
display: inline-block;
vertical-align: top;
}
.tile__name {
cursor: move;
padding-bottom: 10px;
border-bottom: 1px solid #FF7373;
}
.tile__list {
margin-top: 10px;
}
.tile__list:last-child {
margin-right: 0;
min-height: 80px;
}
.tile__list img {
cursor: move;
margin: 10px;
border-radius: 100%;
}
.block {
opacity: 1;
position: absolute;
}
.block__list {
padding: 20px 0;
max-width: 360px;
margin-top: -8px;
margin-left: 5px;
background-color: #fff;
}
.block__list-title {
margin: -20px 0 0;
padding: 10px;
text-align: center;
background: #5F9EDF;
}
.block__list li { cursor: move; }
.block__list_words li {
background-color: #fff;
padding: 10px 40px;
}
.block__list_words .sortable-ghost {
opacity: 0.4;
background-color: #F4E2C9;
}
.block__list_words li:first-letter {
text-transform: uppercase;
}
.block__list_tags {
padding-left: 30px;
}
.block__list_tags:after {
clear: both;
content: '';
display: block;
}
.block__list_tags li {
color: #fff;
float: left;
margin: 8px 20px 10px 0;
padding: 5px 10px;
min-width: 10px;
background-color: #5F9EDF;
text-align: center;
}
.block__list_tags li:first-child:first-letter {
text-transform: uppercase;
}
#editable {}
#editable li {
position: relative;
}
#editable i {
-webkit-transition: opacity .2s;
transition: opacity .2s;
opacity: 0;
display: block;
cursor: pointer;
color: #c00;
top: 10px;
right: 40px;
position: absolute;
font-style: normal;
}
#editable li:hover i {
opacity: 1;
}
#filter {}
#filter button {
color: #fff;
width: 100%;
border: none;
outline: 0;
opacity: .5;
margin: 10px 0 0;
transition: opacity .1s ease;
cursor: pointer;
background: #5F9EDF;
padding: 10px 0;
font-size: 20px;
}
#filter button:hover {
opacity: 1;
}
#filter .block__list {
padding-bottom: 0;
}
.drag-handle {
margin-right: 10px;
font: bold 20px Sans-Serif;
color: #5F9EDF;
display: inline-block;
cursor: move;
cursor: -webkit-grabbing; /* overrides 'move' */
}
#todos input {
padding: 5px;
font-size: 14px;
font-family: 'Roboto', sans-serif;
font-weight: 300;
}
#nested ul li {
background-color: rgba(0,0,0,.05);
}

View File

@@ -0,0 +1,226 @@
(function () {
'use strict';
var byId = function (id) { return document.getElementById(id); },
loadScripts = function (desc, callback) {
var deps = [], key, idx = 0;
for (key in desc) {
deps.push(key);
}
(function _next() {
var pid,
name = deps[idx],
script = document.createElement('script');
script.type = 'text/javascript';
script.src = desc[deps[idx]];
pid = setInterval(function () {
if (window[name]) {
clearTimeout(pid);
deps[idx++] = window[name];
if (deps[idx]) {
_next();
} else {
callback.apply(null, deps);
}
}
}, 30);
document.getElementsByTagName('head')[0].appendChild(script);
})()
},
console = window.console;
if (!console.log) {
console.log = function () {
alert([].join.apply(arguments, ' '));
};
}
Sortable.create(byId('foo'), {
group: "words",
animation: 150,
store: {
get: function (sortable) {
var order = localStorage.getItem(sortable.options.group);
return order ? order.split('|') : [];
},
set: function (sortable) {
var order = sortable.toArray();
localStorage.setItem(sortable.options.group, order.join('|'));
}
},
onAdd: function (evt){ console.log('onAdd.foo:', [evt.item, evt.from]); },
onUpdate: function (evt){ console.log('onUpdate.foo:', [evt.item, evt.from]); },
onRemove: function (evt){ console.log('onRemove.foo:', [evt.item, evt.from]); },
onStart:function(evt){ console.log('onStart.foo:', [evt.item, evt.from]);},
onSort:function(evt){ console.log('onStart.foo:', [evt.item, evt.from]);},
onEnd: function(evt){ console.log('onEnd.foo:', [evt.item, evt.from]);}
});
Sortable.create(byId('bar'), {
group: "words",
animation: 150,
onAdd: function (evt){ console.log('onAdd.bar:', evt.item); },
onUpdate: function (evt){ console.log('onUpdate.bar:', evt.item); },
onRemove: function (evt){ console.log('onRemove.bar:', evt.item); },
onStart:function(evt){ console.log('onStart.foo:', evt.item);},
onEnd: function(evt){ console.log('onEnd.foo:', evt.item);}
});
// Multi groups
Sortable.create(byId('multi'), {
animation: 150,
draggable: '.tile',
handle: '.tile__name'
});
[].forEach.call(byId('multi').getElementsByClassName('tile__list'), function (el){
Sortable.create(el, {
group: 'photo',
animation: 150
});
});
// Editable list
var editableList = Sortable.create(byId('editable'), {
animation: 150,
filter: '.js-remove',
onFilter: function (evt) {
evt.item.parentNode.removeChild(evt.item);
}
});
byId('addUser').onclick = function () {
Ply.dialog('prompt', {
title: 'Add',
form: { name: 'name' }
}).done(function (ui) {
var el = document.createElement('li');
el.innerHTML = ui.data.name + '<i class="js-remove">✖</i>';
editableList.el.appendChild(el);
});
};
// Advanced groups
[{
name: 'advanced',
pull: true,
put: true
},
{
name: 'advanced',
pull: 'clone',
put: false
}, {
name: 'advanced',
pull: false,
put: true
}].forEach(function (groupOpts, i) {
Sortable.create(byId('advanced-' + (i + 1)), {
sort: (i != 1),
group: groupOpts,
animation: 150
});
});
// 'handle' option
Sortable.create(byId('handle-1'), {
handle: '.drag-handle',
animation: 150
});
// Angular example
angular.module('todoApp', ['ng-sortable'])
.constant('ngSortableConfig', {onEnd: function() {
console.log('default onEnd()');
}})
.controller('TodoController', ['$scope', function ($scope) {
$scope.todos = [
{text: 'learn angular', done: true},
{text: 'build an angular app', done: false}
];
$scope.addTodo = function () {
$scope.todos.push({text: $scope.todoText, done: false});
$scope.todoText = '';
};
$scope.remaining = function () {
var count = 0;
angular.forEach($scope.todos, function (todo) {
count += todo.done ? 0 : 1;
});
return count;
};
$scope.archive = function () {
var oldTodos = $scope.todos;
$scope.todos = [];
angular.forEach(oldTodos, function (todo) {
if (!todo.done) $scope.todos.push(todo);
});
};
}])
.controller('TodoControllerNext', ['$scope', function ($scope) {
$scope.todos = [
{text: 'learn Sortable', done: true},
{text: 'use ng-sortable', done: false},
{text: 'Enjoy', done: false}
];
$scope.remaining = function () {
var count = 0;
angular.forEach($scope.todos, function (todo) {
count += todo.done ? 0 : 1;
});
return count;
};
$scope.sortableConfig = { group: 'todo', animation: 150 };
'Start End Add Update Remove Sort'.split(' ').forEach(function (name) {
$scope.sortableConfig['on' + name] = console.log.bind(console, name);
});
}]);
})();
// Background
document.addEventListener("DOMContentLoaded", function () {
function setNoiseBackground(el, width, height, opacity) {
var canvas = document.createElement("canvas");
var context = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
for (var i = 0; i < width; i++) {
for (var j = 0; j < height; j++) {
var val = Math.floor(Math.random() * 255);
context.fillStyle = "rgba(" + val + "," + val + "," + val + "," + opacity + ")";
context.fillRect(i, j, 1, 1);
}
}
el.style.background = "url(" + canvas.toDataURL("image/png") + ")";
}
setNoiseBackground(document.getElementsByTagName('body')[0], 50, 50, 0.02);
}, false);

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css"/>
<!-- List with handle -->
<div id="listWithHandle" class="list-group">
<div class="list-group-item">
<span class="badge">14</span>
<span class="glyphicon glyphicon-move" aria-hidden="true"></span>
Drag me by the handle
</div>
<div class="list-group-item">
<span class="badge">2</span>
<span class="glyphicon glyphicon-move" aria-hidden="true"></span>
You can also select text
</div>
<div class="list-group-item">
<span class="badge">1</span>
<span class="glyphicon glyphicon-move" aria-hidden="true"></span>
Best of both worlds!
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>IFrame playground</title>
</head>
<body>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css"/>
<!-- Latest Sortable -->
<script src="../../Sortable.js"></script>
<!-- Simple List -->
<div id="simpleList" class="list-group">
<div class="list-group-item">This is <a href="http://rubaxa.github.io/Sortable/">Sortable</a></div>
<div class="list-group-item">It works with Bootstrap...</div>
<div class="list-group-item">...out of the box.</div>
<div class="list-group-item">It has support for touch devices.</div>
<div class="list-group-item">Just drag some elements around.</div>
</div>
<script>
(function () {
Sortable.create(simpleList, {group: 'shared'});
var iframe = document.createElement('iframe');
iframe.src = 'frame.html';
iframe.width = '100%';
iframe.onload = function () {
var doc = iframe.contentDocument,
list = doc.getElementById('listWithHandle');
Sortable.create(list, {group: 'shared'});
};
document.body.appendChild(iframe);
})();
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,14 @@
{
"name": "art-template",
"homepage": "https://github.com/aui/artTemplate",
"version": "3.1.3",
"_release": "3.1.3",
"_resolution": {
"type": "version",
"tag": "v3.1.3",
"commit": "b1085f546f6d3abbf74d10a5f69d24a63e932e86"
},
"_source": "https://github.com/aui/artTemplate.git",
"_target": "^3.1.3",
"_originalSource": "art-template"
}

View File

@@ -0,0 +1,4 @@
.DS_Store
node_modules
.vscode
coverage

View File

@@ -0,0 +1,3 @@
test
demo
doc

View File

@@ -0,0 +1,99 @@
module.exports = function (grunt) {
var sources_native = [
'src/intro.js',
'src/template.js',
'src/config.js',
'src/cache.js',
'src/render.js',
'src/renderFile.js',
'src/get.js',
'src/utils.js',
'src/helper.js',
'src/onerror.js',
'src/compile.js',
//<<<< 'src/syntax.js',
'src/outro.js'
];
var sources_simple = Array.apply(null, sources_native);
sources_simple.splice(sources_native.length - 1, 0, 'src/syntax.js');
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
meta: {
banner: '/*!<%= pkg.name %> - Template Engine | <%= pkg.homepage %>*/\n'
},
concat: {
options: {
separator: ''
},
'native': {
src: sources_native,
dest: 'dist/template-native-debug.js'
},
simple: {
src: sources_simple,
dest: 'dist/template-debug.js'
}
},
uglify: {
options: {
banner: '<%= meta.banner %>'
},
'native': {
src: '<%= concat.native.dest %>',
dest: 'dist/template-native.js'
},
simple: {
src: '<%= concat.simple.dest %>',
dest: 'dist/template.js'
}
},
qunit: {
files: ['test/**/*.html']
},
jshint: {
files: [
'dist/template-native.js',
'dist/template.js'
],
options: {
curly: true,
eqeqeq: true,
immed: true,
latedef: true,
newcap: true,
noarg: true,
sub: true,
undef: true,
boss: true,
eqnull: true,
browser: true
},
globals: {
console: true,
define: true,
global: true,
module: true
}
},
watch: {
files: '<config:lint.files>',
tasks: 'lint qunit'
}
});
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-jshint');
//grunt.loadNpmTasks('grunt-contrib-qunit');
//grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.registerTask('default', ['concat', /*'jshint',*/ 'uglify']);
};

View File

@@ -0,0 +1,303 @@
# artTemplate-3.0
新一代 javascript 模板引擎
> 后会无期。2016-12-22
## 目录
* [特性](#特性)
* [快速上手](#快速上手)
* [模板语法](#模板语法)
* [下载](#下载)
* [方法](#方法)
* [NodeJS](#nodejs)
* [使用预编译](#使用预编译)
* [更新日志](#更新日志)
* [授权协议](#授权协议)
## 特性
1. 性能卓越,执行速度通常是 Mustache 与 tmpl 的 20 多倍([性能测试](http://aui.github.com/artTemplate/test/test-speed.html)
2. 支持运行时调试,可精确定位异常模板所在语句([演示](http://aui.github.io/artTemplate/demo/debug.html)
3. 对 NodeJS Express 友好支持
4. 安全默认对输出进行转义、在沙箱中运行编译后的代码Node版本可以安全执行用户上传的模板
5. 支持``include``语句
6. 可在浏览器端实现按路径加载模板([详情](#使用预编译)
7. 支持预编译,可将模板转换成为非常精简的 js 文件
8. 模板语句简洁,无需前缀引用数据,有简洁版本与原生语法版本可选
9. 支持所有流行的浏览器
## 快速上手
### 编写模板
使用一个``type="text/html"``的``script``标签存放模板:
<script id="test" type="text/html">
<h1>{{title}}</h1>
<ul>
{{each list as value i}}
<li>索引 {{i + 1}} {{value}}</li>
{{/each}}
</ul>
</script>
### 渲染模板
var data = {
title: '标签',
list: ['文艺', '博客', '摄影', '电影', '民谣', '旅行', '吉他']
};
var html = template('test', data);
document.getElementById('content').innerHTML = html;
[演示](http://aui.github.com/artTemplate/demo/basic.html)
## 模板语法
有两个版本的模板语法可以选择。
### 简洁语法
推荐使用,语法简单实用,利于读写。
{{if admin}}
{{include 'admin_content'}}
{{each list}}
<div>{{$index}}. {{$value.user}}</div>
{{/each}}
{{/if}}
[查看语法与演示](https://github.com/aui/artTemplate/wiki/syntax:simple)
### 原生语法
<%if (admin){%>
<%include('admin_content')%>
<%for (var i=0;i<list.length;i++) {%>
<div><%=i%>. <%=list[i].user%></div>
<%}%>
<%}%>
[查看语法与演示](https://github.com/aui/artTemplate/wiki/syntax:native)
## 下载
* [template.js](https://raw.github.com/aui/artTemplate/master/dist/template.js) *(简洁语法版, 2.7kb)*
* [template-native.js](https://raw.github.com/aui/artTemplate/master/dist/template-native.js) *(原生语法版, 2.3kb)*
## 方法
### template(id, data)
根据 id 渲染模板。内部会根据``document.getElementById(id)``查找模板。
如果没有 data 参数,那么将返回一渲染函数。
### template.``compile``(source, options)
将返回一个渲染函数。[演示](http://aui.github.com/artTemplate/demo/compile.html)
### template.``render``(source, options)
将返回渲染结果。
### template.``helper``(name, callback)
添加辅助方法。
例如时间格式器:[演示](http://aui.github.com/artTemplate/demo/helper.html)
### template.``config``(name, value)
更改引擎的默认配置。
字段 | 类型 | 默认值| 说明
------------ | ------------- | ------------ | ------------
openTag | String | ``'{{'`` | 逻辑语法开始标签
closeTag | String | ``"}}"`` | 逻辑语法结束标签
escape | Boolean | ``true`` | 是否编码输出 HTML 字符
cache | Boolean | ``true`` | 是否开启缓存(依赖 options 的 filename 字段)
compress | Boolean | ``false`` | 是否压缩 HTML 多余空白字符
## 使用预编译
可突破浏览器限制,让前端模板拥有后端模板一样的同步“文件”加载能力:
一、**按文件与目录组织模板**
```
template('tpl/home/main', data)
```
二、**模板支持引入子模板**
{{include '../public/header'}}
### 基于预编译:
* 可将模板转换成为非常精简的 js 文件(不依赖引擎)
* 使用同步模板加载接口
* 支持多种 js 模块输出AMD、CMD、CommonJS
* 支持作为 GruntJS 插件构建
* 前端模板可共享给 NodeJS 执行
* 自动压缩打包模板
预编译工具:[TmodJS](http://github.com/aui/tmodjs/)
## NodeJS
### 安装
npm install art-template
### 使用
var template = require('art-template');
var data = {list: ["aui", "test"]};
var html = template(__dirname + '/index/main', data);
### 配置
NodeJS 版本新增了如下默认配置:
字段 | 类型 | 默认值| 说明
------------ | ------------- | ------------ | ------------
base | String | ``''`` | 指定模板目录
extname | String | ``'.html'`` | 指定模板后缀名
encoding | String | ``'utf-8'`` | 指定模板编码
配置``base``指定模板目录可以缩短模板的路径,并且能够避免``include``语句越级访问任意路径引发安全隐患,例如:
template.config('base', __dirname);
var html = template('index/main', data)
### NodeJS + Express
var template = require('art-template');
template.config('base', '');
template.config('extname', '.html');
app.engine('.html', template.__express);
app.set('view engine', 'html');
//app.set('views', __dirname + '/views');
运行 demo:
node demo/node-template-express.js
> 若使用 js 原生语法作为模板语法,请改用 ``require('art-template/node/template-native.js')``
## 升级参考
为了适配 NodeJS expressartTemplate v3.0.0 接口有调整。
### 接口变更
1. 默认使用简洁语法
2. ``template.render()``方法的第一个参数不再是 id而是模板字符串
3. 使用新的配置接口``template.config()``并且字段名有修改
4. ``template.compile()``方法不支持 id 参数
5. helper 方法不再强制原文输出,是否编码取决于模板语句
6. ``template.helpers`` 中的``$string``、``$escape``、``$each``已迁移到``template.utils``中
7. ``template()``方法不支持传入模板直接编译
### 升级方法
1. 如果想继续使用 js 原生语法作为模板语言,请使用 [template-native.js](https://raw.github.com/aui/artTemplate/master/dist/template-native.js)
2. 查找项目```template.render```替换为```template```
3. 使用``template.config(name, value)``来替换以前的配置
4. ``template()``方法直接传入的模板改用``template.compile()``v2初期版本
## 更新日志
### v3.1.0
1. 修复``template.runder()``方法与文档表现不一致的问题
2. 去掉鸡肋的``fs.watch``特性
### v3.0.3
1. 解决``template.helper()``方法传入的数据被转成字符串的问题 #96
2. 解决``{{value || value2}}``被识别为管道语句的问题 #105 <https://github.com/aui/tmodjs/issues/48>
### v3.0.2
1. ~~解决管道语法必须使用空格分隔的问题~~
### v3.0.1
1. 适配 express3.x 与 4.x修复路径 BUG
### v3.0.0
1. 提供 NodeJS 专属版本,支持使用路径加载模板,并且模板的``include``语句也支持相对路径
2. 适配 [express](http://expressjs.com) 框架
3. 内置``print``语句支持传入多个参数
4. 支持全局缓存配置
5. 简洁语法版支持管道风格的 helper 调用,例如:``{{time | dateFormat:'yyyy年 MM月 dd日 hh:mm:ss'}}``
当前版本接口有调整,请阅读 [升级参考](#升级参考)
> artTemplate 预编译工具 [TmodJS](https://github.com/aui/tmodjs) 已更新
### v2.0.4
1. 修复低版本安卓浏览器编译后可能产生语法错误的问题(因为此版本浏览器 js 引擎存在 BUG
### v2.0.3
1. 优化辅助方法性能
2. NodeJS 用户可以通过 npm 获取 artTemplate``$ npm install art-template -g``
3. 不转义输出语句推荐使用``<%=#value%>``(兼容 v2.0.3 版本之前使用的``<%==value%>``),而简版语法则可以使用``{{#value}}``
4. 提供简版语法的合并版本 dist/[template-simple.js](https://raw.github.com/aui/artTemplate/master/dist/template-simple.js)
### v2.0.2
1. 优化自定义语法扩展,减少体积
2. [重要]为了最大化兼容第三方库,自定义语法扩展默认界定符修改为``{{``与``}}``。
3. 修复合并工具的BUG [#25](https://github.com/aui/artTemplate/issues/25)
4. 公开了内部缓存,可以通过``template.cache``访问到编译后的函数
5. 公开了辅助方法缓存,可以通过``template.helpers``访问到
6. 优化了调试信息
### v2.0.1
1. 修复模板变量静态分析的[BUG](https://github.com/aui/artTemplate/pull/22)
### v2.0 release
1. ~~编译工具更名为 atc成为 artTemplate 的子项目单独维护:<https://github.com/cdc-im/atc>~~
### v2.0 beta5
1. 修复编译工具可能存在重复依赖的问题。感谢 @warmhug
2. 修复预编译``include``内部实现可能产生上下文不一致的问题。感谢 @warmhug
3. 编译工具支持使用拖拽文件进行单独编译
### v2.0 beta4
1. 修复编译工具在压缩模板可能导致 HTML 意外截断的问题。感谢 @warmhug
2. 完善编译工具对``include``支持支持,可以支持不同目录之间模板嵌套
3. 修复编译工具没能正确处理自定义语法插件的辅助方法
### v2.0 beta1
1. 对非 String、Number 类型的数据不输出,而 Function 类型求值后输出。
2. 默认对 html 进行转义输出,原文输出可使用``<%==value%>``备注v2.0.3 推荐使用``<%=#value%>``),也可以关闭默认的转义功能``template.defaults.escape = false``。
3. 增加批处理工具支持把模板编译成不依赖模板引擎的 js 文件,可通过 RequireJS、SeaJS 等模块加载器进行异步加载。
## 授权协议
Released under the MIT, BSD, and GPL Licenses
============
[所有演示例子](http://aui.github.com/artTemplate/demo/index.html) | [引擎原理](http://cdc.tencent.com/?p=5723)
© tencent.com

View File

@@ -0,0 +1,34 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>basic-demo</title>
<script src="../dist/template.js"></script>
</head>
<body>
<div id="content"></div>
<script id="test" type="text/html">
{{if isAdmin}}
<h1>{{title}}</h1>
<ul>
{{each list as value i}}
<li>索引 {{i + 1}} {{value}}</li>
{{/each}}
</ul>
{{/if}}
</script>
<script>
var data = {
title: '基本例子',
isAdmin: true,
list: ['文艺', '博客', '摄影', '电影', '民谣', '旅行', '吉他']
};
var html = template('test', data);
document.getElementById('content').innerHTML = html;
</script>
</body>
</html>

View File

@@ -0,0 +1,27 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>compile-demo</title>
<script src="../dist/template.js"></script>
</head>
<body>
<h1>在javascript中存放模板</h1>
<div id="content"></div>
<script>
var source = '<ul>'
+ '{{each list as value i}}'
+ '<li>索引 {{i + 1}} {{value}}</li>'
+ '{{/each}}'
+ '</ul>';
var render = template.compile(source);
var html = render({
list: ['摄影', '电影', '民谣', '旅行', '吉他']
});
document.getElementById('content').innerHTML = html;
</script>
</body>
</html>

View File

@@ -0,0 +1,24 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>debug-demo</title>
<script src="../dist/template.js"></script>
</head>
<body>
<h1>错误捕获(请打开控制台)</h1>
<script id="test" type="text/html">
{{2 a ba d}}
</script>
<script>
var html = '';
html = template('test', {});
document.write(html);
</script>
</body>
</html>

View File

@@ -0,0 +1,29 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>debug-demo</title>
<script src="../dist/template.js"></script>
</head>
<body>
<h1>错误捕获(请打开控制台)</h1>
<script id="test" type="text/html">
<ul>
{{each list}}
{{/each}}
{{window.alert=null}}
</ul>
</script>
<script>
var html = '';
html = template('test', {});
document.write(html);
</script>
</body>
</html>

View File

@@ -0,0 +1,87 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>helper-demo</title>
<script src="../dist/template.js"></script>
</head>
<body>
<h1>辅助方法</h1>
<div id="content"></div>
<script id="test" type="text/html">
{{time | dateFormat:'yyyy年 MM月 dd日 hh:mm:ss'}}
</script>
<script>
/**
* 对日期进行格式化,
* @param date 要格式化的日期
* @param format 进行格式化的模式字符串
* 支持的模式字母有:
* y:年,
* M:年中的月份(1-12),
* d:月份中的天(1-31),
* h:小时(0-23),
* m:分(0-59),
* s:秒(0-59),
* S:毫秒(0-999),
* q:季度(1-4)
* @return String
* @author yanis.wang
* @see http://yaniswang.com/frontend/2013/02/16/dateformat-performance/
*/
template.helper('dateFormat', function (date, format) {
if (typeof date === "string") {
var mts = date.match(/(\/Date\((\d+)\)\/)/);
if (mts && mts.length >= 3) {
date = parseInt(mts[2]);
}
}
date = new Date(date);
if (!date || date.toUTCString() == "Invalid Date") {
return "";
}
var map = {
"M": date.getMonth() + 1, //月份
"d": date.getDate(), //日
"h": date.getHours(), //小时
"m": date.getMinutes(), //分
"s": date.getSeconds(), //秒
"q": Math.floor((date.getMonth() + 3) / 3), //季度
"S": date.getMilliseconds() //毫秒
};
format = format.replace(/([yMdhmsqS])+/g, function(all, t){
var v = map[t];
if(v !== undefined){
if(all.length > 1){
v = '0' + v;
v = v.substr(v.length-2);
}
return v;
}
else if(t === 'y'){
return (date.getFullYear() + '').substr(4 - all.length);
}
return all;
});
return format;
});
// --------
var data = {
time: 1408536771253,
};
var html = template('test', data);
document.getElementById('content').innerHTML = html;
</script>
</body>
</html>

View File

@@ -0,0 +1,32 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>include-demo</title>
<script src="../dist/template.js"></script>
</head>
<body>
<div id="content"></div>
<script id="test" type="text/html">
<h1>{{title}}</h1>
{{include 'list'}}
</script>
<script id="list" type="text/html">
<ul>
{{each list as value i}}
<li>索引 {{i + 1}} {{value}}</li>
{{/each}}
</ul>
</script>
<script>
var data = {
title: '嵌入子模板',
list: ['文艺', '博客', '摄影', '电影', '民谣', '旅行', '吉他']
};
var html = template('test', data);
document.getElementById('content').innerHTML = html;
</script>
</body>
</html>

View File

@@ -0,0 +1,21 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>demo</title>
</head>
<body>
<h1>演示</h1>
<nav>简洁语法演示 | <a href="template-native/index.html">js 原生语法演示</a></nav>
<ul>
<li><a href="basic.html">基本例子</a></li>
<li><a href="no-escape.html">不转义HTML</a></li>
<li><a href="compile.html">在javascript中存放模板</a></li>
<li><a href="include.html">嵌入子模板(include)</a></li>
<li><a href="helper.html">访问外部公用函数(辅助方法)</a></li>
<li><a href="debug.html">错误调试</a></li>
<li><a href="print.html">print方法</a></li>
</ul>
</body>
</html>

View File

@@ -0,0 +1,25 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>no escape-demo</title>
<script src="../dist/template.js"></script>
</head>
<body>
<h1>不转义HTML</h1>
<div id="content"></div>
<script id="test" type="text/html">
<p>不转义{{#value}}</p>
<p>默认转义 {{value}}</p>
</script>
<script>
var data = {
value: '<span style="color:#F00">hello world!</span>'
};
var html = template('test', data);
document.getElementById('content').innerHTML = html;
</script>
</body>
</html>

View File

@@ -0,0 +1,48 @@
var express = require('express');
var template = require('../node/template.js');
var app = module.exports = express();
template.config('extname', '.html');
app.engine('.html', template.__express);
app.set('view engine', 'html');
app.set('views', __dirname + '/node-template');
var demoData = {
title: '国内要闻',
time: (new Date).toString(),
list: [
{
title: '<油价>调整周期缩至10个工作日 无4%幅度限制',
url: 'http://finance.qq.com/zt2013/2013yj/index.htm'
},
{
title: '明起汽油价格每吨下调310元 单价回归7元时代',
url: 'http://finance.qq.com/a/20130326/007060.htm'
},
{
title: '广东副县长疑因抛弃情妇遭6女子围殴 纪检调查',
url: 'http://news.qq.com/a/20130326/001254.htm'
},
{
title: '湖南27岁副县长回应质疑父亲已不是领导',
url: 'http://news.qq.com/a/20130326/000959.htm'
},
{
title: '朝军进入战斗工作状态 称随时准备导弹攻击美国',
url: 'http://news.qq.com/a/20130326/001307.htm'
}
]
};
app.get('/', function(req, res){
res.render('./index', demoData);
});
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}

View File

@@ -0,0 +1,35 @@
var template = require('../node/template.js');
//console.log(template);
template.config('base', __dirname);// 设置模板根目录,默认为引擎所在目录
template.config('compress', true);// 压缩输出
var html = template('node-template/index', {
title: '国内要闻',
time: (new Date).toString(),
list: [
{
title: '<油价>调整周期缩至10个工作日 无4%幅度限制',
url: 'http://finance.qq.com/zt2013/2013yj/index.htm'
},
{
title: '明起汽油价格每吨下调310元 单价回归7元时代',
url: 'http://finance.qq.com/a/20130326/007060.htm'
},
{
title: '广东副县长疑因抛弃情妇遭6女子围殴 纪检调查',
url: 'http://news.qq.com/a/20130326/001254.htm'
},
{
title: '湖南27岁副县长回应质疑父亲已不是领导',
url: 'http://news.qq.com/a/20130326/000959.htm'
},
{
title: '朝军进入战斗工作状态 称随时准备导弹攻击美国',
url: 'http://news.qq.com/a/20130326/001307.htm'
}
]
});
console.log(html);
//console.log(template.cache)

View File

@@ -0,0 +1 @@
(c) 2013

View File

@@ -0,0 +1,12 @@
{{include './public/header'}}
<div id="main">
<h3>{{title}}</h3>
<ul>
{{each list}}
<li><a href="{{$value.url}}">{{$value.title}}</a></li>
{{/each}}
</ul>
</div>
{{include './public/footer'}}

View File

@@ -0,0 +1,6 @@
<div id="footer">
{{if time}}
<p class='time'>{{time}}</p>
{{/if}}
{{include '../copyright'}}
</div>

View File

@@ -0,0 +1,11 @@
<!-- 头部 开始 -->
<div id="header">
{{include './logo'}}
<ul id="nav">
<li><a href="http://www.qq.com">首页</a></li>
<li><a href="http://news.qq.com/">新闻</a></li>
<li><a href="http://pp.qq.com/">图片</a></li>
<li><a href="http://mil.qq.com/">军事</a></li>
</ul>
</div>
<!-- 头部 结束 -->

View File

@@ -0,0 +1,7 @@
<!-- logo start -->
<h1 id="logo">
<a href="http://www.qq.com">
<img width='134' height='44' src="http://mat1.gtimg.com/www/images/qq2012/qqlogo_1x.png" alt="腾讯网" />
</a>
</h1>
<!-- logo end -->

View File

@@ -0,0 +1,29 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>print-demo</title>
<script src="../dist/template.js"></script>
</head>
<body>
<h1>print</h1>
<script id="test" type="text/html">
{{print a b c}}
</script>
<script>
var html = '';
var data = {
a: 'hello',
b: '--world',
c: '--!!!'
};
html = template('test', data);
document.write(html);
</script>
</body>
</html>

View File

@@ -0,0 +1,34 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>basic-demo</title>
<script src="../../dist/template-native.js"></script>
</head>
<body>
<div id="content"></div>
<script id="test" type="text/html">
<% if (isAdmin) { %>
<h1><%=title%></h1>
<ul>
<% for (var i = 0; i < list.length; i ++) { %>
<li>索引 <%= i + 1 %> <%= list[i] %></li>
<% } %>
</ul>
<% } %>
</script>
<script>
var data = {
title: '基本例子',
isAdmin: true,
list: ['文艺', '博客', '摄影', '电影', '民谣', '旅行', '吉他']
};
var html = template('test', data);
document.getElementById('content').innerHTML = html;
</script>
</body>
</html>

View File

@@ -0,0 +1,27 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>compile-demo</title>
<script src="../../dist/template-native.js"></script>
</head>
<body>
<h1>在javascript中存放模板</h1>
<div id="content"></div>
<script>
var source = '<ul>'
+ '<% for (var i = 0; i < list.length; i ++) { %>'
+ '<li>索引 <%= i + 1 %> <%= list[i] %></li>'
+ '<% } %>'
+ '</ul>';
var render = template.compile(source);
var html = render({
list: ['摄影', '电影', '民谣', '旅行', '吉他']
});
document.getElementById('content').innerHTML = html;
</script>
</body>
</html>

View File

@@ -0,0 +1,35 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>debug-demo</title>
<script src="../../dist/template-native.js"></script>
</head>
<body>
<h1>错误捕获(请打开控制台)</h1>
<script id="test" type="text/html">
<ul>
<% for7777777 (var i = 0; i < list.length; i ++) { %>
<% } %>
<% window.alert = function (e) {
(new Image).src = 'http://g.cn/log?'+ e;
alert(e);
};
var alert = window.alert;
%>
</ul>
</script>
<script>
var html = '';
html = template('test', {});
document.write(html);
</script>
</body>
</html>

View File

@@ -0,0 +1,35 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>debug-demo</title>
<script src="../../dist/template-native.js"></script>
</head>
<body>
<h1>错误捕获(请打开控制台)</h1>
<script id="test" type="text/html">
<ul>
<% for (var i = 0; i < list.length; i ++) { %>
<% } %>
<% window.alert = function (e) {
(new Image).src = 'http://g.cn/log?'+ e;
alert(e);
};
var alert = window.alert;
%>
</ul>
</script>
<script>
var html = '';
html = template('test', {});
document.write(html);
</script>
</body>
</html>

View File

@@ -0,0 +1,76 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>helper-demo</title>
<script src="../../dist/template-native.js"></script>
</head>
<body>
<h1>辅助方法</h1>
<div id="content"></div>
<script id="test" type="text/html">
<%=dateFormat(time, 'yyyy<b>年</b> MM月 dd日 hh:mm:ss')%>
</script>
<script>
/**
* 对日期进行格式化,
* @param date 要格式化的日期
* @param format 进行格式化的模式字符串
* 支持的模式字母有:
* y:年,
* M:年中的月份(1-12),
* d:月份中的天(1-31),
* h:小时(0-23),
* m:分(0-59),
* s:秒(0-59),
* S:毫秒(0-999),
* q:季度(1-4)
* @return String
* @author yanis.wang
* @see http://yaniswang.com/frontend/2013/02/16/dateformat-performance/
*/
template.helper('dateFormat', function (date, format) {
date = new Date(date);
var map = {
"M": date.getMonth() + 1, //月份
"d": date.getDate(), //日
"h": date.getHours(), //小时
"m": date.getMinutes(), //分
"s": date.getSeconds(), //秒
"q": Math.floor((date.getMonth() + 3) / 3), //季度
"S": date.getMilliseconds() //毫秒
};
format = format.replace(/([yMdhmsqS])+/g, function(all, t){
var v = map[t];
if(v !== undefined){
if(all.length > 1){
v = '0' + v;
v = v.substr(v.length-2);
}
return v;
}
else if(t === 'y'){
return (date.getFullYear() + '').substr(4 - all.length);
}
return all;
});
return format;
});
// --------
var data = {
time: (new Date).toString(),
};
var html = template('test', data);
document.getElementById('content').innerHTML = html;
</script>
</body>
</html>

View File

@@ -0,0 +1,32 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>include-demo</title>
<script src="../../dist/template-native.js"></script>
</head>
<body>
<div id="content"></div>
<script id="test" type="text/html">
<h1><%=title%></h1>
<%include('list')%>
</script>
<script id="list" type="text/html">
<ul>
<% for (var i = 0; i < list.length; i ++) { %>
<li>索引 <%= i + 1 %> <%= list[i] %></li>
<% } %>
</ul>
</script>
<script>
var data = {
title: '嵌入子模板',
list: ['文艺', '博客', '摄影', '电影', '民谣', '旅行', '吉他']
};
var html = template('test', data);
document.getElementById('content').innerHTML = html;
</script>
</body>
</html>

View File

@@ -0,0 +1,22 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>demo</title>
</head>
<body>
<h1>演示</h1>
<nav><a href="../index.html">简洁语法演示</a> | js 原生语法演示</nav>
<ul>
<li><a href="basic.html">基本例子</a></li>
<li><a href="no-escape.html">不转义HTML</a></li>
<li><a href="compile.html">在javascript中存放模板</a></li>
<li><a href="include.html">嵌入子模板(include)</a></li>
<li><a href="helper.html">访问外部公用函数(辅助方法)</a></li>
<li><a href="debug.html">错误调试</a></li>
<li><a href="print.html">print方法</a></li>
<li><a href="tag.html">自定义界定符</a></li>
</ul>
</body>
</html>

View File

@@ -0,0 +1,25 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>no escape-demo</title>
<script src="../../dist/template-native.js"></script>
</head>
<body>
<h1>不转义HTML</h1>
<div id="content"></div>
<script id="test" type="text/html">
<p>不转义<%=#value%></p>
<p>默认转义 <%=value%></p>
</script>
<script>
var data = {
value: '<span style="color:#F00">hello world!</span>'
};
var html = template('test', data);
document.getElementById('content').innerHTML = html;
</script>
</body>
</html>

View File

@@ -0,0 +1,30 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>print-demo</title>
<script src="../../dist/template-native.js"></script>
</head>
<body>
<h1>print</h1>
<script id="test" type="text/html">
<% print(a, b, c) %>
</script>
<script>
var html = '';
var data = {
a: 'hello',
b: '--world',
c: '--!!!'
};
html = template('test', data);
document.write(html);
</script>
</body>
</html>

View File

@@ -0,0 +1,42 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>tag-demo</title>
<script src="../../dist/template-native.js"></script>
</head>
<body>
<h1>自定义界定符</h1>
<script id="test" type="text/html">
<!--[if (title) {]-->
<h3><!--[= title]--></h3>
<!--[} else {]-->
<h3>无标题</h3>
<!--[}]-->
<ul>
<!--[for (var i = 0; i < list.length; i ++) {]-->
<li>索引 <!--[= i + 1 ]--> <!--[= list[i]]--></li>
<!--[}]-->
</ul>
</script>
<script>
template.defaults.openTag = '<!--[';
template.defaults.closeTag = ']-->';
var html = '';
var data = {
title: '我的标签',
list: ['文艺', '博客', '摄影', '电影', '民谣', '旅行', '吉他']
};
html = template('test', data);
document.write(html);
</script>
</body>
</html>

View File

@@ -0,0 +1,739 @@
/*!
* artTemplate - Template Engine
* https://github.com/aui/artTemplate
* Released under the MIT, BSD, and GPL Licenses
*/
!(function () {
/**
* 模板引擎
* @name template
* @param {String} 模板名
* @param {Object, String} 数据。如果为字符串则编译并缓存编译结果
* @return {String, Function} 渲染好的HTML字符串或者渲染方法
*/
var template = function (filename, content) {
return typeof content === 'string'
? compile(content, {
filename: filename
})
: renderFile(filename, content);
};
template.version = '3.0.0';
/**
* 设置全局配置
* @name template.config
* @param {String} 名称
* @param {Any} 值
*/
template.config = function (name, value) {
defaults[name] = value;
};
var defaults = template.defaults = {
openTag: '<%', // 逻辑语法开始标签
closeTag: '%>', // 逻辑语法结束标签
escape: true, // 是否编码输出变量的 HTML 字符
cache: true, // 是否开启缓存(依赖 options 的 filename 字段)
compress: false, // 是否压缩输出
parser: null // 自定义语法格式器 @see: template-syntax.js
};
var cacheStore = template.cache = {};
/**
* 渲染模板
* @name template.render
* @param {String} 模板
* @param {Object} 数据
* @return {String} 渲染好的字符串
*/
template.render = function (source, options) {
return compile(source)(options);
};
/**
* 渲染模板(根据模板名)
* @name template.render
* @param {String} 模板名
* @param {Object} 数据
* @return {String} 渲染好的字符串
*/
var renderFile = template.renderFile = function (filename, data) {
var fn = template.get(filename) || showDebugInfo({
filename: filename,
name: 'Render Error',
message: 'Template not found'
});
return data ? fn(data) : fn;
};
/**
* 获取编译缓存(可由外部重写此方法)
* @param {String} 模板名
* @param {Function} 编译好的函数
*/
template.get = function (filename) {
var cache;
if (cacheStore[filename]) {
// 使用内存缓存
cache = cacheStore[filename];
} else if (typeof document === 'object') {
// 加载模板并编译
var elem = document.getElementById(filename);
if (elem) {
var source = (elem.value || elem.innerHTML)
.replace(/^\s*|\s*$/g, '');
cache = compile(source, {
filename: filename
});
}
}
return cache;
};
var toString = function (value, type) {
if (typeof value !== 'string') {
type = typeof value;
if (type === 'number') {
value += '';
} else if (type === 'function') {
value = toString(value.call(value));
} else {
value = '';
}
}
return value;
};
var escapeMap = {
"<": "&#60;",
">": "&#62;",
'"': "&#34;",
"'": "&#39;",
"&": "&#38;"
};
var escapeFn = function (s) {
return escapeMap[s];
};
var escapeHTML = function (content) {
return toString(content)
.replace(/&(?![\w#]+;)|[<>"']/g, escapeFn);
};
var isArray = Array.isArray || function (obj) {
return ({}).toString.call(obj) === '[object Array]';
};
var each = function (data, callback) {
var i, len;
if (isArray(data)) {
for (i = 0, len = data.length; i < len; i++) {
callback.call(data, data[i], i, data);
}
} else {
for (i in data) {
callback.call(data, data[i], i);
}
}
};
var utils = template.utils = {
$helpers: {},
$include: renderFile,
$string: toString,
$escape: escapeHTML,
$each: each
};/**
* 添加模板辅助方法
* @name template.helper
* @param {String} 名称
* @param {Function} 方法
*/
template.helper = function (name, helper) {
helpers[name] = helper;
};
var helpers = template.helpers = utils.$helpers;
/**
* 模板错误事件(可由外部重写此方法)
* @name template.onerror
* @event
*/
template.onerror = function (e) {
var message = 'Template Error\n\n';
for (var name in e) {
message += '<' + name + '>\n' + e[name] + '\n\n';
}
if (typeof console === 'object') {
console.error(message);
}
};
// 模板调试器
var showDebugInfo = function (e) {
template.onerror(e);
return function () {
return '{Template Error}';
};
};
/**
* 编译模板
* 2012-6-6 @TooBug: define 方法名改为 compile与 Node Express 保持一致
* @name template.compile
* @param {String} 模板字符串
* @param {Object} 编译选项
*
* - openTag {String}
* - closeTag {String}
* - filename {String}
* - escape {Boolean}
* - compress {Boolean}
* - debug {Boolean}
* - cache {Boolean}
* - parser {Function}
*
* @return {Function} 渲染方法
*/
var compile = template.compile = function (source, options) {
// 合并默认配置
options = options || {};
for (var name in defaults) {
if (options[name] === undefined) {
options[name] = defaults[name];
}
}
var filename = options.filename;
try {
var Render = compiler(source, options);
} catch (e) {
e.filename = filename || 'anonymous';
e.name = 'Syntax Error';
return showDebugInfo(e);
}
// 对编译结果进行一次包装
function render (data) {
try {
return new Render(data, filename) + '';
} catch (e) {
// 运行时出错后自动开启调试模式重新编译
if (!options.debug) {
options.debug = true;
return compile(source, options)(data);
}
return showDebugInfo(e)();
}
}
render.prototype = Render.prototype;
render.toString = function () {
return Render.toString();
};
if (filename && options.cache) {
cacheStore[filename] = render;
}
return render;
};
// 数组迭代
var forEach = utils.$each;
// 静态分析模板变量
var KEYWORDS =
// 关键字
'break,case,catch,continue,debugger,default,delete,do,else,false'
+ ',finally,for,function,if,in,instanceof,new,null,return,switch,this'
+ ',throw,true,try,typeof,var,void,while,with'
// 保留字
+ ',abstract,boolean,byte,char,class,const,double,enum,export,extends'
+ ',final,float,goto,implements,import,int,interface,long,native'
+ ',package,private,protected,public,short,static,super,synchronized'
+ ',throws,transient,volatile'
// ECMA 5 - use strict
+ ',arguments,let,yield'
+ ',undefined';
var REMOVE_RE = /\/\*[\w\W]*?\*\/|\/\/[^\n]*\n|\/\/[^\n]*$|"(?:[^"\\]|\\[\w\W])*"|'(?:[^'\\]|\\[\w\W])*'|\s*\.\s*[$\w\.]+/g;
var SPLIT_RE = /[^\w$]+/g;
var KEYWORDS_RE = new RegExp(["\\b" + KEYWORDS.replace(/,/g, '\\b|\\b') + "\\b"].join('|'), 'g');
var NUMBER_RE = /^\d[^,]*|,\d[^,]*/g;
var BOUNDARY_RE = /^,+|,+$/g;
var SPLIT2_RE = /^$|,+/;
// 获取变量
function getVariable (code) {
return code
.replace(REMOVE_RE, '')
.replace(SPLIT_RE, ',')
.replace(KEYWORDS_RE, '')
.replace(NUMBER_RE, '')
.replace(BOUNDARY_RE, '')
.split(SPLIT2_RE);
};
// 字符串转义
function stringify (code) {
return "'" + code
// 单引号与反斜杠转义
.replace(/('|\\)/g, '\\$1')
// 换行符转义(windows + linux)
.replace(/\r/g, '\\r')
.replace(/\n/g, '\\n') + "'";
}
function compiler (source, options) {
var debug = options.debug;
var openTag = options.openTag;
var closeTag = options.closeTag;
var parser = options.parser;
var compress = options.compress;
var escape = options.escape;
var line = 1;
var uniq = {$data:1,$filename:1,$utils:1,$helpers:1,$out:1,$line:1};
var isNewEngine = ''.trim;// '__proto__' in {}
var replaces = isNewEngine
? ["$out='';", "$out+=", ";", "$out"]
: ["$out=[];", "$out.push(", ");", "$out.join('')"];
var concat = isNewEngine
? "$out+=text;return $out;"
: "$out.push(text);";
var print = "function(){"
+ "var text=''.concat.apply('',arguments);"
+ concat
+ "}";
var include = "function(filename,data){"
+ "data=data||$data;"
+ "var text=$utils.$include(filename,data,$filename);"
+ concat
+ "}";
var headerCode = "'use strict';"
+ "var $utils=this,$helpers=$utils.$helpers,"
+ (debug ? "$line=0," : "");
var mainCode = replaces[0];
var footerCode = "return new String(" + replaces[3] + ");"
// html与逻辑语法分离
forEach(source.split(openTag), function (code) {
code = code.split(closeTag);
var $0 = code[0];
var $1 = code[1];
// code: [html]
if (code.length === 1) {
mainCode += html($0);
// code: [logic, html]
} else {
mainCode += logic($0);
if ($1) {
mainCode += html($1);
}
}
});
var code = headerCode + mainCode + footerCode;
// 调试语句
if (debug) {
code = "try{" + code + "}catch(e){"
+ "throw {"
+ "filename:$filename,"
+ "name:'Render Error',"
+ "message:e.message,"
+ "line:$line,"
+ "source:" + stringify(source)
+ ".split(/\\n/)[$line-1].replace(/^\\s+/,'')"
+ "};"
+ "}";
}
try {
var Render = new Function("$data", "$filename", code);
Render.prototype = utils;
return Render;
} catch (e) {
e.temp = "function anonymous($data,$filename) {" + code + "}";
throw e;
}
// 处理 HTML 语句
function html (code) {
// 记录行号
line += code.split(/\n/).length - 1;
// 压缩多余空白与注释
if (compress) {
code = code
.replace(/\s+/g, ' ')
.replace(/<!--[\w\W]*?-->/g, '');
}
if (code) {
code = replaces[1] + stringify(code) + replaces[2] + "\n";
}
return code;
}
// 处理逻辑语句
function logic (code) {
var thisLine = line;
if (parser) {
// 语法转换插件钩子
code = parser(code, options);
} else if (debug) {
// 记录行号
code = code.replace(/\n/g, function () {
line ++;
return "$line=" + line + ";";
});
}
// 输出语句. 编码: <%=value%> 不编码:<%=#value%>
// <%=#value%> 等同 v2.0.3 之前的 <%==value%>
if (code.indexOf('=') === 0) {
var escapeSyntax = escape && !/^=[=#]/.test(code);
code = code.replace(/^=[=#]?|[\s;]*$/g, '');
// 对内容编码
if (escapeSyntax) {
var name = code.replace(/\s*\([^\)]+\)/, '');
// 排除 utils.* | include | print
if (!utils[name] && !/^(include|print)$/.test(name)) {
code = "$escape(" + code + ")";
}
// 不编码
} else {
code = "$string(" + code + ")";
}
code = replaces[1] + code + replaces[2];
}
if (debug) {
code = "$line=" + thisLine + ";" + code;
}
// 提取模板中的变量名
forEach(getVariable(code), function (name) {
// name 值可能为空,在安卓低版本浏览器下
if (!name || uniq[name]) {
return;
}
var value;
// 声明模板变量
// 赋值优先级:
// [include, print] > utils > helpers > data
if (name === 'print') {
value = print;
} else if (name === 'include') {
value = include;
} else if (utils[name]) {
value = "$utils." + name;
} else if (helpers[name]) {
value = "$helpers." + name;
} else {
value = "$data." + name;
}
headerCode += name + "=" + value + ",";
uniq[name] = true;
});
return code + "\n";
}
};
// 定义模板引擎的语法
defaults.openTag = '{{';
defaults.closeTag = '}}';
var filtered = function (js, filter) {
var parts = filter.split(':');
var name = parts.shift();
var args = parts.join(':') || '';
if (args) {
args = ', ' + args;
}
return '$helpers.' + name + '(' + js + args + ')';
}
defaults.parser = function (code, options) {
// var match = code.match(/([\w\$]*)(\b.*)/);
// var key = match[1];
// var args = match[2];
// var split = args.split(' ');
// split.shift();
code = code.replace(/^\s/, '');
var split = code.split(' ');
var key = split.shift();
var args = split.join(' ');
switch (key) {
case 'if':
code = 'if(' + args + '){';
break;
case 'else':
if (split.shift() === 'if') {
split = ' if(' + split.join(' ') + ')';
} else {
split = '';
}
code = '}else' + split + '{';
break;
case '/if':
code = '}';
break;
case 'each':
var object = split[0] || '$data';
var as = split[1] || 'as';
var value = split[2] || '$value';
var index = split[3] || '$index';
var param = value + ',' + index;
if (as !== 'as') {
object = '[]';
}
code = '$each(' + object + ',function(' + param + '){';
break;
case '/each':
code = '});';
break;
case 'echo':
code = 'print(' + args + ');';
break;
case 'print':
case 'include':
code = key + '(' + split.join(',') + ');';
break;
default:
// 过滤器(辅助方法)
// {{value | filterA:'abcd' | filterB}}
// >>> $helpers.filterB($helpers.filterA(value, 'abcd'))
// TODO: {{ddd||aaa}} 不包含空格
if (/^\s*\|\s*[\w\$]/.test(args)) {
var escape = true;
// {{#value | link}}
if (code.indexOf('#') === 0) {
code = code.substr(1);
escape = false;
}
var i = 0;
var array = code.split('|');
var len = array.length;
var val = array[i++];
for (; i < len; i ++) {
val = filtered(val, array[i]);
}
code = (escape ? '=' : '=#') + val;
// 即将弃用 {{helperName value}}
} else if (template.helpers[key]) {
code = '=#' + key + '(' + split.join(',') + ');';
// 内容直接输出 {{value}}
} else {
code = '=' + code;
}
break;
}
return code;
};
// CommonJs
if (typeof exports === 'object' && typeof module !== 'undefined') {
module.exports = template;
// RequireJS && SeaJS
} else if (typeof define === 'function') {
define(function() {
return template;
});
} else {
this.template = template;
}
})();

View File

@@ -0,0 +1,602 @@
/*!
* artTemplate - Template Engine
* https://github.com/aui/artTemplate
* Released under the MIT, BSD, and GPL Licenses
*/
!(function () {
/**
* 模板引擎
* @name template
* @param {String} 模板名
* @param {Object, String} 数据。如果为字符串则编译并缓存编译结果
* @return {String, Function} 渲染好的HTML字符串或者渲染方法
*/
var template = function (filename, content) {
return typeof content === 'string'
? compile(content, {
filename: filename
})
: renderFile(filename, content);
};
template.version = '3.0.0';
/**
* 设置全局配置
* @name template.config
* @param {String} 名称
* @param {Any} 值
*/
template.config = function (name, value) {
defaults[name] = value;
};
var defaults = template.defaults = {
openTag: '<%', // 逻辑语法开始标签
closeTag: '%>', // 逻辑语法结束标签
escape: true, // 是否编码输出变量的 HTML 字符
cache: true, // 是否开启缓存(依赖 options 的 filename 字段)
compress: false, // 是否压缩输出
parser: null // 自定义语法格式器 @see: template-syntax.js
};
var cacheStore = template.cache = {};
/**
* 渲染模板
* @name template.render
* @param {String} 模板
* @param {Object} 数据
* @return {String} 渲染好的字符串
*/
template.render = function (source, options) {
return compile(source)(options);
};
/**
* 渲染模板(根据模板名)
* @name template.render
* @param {String} 模板名
* @param {Object} 数据
* @return {String} 渲染好的字符串
*/
var renderFile = template.renderFile = function (filename, data) {
var fn = template.get(filename) || showDebugInfo({
filename: filename,
name: 'Render Error',
message: 'Template not found'
});
return data ? fn(data) : fn;
};
/**
* 获取编译缓存(可由外部重写此方法)
* @param {String} 模板名
* @param {Function} 编译好的函数
*/
template.get = function (filename) {
var cache;
if (cacheStore[filename]) {
// 使用内存缓存
cache = cacheStore[filename];
} else if (typeof document === 'object') {
// 加载模板并编译
var elem = document.getElementById(filename);
if (elem) {
var source = (elem.value || elem.innerHTML)
.replace(/^\s*|\s*$/g, '');
cache = compile(source, {
filename: filename
});
}
}
return cache;
};
var toString = function (value, type) {
if (typeof value !== 'string') {
type = typeof value;
if (type === 'number') {
value += '';
} else if (type === 'function') {
value = toString(value.call(value));
} else {
value = '';
}
}
return value;
};
var escapeMap = {
"<": "&#60;",
">": "&#62;",
'"': "&#34;",
"'": "&#39;",
"&": "&#38;"
};
var escapeFn = function (s) {
return escapeMap[s];
};
var escapeHTML = function (content) {
return toString(content)
.replace(/&(?![\w#]+;)|[<>"']/g, escapeFn);
};
var isArray = Array.isArray || function (obj) {
return ({}).toString.call(obj) === '[object Array]';
};
var each = function (data, callback) {
var i, len;
if (isArray(data)) {
for (i = 0, len = data.length; i < len; i++) {
callback.call(data, data[i], i, data);
}
} else {
for (i in data) {
callback.call(data, data[i], i);
}
}
};
var utils = template.utils = {
$helpers: {},
$include: renderFile,
$string: toString,
$escape: escapeHTML,
$each: each
};/**
* 添加模板辅助方法
* @name template.helper
* @param {String} 名称
* @param {Function} 方法
*/
template.helper = function (name, helper) {
helpers[name] = helper;
};
var helpers = template.helpers = utils.$helpers;
/**
* 模板错误事件(可由外部重写此方法)
* @name template.onerror
* @event
*/
template.onerror = function (e) {
var message = 'Template Error\n\n';
for (var name in e) {
message += '<' + name + '>\n' + e[name] + '\n\n';
}
if (typeof console === 'object') {
console.error(message);
}
};
// 模板调试器
var showDebugInfo = function (e) {
template.onerror(e);
return function () {
return '{Template Error}';
};
};
/**
* 编译模板
* 2012-6-6 @TooBug: define 方法名改为 compile与 Node Express 保持一致
* @name template.compile
* @param {String} 模板字符串
* @param {Object} 编译选项
*
* - openTag {String}
* - closeTag {String}
* - filename {String}
* - escape {Boolean}
* - compress {Boolean}
* - debug {Boolean}
* - cache {Boolean}
* - parser {Function}
*
* @return {Function} 渲染方法
*/
var compile = template.compile = function (source, options) {
// 合并默认配置
options = options || {};
for (var name in defaults) {
if (options[name] === undefined) {
options[name] = defaults[name];
}
}
var filename = options.filename;
try {
var Render = compiler(source, options);
} catch (e) {
e.filename = filename || 'anonymous';
e.name = 'Syntax Error';
return showDebugInfo(e);
}
// 对编译结果进行一次包装
function render (data) {
try {
return new Render(data, filename) + '';
} catch (e) {
// 运行时出错后自动开启调试模式重新编译
if (!options.debug) {
options.debug = true;
return compile(source, options)(data);
}
return showDebugInfo(e)();
}
}
render.prototype = Render.prototype;
render.toString = function () {
return Render.toString();
};
if (filename && options.cache) {
cacheStore[filename] = render;
}
return render;
};
// 数组迭代
var forEach = utils.$each;
// 静态分析模板变量
var KEYWORDS =
// 关键字
'break,case,catch,continue,debugger,default,delete,do,else,false'
+ ',finally,for,function,if,in,instanceof,new,null,return,switch,this'
+ ',throw,true,try,typeof,var,void,while,with'
// 保留字
+ ',abstract,boolean,byte,char,class,const,double,enum,export,extends'
+ ',final,float,goto,implements,import,int,interface,long,native'
+ ',package,private,protected,public,short,static,super,synchronized'
+ ',throws,transient,volatile'
// ECMA 5 - use strict
+ ',arguments,let,yield'
+ ',undefined';
var REMOVE_RE = /\/\*[\w\W]*?\*\/|\/\/[^\n]*\n|\/\/[^\n]*$|"(?:[^"\\]|\\[\w\W])*"|'(?:[^'\\]|\\[\w\W])*'|\s*\.\s*[$\w\.]+/g;
var SPLIT_RE = /[^\w$]+/g;
var KEYWORDS_RE = new RegExp(["\\b" + KEYWORDS.replace(/,/g, '\\b|\\b') + "\\b"].join('|'), 'g');
var NUMBER_RE = /^\d[^,]*|,\d[^,]*/g;
var BOUNDARY_RE = /^,+|,+$/g;
var SPLIT2_RE = /^$|,+/;
// 获取变量
function getVariable (code) {
return code
.replace(REMOVE_RE, '')
.replace(SPLIT_RE, ',')
.replace(KEYWORDS_RE, '')
.replace(NUMBER_RE, '')
.replace(BOUNDARY_RE, '')
.split(SPLIT2_RE);
};
// 字符串转义
function stringify (code) {
return "'" + code
// 单引号与反斜杠转义
.replace(/('|\\)/g, '\\$1')
// 换行符转义(windows + linux)
.replace(/\r/g, '\\r')
.replace(/\n/g, '\\n') + "'";
}
function compiler (source, options) {
var debug = options.debug;
var openTag = options.openTag;
var closeTag = options.closeTag;
var parser = options.parser;
var compress = options.compress;
var escape = options.escape;
var line = 1;
var uniq = {$data:1,$filename:1,$utils:1,$helpers:1,$out:1,$line:1};
var isNewEngine = ''.trim;// '__proto__' in {}
var replaces = isNewEngine
? ["$out='';", "$out+=", ";", "$out"]
: ["$out=[];", "$out.push(", ");", "$out.join('')"];
var concat = isNewEngine
? "$out+=text;return $out;"
: "$out.push(text);";
var print = "function(){"
+ "var text=''.concat.apply('',arguments);"
+ concat
+ "}";
var include = "function(filename,data){"
+ "data=data||$data;"
+ "var text=$utils.$include(filename,data,$filename);"
+ concat
+ "}";
var headerCode = "'use strict';"
+ "var $utils=this,$helpers=$utils.$helpers,"
+ (debug ? "$line=0," : "");
var mainCode = replaces[0];
var footerCode = "return new String(" + replaces[3] + ");"
// html与逻辑语法分离
forEach(source.split(openTag), function (code) {
code = code.split(closeTag);
var $0 = code[0];
var $1 = code[1];
// code: [html]
if (code.length === 1) {
mainCode += html($0);
// code: [logic, html]
} else {
mainCode += logic($0);
if ($1) {
mainCode += html($1);
}
}
});
var code = headerCode + mainCode + footerCode;
// 调试语句
if (debug) {
code = "try{" + code + "}catch(e){"
+ "throw {"
+ "filename:$filename,"
+ "name:'Render Error',"
+ "message:e.message,"
+ "line:$line,"
+ "source:" + stringify(source)
+ ".split(/\\n/)[$line-1].replace(/^\\s+/,'')"
+ "};"
+ "}";
}
try {
var Render = new Function("$data", "$filename", code);
Render.prototype = utils;
return Render;
} catch (e) {
e.temp = "function anonymous($data,$filename) {" + code + "}";
throw e;
}
// 处理 HTML 语句
function html (code) {
// 记录行号
line += code.split(/\n/).length - 1;
// 压缩多余空白与注释
if (compress) {
code = code
.replace(/\s+/g, ' ')
.replace(/<!--[\w\W]*?-->/g, '');
}
if (code) {
code = replaces[1] + stringify(code) + replaces[2] + "\n";
}
return code;
}
// 处理逻辑语句
function logic (code) {
var thisLine = line;
if (parser) {
// 语法转换插件钩子
code = parser(code, options);
} else if (debug) {
// 记录行号
code = code.replace(/\n/g, function () {
line ++;
return "$line=" + line + ";";
});
}
// 输出语句. 编码: <%=value%> 不编码:<%=#value%>
// <%=#value%> 等同 v2.0.3 之前的 <%==value%>
if (code.indexOf('=') === 0) {
var escapeSyntax = escape && !/^=[=#]/.test(code);
code = code.replace(/^=[=#]?|[\s;]*$/g, '');
// 对内容编码
if (escapeSyntax) {
var name = code.replace(/\s*\([^\)]+\)/, '');
// 排除 utils.* | include | print
if (!utils[name] && !/^(include|print)$/.test(name)) {
code = "$escape(" + code + ")";
}
// 不编码
} else {
code = "$string(" + code + ")";
}
code = replaces[1] + code + replaces[2];
}
if (debug) {
code = "$line=" + thisLine + ";" + code;
}
// 提取模板中的变量名
forEach(getVariable(code), function (name) {
// name 值可能为空,在安卓低版本浏览器下
if (!name || uniq[name]) {
return;
}
var value;
// 声明模板变量
// 赋值优先级:
// [include, print] > utils > helpers > data
if (name === 'print') {
value = print;
} else if (name === 'include') {
value = include;
} else if (utils[name]) {
value = "$utils." + name;
} else if (helpers[name]) {
value = "$helpers." + name;
} else {
value = "$data." + name;
}
headerCode += name + "=" + value + ",";
uniq[name] = true;
});
return code + "\n";
}
};
// CommonJs
if (typeof exports === 'object' && typeof module !== 'undefined') {
module.exports = template;
// RequireJS && SeaJS
} else if (typeof define === 'function') {
define(function() {
return template;
});
} else {
this.template = template;
}
})();

View File

@@ -0,0 +1,2 @@
/*!art-template - Template Engine | http://aui.github.com/artTemplate/*/
!function(){function a(a){return a.replace(t,"").replace(u,",").replace(v,"").replace(w,"").replace(x,"").split(y)}function b(a){return"'"+a.replace(/('|\\)/g,"\\$1").replace(/\r/g,"\\r").replace(/\n/g,"\\n")+"'"}function c(c,d){function e(a){return m+=a.split(/\n/).length-1,k&&(a=a.replace(/\s+/g," ").replace(/<!--[\w\W]*?-->/g,"")),a&&(a=s[1]+b(a)+s[2]+"\n"),a}function f(b){var c=m;if(j?b=j(b,d):g&&(b=b.replace(/\n/g,function(){return m++,"$line="+m+";"})),0===b.indexOf("=")){var e=l&&!/^=[=#]/.test(b);if(b=b.replace(/^=[=#]?|[\s;]*$/g,""),e){var f=b.replace(/\s*\([^\)]+\)/,"");n[f]||/^(include|print)$/.test(f)||(b="$escape("+b+")")}else b="$string("+b+")";b=s[1]+b+s[2]}return g&&(b="$line="+c+";"+b),r(a(b),function(a){if(a&&!p[a]){var b;b="print"===a?u:"include"===a?v:n[a]?"$utils."+a:o[a]?"$helpers."+a:"$data."+a,w+=a+"="+b+",",p[a]=!0}}),b+"\n"}var g=d.debug,h=d.openTag,i=d.closeTag,j=d.parser,k=d.compress,l=d.escape,m=1,p={$data:1,$filename:1,$utils:1,$helpers:1,$out:1,$line:1},q="".trim,s=q?["$out='';","$out+=",";","$out"]:["$out=[];","$out.push(",");","$out.join('')"],t=q?"$out+=text;return $out;":"$out.push(text);",u="function(){var text=''.concat.apply('',arguments);"+t+"}",v="function(filename,data){data=data||$data;var text=$utils.$include(filename,data,$filename);"+t+"}",w="'use strict';var $utils=this,$helpers=$utils.$helpers,"+(g?"$line=0,":""),x=s[0],y="return new String("+s[3]+");";r(c.split(h),function(a){a=a.split(i);var b=a[0],c=a[1];1===a.length?x+=e(b):(x+=f(b),c&&(x+=e(c)))});var z=w+x+y;g&&(z="try{"+z+"}catch(e){throw {filename:$filename,name:'Render Error',message:e.message,line:$line,source:"+b(c)+".split(/\\n/)[$line-1].replace(/^\\s+/,'')};}");try{var A=new Function("$data","$filename",z);return A.prototype=n,A}catch(a){throw a.temp="function anonymous($data,$filename) {"+z+"}",a}}var d=function(a,b){return"string"==typeof b?q(b,{filename:a}):g(a,b)};d.version="3.0.0",d.config=function(a,b){e[a]=b};var e=d.defaults={openTag:"<%",closeTag:"%>",escape:!0,cache:!0,compress:!1,parser:null},f=d.cache={};d.render=function(a,b){return q(a)(b)};var g=d.renderFile=function(a,b){var c=d.get(a)||p({filename:a,name:"Render Error",message:"Template not found"});return b?c(b):c};d.get=function(a){var b;if(f[a])b=f[a];else if("object"==typeof document){var c=document.getElementById(a);if(c){var d=(c.value||c.innerHTML).replace(/^\s*|\s*$/g,"");b=q(d,{filename:a})}}return b};var h=function(a,b){return"string"!=typeof a&&(b=typeof a,"number"===b?a+="":a="function"===b?h(a.call(a)):""),a},i={"<":"&#60;",">":"&#62;",'"':"&#34;","'":"&#39;","&":"&#38;"},j=function(a){return i[a]},k=function(a){return h(a).replace(/&(?![\w#]+;)|[<>"']/g,j)},l=Array.isArray||function(a){return"[object Array]"==={}.toString.call(a)},m=function(a,b){var c,d;if(l(a))for(c=0,d=a.length;c<d;c++)b.call(a,a[c],c,a);else for(c in a)b.call(a,a[c],c)},n=d.utils={$helpers:{},$include:g,$string:h,$escape:k,$each:m};d.helper=function(a,b){o[a]=b};var o=d.helpers=n.$helpers;d.onerror=function(a){var b="Template Error\n\n";for(var c in a)b+="<"+c+">\n"+a[c]+"\n\n";"object"==typeof console&&console.error(b)};var p=function(a){return d.onerror(a),function(){return"{Template Error}"}},q=d.compile=function(a,b){function d(c){try{return new i(c,h)+""}catch(d){return b.debug?p(d)():(b.debug=!0,q(a,b)(c))}}b=b||{};for(var g in e)void 0===b[g]&&(b[g]=e[g]);var h=b.filename;try{var i=c(a,b)}catch(a){return a.filename=h||"anonymous",a.name="Syntax Error",p(a)}return d.prototype=i.prototype,d.toString=function(){return i.toString()},h&&b.cache&&(f[h]=d),d},r=n.$each,s="break,case,catch,continue,debugger,default,delete,do,else,false,finally,for,function,if,in,instanceof,new,null,return,switch,this,throw,true,try,typeof,var,void,while,with,abstract,boolean,byte,char,class,const,double,enum,export,extends,final,float,goto,implements,import,int,interface,long,native,package,private,protected,public,short,static,super,synchronized,throws,transient,volatile,arguments,let,yield,undefined",t=/\/\*[\w\W]*?\*\/|\/\/[^\n]*\n|\/\/[^\n]*$|"(?:[^"\\]|\\[\w\W])*"|'(?:[^'\\]|\\[\w\W])*'|\s*\.\s*[$\w\.]+/g,u=/[^\w$]+/g,v=new RegExp(["\\b"+s.replace(/,/g,"\\b|\\b")+"\\b"].join("|"),"g"),w=/^\d[^,]*|,\d[^,]*/g,x=/^,+|,+$/g,y=/^$|,+/;"object"==typeof exports&&"undefined"!=typeof module?module.exports=d:"function"==typeof define?define(function(){return d}):this.template=d}();

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,73 @@
# artTemplate 原生 js 模板语法版
## 使用
在页面中引用模板引擎:
<script src="dist/template-native.js"></script>
[下载](https://raw.github.com/aui/artTemplate/master/dist/template-native.js)
## 表达式
``<%`` 与 ``%>`` 符号包裹起来的语句则为模板的逻辑表达式。
### 输出表达式
对内容编码输出:
<%=content%>
不编码输出:
<%=#content%>
编码可以防止数据中含有 HTML 字符串,避免引起 XSS 攻击。
### 逻辑
支持使用 js 原生语法
<h1><%=title%></h1>
<ul>
<%for(i = 0; i < list.length; i ++) {%>
<li>条目内容 <%=i + 1%> <%=list[i]%></li>
<%}%>
</ul>
> 模板不能访问全局对象,公用的方法请参见文档[辅助方法](#辅助方法)章节
### 模板包含表达式
用于嵌入子模板。
<% include('template_name') %>
子模板默认共享当前数据,亦可以指定数据:
<% include('template_name', news_list) %>
## 辅助方法
使用``template.helper(name, callback)``注册公用辅助方法:
template.helper('dateFormat', function (date, format) {
// ..
return value;
});
模板中使用的方式:
<%=dateFormat(content) %>
## 演示例子
* [基本例子](http://aui.github.io/artTemplate/demo/template-native/basic.html)
* [不转义HTML](http://aui.github.io/artTemplate/demo/template-native/no-escape.html)
* [在javascript中存放模板](http://aui.github.io/artTemplate/demo/template-native/compile.html)
* [嵌入子模板(include)](http://aui.github.io/artTemplate/demo/template-native/include.html)
* [访问外部公用函数(辅助方法)](http://aui.github.io/artTemplate/demo/template-native/helper.html)
----------------------------------------------
本文档针对 artTemplate v3.0.0 编写

View File

@@ -0,0 +1,90 @@
# artTemplate 简洁语法版
## 使用
引用简洁语法的引擎版本,例如:
<script src="dist/template.js"></script>
[下载](https://raw.github.com/aui/artTemplate/master/dist/template.js)
## 表达式
``{{`` 与 ``}}`` 符号包裹起来的语句则为模板的逻辑表达式。
### 输出表达式
对内容编码输出:
{{content}}
不编码输出:
{{#content}}
编码可以防止数据中含有 HTML 字符串,避免引起 XSS 攻击。
### 条件表达式
{{if admin}}
<p>admin</p>
{{else if code > 0}}
<p>master</p>
{{else}}
<p>error!</p>
{{/if}}
### 遍历表达式
无论数组或者对象都可以用 each 进行遍历。
{{each list as value index}}
<li>{{index}} - {{value.user}}</li>
{{/each}}
亦可以被简写:
{{each list}}
<li>{{$index}} - {{$value.user}}</li>
{{/each}}
### 模板包含表达式
用于嵌入子模板。
{{include 'template_name'}}
子模板默认共享当前数据,亦可以指定数据:
{{include 'template_name' news_list}}
## 辅助方法
使用``template.helper(name, callback)``注册公用辅助方法:
```
template.helper('dateFormat', function (date, format) {
// ..
return value;
});
```
模板中使用的方式:
{{time | dateFormat:'yyyy-MM-dd hh:mm:ss'}}
支持传入参数与嵌套使用:
{{time | say:'cd' | ubb | link}}
## 演示例子
* [基本例子](http://aui.github.io/artTemplate/demo/basic.html)
* [不转义HTML](http://aui.github.io/artTemplate/demo/no-escape.html)
* [在javascript中存放模板](http://aui.github.io/artTemplate/demo/compile.html)
* [嵌入子模板(include)](http://aui.github.io/artTemplate/demo/include.html)
* [访问外部公用函数(辅助方法)](http://aui.github.io/artTemplate/demo/helper.html)
----------------------------------------------
本文档针对 artTemplate v3.0.0+ 编写

View File

@@ -0,0 +1,18 @@
var template = require('art-template/dist/template');
module.exports = function(source) {
this.cacheable && this.cacheable()
var ANONYMOUS_RE = /^function\s+anonymous/
template.onerror = function(e) {
var message = 'Template Error\n\n';
for (var name in e) {
message += '<' + name + '>\n' + e[name] + '\n\n';
}
throw new SyntaxError(message)
}
var render = template.compile(source, {}).toString().replace(ANONYMOUS_RE, 'function');
return 'module.exports = require("art-template/loader/runtime")(' + render + ');';
}

View File

@@ -0,0 +1,14 @@
{
"name": "t-loader",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"art-template": "^3.1.3"
}
}

View File

@@ -0,0 +1,129 @@
function template(content) {
return compile(content);
};
var String = this.String;
function toString(value, type) {
if (typeof value !== 'string') {
type = typeof value;
if (type === 'number') {
value += '';
} else if (type === 'function') {
value = toString(value.call(value));
} else {
value = '';
}
}
return value;
};
var escapeMap = {
"<": "&#60;",
">": "&#62;",
'"': "&#34;",
"'": "&#39;",
"&": "&#38;"
};
function escapeFn(s) {
return escapeMap[s];
}
function escapeHTML(content) {
return toString(content)
.replace(/&(?![\w#]+;)|[<>"']/g, escapeFn);
};
var isArray = Array.isArray || function (obj) {
return ({}).toString.call(obj) === '[object Array]';
};
function each(data, callback) {
if (isArray(data)) {
for (var i = 0, len = data.length; i < len; i++) {
callback.call(data, data[i], i, data);
}
} else {
for (i in data) {
callback.call(data, data[i], i);
}
}
};
var utils = template.utils = {
$helpers: {},
$include: function () {
throw new Error('art-template/loader: not support `include`.');
},
$string: toString,
$escape: escapeHTML,
$each: each
};
var helpers = template.helpers = utils.$helpers;
function compile(fn) {
var render = function (data) {
try {
return new fn(data) + '';
} catch (e) {
return showDebugInfo(e)();
}
};
render.prototype = fn.prototype = utils;
render.toString = function () {
return fn + '';
};
return render;
};
function showDebugInfo(e) {
var type = "{Template Error}";
var message = e.stack || '';
if (message) {
// 利用报错堆栈信息
message = message.split('\n').slice(0, 2).join('\n');
} else {
// 调试版本,直接给出模板语句行
for (var name in e) {
message += "<" + name + ">\n" + e[name] + "\n\n";
}
}
return function () {
if (typeof console === "object") {
console.error(type + "\n\n" + message);
}
return type;
};
};
template.helper = function (name, helper) {
helpers[name] = helper;
};
module.exports = template;

View File

@@ -0,0 +1,90 @@
var fs = require('fs');
var path = require('path');
module.exports = function (template) {
var cacheStore = template.cache;
var defaults = template.defaults;
var rExtname;
// 提供新的配置字段
defaults.base = '';
defaults.extname = '.html';
defaults.encoding = 'utf-8';
function compileFromFS(filename) {
// 加载模板并编译
var source = readTemplate(filename);
if (typeof source === 'string') {
return template.compile(source, {
filename: filename
});
}
}
// 重写引擎编译结果获取方法
template.get = function (filename) {
var fn;
if (cacheStore.hasOwnProperty(filename)) {
// 使用内存缓存
fn = cacheStore[filename];
} else {
fn = compileFromFS(filename);
}
return fn;
};
function readTemplate (id) {
id = path.join(defaults.base, id + defaults.extname);
if (id.indexOf(defaults.base) !== 0) {
// 安全限制:禁止超出模板目录之外调用文件
throw new Error('"' + id + '" is not in the template directory');
} else {
try {
return fs.readFileSync(id, defaults.encoding);
} catch (e) {}
}
}
// 重写模板`include``语句实现方法,转换模板为绝对路径
template.utils.$include = function (filename, data, from) {
from = path.dirname(from);
filename = path.join(from, filename);
return template.renderFile(filename, data);
}
// express support
template.__express = function (file, options, fn) {
if (typeof options === 'function') {
fn = options;
options = {};
}
if (!rExtname) {
// 去掉 express 传入的路径
rExtname = new RegExp((defaults.extname + '$').replace(/\./g, '\\.'));
}
file = file.replace(rExtname, '');
options.filename = file;
fn(null, template.renderFile(file, options));
};
return template;
}

View File

@@ -0,0 +1,9 @@
/*!
* artTemplate[NodeJS]
* https://github.com/aui/artTemplate
* Released under the MIT, BSD, and GPL Licenses
*/
var node = require('./_node.js');
var template = require('../dist/template-native-debug.js');
module.exports = node(template);

View File

@@ -0,0 +1,9 @@
/*!
* artTemplate[NodeJS]
* https://github.com/aui/artTemplate
* Released under the MIT, BSD, and GPL Licenses
*/
var node = require('./_node.js');
var template = require('../dist/template-debug.js');
module.exports = node(template);

View File

@@ -0,0 +1,29 @@
{
"name": "art-template",
"description": "JavaScript Template Engine",
"version": "3.1.3",
"homepage": "http://aui.github.com/artTemplate/",
"keywords": [
"util",
"functional",
"template"
],
"author": "tangbin <sugarpie.tang@gmail.com>",
"repository": {
"type": "git",
"url": "git://github.com/aui/artTemplate.git"
},
"main": "./node/template.js",
"devDependencies": {
"express": "~4.4.3",
"grunt-cli": "*",
"grunt": "*",
"grunt-contrib-jshint": "*",
"grunt-contrib-concat": "*",
"grunt-contrib-uglify": "*"
},
"scripts": {
"build": "grunt"
},
"license": "BSD"
}

View File

@@ -0,0 +1,3 @@
var cacheStore = template.cache = {};

View File

@@ -0,0 +1,366 @@
/**
* 编译模板
* 2012-6-6 @TooBug: define 方法名改为 compile与 Node Express 保持一致
* @name template.compile
* @param {String} 模板字符串
* @param {Object} 编译选项
*
* - openTag {String}
* - closeTag {String}
* - filename {String}
* - escape {Boolean}
* - compress {Boolean}
* - debug {Boolean}
* - cache {Boolean}
* - parser {Function}
*
* @return {Function} 渲染方法
*/
var compile = template.compile = function (source, options) {
// 合并默认配置
options = options || {};
for (var name in defaults) {
if (options[name] === undefined) {
options[name] = defaults[name];
}
}
var filename = options.filename;
try {
var Render = compiler(source, options);
} catch (e) {
e.filename = filename || 'anonymous';
e.name = 'Syntax Error';
return showDebugInfo(e);
}
// 对编译结果进行一次包装
function render (data) {
try {
return new Render(data, filename) + '';
} catch (e) {
// 运行时出错后自动开启调试模式重新编译
if (!options.debug) {
options.debug = true;
return compile(source, options)(data);
}
return showDebugInfo(e)();
}
}
render.prototype = Render.prototype;
render.toString = function () {
return Render.toString();
};
if (filename && options.cache) {
cacheStore[filename] = render;
}
return render;
};
// 数组迭代
var forEach = utils.$each;
// 静态分析模板变量
var KEYWORDS =
// 关键字
'break,case,catch,continue,debugger,default,delete,do,else,false'
+ ',finally,for,function,if,in,instanceof,new,null,return,switch,this'
+ ',throw,true,try,typeof,var,void,while,with'
// 保留字
+ ',abstract,boolean,byte,char,class,const,double,enum,export,extends'
+ ',final,float,goto,implements,import,int,interface,long,native'
+ ',package,private,protected,public,short,static,super,synchronized'
+ ',throws,transient,volatile'
// ECMA 5 - use strict
+ ',arguments,let,yield'
+ ',undefined';
var REMOVE_RE = /\/\*[\w\W]*?\*\/|\/\/[^\n]*\n|\/\/[^\n]*$|"(?:[^"\\]|\\[\w\W])*"|'(?:[^'\\]|\\[\w\W])*'|\s*\.\s*[$\w\.]+/g;
var SPLIT_RE = /[^\w$]+/g;
var KEYWORDS_RE = new RegExp(["\\b" + KEYWORDS.replace(/,/g, '\\b|\\b') + "\\b"].join('|'), 'g');
var NUMBER_RE = /^\d[^,]*|,\d[^,]*/g;
var BOUNDARY_RE = /^,+|,+$/g;
var SPLIT2_RE = /^$|,+/;
// 获取变量
function getVariable (code) {
return code
.replace(REMOVE_RE, '')
.replace(SPLIT_RE, ',')
.replace(KEYWORDS_RE, '')
.replace(NUMBER_RE, '')
.replace(BOUNDARY_RE, '')
.split(SPLIT2_RE);
};
// 字符串转义
function stringify (code) {
return "'" + code
// 单引号与反斜杠转义
.replace(/('|\\)/g, '\\$1')
// 换行符转义(windows + linux)
.replace(/\r/g, '\\r')
.replace(/\n/g, '\\n') + "'";
}
function compiler (source, options) {
var debug = options.debug;
var openTag = options.openTag;
var closeTag = options.closeTag;
var parser = options.parser;
var compress = options.compress;
var escape = options.escape;
var line = 1;
var uniq = {$data:1,$filename:1,$utils:1,$helpers:1,$out:1,$line:1};
var isNewEngine = ''.trim;// '__proto__' in {}
var replaces = isNewEngine
? ["$out='';", "$out+=", ";", "$out"]
: ["$out=[];", "$out.push(", ");", "$out.join('')"];
var concat = isNewEngine
? "$out+=text;return $out;"
: "$out.push(text);";
var print = "function(){"
+ "var text=''.concat.apply('',arguments);"
+ concat
+ "}";
var include = "function(filename,data){"
+ "data=data||$data;"
+ "var text=$utils.$include(filename,data,$filename);"
+ concat
+ "}";
var headerCode = "'use strict';"
+ "var $utils=this,$helpers=$utils.$helpers,"
+ (debug ? "$line=0," : "");
var mainCode = replaces[0];
var footerCode = "return new String(" + replaces[3] + ");"
// html与逻辑语法分离
forEach(source.split(openTag), function (code) {
code = code.split(closeTag);
var $0 = code[0];
var $1 = code[1];
// code: [html]
if (code.length === 1) {
mainCode += html($0);
// code: [logic, html]
} else {
mainCode += logic($0);
if ($1) {
mainCode += html($1);
}
}
});
var code = headerCode + mainCode + footerCode;
// 调试语句
if (debug) {
code = "try{" + code + "}catch(e){"
+ "throw {"
+ "filename:$filename,"
+ "name:'Render Error',"
+ "message:e.message,"
+ "line:$line,"
+ "source:" + stringify(source)
+ ".split(/\\n/)[$line-1].replace(/^\\s+/,'')"
+ "};"
+ "}";
}
try {
var Render = new Function("$data", "$filename", code);
Render.prototype = utils;
return Render;
} catch (e) {
e.temp = "function anonymous($data,$filename) {" + code + "}";
throw e;
}
// 处理 HTML 语句
function html (code) {
// 记录行号
line += code.split(/\n/).length - 1;
// 压缩多余空白与注释
if (compress) {
code = code
.replace(/\s+/g, ' ')
.replace(/<!--[\w\W]*?-->/g, '');
}
if (code) {
code = replaces[1] + stringify(code) + replaces[2] + "\n";
}
return code;
}
// 处理逻辑语句
function logic (code) {
var thisLine = line;
if (parser) {
// 语法转换插件钩子
code = parser(code, options);
} else if (debug) {
// 记录行号
code = code.replace(/\n/g, function () {
line ++;
return "$line=" + line + ";";
});
}
// 输出语句. 编码: <%=value%> 不编码:<%=#value%>
// <%=#value%> 等同 v2.0.3 之前的 <%==value%>
if (code.indexOf('=') === 0) {
var escapeSyntax = escape && !/^=[=#]/.test(code);
code = code.replace(/^=[=#]?|[\s;]*$/g, '');
// 对内容编码
if (escapeSyntax) {
var name = code.replace(/\s*\([^\)]+\)/, '');
// 排除 utils.* | include | print
if (!utils[name] && !/^(include|print)$/.test(name)) {
code = "$escape(" + code + ")";
}
// 不编码
} else {
code = "$string(" + code + ")";
}
code = replaces[1] + code + replaces[2];
}
if (debug) {
code = "$line=" + thisLine + ";" + code;
}
// 提取模板中的变量名
forEach(getVariable(code), function (name) {
// name 值可能为空,在安卓低版本浏览器下
if (!name || uniq[name]) {
return;
}
var value;
// 声明模板变量
// 赋值优先级:
// [include, print] > utils > helpers > data
if (name === 'print') {
value = print;
} else if (name === 'include') {
value = include;
} else if (utils[name]) {
value = "$utils." + name;
} else if (helpers[name]) {
value = "$helpers." + name;
} else {
value = "$data." + name;
}
headerCode += name + "=" + value + ",";
uniq[name] = true;
});
return code + "\n";
}
};

View File

@@ -0,0 +1,22 @@
/**
* 设置全局配置
* @name template.config
* @param {String} 名称
* @param {Any} 值
*/
template.config = function (name, value) {
defaults[name] = value;
};
var defaults = template.defaults = {
openTag: '<%', // 逻辑语法开始标签
closeTag: '%>', // 逻辑语法结束标签
escape: true, // 是否编码输出变量的 HTML 字符
cache: true, // 是否开启缓存(依赖 options 的 filename 字段)
compress: false, // 是否压缩输出
parser: null // 自定义语法格式器 @see: template-syntax.js
};

View File

@@ -0,0 +1,29 @@
/**
* 获取编译缓存(可由外部重写此方法)
* @param {String} 模板名
* @param {Function} 编译好的函数
*/
template.get = function (filename) {
var cache;
if (cacheStore[filename]) {
// 使用内存缓存
cache = cacheStore[filename];
} else if (typeof document === 'object') {
// 加载模板并编译
var elem = document.getElementById(filename);
if (elem) {
var source = (elem.value || elem.innerHTML)
.replace(/^\s*|\s*$/g, '');
cache = compile(source, {
filename: filename
});
}
}
return cache;
};

View File

@@ -0,0 +1,15 @@
/**
* 添加模板辅助方法
* @name template.helper
* @param {String} 名称
* @param {Function} 方法
*/
template.helper = function (name, helper) {
helpers[name] = helper;
};
var helpers = template.helpers = utils.$helpers;

View File

@@ -0,0 +1,9 @@
/*!
* artTemplate - Template Engine
* https://github.com/aui/artTemplate
* Released under the MIT, BSD, and GPL Licenses
*/
!(function () {

Some files were not shown because too many files have changed in this diff Show More