tppl
Version:
js模板引擎,代码由https://github.com/jojoin/tppl 提供,由jover对其进行npm打包
1,508 lines (1,375 loc) • 456 kB
JavaScript
/*
Copyright 2012, KISSY UI Library v1.20
MIT Licensed
build time: Feb 8 17:28
*/
/*
* a seed where KISSY grows up from , KISS Yeah !
* @author lifesinger@gmail.com,yiminghe@gmail.com
*/
(function (S, undefined) {
/**
* @namespace KISSY
*/
var host = this,
meta = {
/**
* Copies all the properties of s to r.
* @param deep {boolean} whether recursive mix if encounter object
* @return {Object} the augmented object
*/
mix:function (r, s, ov, wl, deep) {
if (!s || !r) {
return r;
}
if (ov === undefined) {
ov = true;
}
var i, p, len;
if (wl && (len = wl.length)) {
for (i = 0; i < len; i++) {
p = wl[i];
if (p in s) {
_mix(p, r, s, ov, deep);
}
}
} else {
for (p in s) {
_mix(p, r, s, ov, deep);
}
}
return r;
}
},
_mix = function (p, r, s, ov, deep) {
if (ov || !(p in r)) {
var target = r[p], src = s[p];
// prevent never-end loop
if (target === src) {
return;
}
// 来源是数组和对象,并且要求深度 mix
if (deep && src && (S.isArray(src) || S.isPlainObject(src))) {
// 目标值为对象或数组,直接 mix
// 否则 新建一个和源值类型一样的空数组/对象,递归 mix
var clone = target && (S.isArray(target) || S.isPlainObject(target)) ?
target :
(S.isArray(src) ? [] : {});
r[p] = S.mix(clone, src, ov, undefined, true);
} else if (src !== undefined) {
r[p] = s[p];
}
}
},
// If KISSY is already defined, the existing KISSY object will not
// be overwritten so that defined namespaces are preserved.
seed = (host && host[S]) || {},
guid = 0,
EMPTY = '';
// The host of runtime environment. specify by user's seed or <this>,
// compatibled for '<this> is null' in unknown engine.
host = seed.__HOST || (seed.__HOST = host || {});
// shortcut and meta for seed.
// override previous kissy
S = host[S] = meta.mix(seed, meta);
S.mix(S, {
configs:{},
// S.app() with these members.
__APP_MEMBERS:['namespace'],
__APP_INIT_METHODS:['__init'],
/**
* The version of the library.
* @type {String}
*/
version:'1.20',
buildTime:'20120208172832',
/**
* Returns a new object containing all of the properties of
* all the supplied objects. The properties from later objects
* will overwrite those in earlier objects. Passing in a
* single object will create a shallow copy of it.
* @return {Object} the new merged object
*/
merge:function () {
var o = {}, i, l = arguments.length;
for (i = 0; i < l; i++) {
S.mix(o, arguments[i]);
}
return o;
},
/**
* Applies prototype properties from the supplier to the receiver.
* @return {Object} the augmented object
*/
augment:function (/*r, s1, s2, ..., ov, wl*/) {
var args = S.makeArray(arguments),
len = args.length - 2,
r = args[0],
ov = args[len],
wl = args[len + 1],
i = 1;
if (!S.isArray(wl)) {
ov = wl;
wl = undefined;
len++;
}
if (!S.isBoolean(ov)) {
ov = undefined;
len++;
}
for (; i < len; i++) {
S.mix(r.prototype, args[i].prototype || args[i], ov, wl);
}
return r;
},
/**
* Utility to set up the prototype, constructor and superclass properties to
* support an inheritance strategy that can chain constructors and methods.
* Static members will not be inherited.
* @param r {Function} the object to modify
* @param s {Function} the object to inherit
* @param px {Object} prototype properties to add/override
* @param {Object} [sx] static properties to add/override
* @return r {Object}
*/
extend:function (r, s, px, sx) {
if (!s || !r) {
return r;
}
var create = Object.create ?
function (proto, c) {
return Object.create(proto, {
constructor:{
value:c
}
});
} :
function (proto, c) {
function F() {
}
F.prototype = proto;
var o = new F();
o.constructor = c;
return o;
},
sp = s.prototype,
rp;
// add prototype chain
rp = create(sp, r);
r.prototype = S.mix(rp, r.prototype);
r.superclass = create(sp, s);
// add prototype overrides
if (px) {
S.mix(rp, px);
}
// add object overrides
if (sx) {
S.mix(r, sx);
}
return r;
},
/****************************************************************************************
* The KISSY System Framework *
****************************************************************************************/
/**
* Initializes KISSY
*/
__init:function () {
this.Config = this.Config || {};
this.Env = this.Env || {};
// NOTICE: '@DEBUG@' will replace with '' when compressing.
// So, if loading source file, debug is on by default.
// If loading min version, debug is turned off automatically.
this.Config.debug = '@DEBUG@';
},
/**
* Returns the namespace specified and creates it if it doesn't exist. Be careful
* when naming packages. Reserved words may work in some browsers and not others.
* <code>
* S.namespace('KISSY.app'); // returns KISSY.app
* S.namespace('app.Shop'); // returns KISSY.app.Shop
* S.namespace('TB.app.Shop', true); // returns TB.app.Shop
* </code>
* @return {Object} A reference to the last namespace object created
*/
namespace:function () {
var args = S.makeArray(arguments),
l = args.length,
o = null, i, j, p,
global = (args[l - 1] === true && l--);
for (i = 0; i < l; i++) {
p = (EMPTY + args[i]).split('.');
o = global ? host : this;
for (j = (host[p[0]] === o) ? 1 : 0; j < p.length; ++j) {
o = o[p[j]] = o[p[j]] || { };
}
}
return o;
},
/**
* create app based on KISSY.
* @param name {String} the app name
* @param sx {Object} static properties to add/override
* <code>
* S.app('TB');
* TB.namespace('app'); // returns TB.app
* </code>
* @return {Object} A reference to the app global object
*/
app:function (name, sx) {
var isStr = S.isString(name),
O = isStr ? host[name] || {} : name,
i = 0,
len = S.__APP_INIT_METHODS.length;
S.mix(O, this, true, S.__APP_MEMBERS);
for (; i < len; i++) {
S[S.__APP_INIT_METHODS[i]].call(O);
}
S.mix(O, S.isFunction(sx) ? sx() : sx);
isStr && (host[name] = O);
return O;
},
config:function (c) {
var configs, cfg, r;
for (var p in c) {
if (c.hasOwnProperty(p)) {
if ((configs = this['configs']) &&
(cfg = configs[p])) {
r = cfg(c[p]);
}
}
}
return r;
},
/**
* Prints debug info.
* @param msg {String} the message to log.
* @param {String} [cat] the log category for the message. Default
* categories are "info", "warn", "error", "time" etc.
* @param {String} [src] the source of the the message (opt)
*/
log:function (msg, cat, src) {
if (S.Config.debug) {
if (src) {
msg = src + ': ' + msg;
}
if (host['console'] !== undefined && console.log) {
console[cat && console[cat] ? cat : 'log'](msg);
}
}
},
/**
* Throws error message.
*/
error:function (msg) {
if (S.Config.debug) {
throw msg;
}
},
/*
* Generate a global unique id.
* @param {String} [pre] guid prefix
* @return {String} the guid
*/
guid:function (pre) {
return (pre || EMPTY) + guid++;
}
});
S.__init();
return S;
})('KISSY', undefined);
/**
* @module lang
* @author lifesinger@gmail.com,yiminghe@gmail.com
* @description this code can run in any ecmascript compliant environment
*/
(function (S, undefined) {
var host = S.__HOST,
TRUE = true,
FALSE = false,
OP = Object.prototype,
toString = OP.toString,
hasOwnProperty = OP.hasOwnProperty,
AP = Array.prototype,
indexOf = AP.indexOf,
lastIndexOf = AP.lastIndexOf,
filter = AP.filter,
every = AP.every,
some = AP.some,
//reduce = AP.reduce,
trim = String.prototype.trim,
map = AP.map,
EMPTY = '',
HEX_BASE = 16,
CLONE_MARKER = '__~ks_cloned',
COMPARE_MARKER = '__~ks_compared',
STAMP_MARKER = '__~ks_stamped',
RE_TRIM = /^[\s\xa0]+|[\s\xa0]+$/g,
encode = encodeURIComponent,
decode = decodeURIComponent,
SEP = '&',
EQ = '=',
// [[Class]] -> type pairs
class2type = {},
// http://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet
htmlEntities = {
'&':'&',
'>':'>',
'<':'<',
'`':'`',
'/':'/',
'"':'"',
''':"'"
},
reverseEntities = {},
escapeReg,
unEscapeReg,
// - # $ ^ * ( ) + [ ] { } | \ , . ?
escapeRegExp = /[\-#$\^*()+\[\]{}|\\,.?\s]/g;
(function () {
for (var k in htmlEntities) {
if (htmlEntities.hasOwnProperty(k)) {
reverseEntities[htmlEntities[k]] = k;
}
}
})();
function getEscapeReg() {
if (escapeReg) {
return escapeReg
}
var str = EMPTY;
S.each(htmlEntities, function (entity) {
str += entity + '|';
});
str = str.slice(0, -1);
return escapeReg = new RegExp(str, "g");
}
function getUnEscapeReg() {
if (unEscapeReg) {
return unEscapeReg
}
var str = EMPTY;
S.each(reverseEntities, function (entity) {
str += entity + '|';
});
str += '&#(\\d{1,5});';
return unEscapeReg = new RegExp(str, "g");
}
function isValidParamValue(val) {
var t = typeof val;
// If the type of val is null, undefined, number, string, boolean, return true.
return nullOrUndefined(val) || (t !== 'object' && t !== 'function');
}
S.mix(S, {
/**
* stamp a object by guid
* @return guid associated with this object
*/
stamp:function (o, readOnly, marker) {
if (!o) {
return o
}
marker = marker || STAMP_MARKER;
var guid = o[marker];
if (guid) {
return guid;
} else if (!readOnly) {
try {
guid = o[marker] = S.guid(marker);
}
catch (e) {
guid = undefined;
}
}
return guid;
},
noop:function () {
},
/**
* Determine the internal JavaScript [[Class]] of an object.
*/
type:function (o) {
return nullOrUndefined(o) ?
String(o) :
class2type[toString.call(o)] || 'object';
},
isNullOrUndefined:nullOrUndefined,
isNull:function (o) {
return o === null;
},
isUndefined:function (o) {
return o === undefined;
},
/**
* Checks to see if an object is empty.
*/
isEmptyObject:function (o) {
for (var p in o) {
if (p !== undefined) {
return FALSE;
}
}
return TRUE;
},
/**
* Checks to see if an object is a plain object (created using "{}"
* or "new Object()" or "new FunctionClass()").
* Ref: http://lifesinger.org/blog/2010/12/thinking-of-isplainobject/
*/
isPlainObject:function (o) {
/**
* note by yiminghe
* isPlainObject(node=document.getElementById("xx")) -> false
* toString.call(node) : ie678 == '[object Object]',other =='[object HTMLElement]'
* 'isPrototypeOf' in node : ie678 === false ,other === true
*/
return o && toString.call(o) === '[object Object]' && 'isPrototypeOf' in o;
},
/**
* 两个目标是否内容相同
*
* @param a 比较目标1
* @param b 比较目标2
* @param [mismatchKeys] internal use
* @param [mismatchValues] internal use
*/
equals:function (a, b, /*internal use*/mismatchKeys, /*internal use*/mismatchValues) {
// inspired by jasmine
mismatchKeys = mismatchKeys || [];
mismatchValues = mismatchValues || [];
if (a === b) {
return TRUE;
}
if (a === undefined || a === null || b === undefined || b === null) {
// need type coercion
return nullOrUndefined(a) && nullOrUndefined(b);
}
if (a instanceof Date && b instanceof Date) {
return a.getTime() == b.getTime();
}
if (S.isString(a) && S.isString(b)) {
return (a == b);
}
if (S.isNumber(a) && S.isNumber(b)) {
return (a == b);
}
if (typeof a === "object" && typeof b === "object") {
return compareObjects(a, b, mismatchKeys, mismatchValues);
}
// Straight check
return (a === b);
},
/**
* Creates a deep copy of a plain object or array. Others are returned untouched.
* 稍微改改就和规范一样了 :)
* @param input
* @param {Function} filter filter function
* @refer http://www.w3.org/TR/html5/common-dom-interfaces.html#safe-passing-of-structured-data
*/
clone:function (input, filter) {
// Let memory be an association list of pairs of objects,
// initially empty. This is used to handle duplicate references.
// In each pair of objects, one is called the source object
// and the other the destination object.
var memory = {},
ret = cloneInternal(input, filter, memory);
S.each(memory, function (v) {
// 清理在源对象上做的标记
v = v.input;
if (v[CLONE_MARKER]) {
try {
delete v[CLONE_MARKER];
} catch (e) {
S.log("delete CLONE_MARKER error : ");
v[CLONE_MARKER] = undefined;
}
}
});
memory = null;
return ret;
},
/**
* Removes the whitespace from the beginning and end of a string.
*/
trim:trim ?
function (str) {
return nullOrUndefined(str) ? EMPTY : trim.call(str);
} :
function (str) {
return nullOrUndefined(str) ? EMPTY : str.toString().replace(RE_TRIM, EMPTY);
},
/**
* Substitutes keywords in a string using an object/array.
* Removes undefined keywords and ignores escaped keywords.
*/
substitute:function (str, o, regexp) {
if (!S.isString(str)
|| !S.isPlainObject(o)) {
return str;
}
return str.replace(regexp || /\\?\{([^{}]+)\}/g, function (match, name) {
if (match.charAt(0) === '\\') {
return match.slice(1);
}
return (o[name] === undefined) ? EMPTY : o[name];
});
},
/**
* Executes the supplied function on each item in the array.
* @param object {Object} the object to iterate
* @param fn {Function} the function to execute on each item. The function
* receives three arguments: the value, the index, the full array.
* @param {Object} [context]
*/
each:function (object, fn, context) {
if (object) {
var key,
val,
i = 0,
length = object && object.length,
isObj = length === undefined || S.type(object) === 'function';
context = context || host;
if (isObj) {
for (key in object) {
// can not use hasOwnProperty
if (fn.call(context, object[key], key, object) === FALSE) {
break;
}
}
} else {
for (val = object[0];
i < length && fn.call(context, val, i, object) !== FALSE; val = object[++i]) {
}
}
}
return object;
},
/**
* Search for a specified value within an array.
*/
indexOf:indexOf ?
function (item, arr) {
return indexOf.call(arr, item);
} :
function (item, arr) {
for (var i = 0, len = arr.length; i < len; ++i) {
if (arr[i] === item) {
return i;
}
}
return -1;
},
/**
* Returns the index of the last item in the array
* that contains the specified value, -1 if the
* value isn't found.
*/
lastIndexOf:(lastIndexOf) ?
function (item, arr) {
return lastIndexOf.call(arr, item);
} :
function (item, arr) {
for (var i = arr.length - 1; i >= 0; i--) {
if (arr[i] === item) {
break;
}
}
return i;
},
/**
* Returns a copy of the array with the duplicate entries removed
* @param a {Array} the array to find the subset of uniques for
* @param override {Boolean}
* if override is true, S.unique([a, b, a]) => [b, a]
* if override is false, S.unique([a, b, a]) => [a, b]
* @return {Array} a copy of the array with duplicate entries removed
*/
unique:function (a, override) {
var b = a.slice();
if (override) {
b.reverse();
}
var i = 0,
n,
item;
while (i < b.length) {
item = b[i];
while ((n = S.lastIndexOf(item, b)) !== i) {
b.splice(n, 1);
}
i += 1;
}
if (override) {
b.reverse();
}
return b;
},
/**
* Search for a specified value index within an array.
*/
inArray:function (item, arr) {
return S.indexOf(item, arr) > -1;
},
/**
* Executes the supplied function on each item in the array.
* Returns a new array containing the items that the supplied
* function returned true for.
* @param arr {Array} the array to iterate
* @param fn {Function} the function to execute on each item
* @param context {Object} optional context object
* @return {Array} The items on which the supplied function
* returned true. If no items matched an empty array is
* returned.
*/
filter:filter ?
function (arr, fn, context) {
return filter.call(arr, fn, context || this);
} :
function (arr, fn, context) {
var ret = [];
S.each(arr, function (item, i, arr) {
if (fn.call(context || this, item, i, arr)) {
ret.push(item);
}
});
return ret;
},
// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/map
map:map ?
function (arr, fn, context) {
return map.call(arr, fn, context || this);
} :
function (arr, fn, context) {
var len = arr.length,
res = new Array(len);
for (var i = 0; i < len; i++) {
var el = S.isString(arr) ? arr.charAt(i) : arr[i];
if (el
||
//ie<9 in invalid when typeof arr == string
i in arr) {
res[i] = fn.call(context || this, el, i, arr);
}
}
return res;
},
/**
* @refer https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/reduce
*/
reduce:/*
NaN ?
reduce ? function(arr, callback, initialValue) {
return arr.reduce(callback, initialValue);
} : */function (arr, callback, initialValue) {
var len = arr.length;
if (typeof callback !== "function") {
throw new TypeError("callback is not function!");
}
// no value to return if no initial value and an empty array
if (len === 0 && arguments.length == 2) {
throw new TypeError("arguments invalid");
}
var k = 0;
var accumulator;
if (arguments.length >= 3) {
accumulator = arguments[2];
}
else {
do {
if (k in arr) {
accumulator = arr[k++];
break;
}
// if array contains no values, no initial value to return
k += 1;
if (k >= len) {
throw new TypeError();
}
}
while (TRUE);
}
while (k < len) {
if (k in arr) {
accumulator = callback.call(undefined, accumulator, arr[k], k, arr);
}
k++;
}
return accumulator;
},
every:every ?
function (arr, fn, context) {
return every.call(arr, fn, context || this);
} :
function (arr, fn, context) {
var len = arr && arr.length || 0;
for (var i = 0; i < len; i++) {
if (i in arr && !fn.call(context, arr[i], i, arr)) {
return FALSE;
}
}
return TRUE;
},
some:some ?
function (arr, fn, context) {
return some.call(arr, fn, context || this);
} :
function (arr, fn, context) {
var len = arr && arr.length || 0;
for (var i = 0; i < len; i++) {
if (i in arr && fn.call(context, arr[i], i, arr)) {
return TRUE;
}
}
return FALSE;
},
/**
* it is not same with native bind
* @refer https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind
*/
bind:function (fn, obj) {
var slice = [].slice,
args = slice.call(arguments, 2),
fNOP = function () {
},
bound = function () {
return fn.apply(this instanceof fNOP ? this : obj,
args.concat(slice.call(arguments)));
};
fNOP.prototype = fn.prototype;
bound.prototype = new fNOP();
return bound;
},
/**
* Gets current date in milliseconds.
* @refer https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Date/now
* http://j-query.blogspot.com/2011/02/timing-ecmascript-5-datenow-function.html
* http://kangax.github.com/es5-compat-table/
*/
now:Date.now || function () {
return +new Date();
},
/**
* frequently used in taobao cookie about nick
*/
fromUnicode:function (str) {
return str.replace(/\\u([a-f\d]{4})/ig, function (m, u) {
return String.fromCharCode(parseInt(u, HEX_BASE));
});
},
/**
* escape string to html
* @refer http://yiminghe.javaeye.com/blog/788929
* http://wonko.com/post/html-escaping
* @param str {string} text2html show
*/
escapeHTML:function (str) {
return str.replace(getEscapeReg(), function (m) {
return reverseEntities[m];
});
},
escapeRegExp:function (str) {
return str.replace(escapeRegExp, '\\$&');
},
/**
* unescape html to string
* @param str {string} html2text
*/
unEscapeHTML:function (str) {
return str.replace(getUnEscapeReg(), function (m, n) {
return htmlEntities[m] || String.fromCharCode(+n);
});
},
/**
* Converts object to a true array.
* @param o {object|Array} array like object or array
* @return {Array}
*/
makeArray:function (o) {
if (nullOrUndefined(o)) {
return [];
}
if (S.isArray(o)) {
return o;
}
// The strings and functions also have 'length'
if (typeof o.length !== 'number' || S.isString(o) || S.isFunction(o)) {
return [o];
}
var ret = [];
for (var i = 0, l = o.length; i < l; i++) {
ret[i] = o[i];
}
return ret;
},
/**
* Creates a serialized string of an array or object.
* @return {String}
* <code>
* {foo: 1, bar: 2} // -> 'foo=1&bar=2'
* {foo: 1, bar: [2, 3]} // -> 'foo=1&bar=2&bar=3'
* {foo: '', bar: 2} // -> 'foo=&bar=2'
* {foo: undefined, bar: 2} // -> 'foo=undefined&bar=2'
* {foo: true, bar: 2} // -> 'foo=true&bar=2'
* </code>
*/
param:function (o, sep, eq, arr) {
if (!S.isPlainObject(o)) {
return EMPTY;
}
sep = sep || SEP;
eq = eq || EQ;
if (S.isUndefined(arr)) {
arr = TRUE;
}
var buf = [], key, val;
for (key in o) {
if (o.hasOwnProperty(key)) {
val = o[key];
key = encode(key);
// val is valid non-array value
if (isValidParamValue(val)) {
buf.push(key, eq, encode(val + EMPTY), sep);
}
// val is not empty array
else if (S.isArray(val) && val.length) {
for (var i = 0, len = val.length; i < len; ++i) {
if (isValidParamValue(val[i])) {
buf.push(key,
(arr ? encode("[]") : EMPTY),
eq, encode(val[i] + EMPTY), sep);
}
}
}
// ignore other cases, including empty array, Function, RegExp, Date etc.
}
}
buf.pop();
return buf.join(EMPTY);
},
/**
* Parses a URI-like query string and returns an object composed of parameter/value pairs.
* <code>
* 'section=blog&id=45' // -> {section: 'blog', id: '45'}
* 'section=blog&tag=js&tag=doc' // -> {section: 'blog', tag: ['js', 'doc']}
* 'tag=ruby%20on%20rails' // -> {tag: 'ruby on rails'}
* 'id=45&raw' // -> {id: '45', raw: ''}
* </code>
*/
unparam:function (str, sep, eq) {
if (typeof str !== 'string'
|| (str = S.trim(str)).length === 0) {
return {};
}
sep = sep || SEP;
eq = eq || EQ;
var ret = {},
pairs = str.split(sep),
pair, key, val,
i = 0, len = pairs.length;
for (; i < len; ++i) {
pair = pairs[i].split(eq);
key = decode(pair[0]);
try {
val = decode(pair[1] || EMPTY);
} catch (e) {
S.log(e + "decodeURIComponent error : " + pair[1], "error");
val = pair[1] || EMPTY;
}
if (S.endsWith(key, "[]")) {
key = key.substring(0, key.length - 2);
}
if (hasOwnProperty.call(ret, key)) {
if (S.isArray(ret[key])) {
ret[key].push(val);
} else {
ret[key] = [ret[key], val];
}
} else {
ret[key] = val;
}
}
return ret;
},
/**
* Executes the supplied function in the context of the supplied
* object 'when' milliseconds later. Executes the function a
* single time unless periodic is set to true.
* @param fn {Function|String} the function to execute or the name of the method in
* the 'o' object to execute.
* @param when {Number} the number of milliseconds to wait until the fn is executed.
* @param periodic {Boolean} if true, executes continuously at supplied interval
* until canceled.
* @param context {Object} the context object.
* @param [data] that is provided to the function. This accepts either a single
* item or an array. If an array is provided, the function is executed with
* one parameter for each array item. If you need to pass a single array
* parameter, it needs to be wrapped in an array [myarray].
* @return {Object} a timer object. Call the cancel() method on this object to stop
* the timer.
*/
later:function (fn, when, periodic, context, data) {
when = when || 0;
var m = fn,
d = S.makeArray(data),
f,
r;
if (S.isString(fn)) {
m = context[fn];
}
if (!m) {
S.error('method undefined');
}
f = function () {
m.apply(context, d);
};
r = (periodic) ? setInterval(f, when) : setTimeout(f, when);
return {
id:r,
interval:periodic,
cancel:function () {
if (this.interval) {
clearInterval(r);
} else {
clearTimeout(r);
}
}
};
},
startsWith:function (str, prefix) {
return str.lastIndexOf(prefix, 0) === 0;
},
endsWith:function (str, suffix) {
var ind = str.length - suffix.length;
return ind >= 0 && str.indexOf(suffix, ind) == ind;
},
/**
* Based on YUI3
* Throttles a call to a method based on the time between calls.
* @param {function} fn The function call to throttle.
* @param {object} context ontext fn to run
* @param {Number} ms The number of milliseconds to throttle the method call.
* Passing a -1 will disable the throttle. Defaults to 150.
* @return {function} Returns a wrapped function that calls fn throttled.
*/
throttle:function (fn, ms, context) {
ms = ms || 150;
if (ms === -1) {
return (function () {
fn.apply(context || this, arguments);
});
}
var last = S.now();
return (function () {
var now = S.now();
if (now - last > ms) {
last = now;
fn.apply(context || this, arguments);
}
});
},
/**
* buffers a call between a fixed time
* @param {function} fn
* @param {object} [context]
* @param {Number} ms
*/
buffer:function (fn, ms, context) {
ms = ms || 150;
if (ms === -1) {
return (function () {
fn.apply(context || this, arguments);
});
}
var bufferTimer = null;
function f() {
f.stop();
bufferTimer = S.later(fn, ms, FALSE, context || this);
}
f.stop = function () {
if (bufferTimer) {
bufferTimer.cancel();
bufferTimer = 0;
}
};
return f;
}
});
// for idea ..... auto-hint
S.mix(S, {
isBoolean:isValidParamValue,
isNumber:isValidParamValue,
isString:isValidParamValue,
isFunction:isValidParamValue,
isArray:isValidParamValue,
isDate:isValidParamValue,
isRegExp:isValidParamValue,
isObject:isValidParamValue
});
S.each('Boolean Number String Function Array Date RegExp Object'.split(' '),
function (name, lc) {
// populate the class2type map
class2type['[object ' + name + ']'] = (lc = name.toLowerCase());
// add isBoolean/isNumber/...
S['is' + name] = function (o) {
return S.type(o) == lc;
}
});
function nullOrUndefined(o) {
return S.isNull(o) || S.isUndefined(o);
}
function cloneInternal(input, f, memory) {
var destination = input,
isArray,
isPlainObject,
k,
stamp;
if (!input) {
return destination;
}
// If input is the source object of a pair of objects in memory,
// then return the destination object in that pair of objects .
// and abort these steps.
if (input[CLONE_MARKER]) {
// 对应的克隆后对象
return memory[input[CLONE_MARKER]].destination;
} else if (typeof input === "object") {
// 引用类型要先记录
var constructor = input.constructor;
if (S.inArray(constructor, [Boolean, String, Number, Date, RegExp])) {
destination = new constructor(input.valueOf());
}
// ImageData , File, Blob , FileList .. etc
else if (isArray = S.isArray(input)) {
destination = f ? S.filter(input, f) : input.concat();
} else if (isPlainObject = S.isPlainObject(input)) {
destination = {};
}
// Add a mapping from input (the source object)
// to output (the destination object) to memory.
// 做标记
input[CLONE_MARKER] = (stamp = S.guid());
// 存储源对象以及克隆后的对象
memory[stamp] = {destination:destination, input:input};
}
// If input is an Array object or an Object object,
// then, for each enumerable property in input,
// add a new property to output having the same name,
// and having a value created from invoking the internal structured cloning algorithm recursively
// with the value of the property as the "input" argument and memory as the "memory" argument.
// The order of the properties in the input and output objects must be the same.
// clone it
if (isArray) {
for (var i = 0; i < destination.length; i++) {
destination[i] = cloneInternal(destination[i], f, memory);
}
} else if (isPlainObject) {
for (k in input) {
if (input.hasOwnProperty(k)) {
if (k !== CLONE_MARKER &&
(!f || (f.call(input, input[k], k, input) !== FALSE))) {
destination[k] = cloneInternal(input[k], f, memory);
}
}
}
}
return destination;
}
function compareObjects(a, b, mismatchKeys, mismatchValues) {
// 两个比较过了,无需再比较,防止循环比较
if (a[COMPARE_MARKER] === b && b[COMPARE_MARKER] === a) {
return TRUE;
}
a[COMPARE_MARKER] = b;
b[COMPARE_MARKER] = a;
var hasKey = function (obj, keyName) {
return (obj !== null && obj !== undefined) && obj[keyName] !== undefined;
};
for (var property in b) {
if (b.hasOwnProperty(property)) {
if (!hasKey(a, property) && hasKey(b, property)) {
mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
}
}
}
for (property in a) {
if (a.hasOwnProperty(property)) {
if (!hasKey(b, property) && hasKey(a, property)) {
mismatchKeys.push("expected missing key '" + property + "', but present in actual.");
}
}
}
for (property in b) {
if (b.hasOwnProperty(property)) {
if (property == COMPARE_MARKER) {
continue;
}
if (!S.equals(a[property], b[property], mismatchKeys, mismatchValues)) {
mismatchValues.push("'" + property + "' was '" + (b[property] ? (b[property].toString()) : b[property])
+ "' in expected, but was '" +
(a[property] ? (a[property].toString()) : a[property]) + "' in actual.");
}
}
}
if (S.isArray(a) && S.isArray(b) && a.length != b.length) {
mismatchValues.push("arrays were not the same length");
}
delete a[COMPARE_MARKER];
delete b[COMPARE_MARKER];
return (mismatchKeys.length === 0 && mismatchValues.length === 0);
}
})(KISSY, undefined);
/**
* setup data structure for kissy loader
* @author yiminghe@gmail.com
*/
(function(S){
if("require" in this) {
return;
}
S.__loader={};
S.__loaderUtils={};
S.__loaderData={};
})(KISSY);/**
* map mechanism
* @author yiminghe@gmail.com
*/
(function (S, loader) {
if ("require" in this) {
return;
}
/**
* modify current module path
* @param rules
* @example
* [
* [/(.+-)min(.js(\?t=\d+)?)$/,"$1$2"],
* [/(.+-)min(.js(\?t=\d+)?)$/,function(_,m1,m2){
* return m1+m2;
* }]
* ]
*/
S.configs.map = function (rules) {
S.Config.mappedRules = (S.Config.mappedRules || []).concat(rules);
};
S.mix(loader, {
__getMappedPath:function (path) {
var __mappedRules = S.Config.mappedRules || [];
for (var i = 0; i < __mappedRules.length; i++) {
var m, rule = __mappedRules[i];
if (m = path.match(rule[0])) {
return path.replace(rule[0], rule[1]);
}
}
return path;
}
});
})(KISSY, KISSY.__loader);/**
* combine mechanism
* @author yiminghe@gmail.com
*/
(function (S, loader) {
if ("require" in this) {
return;
}
var combines;
/**
* compress 'from module' to 'to module'
* {
* core:['dom','ua','event','node','json','ajax','anim','base','cookie']
* }
*/
combines = S.configs.combines = function (from, to) {
var cs;
if (S.isObject(from)) {
S.each(from, function (v, k) {
S.each(v, function (v2) {
combines(v2, k);
});
});
return;
}
cs = S.Config.combines = S.Config.combines || {};
if (to) {
cs[from] = to;
} else {
return cs[from] || from;
}
};
S.mix(loader, {
__getCombinedMod:function (modName) {
var cs;
cs = S.Config.combines = S.Config.combines || {};
return cs[modName] || modName;
}
});
})(KISSY, KISSY.__loader);/**
* status constants
* @author yiminghe@gmail.com
*/
(function(S, data) {
if ("require" in this) {
return;
}
// 脚本(loadQueue)/模块(mod) 公用状态
S.mix(data, {
"INIT":0,
"LOADING" : 1,
"LOADED" : 2,
"ERROR" : 3,
// 模块特有
"ATTACHED" : 4
});
})(KISSY, KISSY.__loaderData);/**
* utils for kissy loader
* @author yiminghe@gmail.com
*/
(function(S, loader, utils) {
if ("require" in this) {
return;
}
var ua = navigator.userAgent,doc = document;
S.mix(utils, {
docHead:function() {
return doc.getElementsByTagName('head')[0] || doc.documentElement;
},
isWebKit:!!ua.match(/AppleWebKit/),
IE : !!ua.match(/MSIE/),
isCss:function(url) {
return /\.css(?:\?|$)/i.test(url);
},
isLinkNode:function(n) {
return n.nodeName.toLowerCase() == 'link';
},
/**
* resolve relative part of path
* x/../y/z -> y/z
* x/./y/z -> x/y/z
* @param path uri path
* @return {string} resolved path
* @description similar to path.normalize in nodejs
*/
normalizePath:function(path) {
var paths = path.split("/"),
re = [],
p;
for (var i = 0; i < paths.length; i++) {
p = paths[i];
if (p == ".") {
} else if (p == "..") {
re.pop();
} else {
re.push(p);
}
}
return re.join("/");
},
/**
* 根据当前模块以及依赖模块的相对路径,得到依赖模块的绝对路径
* @param moduleName 当前模块
* @param depName 依赖模块
* @return {string|Array} 依赖模块的绝对路径
* @description similar to path.resolve in nodejs
*/
normalDepModuleName:function normalDepModuleName(moduleName, depName) {
if (!depName) {
return depName;
}
if (S.isArray(depName)) {
for (var i = 0; i < depName.length; i++) {
depName[i] = normalDepModuleName(moduleName, depName[i]);
}
return depName;
}
if (startsWith(depName, "../") || startsWith(depName, "./")) {
var anchor = "",index;
// x/y/z -> x/y/
if ((index = moduleName.lastIndexOf("/")) != -1) {
anchor = moduleName.substring(0, index + 1);
}
return normalizePath(anchor + depName);
} else if (depName.indexOf("./") != -1
|| depName.indexOf("../") != -1) {
return normalizePath(depName);
} else {
return depName;
}
},
//去除后缀名,要考虑时间戳?
removePostfix:function (path) {
return path.replace(/(-min)?\.js[^/]*$/i, "");
},
/**
* 路径正则化,不能是相对地址
* 相对地址则转换成相对页面的绝对地址
* 用途:
* package path 相对地址则相对于当前页面获取绝对地址
*/
normalBasePath:function (path) {
path = S.trim(path);
// path 为空时,不能变成 "/"
if (path && path.charAt(path.length - 1) != '/') {
path += "/";
}
/**
* 一定要正则化,防止出现 ../ 等相对路径
* 考虑本地路径
*/
if (!path.match(/^(http(s)?)|(file):/i)
&& !startsWith(path, "/")) {
path = loader.__pagePath + path;
}
return normalizePath(path);
},
/**
* 相对路径文件名转换为绝对路径
* @param path
*/
absoluteFilePath:function(path) {
path = utils.normalBasePath(path);
return path.substring(0, path.length - 1);
},
//http://wiki.commonjs.org/wiki/Packages/Mappings/A
//如果模块名以 / 结尾,自动加 index
indexMapping:function (names) {
for (var i = 0; i < names.length; i++) {
if (names[i].match(/\/$/)) {
names[i] += "index";
}
}
return names;
}
});
var startsWith = S.startsWith,normalizePath = utils.normalizePath;
})(KISSY, KISSY.__loader, KISSY.__loaderUtils);/**
* script/css load across browser
* @author yiminghe@gmail.com
*/
(function(S, utils) {
if ("require" in this) {
return;
}
var CSS_POLL_INTERVAL = 30,
/**
* central poll for link node
*/
timer = 0,
monitors = {
/**
* node.id:[callback]
*/
};
function startCssTimer() {
if (!timer) {
S.log("start css polling");
cssPoll();
}
}
// single thread is ok
function cssPoll() {
for (var url in monitors) {
var callbacks = monitors[url],
node = callbacks.node,
loaded = 0;
if (utils.isWebKit) {
if (node['sheet']) {
S.log("webkit loaded : " +