You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

6735 lines
231 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*==================================================
Copyright (c) 2013-2016 司徒正美 and other contributors
http://www.cnblogs.com/rubylouvre/
https://github.com/RubyLouvre
http://weibo.com/jslouvre/
Released under the MIT license
avalon.js 1.5.9 built in 2016.11.27
support IE6+ and other browsers
==================================================*/
(function(global, factory) {
if (typeof module === "object" && typeof module.exports === "object") {
// For CommonJS and CommonJS-like environments where a proper `window`
// is present, execute the factory and get avalon.
// For environments that do not have a `window` with a `document`
// (such as Node.js), expose a factory as module.exports.
// This accentuates the need for the creation of a real `window`.
// e.g. var avalon = require("avalon")(window);
module.exports = global.document ? factory(global, true) : function(w) {
if (!w.document) {
throw new Error("Avalon requires a window with a document")
}
return factory(w)
}
} else {
factory(global)
}
// Pass this if window is not defined yet
}(typeof window !== "undefined" ? window : this, function(window, noGlobal){
/*********************************************************************
* 全局变量及方法 *
**********************************************************************/
var expose = new Date() - 0
//http://stackoverflow.com/questions/7290086/javascript-use-strict-and-nicks-find-global-function
var DOC = window.document
var head = DOC.getElementsByTagName("head")[0] //HEAD元素
var ifGroup = head.insertBefore(document.createElement("avalon"), head.firstChild) //避免IE6 base标签BUG
ifGroup.innerHTML = "X<style id='avalonStyle'>.avalonHide{ display: none!important }</style>"
ifGroup.setAttribute("ms-skip", "1")
ifGroup.className = "avalonHide"
var rnative = /\[native code\]/ //判定是否原生函数
function log() {
if (window.console && kernel.debug) {
// http://stackoverflow.com/questions/8785624/how-to-safely-wrap-console-log
Function.apply.call(console.log, console, arguments)
}
}
var subscribers = "$" + expose
var nullObject = {} //作用类似于noop只用于代码防御千万不要在它上面添加属性
var rword = /[^, ]+/g //切割字符串为一个个小块以空格或豆号分开它们结合replace实现字符串的forEach
var rw20g = /\w+/g
var rsvg = /^\[object SVG\w*Element\]$/
var rwindow = /^\[object (?:Window|DOMWindow|global)\]$/
var oproto = Object.prototype
var ohasOwn = oproto.hasOwnProperty
var serialize = oproto.toString
var ap = Array.prototype
var aslice = ap.slice
var W3C = window.dispatchEvent
var root = DOC.documentElement
var avalonFragment = DOC.createDocumentFragment()
var cinerator = DOC.createElement("div")
var class2type = {}
"Boolean Number String Function Array Date RegExp Object Error".replace(rword, function (name) {
class2type["[object " + name + "]"] = name.toLowerCase()
})
var bindingID = 1024
var IEVersion = NaN
if (window.VBArray) {
IEVersion = document.documentMode || (window.XMLHttpRequest ? 7 : 6)
}
function noop(){}
function scpCompile(array){
return Function.apply(noop, array)
}
function oneObject(array, val) {
if (typeof array === "string") {
array = array.match(rword) || []
}
var result = {},
value = val !== void 0 ? val : 1
for (var i = 0, n = array.length; i < n; i++) {
result[array[i]] = value
}
return result
}
//生成UUID http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
var generateID = function (prefix) {
prefix = prefix || "avalon"
return String(Math.random() + Math.random()).replace(/\d\.\d{4}/, prefix)
}
avalon = function (el) { //创建jQuery式的无new 实例化结构
return new avalon.init(el)
}
/*视浏览器情况采用最快的异步回调*/
avalon.nextTick = new function () {// jshint ignore:line
var tickImmediate = window.setImmediate
var tickObserver = window.MutationObserver
if (tickImmediate) {
return tickImmediate.bind(window)
}
var queue = []
function callback() {
var n = queue.length
for (var i = 0; i < n; i++) {
queue[i]()
}
queue = queue.slice(n)
}
if (tickObserver) {
var node = document.createTextNode("avalon")
new tickObserver(callback).observe(node, {characterData: true})// jshint ignore:line
var bool = false
return function (fn) {
queue.push(fn)
bool = !bool
node.data = bool
}
}
return function (fn) {
setTimeout(fn, 4)
}
}// jshint ignore:line
/*********************************************************************
* javascript 底层补丁 *
**********************************************************************/
if (!"司徒正美".trim) {
var rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g
String.prototype.trim = function () {
return this.replace(rtrim, "")
}
}
var hasDontEnumBug = !({
'toString': null
}).propertyIsEnumerable('toString'),
hasProtoEnumBug = (function () {
}).propertyIsEnumerable('prototype'),
dontEnums = [
"toString",
"toLocaleString",
"valueOf",
"hasOwnProperty",
"isPrototypeOf",
"propertyIsEnumerable",
"constructor"
],
dontEnumsLength = dontEnums.length;
if (!Object.keys) {
Object.keys = function (object) { //ecma262v5 15.2.3.14
var theKeys = []
var skipProto = hasProtoEnumBug && typeof object === "function"
if (typeof object === "string" || (object && object.callee)) {
for (var i = 0; i < object.length; ++i) {
theKeys.push(String(i))
}
} else {
for (var name in object) {
if (!(skipProto && name === "prototype") && ohasOwn.call(object, name)) {
theKeys.push(String(name))
}
}
}
if (hasDontEnumBug) {
var ctor = object.constructor,
skipConstructor = ctor && ctor.prototype === object
for (var j = 0; j < dontEnumsLength; j++) {
var dontEnum = dontEnums[j]
if (!(skipConstructor && dontEnum === "constructor") && ohasOwn.call(object, dontEnum)) {
theKeys.push(dontEnum)
}
}
}
return theKeys
}
}
if (!Array.isArray) {
Array.isArray = function (a) {
return serialize.call(a) === "[object Array]"
}
}
if (!noop.bind) {
Function.prototype.bind = function (scope) {
if (arguments.length < 2 && scope === void 0)
return this
var fn = this,
argv = arguments
return function () {
var args = [],
i
for (i = 1; i < argv.length; i++)
args.push(argv[i])
for (i = 0; i < arguments.length; i++)
args.push(arguments[i])
return fn.apply(scope, args)
}
}
}
var __slice = ap.slice
try {
// Can't be used with DOM elements in IE < 9
__slice.call(document.documentElement)
} catch (e) { // Fails in IE < 9
// This will work for genuine arrays, array-like objects,
// NamedNodeMap (attributes, entities, notations),
// NodeList (e.g., getElementsByTagName), HTMLCollection (e.g., childNodes),
// and will not fail on other DOM objects (as do DOM elements in IE < 9)
aslice = ap.slice = function (begin, end) {
// IE < 9 gets unhappy with an undefined end argument
end = (typeof end !== 'undefined') ? end : this.length
// For native Array objects, we use the native slice function
if (Array.isArray(this)) {
return __slice.call(this, begin, end)
}
// For array like object we handle it ourselves.
var i, cloned = [],
size, len = this.length
// Handle negative value for "begin"
var start = begin || 0
start = (start >= 0) ? start : len + start
// Handle negative value for "end"
var upTo = (end) ? end : len
if (end < 0) {
upTo = len + end
}
// Actual expected size of the slice
size = upTo - start
if (size > 0) {
cloned = new Array(size)
if (this.charAt) {
for (i = 0; i < size; i++) {
cloned[i] = this.charAt(start + i)
}
} else {
for (i = 0; i < size; i++) {
cloned[i] = this[start + i]
}
}
}
return cloned
}
}
function iterator(vars, body, ret) {
var fun = 'for(var ' + vars + 'i=0,n = this.length; i < n; i++){' +
body.replace('_', '((i in this) && fn.call(scope,this[i],i,this))') +
'}' + ret
/* jshint ignore:start */
return Function('fn,scope', fun)
/* jshint ignore:end */
}
if (!/\[native code\]/.test(ap.map)) {
var shim = {
//定位操作,返回数组中第一个等于给定参数的元素的索引值。
indexOf: function (item, index) {
var n = this.length,
i = ~~index
if (i < 0)
i += n
for (; i < n; i++)
if (this[i] === item)
return i
return -1
},
//定位操作,同上,不过是从后遍历。
lastIndexOf: function (item, index) {
var n = this.length,
i = index == null ? n - 1 : index
if (i < 0)
i = Math.max(0, n + i)
for (; i >= 0; i--)
if (this[i] === item)
return i
return -1
},
//迭代操作将数组的元素挨个儿传入一个函数中执行。Prototype.js的对应名字为each。
forEach: iterator('', '_', ''),
//迭代类 在数组中的每个项上运行一个函数,如果此函数的值为真,则此元素作为新数组的元素收集起来,并返回新数组
filter: iterator('r=[],j=0,', 'if(_)r[j++]=this[i]', 'return r'),
//收集操作将数组的元素挨个儿传入一个函数中执行然后把它们的返回值组成一个新数组返回。Prototype.js的对应名字为collect。
map: iterator('r=[],', 'r[i]=_', 'return r'),
//只要数组中有一个元素满足条件放进给定函数返回true那么它就返回true。Prototype.js的对应名字为any。
some: iterator('', 'if(_)return true', 'return false'),
//只有数组中的元素都满足条件放进给定函数返回true它才返回true。Prototype.js的对应名字为all。
every: iterator('', 'if(!_)return false', 'return true')
}
for (var i in shim) {
ap[i] = shim[i]
}
}
/*********************************************************************
* avalon的静态方法定义区 *
**********************************************************************/
avalon.init = function (el) {
this[0] = this.element = el
}
avalon.fn = avalon.prototype = avalon.init.prototype
avalon.type = function (obj) { //取得目标的类型
if (obj == null) {
return String(obj)
}
// 早期的webkit内核浏览器实现了已废弃的ecma262v4标准可以将正则字面量当作函数使用因此typeof在判定正则时会返回function
return typeof obj === "object" || typeof obj === "function" ?
class2type[serialize.call(obj)] || "object" :
typeof obj
}
avalon.isFunction = typeof alert === "object" ? function (fn) {
try {
return /^\s*\bfunction\b/.test(fn + "")
} catch (e) {
return false
}
} : function (fn) {
return serialize.call(fn) === "[object Function]"
}
avalon.isWindow = function (obj) {
if (!obj)
return false
// 利用IE678 window == document为true,document == window竟然为false的神奇特性
// 标准浏览器及IE9IE10等使用 正则检测
return obj == obj.document && obj.document != obj //jshint ignore:line
}
function isWindow(obj) {
return rwindow.test(serialize.call(obj))
}
if (isWindow(window)) {
avalon.isWindow = isWindow
}
var enu, enumerateBUG
for (enu in avalon({})) {
break
}
enumerateBUG = enu !== "0" //IE6下为true, 其他为false
/*判定是否是一个朴素的javascript对象Object不是DOM对象不是BOM对象不是自定义类的实例*/
avalon.isPlainObject = function (obj, key) {
if (!obj || avalon.type(obj) !== "object" || obj.nodeType || avalon.isWindow(obj)) {
return false;
}
try { //IE内置对象没有constructor
if (obj.constructor && !ohasOwn.call(obj, "constructor") && !ohasOwn.call(obj.constructor.prototype, "isPrototypeOf")) {
return false;
}
} catch (e) { //IE8 9会在这里抛错
return false;
}
if (enumerateBUG) {
for (key in obj) {
return ohasOwn.call(obj, key)
}
}
for (key in obj) {
}
return key === void 0 || ohasOwn.call(obj, key)
}
if (rnative.test(Object.getPrototypeOf)) {
avalon.isPlainObject = function (obj) {
// 简单的 typeof obj === "object"检测会致使用isPlainObject(window)在opera下通不过
return serialize.call(obj) === "[object Object]" && Object.getPrototypeOf(obj) === oproto
}
}
//与jQuery.extend方法可用于浅拷贝深拷贝
avalon.mix = avalon.fn.mix = function () {
var options, name, src, copy, copyIsArray, clone,
target = arguments[0] || {},
i = 1,
length = arguments.length,
deep = false
// 如果第一个参数为布尔,判定是否深拷贝
if (typeof target === "boolean") {
deep = target
target = arguments[1] || {}
i++
}
//确保接受方为一个复杂的数据类型
if (typeof target !== "object" && !avalon.isFunction(target)) {
target = {}
}
//如果只有一个参数那么新成员添加于mix所在的对象上
if (i === length) {
target = this
i--
}
for (; i < length; i++) {
//只处理非空参数
if ((options = arguments[i]) != null) {
for (name in options) {
src = target[name]
try {
copy = options[name] //当options为VBS对象时报错
} catch (e) {
continue
}
// 防止环引用
if (target === copy) {
continue
}
if (deep && copy && (avalon.isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))) {
if (copyIsArray) {
copyIsArray = false
clone = src && Array.isArray(src) ? src : []
} else {
clone = src && avalon.isPlainObject(src) ? src : {}
}
target[name] = avalon.mix(deep, clone, copy)
} else if (copy !== void 0) {
target[name] = copy
}
}
}
}
return target
}
function _number(a, len) { //用于模拟slice, splice的效果
a = Math.floor(a) || 0
return a < 0 ? Math.max(len + a, 0) : Math.min(a, len);
}
avalon.mix({
rword: rword,
subscribers: subscribers,
version: 1.59,
ui: {},
log: log,
slice: function (nodes, start, end) {
return aslice.call(nodes, start, end)
} ,
noop: noop,
/*如果不用Error对象封装一下str在控制台下可能会乱码*/
error: function (str, e) {
throw (e || Error)(str)
},
/*将一个以空格或逗号隔开的字符串或数组,转换成一个键值都为1的对象*/
oneObject: oneObject,
/* avalon.range(10)
=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
avalon.range(1, 11)
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
avalon.range(0, 30, 5)
=> [0, 5, 10, 15, 20, 25]
avalon.range(0, -10, -1)
=> [0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
avalon.range(0)
=> []*/
range: function (start, end, step) { // 用于生成整数数组
step || (step = 1)
if (end == null) {
end = start || 0
start = 0
}
var index = -1,
length = Math.max(0, Math.ceil((end - start) / step)),
result = new Array(length)
while (++index < length) {
result[index] = start
start += step
}
return result
},
eventHooks: {},
/*绑定事件*/
bind: function (el, type, fn, phase) {
var hooks = avalon.eventHooks
var hook = hooks[type]
if (typeof hook === "object") {
type = hook.type || type
phase = hook.phase || !!phase
fn = hook.fix ? hook.fix(el, fn) : fn
}
var callback = W3C ? fn : function (e) {
fn.call(el, fixEvent(e));
}
if (W3C) {
el.addEventListener(type, callback, phase)
} else {
el.attachEvent("on" + type, callback)
}
return callback
},
/*卸载事件*/
unbind: function (el, type, fn, phase) {
var hooks = avalon.eventHooks
var hook = hooks[type]
var callback = fn || noop
if (typeof hook === "object") {
type = hook.type || type
phase = hook.phase || !!phase
}
if (W3C) {
el.removeEventListener(type, callback, phase)
} else {
el.detachEvent("on" + type, callback)
}
},
/*读写删除元素节点的样式*/
css: function (node, name, value) {
if (node instanceof avalon) {
node = node[0]
}
var prop = /[_-]/.test(name) ? camelize(name) : name,
fn
name = avalon.cssName(prop) || prop
if (value === void 0 || typeof value === "boolean") { //获取样式
fn = cssHooks[prop + ":get"] || cssHooks["@:get"]
if (name === "background") {
name = "backgroundColor"
}
var val = fn(node, name)
return value === true ? parseFloat(val) || 0 : val
} else if (value === "") { //请除样式
node.style[name] = ""
} else { //设置样式
if (value == null || value !== value) {
return
}
if (isFinite(value) && !avalon.cssNumber[prop]) {
value += "px"
}
fn = cssHooks[prop + ":set"] || cssHooks["@:set"]
fn(node, name, value)
}
},
/*遍历数组与对象,回调的第一个参数为索引或键名,第二个或元素或键值*/
each: function (obj, fn) {
if (obj) { //排除null, undefined
var i = 0
if (isArrayLike(obj)) {
for (var n = obj.length; i < n; i++) {
if (fn(i, obj[i]) === false)
break
}
} else {
for (i in obj) {
if (obj.hasOwnProperty(i) && fn(i, obj[i]) === false) {
break
}
}
}
}
},
//收集元素的data-{{prefix}}-*属性,并转换为对象
getWidgetData: function (elem, prefix) {
var raw = avalon(elem).data()
var result = {}
for (var i in raw) {
if (i.indexOf(prefix) === 0) {
result[i.replace(prefix, "").replace(/\w/, function (a) {
return a.toLowerCase()
})] = raw[i]
}
}
return result
},
Array: {
/*只有当前数组不存在此元素时只添加它*/
ensure: function (target, item) {
if (target.indexOf(item) === -1) {
return target.push(item)
}
},
/*移除数组中指定位置的元素,返回布尔表示成功与否*/
removeAt: function (target, index) {
return !!target.splice(index, 1).length
},
/*移除数组中第一个匹配传参的那个元素,返回布尔表示成功与否*/
remove: function (target, item) {
var index = target.indexOf(item)
if (~index)
return avalon.Array.removeAt(target, index)
return false
}
}
})
var bindingHandlers = avalon.bindingHandlers = {}
var bindingExecutors = avalon.bindingExecutors = {}
var directives = avalon.directives = {}
avalon.directive = function (name, obj) {
bindingHandlers[name] = obj.init = (obj.init || noop)
bindingExecutors[name] = obj.update = (obj.update || noop)
return directives[name] = obj
}
/*判定是否类数组如节点集合纯数组arguments与拥有非负整数的length属性的纯JS对象*/
function isArrayLike(obj) {
if (!obj)
return false
var n = obj.length
if (n === (n >>> 0)) { //检测length属性是否为非负整数
var type = serialize.call(obj).slice(8, -1)
if (/(?:regexp|string|function|window|global)$/i.test(type))
return false
if (type === "Array")
return true
try {
if ({}.propertyIsEnumerable.call(obj, "length") === false) { //如果是原生对象
return /^\s?function/.test(obj.item || obj.callee)
}
return true
} catch (e) { //IE的NodeList直接抛错
return !obj.window //IE6-8 window
}
}
return false
}
// https://github.com/rsms/js-lru
var Cache = new function() {// jshint ignore:line
function LRU(maxLength) {
this.size = 0
this.limit = maxLength
this.head = this.tail = void 0
this._keymap = {}
}
var p = LRU.prototype
p.put = function(key, value) {
var entry = {
key: key,
value: value
}
this._keymap[key] = entry
if (this.tail) {
this.tail.newer = entry
entry.older = this.tail
} else {
this.head = entry
}
this.tail = entry
if (this.size === this.limit) {
this.shift()
} else {
this.size++
}
return value
}
p.shift = function() {
var entry = this.head
if (entry) {
this.head = this.head.newer
this.head.older =
entry.newer =
entry.older =
this._keymap[entry.key] = void 0
delete this._keymap[entry.key] //#1029
}
}
p.get = function(key) {
var entry = this._keymap[key]
if (entry === void 0)
return
if (entry === this.tail) {
return entry.value
}
// HEAD--------------TAIL
// <.older .newer>
// <--- add direction --
// A B C <D> E
if (entry.newer) {
if (entry === this.head) {
this.head = entry.newer
}
entry.newer.older = entry.older // C <-- E.
}
if (entry.older) {
entry.older.newer = entry.newer // C. --> E
}
entry.newer = void 0 // D --x
entry.older = this.tail // D. --> E
if (this.tail) {
this.tail.newer = entry // E. <-- D
}
this.tail = entry
return entry.value
}
return LRU
}// jshint ignore:line
/*********************************************************************
* DOM 底层补丁 *
**********************************************************************/
avalon.contains = function(root, el) {
try { //IE6-8,游离于DOM树外的文本节点访问parentNode有时会抛错
while ((el = el.parentNode))
if (el === root)
return true
return false
} catch (e) {
return false
}
}
//IE6-11的文档对象没有contains
if (!DOC.contains) {
DOC.contains = function (b) {
return avalon.contains(DOC, b)
}
}
function outerHTML() {
return new XMLSerializer().serializeToString(this)
}
if (window.SVGElement) {
//safari5+是把contains方法放在Element.prototype上而不是Node.prototype
if (!DOC.createTextNode("x").contains) {
Node.prototype.contains = function (arg) {//IE6-8没有Node对象
return !!(this.compareDocumentPosition(arg) & 16)
}
}
var svgns = "http://www.w3.org/2000/svg"
var svg = DOC.createElementNS(svgns, "svg")
svg.innerHTML = '<circle cx="50" cy="50" r="40" fill="red" />'
if (!rsvg.test(svg.firstChild)) { // #409
function enumerateNode(node, targetNode) {// jshint ignore:line
if (node && node.childNodes) {
var nodes = node.childNodes
for (var i = 0, el; el = nodes[i++]; ) {
if (el.tagName) {
var svg = DOC.createElementNS(svgns,
el.tagName.toLowerCase())
ap.forEach.call(el.attributes, function (attr) {
svg.setAttribute(attr.name, attr.value) //复制属性
})// jshint ignore:line
// 递归处理子节点
enumerateNode(el, svg)
targetNode.appendChild(svg)
}
}
}
}
Object.defineProperties(SVGElement.prototype, {
"outerHTML": {//IE9-11,firefox不支持SVG元素的innerHTML,outerHTML属性
enumerable: true,
configurable: true,
get: outerHTML,
set: function (html) {
var tagName = this.tagName.toLowerCase(),
par = this.parentNode,
frag = avalon.parseHTML(html)
// 操作的svg直接插入
if (tagName === "svg") {
par.insertBefore(frag, this)
// svg节点的子节点类似
} else {
var newFrag = DOC.createDocumentFragment()
enumerateNode(frag, newFrag)
par.insertBefore(newFrag, this)
}
par.removeChild(this)
}
},
"innerHTML": {
enumerable: true,
configurable: true,
get: function () {
var s = this.outerHTML
var ropen = new RegExp("<" + this.nodeName + '\\b(?:(["\'])[^"]*?(\\1)|[^>])*>', "i")
var rclose = new RegExp("<\/" + this.nodeName + ">$", "i")
return s.replace(ropen, "").replace(rclose, "")
},
set: function (html) {
if (avalon.clearHTML) {
avalon.clearHTML(this)
var frag = avalon.parseHTML(html)
enumerateNode(frag, this)
}
}
}
})
}
}
if (!root.outerHTML && window.HTMLElement) { //firefox 到11时才有outerHTML
HTMLElement.prototype.__defineGetter__("outerHTML", outerHTML);
}
//============================= event binding =======================
var rmouseEvent = /^(?:mouse|contextmenu|drag)|click/
function fixEvent(event) {
var ret = {}
for (var i in event) {
ret[i] = event[i]
}
var target = ret.target = event.srcElement
if (event.type.indexOf("key") === 0) {
ret.which = event.charCode != null ? event.charCode : event.keyCode
} else if (rmouseEvent.test(event.type)) {
var doc = target.ownerDocument || DOC
var box = doc.compatMode === "BackCompat" ? doc.body : doc.documentElement
ret.pageX = event.clientX + (box.scrollLeft >> 0) - (box.clientLeft >> 0)
ret.pageY = event.clientY + (box.scrollTop >> 0) - (box.clientTop >> 0)
ret.wheelDeltaY = ret.wheelDelta
ret.wheelDeltaX = 0
}
ret.timeStamp = new Date() - 0
ret.originalEvent = event
ret.preventDefault = function () { //阻止默认行为
event.returnValue = false
}
ret.stopPropagation = function () { //阻止事件在DOM树中的传播
event.cancelBubble = true
}
return ret
}
var eventHooks = avalon.eventHooks
//针对firefox, chrome修正mouseenter, mouseleave
if (!("onmouseenter" in root)) {
avalon.each({
mouseenter: "mouseover",
mouseleave: "mouseout"
}, function (origType, fixType) {
eventHooks[origType] = {
type: fixType,
fix: function (elem, fn) {
return function (e) {
var t = e.relatedTarget
if (!t || (t !== elem && !(elem.compareDocumentPosition(t) & 16))) {
delete e.type
e.type = origType
return fn.call(elem, e)
}
}
}
}
})
}
//针对IE9+, w3c修正animationend
avalon.each({
AnimationEvent: "animationend",
WebKitAnimationEvent: "webkitAnimationEnd"
}, function (construct, fixType) {
if (window[construct] && !eventHooks.animationend) {
eventHooks.animationend = {
type: fixType
}
}
})
//针对IE6-8修正input
if (!("oninput" in DOC.createElement("input"))) {
eventHooks.input = {
type: "propertychange",
fix: function (elem, fn) {
return function (e) {
if (e.propertyName === "value") {
e.type = "input"
return fn.call(elem, e)
}
}
}
}
}
if (DOC.onmousewheel === void 0) {
/* IE6-11 chrome mousewheel wheelDetla 下 -120 上 120
firefox DOMMouseScroll detail 下3 上-3
firefox wheel detlaY 下3 上-3
IE9-11 wheel deltaY 下40 上-40
chrome wheel deltaY 下100 上-100 */
var fixWheelType = DOC.onwheel !== void 0 ? "wheel" : "DOMMouseScroll"
var fixWheelDelta = fixWheelType === "wheel" ? "deltaY" : "detail"
eventHooks.mousewheel = {
type: fixWheelType,
fix: function (elem, fn) {
return function (e) {
e.wheelDeltaY = e.wheelDelta = e[fixWheelDelta] > 0 ? -120 : 120
e.wheelDeltaX = 0
if (Object.defineProperty) {
Object.defineProperty(e, "type", {
value: "mousewheel"
})
}
fn.call(elem, e)
}
}
}
}
/*********************************************************************
* 配置系统 *
**********************************************************************/
function kernel(settings) {
for (var p in settings) {
if (!ohasOwn.call(settings, p))
continue
var val = settings[p]
if (typeof kernel.plugins[p] === "function") {
kernel.plugins[p](val)
} else if (typeof kernel[p] === "object") {
avalon.mix(kernel[p], val)
} else {
kernel[p] = val
}
}
return this
}
avalon.config = kernel
var openTag, closeTag, rexpr, rexprg, rbind, rregexp = /[-.*+?^${}()|[\]\/\\]/g
function escapeRegExp(target) {
//http://stevenlevithan.com/regex/xregexp/
//将字符串安全格式化为正则表达式的源码
return (target + "").replace(rregexp, "\\$&")
}
var plugins = {
interpolate: function (array) {
openTag = array[0]
closeTag = array[1]
if (openTag === closeTag) {
throw new SyntaxError("openTag!==closeTag")
var test = openTag + "test" + closeTag
cinerator.innerHTML = test
if (cinerator.innerHTML !== test && cinerator.innerHTML.indexOf("&lt;") > -1) {
throw new SyntaxError("此定界符不合法")
}
cinerator.innerHTML = ""
}
kernel.openTag = openTag
kernel.closeTag = closeTag
var o = escapeRegExp(openTag),
c = escapeRegExp(closeTag)
rexpr = new RegExp(o + "([\\s\\S]*)" + c)
rexprg = new RegExp(o + "([\\s\\S]*)" + c, "g")
rbind = new RegExp(o + "[\\s\\S]*" + c + "|\\sms-")
}
}
kernel.plugins = plugins
kernel.plugins['interpolate'](["{{", "}}"])
kernel.async = true
kernel.debug = true
kernel.paths = {}
kernel.shim = {}
kernel.maxRepeatSize = 100
function $watch(expr, binding) {
var $events = this.$events || (this.$events = {}),
queue = $events[expr] || ($events[expr] = [])
if (typeof binding === "function") {
var backup = binding
backup.uuid = "_"+ (++bindingID)
binding = {
element: root,
type: "user-watcher",
handler: noop,
vmodels: [this],
expr: expr,
uuid: backup.uuid
}
binding.wildcard = /\*/.test(expr)
}
if (!binding.update) {
if (/\w\.*\B/.test(expr) || expr === "*") {
binding.getter = noop
var host = this
binding.update = function () {
var args = this.fireArgs || []
if (args[2])
binding.handler.apply(host, args)
delete this.fireArgs
}
queue.sync = true
avalon.Array.ensure(queue, binding)
} else {
avalon.injectBinding(binding)
}
if (backup) {
binding.handler = backup
}
} else if (!binding.oneTime) {
avalon.Array.ensure(queue, binding)
}
return function () {
binding.update = binding.getter = binding.handler = noop
binding.element = DOC.createElement("a")
}
}
function $emit(key, args) {
var event = this.$events
if (event && event[key]) {
if (args) {
args[2] = key
}
var arr = event[key]
notifySubscribers(arr, args)
if (args && event["*"] && !/\./.test(key)) {
for (var sub, k = 0; sub = event["*"][k++]; ) {
try {
sub.handler.apply(this, args)
} catch (e) {
}
}
}
var parent = this.$up
if (parent) {
if (this.$pathname) {
$emit.call(parent, this.$pathname + "." + key, args)//以确切的值往上冒泡
}
$emit.call(parent, "*." + key, args)//以模糊的值往上冒泡
}
} else {
parent = this.$up
if (this.$ups) {
for (var i in this.$ups) {
$emit.call(this.$ups[i], i + "." + key, args)//以确切的值往上冒泡
}
return
}
if (parent) {
var p = this.$pathname
if (p === "")
p = "*"
var path = p + "." + key
arr = path.split(".")
if (arr.indexOf("*") === -1) {
$emit.call(parent, path, args)//以确切的值往上冒泡
arr[1] = "*"
$emit.call(parent, arr.join("."), args)//以模糊的值往上冒泡
} else {
$emit.call(parent, path, args)//以确切的值往上冒泡
}
}
}
}
function collectDependency(el, key) {
do {
if (el.$watch) {
var e = el.$events || (el.$events = {})
var array = e[key] || (e[key] = [])
dependencyDetection.collectDependency(array)
return
}
el = el.$up
if (el) {
key = el.$pathname + "." + key
} else {
break
}
} while (true)
}
function notifySubscribers(subs, args) {
if (!subs)
return
if (new Date() - beginTime > 444 && typeof subs[0] === "object") {
rejectDisposeQueue()
}
var users = [], renders = []
for (var i = 0, sub; sub = subs[i++]; ) {
if (sub.type === "user-watcher") {
users.push(sub)
} else {
renders.push(sub)
}
}
if (kernel.async) {
buffer.render()//1
for (i = 0; sub = renders[i++]; ) {
if (sub.update) {
sub.uuid = sub.uuid || "_"+(++bindingID)
var uuid = sub.uuid
if (!buffer.queue[uuid]) {
buffer.queue[uuid] = "__"
buffer.queue.push(sub)
}
}
}
} else {
for (i = 0; sub = renders[i++]; ) {
if (sub.update) {
sub.update()//最小化刷新DOM树
}
}
}
for (i = 0; sub = users[i++]; ) {
if (args && args[2] === sub.expr || sub.wildcard) {
sub.fireArgs = args
}
sub.update()
}
}
//avalon最核心的方法的两个方法之一另一个是avalon.scan返回一个ViewModel(VM)
var VMODELS = avalon.vmodels = {} //所有vmodel都储存在这里
avalon.define = function (source) {
var $id = source.$id
if (!$id) {
log("warning: vm必须指定$id")
}
var vmodel = modelFactory(source)
vmodel.$id = $id
return VMODELS[$id] = vmodel
}
//一些不需要被监听的属性
var $$skipArray = oneObject("$id,$watch,$fire,$events,$model,$skipArray,$active,$pathname,$up,$track,$accessors,$ups")
var defineProperty = Object.defineProperty
var canHideOwn = true
//如果浏览器不支持ecma262v5的Object.defineProperties或者存在BUG比如IE8
//标准浏览器使用__defineGetter__, __defineSetter__实现
try {
defineProperty({}, "_", {
value: "x"
})
var defineProperties = Object.defineProperties
} catch (e) {
canHideOwn = false
}
function modelFactory(source, options) {
options = options || {}
options.watch = true
return observeObject(source, options)
}
//监听对象属性值的变化(注意,数组元素不是数组的属性),通过对劫持当前对象的访问器实现
//监听对象或数组的结构变化, 对对象的键值对进行增删重排, 或对数组的进行增删重排,都属于这范畴
// 通过比较前后代理VM顺序实现
function Component() {
}
function observeObject(source, options) {
if (!source || (source.$id && source.$accessors) || (source.nodeName && source.nodeType > 0)) {
return source
}
//source为原对象,不能是元素节点或null
//options,可选,配置对象,里面有old, force, watch这三个属性
options = options || nullObject
var force = options.force || nullObject
var old = options.old
var oldAccessors = old && old.$accessors || nullObject
var $vmodel = new Component() //要返回的对象, 它在IE6-8下可能被偷龙转凤
var accessors = {} //监控属性
var hasOwn = {}
var skip = []
var simple = []
var $skipArray = {}
if (source.$skipArray) {
$skipArray = oneObject(source.$skipArray)
delete source.$skipArray
}
//处理计算属性
var computed = source.$computed
if (computed) {
delete source.$computed
for (var name in computed) {
hasOwn[name] = true;
(function (key, value) {
var old
accessors[key] = {
get: function () {
return old = value.get.call(this)
},
set: function (x) {
if (typeof value.set === "function") {
var older = old
value.set.call(this, x)
var newer = this[key]
if (this.$fire && (newer !== older)) {
this.$fire(key, newer, older)
}
}
},
enumerable: true,
configurable: true
}
})(name, computed[name])// jshint ignore:line
}
}
for (name in source) {
var value = source[name]
if (!$$skipArray[name])
hasOwn[name] = true
if (typeof value === "function" || (value && value.nodeName && value.nodeType > 0) ||
(!force[name] && (name.charAt(0) === "$" || $$skipArray[name] || $skipArray[name]))) {
skip.push(name)
} else if (isComputed(value)) {
log("warning:计算属性建议放在$computed对象中统一定义");
(function (key, value) {
var old
accessors[key] = {
get: function () {
return old = value.get.call(this)
},
set: function (x) {
if (typeof value.set === "function") {
var older = old
value.set.call(this, x)
var newer = this[key]
if (this.$fire && (newer !== older)) {
this.$fire(key, newer, older)
}
}
},
enumerable: true,
configurable: true
}
})(name, value)// jshint ignore:line
} else {
simple.push(name)
if (oldAccessors[name]) {
accessors[name] = oldAccessors[name]
} else {
accessors[name] = makeGetSet(name, value)
}
}
}
accessors["$model"] = $modelDescriptor
$vmodel = defineProperties($vmodel, accessors, source)
function trackBy(name) {
return hasOwn[name] === true
}
skip.forEach(function (name) {
$vmodel[name] = source[name]
})
/* jshint ignore:start */
hideProperty($vmodel, "$ups", null)
hideProperty($vmodel, "$id", "anonymous")
hideProperty($vmodel, "$up", old ? old.$up : null)
hideProperty($vmodel, "$track", Object.keys(hasOwn))
hideProperty($vmodel, "$active", false)
hideProperty($vmodel, "$pathname", old ? old.$pathname : "")
hideProperty($vmodel, "$accessors", accessors)
hideProperty($vmodel, "hasOwnProperty", trackBy)
if (options.watch) {
hideProperty($vmodel, "$watch", function () {
return $watch.apply($vmodel, arguments)
})
hideProperty($vmodel, "$fire", function (path, a) {
if(path.indexOf("all!") === 0 ){
var ee = path.slice(4)
for(var i in avalon.vmodels){
var v = avalon.vmodels[i]
v.$fire && v.$fire.apply(v, [ee, a])
}
}else{
$emit.call($vmodel, path, [a])
}
})
}
/* jshint ignore:end */
//必须设置了$active,$events
simple.forEach(function (name) {
var oldVal = old && old[name]
var val = $vmodel[name] = source[name]
if (val && typeof val === "object") {
val.$up = $vmodel
val.$pathname = name
}
$emit.call($vmodel, name, [val,oldVal])
})
for (name in computed) {
value = $vmodel[name]
}
$vmodel.$active = true
return $vmodel
}
/*
新的VM拥有如下私有属性
$id: vm.id
$events: 放置$watch回调与绑定对象
$watch: 增强版$watch
$fire: 触发$watch回调
$track:一个数组,里面包含用户定义的所有键名
$active:boolean,false时防止依赖收集
$model:返回一个纯净的JS对象
$accessors:放置所有读写器的数据描述对象
$up:返回其上级对象
$pathname:返回此对象在上级对象的名字,注意,数组元素的$pathname为空字符串
=============================
$skipArray:用于指定不可监听的属性,但VM生成是没有此属性的
*/
function isComputed(val) {//speed up!
if (val && typeof val === "object") {
for (var i in val) {
if (i !== "get" && i !== "set") {
return false
}
}
return typeof val.get === "function"
}
}
function makeGetSet(key, value) {
var childVm
value = NaN
return {
get: function () {
if (this.$active) {
collectDependency(this, key)
}
return value
},
set: function (newVal) {
if (value === newVal)
return
var oldValue = value
childVm = observe(newVal, value)
if (childVm) {
value = childVm
} else {
childVm = void 0
value = newVal
}
if (Object(childVm) === childVm) {
childVm.$pathname = key
childVm.$up = this
}
if (this.$active) {
$emit.call(this, key, [value, oldValue])
}
},
enumerable: true,
configurable: true
}
}
function observe(obj, old, hasReturn, watch) {
if (Array.isArray(obj)) {
return observeArray(obj, old, watch)
} else if (avalon.isPlainObject(obj)) {
if (old && typeof old === 'object') {
var keys = getKeys(obj)
var keys2 = getKeys(old)
if (keys.join(";") === keys2.join(";")) {
for (var i in obj) {
if (obj.hasOwnProperty(i)) {
old[i] = obj[i]
}
}
return old
}
old.$active = false
}
return observeObject(obj, {
old: old,
watch: watch
})
}
if (hasReturn) {
return obj
}
}
var getKeys = rnative.test(Object.key) ? Object.key : function (a) {
var ret = []
for (var i in a) {
if (a.hasOwnProperty(i) && !$$skipArray[i]) {
ret.push(i)
}
}
return ret
}
function observeArray(array, old, watch) {
if (old && old.splice) {
var args = [0, old.length].concat(array)
old.splice.apply(old, args)
return old
} else {
for (var i in newProto) {
array[i] = newProto[i]
}
hideProperty(array, "$up", null)
hideProperty(array, "$pathname", "")
hideProperty(array, "$track", createTrack(array.length))
array._ = observeObject({
length: NaN
}, {
watch: true
})
array._.length = array.length
array._.$watch("length", function (a, b) {
$emit.call(array.$up, array.$pathname + ".length", [a, b])
})
if (watch) {
hideProperty(array, "$watch", function () {
return $watch.apply(array, arguments)
})
}
if (W3C) {
Object.defineProperty(array, "$model", $modelDescriptor)
} else {
array.$model = toJson(array)
}
for (var j = 0, n = array.length; j < n; j++) {
var el = array[j] = observe(array[j], 0, 1, 1)
if (Object(el) === el) {//#1077
el.$up = array
}
}
return array
}
}
function hideProperty(host, name, value) {
if (canHideOwn) {
Object.defineProperty(host, name, {
value: value,
writable: true,
enumerable: false,
configurable: true
})
} else {
host[name] = value
}
}
function toJson(val) {
var xtype = avalon.type(val)
if (xtype === "array") {
var array = []
for (var i = 0; i < val.length; i++) {
array[i] = toJson(val[i])
}
return array
} else if (xtype === "object") {
var obj = {}
for (i in val) {
if(i === "__proxy__" || i === "__data__" || i === "__const__")
continue
if (val.hasOwnProperty(i)) {
var value = val[i]
obj[i] = value && value.nodeType ? value :toJson(value)
}
}
return obj
}
return val
}
var $modelDescriptor = {
get: function () {
return toJson(this)
},
set: noop,
enumerable: false,
configurable: true
}
//===================修复浏览器对Object.defineProperties的支持=================
if (!canHideOwn) {
if ("__defineGetter__" in avalon) {
defineProperty = function (obj, prop, desc) {
if ('value' in desc) {
obj[prop] = desc.value
}
if ("get" in desc) {
obj.__defineGetter__(prop, desc.get)
}
if ('set' in desc) {
obj.__defineSetter__(prop, desc.set)
}
return obj
}
defineProperties = function (obj, descs) {
for (var prop in descs) {
if (descs.hasOwnProperty(prop)) {
defineProperty(obj, prop, descs[prop])
}
}
return obj
}
}
if (IEVersion) {
var VBClassPool = {}
window.execScript([// jshint ignore:line
"Function parseVB(code)",
"\tExecuteGlobal(code)",
"End Function" //转换一段文本为VB代码
].join("\n"), "VBScript")
function VBMediator(instance, accessors, name, value) {// jshint ignore:line
var accessor = accessors[name]
if (arguments.length === 4) {
accessor.set.call(instance, value)
} else {
return accessor.get.call(instance)
}
}
defineProperties = function (name, accessors, properties) {
// jshint ignore:line
var buffer = []
buffer.push(
"\r\n\tPrivate [__data__], [__proxy__]",
"\tPublic Default Function [__const__](d" + expose + ", p" + expose + ")",
"\t\tSet [__data__] = d" + expose + ": set [__proxy__] = p" + expose,
"\t\tSet [__const__] = Me", //链式调用
"\tEnd Function")
//添加普通属性,因为VBScript对象不能像JS那样随意增删属性必须在这里预先定义好
var uniq = {}
//添加访问器属性
for (name in accessors) {
uniq[name] = true
buffer.push(
//由于不知对方会传入什么,因此set, let都用上
"\tPublic Property Let [" + name + "](val" + expose + ")", //setter
"\t\tCall [__proxy__](Me,[__data__], \"" + name + "\", val" + expose + ")",
"\tEnd Property",
"\tPublic Property Set [" + name + "](val" + expose + ")", //setter
"\t\tCall [__proxy__](Me,[__data__], \"" + name + "\", val" + expose + ")",
"\tEnd Property",
"\tPublic Property Get [" + name + "]", //getter
"\tOn Error Resume Next", //必须优先使用set语句,否则它会误将数组当字符串返回
"\t\tSet[" + name + "] = [__proxy__](Me,[__data__],\"" + name + "\")",
"\tIf Err.Number <> 0 Then",
"\t\t[" + name + "] = [__proxy__](Me,[__data__],\"" + name + "\")",
"\tEnd If",
"\tOn Error Goto 0",
"\tEnd Property")
}
for (name in properties) {
if (uniq[name] !== true) {
uniq[name] = true
buffer.push("\tPublic [" + name + "]")
}
}
for (name in $$skipArray) {
if (uniq[name] !== true) {
uniq[name] = true
buffer.push("\tPublic [" + name + "]")
}
}
buffer.push("\tPublic [" + 'hasOwnProperty' + "]")
buffer.push("End Class")
var body = buffer.join("\r\n")
var className = VBClassPool[body]
if (!className) {
className = generateID("VBClass")
window.parseVB("Class " + className + body)
window.parseVB([
"Function " + className + "Factory(a, b)", //创建实例并传入两个关键的参数
"\tDim o",
"\tSet o = (New " + className + ")(a, b)",
"\tSet " + className + "Factory = o",
"End Function"
].join("\r\n"))
VBClassPool[body] = className
}
var ret = window[className + "Factory"](accessors, VBMediator) //得到其产品
return ret //得到其产品
}
}
}
/*********************************************************************
* 监控数组与ms-each, ms-repeat配合使用 *
**********************************************************************/
var arrayMethods = ['push', 'pop', 'shift', 'unshift', 'splice']
var arrayProto = Array.prototype
var newProto = {
notify: function () {
$emit.call(this.$up, this.$pathname)
},
set: function (index, val) {
if (((index >>> 0) === index) && this[index] !== val) {
if (index > this.length) {
throw Error(index + "set方法的第一个参数不能大于原数组长度")
}
$emit.call(this.$up, this.$pathname + ".*", [val, this[index]])
this.splice(index, 1, val)
}
},
contains: function (el) { //判定是否包含
return this.indexOf(el) !== -1
},
ensure: function (el) {
if (!this.contains(el)) { //只有不存在才push
this.push(el)
}
return this
},
pushArray: function (arr) {
return this.push.apply(this, arr)
},
remove: function (el) { //移除第一个等于给定值的元素
return this.removeAt(this.indexOf(el))
},
removeAt: function (index) { //移除指定索引上的元素
if ((index >>> 0) === index) {
return this.splice(index, 1)
}
return []
},
size: function () { //取得数组长度这个函数可以同步视图length不能
return this._.length
},
removeAll: function (all) { //移除N个元素
var eliminate = Array.isArray(all) ?
function(el) {
return all.indexOf(el) !== -1
} : typeof all === 'function' ?
all : false
if (eliminate) {
for (var i = this.length - 1; i >= 0; i--) {
if (eliminate(this[i], i)) {
_splice.call(this.$track, i, 1)
_splice.call(this, i, 1)
}
}
} else {
_splice.call(this.$track, 0, this.length)
_splice.call(this, 0, this.length)
}
if (!W3C) {
this.$model = toJson(this)
}
this.notify()
this._.length = this.length
},
clear: function () {
this.removeAll()
return this
}
}
var _splice = arrayProto.splice
arrayMethods.forEach(function (method) {
var original = arrayProto[method]
newProto[method] = function () {
// 继续尝试劫持数组元素的属性
var args = []
for (var i = 0, n = arguments.length; i < n; i++) {
args[i] = observe(arguments[i], 0, 1, 1)
}
var result = original.apply(this, args)
addTrack(this.$track, method, args)
if (!W3C) {
this.$model = toJson(this)
}
this.notify()
this._.length = this.length
return result
}
})
"sort,reverse".replace(rword, function (method) {
newProto[method] = function () {
var oldArray = this.concat() //保持原来状态的旧数组
var newArray = this
var mask = Math.random()
var indexes = []
var hasSort = false
arrayProto[method].apply(newArray, arguments) //排序
for (var i = 0, n = oldArray.length; i < n; i++) {
var neo = newArray[i]
var old = oldArray[i]
if (neo === old) {
indexes.push(i)
} else {
var index = oldArray.indexOf(neo)
indexes.push(index)//得到新数组的每个元素在旧数组对应的位置
oldArray[index] = mask //屏蔽已经找过的元素
hasSort = true
}
}
if (hasSort) {
sortByIndex(this.$track, indexes)
if (!W3C) {
this.$model = toJson(this)
}
this.notify()
}
return this
}
})
function sortByIndex(array, indexes) {
var map = {};
for (var i = 0, n = indexes.length; i < n; i++) {
map[i] = array[i]
var j = indexes[i]
if (j in map) {
array[i] = map[j]
delete map[j]
} else {
array[i] = array[j]
}
}
}
function createTrack(n) {
var ret = []
for (var i = 0; i < n; i++) {
ret[i] = generateID("$proxy$each")
}
return ret
}
function addTrack(track, method, args) {
switch (method) {
case 'push':
case 'unshift':
args = createTrack(args.length)
break
case 'splice':
if (args.length > 2) {
// 0, 5, a, b, c --> 0, 2, 0
// 0, 5, a, b, c, d, e, f, g--> 0, 0, 3
var del = args[1]
var add = args.length - 2
// args = [args[0], Math.max(del - add, 0)].concat(createTrack(Math.max(add - del, 0)))
args = [args[0], args[1]].concat(createTrack(args.length - 2))
}
break
}
Array.prototype[method].apply(track, args)
}
/*********************************************************************
* 依赖调度系统 *
**********************************************************************/
//检测两个对象间的依赖关系
var dependencyDetection = (function () {
var outerFrames = []
var currentFrame
return {
begin: function (binding) {
//accessorObject为一个拥有callback的对象
outerFrames.push(currentFrame)
currentFrame = binding
},
end: function () {
currentFrame = outerFrames.pop()
},
collectDependency: function (array) {
if (currentFrame) {
//被dependencyDetection.begin调用
currentFrame.callback(array)
}
}
};
})()
//将绑定对象注入到其依赖项的订阅数组中
var roneval = /^on$/
function returnRandom() {
return new Date() - 0
}
avalon.injectBinding = function (binding) {
binding.handler = binding.handler || directives[binding.type].update || noop
binding.update = function () {
var begin = false
if (!binding.getter) {
begin = true
dependencyDetection.begin({
callback: function (array) {
injectDependency(array, binding)
}
})
binding.getter = parseExpr(binding.expr, binding.vmodels, binding)
binding.observers.forEach(function (a) {
a.v.$watch(a.p, binding)
})
delete binding.observers
}
try {
var args = binding.fireArgs, a, b
delete binding.fireArgs
if (!args) {
if (binding.type === "on") {
a = binding.getter + ""
} else {
try {
a = binding.getter.apply(0, binding.args)
} catch(e) {
a = null
avalon.log('execute expr : ' + binding.expr + ' Error. ' + e)
}
}
} else {
a = args[0]
b = args[1]
}
b = typeof b === "undefined" ? binding.oldValue : b
if (binding._filters) {
a = filters.$filter.apply(0, [a].concat(binding._filters))
}
if (binding.signature) {
var xtype = avalon.type(a)
if (xtype !== "array" && xtype !== "object") {
throw Error("warning:" + binding.expr + "只能是对象或数组")
}
binding.xtype = xtype
var vtrack = getProxyIds(binding.proxies || [], xtype)
var mtrack = a.$track || (xtype === "array" ? createTrack(a.length) :
Object.keys(a))
binding.track = mtrack
if (vtrack !== mtrack.join(";")) {
binding.handler(a, b)
binding.oldValue = 1
}
} else if (Array.isArray(a) ? a.length !== (b && b.length) : false) {
binding.handler(a, b)
binding.oldValue = a.concat()
} else if (!("oldValue" in binding) || a !== b) {
binding.handler(a, b)
binding.oldValue = Array.isArray(a) ? a.concat() : a
}
} catch (e) {
delete binding.getter
log("warning:exception throwed in [avalon.injectBinding] ", e)
var node = binding.element
if (node && node.nodeType === 3) {
node.nodeValue = openTag + (binding.oneTime ? "::" : "") + binding.expr + closeTag
}
} finally {
begin && dependencyDetection.end()
}
}
binding.update()
}
//将依赖项(比它高层的访问器或构建视图刷新函数的绑定对象)注入到订阅者数组
function injectDependency(list, binding) {
if (binding.oneTime)
return
if (list && avalon.Array.ensure(list, binding) && binding.element) {
injectDisposeQueue(binding, list)
if (new Date() - beginTime > 444) {
rejectDisposeQueue()
}
}
}
function getProxyIds(a, isArray) {
var ret = []
for (var i = 0, el; el = a[i++]; ) {
ret.push(isArray ? el.$id : el.$key)
}
return ret.join(";")
}
/*********************************************************************
* 定时GC回收机制 (基于1.6基于频率的GC) *
**********************************************************************/
var disposeQueue = avalon.$$subscribers = []
var beginTime = new Date()
//添加到回收列队中
function injectDisposeQueue(data, list) {
data.list = list
data.i = ~~data.i
if (!data.uuid) {
data.uuid = "_" + (++bindingID)
}
if (!disposeQueue[data.uuid]) {
disposeQueue[data.uuid] = "__"
disposeQueue.push(data)
}
}
var lastGCIndex = 0
function rejectDisposeQueue(data) {
var i = lastGCIndex || disposeQueue.length
var threshold = 0
while (data = disposeQueue[--i]) {
if (data.i < 7) {
if (data.element === null) {
disposeQueue.splice(i, 1)
if (data.list) {
avalon.Array.remove(data.list, data)
delete disposeQueue[data.uuid]
}
continue
}
if (shouldDispose(data.element)) { //如果它的虚拟DOM不在VTree上或其属性不在VM上
disposeQueue.splice(i, 1)
avalon.Array.remove(data.list, data)
disposeData(data)
//avalon会在每次全量更新时,比较上次执行时间,
//假若距离上次有半秒,就会发起一次GC,并且只检测当中的500个绑定
//而一个正常的页面不会超过2000个绑定(500即取其4分之一)
//用户频繁操作页面,那么2,3秒内就把所有绑定检测一遍,将无效的绑定移除
if (threshold++ > 500) {
lastGCIndex = i
break
}
continue
}
data.i++
//基于检测频率如果检测过7次可以认为其是长久存在的节点那么以后每7次才检测一次
if (data.i === 7) {
data.i = 14
}
} else {
data.i--
}
}
beginTime = new Date()
}
function disposeData(data) {
delete disposeQueue[data.uuid] // 先清除,不然无法回收了
data.element = null
data.rollback && data.rollback()
for (var key in data) {
data[key] = null
}
}
function shouldDispose(el) {
try {//IE下如果文本节点脱离DOM树访问parentNode会报错
var fireError = el.parentNode.nodeType
} catch (e) {
return true
}
if (el.ifRemove) {
// 如果节点被放到ifGroup才移除
if (!root.contains(el.ifRemove) && (ifGroup === el.parentNode)) {
el.parentNode && el.parentNode.removeChild(el)
return true
}
}
return el.msRetain ? 0 : (el.nodeType === 1 ? !root.contains(el) : !avalon.contains(root, el))
}
/************************************************************************
* HTML处理(parseHTML, innerHTML, clearHTML) *
*************************************************************************/
// We have to close these tags to support XHTML
var tagHooks = {
area: [1, "<map>", "</map>"],
param: [1, "<object>", "</object>"],
col: [2, "<table><colgroup>", "</colgroup></table>"],
legend: [1, "<fieldset>", "</fieldset>"],
option: [1, "<select multiple='multiple'>", "</select>"],
thead: [1, "<table>", "</table>"],
tr: [2, "<table>", "</table>"],
td: [3, "<table><tr>", "</tr></table>"],
g: [1, '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">', '</svg>'],
//IE6-8在用innerHTML生成节点时不能直接创建no-scope元素与HTML5的新标签
_default: W3C ? [0, "", ""] : [1, "X<div>", "</div>"] //div可以不用闭合
}
tagHooks.th = tagHooks.td
tagHooks.optgroup = tagHooks.option
tagHooks.tbody = tagHooks.tfoot = tagHooks.colgroup = tagHooks.caption = tagHooks.thead
String("circle,defs,ellipse,image,line,path,polygon,polyline,rect,symbol,text,use").replace(rword, function (tag) {
tagHooks[tag] = tagHooks.g //处理SVG
})
var rtagName = /<([\w:]+)/ //取得其tagName
var rxhtml = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig
var rcreate = W3C ? /[^\d\D]/ : /(<(?:script|link|style|meta|noscript))/ig
var scriptTypes = oneObject(["", "text/javascript", "text/ecmascript", "application/ecmascript", "application/javascript"])
var rnest = /<(?:tb|td|tf|th|tr|col|opt|leg|cap|area)/ //需要处理套嵌关系的标签
var script = DOC.createElement("script")
var rhtml = /<|&#?\w+;/
avalon.parseHTML = function (html) {
var fragment = avalonFragment.cloneNode(false)
if (typeof html !== "string") {
return fragment
}
if (!rhtml.test(html)) {
fragment.appendChild(DOC.createTextNode(html))
return fragment
}
html = html.replace(rxhtml, "<$1></$2>").trim()
var tag = (rtagName.exec(html) || ["", ""])[1].toLowerCase(),
//取得其标签名
wrap = tagHooks[tag] || tagHooks._default,
wrapper = cinerator,
firstChild, neo
if (!W3C) { //fix IE
html = html.replace(rcreate, "<br class=msNoScope>$1") //在link style script等标签之前添加一个补丁
}
wrapper.innerHTML = wrap[1] + html + wrap[2]
var els = wrapper.getElementsByTagName("script")
if (els.length) { //使用innerHTML生成的script节点不会发出请求与执行text属性
for (var i = 0, el; el = els[i++];) {
if (scriptTypes[el.type]) {
//以偷龙转凤方式恢复执行脚本功能
neo = script.cloneNode(false) //FF不能省略参数
ap.forEach.call(el.attributes, function (attr) {
if (attr && attr.specified) {
neo[attr.name] = attr.value //复制其属性
neo.setAttribute(attr.name, attr.value)
}
}) // jshint ignore:line
neo.text = el.text
el.parentNode.replaceChild(neo, el) //替换节点
}
}
}
if (!W3C) { //fix IE
var target = wrap[1] === "X<div>" ? wrapper.lastChild.firstChild : wrapper.lastChild
if (target && target.tagName === "TABLE" && tag !== "tbody") {
//IE6-7处理 <thead> --> <thead>,<tbody>
//<tfoot> --> <tfoot>,<tbody>
//<table> --> <table><tbody></table>
for (els = target.childNodes, i = 0; el = els[i++];) {
if (el.tagName === "TBODY" && !el.innerHTML) {
target.removeChild(el)
break
}
}
}
els = wrapper.getElementsByTagName("br")
var n = els.length
while (el = els[--n]) {
if (el.className === "msNoScope") {
el.parentNode.removeChild(el)
}
}
for (els = wrapper.all, i = 0; el = els[i++];) { //fix VML
if (isVML(el)) {
fixVML(el)
}
}
}
//移除我们为了符合套嵌关系而添加的标签
for (i = wrap[0]; i--; wrapper = wrapper.lastChild) {}
while (firstChild = wrapper.firstChild) { // 将wrapper上的节点转移到文档碎片上
fragment.appendChild(firstChild)
}
return fragment
}
function isVML(src) {
var nodeName = src.nodeName
return nodeName.toLowerCase() === nodeName && src.scopeName && src.outerText === ""
}
function fixVML(node) {
if (node.currentStyle.behavior !== "url(#default#VML)") {
node.style.behavior = "url(#default#VML)"
node.style.display = "inline-block"
node.style.zoom = 1 //hasLayout
}
}
avalon.innerHTML = function (node, html) {
if (!W3C && (!rcreate.test(html) && !rnest.test(html))) {
try {
node.innerHTML = html
return
} catch (e) {}
}
var a = this.parseHTML(html)
this.clearHTML(node).appendChild(a)
}
avalon.clearHTML = function (node) {
node.textContent = ""
while (node.firstChild) {
node.removeChild(node.firstChild)
}
return node
}
/*********************************************************************
* avalon的原型方法定义区 *
**********************************************************************/
function hyphen(target) {
//转换为连字符线风格
return target.replace(/([a-z\d])([A-Z]+)/g, "$1-$2").toLowerCase()
}
function camelize(target) {
//提前判断提高getStyle等的效率
if (!target || target.indexOf("-") < 0 && target.indexOf("_") < 0) {
return target
}
//转换为驼峰风格
return target.replace(/[-_][^-_]/g, function (match) {
return match.charAt(1).toUpperCase()
})
}
var fakeClassListMethods = {
_toString: function () {
var node = this.node
var cls = node.className
var str = typeof cls === "string" ? cls : cls.baseVal
return str.split(/\s+/).join(" ")
},
_contains: function (cls) {
return (" " + this + " ").indexOf(" " + cls + " ") > -1
},
_add: function (cls) {
if (!this.contains(cls)) {
this._set(this + " " + cls)
}
},
_remove: function (cls) {
this._set((" " + this + " ").replace(" " + cls + " ", " "))
},
__set: function (cls) {
cls = cls.trim()
var node = this.node
if (rsvg.test(node)) {
//SVG元素的className是一个对象 SVGAnimatedString { baseVal="", animVal=""}只能通过set/getAttribute操作
node.setAttribute("class", cls)
} else {
node.className = cls
}
} //toggle存在版本差异因此不使用它
}
function fakeClassList(node) {
if (!("classList" in node)) {
node.classList = {
node: node
}
for (var k in fakeClassListMethods) {
node.classList[k.slice(1)] = fakeClassListMethods[k]
}
}
return node.classList
}
"add,remove".replace(rword, function (method) {
avalon.fn[method + "Class"] = function (cls) {
var el = this[0]
//https://developer.mozilla.org/zh-CN/docs/Mozilla/Firefox/Releases/26
if (cls && typeof cls === "string" && el && el.nodeType === 1) {
cls.replace(/\S+/g, function (c) {
fakeClassList(el)[method](c)
})
}
return this
}
})
avalon.fn.mix({
hasClass: function (cls) {
var el = this[0] || {}
return el.nodeType === 1 && fakeClassList(el).contains(cls)
},
toggleClass: function (value, stateVal) {
var className, i = 0
var classNames = String(value).match(/\S+/g) || []
var isBool = typeof stateVal === "boolean"
while ((className = classNames[i++])) {
var state = isBool ? stateVal : !this.hasClass(className)
this[state ? "addClass" : "removeClass"](className)
}
return this
},
attr: function (name, value) {
if (arguments.length === 2) {
this[0].setAttribute(name, value)
return this
} else {
return this[0].getAttribute(name)
}
},
data: function (name, value) {
name = "data-" + hyphen(name || "")
switch (arguments.length) {
case 2:
this.attr(name, value)
return this
case 1:
var val = this.attr(name)
return parseData(val)
case 0:
var ret = {}
ap.forEach.call(this[0].attributes, function (attr) {
if (attr) {
name = attr.name
if (!name.indexOf("data-")) {
name = camelize(name.slice(5))
ret[name] = parseData(attr.value)
}
}
})
return ret
}
},
removeData: function (name) {
name = "data-" + hyphen(name)
this[0].removeAttribute(name)
return this
},
css: function (name, value) {
if (avalon.isPlainObject(name)) {
for (var i in name) {
avalon.css(this, i, name[i])
}
} else {
var ret = avalon.css(this, name, value)
}
return ret !== void 0 ? ret : this
},
position: function () {
var offsetParent, offset,
elem = this[0],
parentOffset = {
top: 0,
left: 0
}
if (!elem) {
return
}
if (this.css("position") === "fixed") {
offset = elem.getBoundingClientRect()
} else {
offsetParent = this.offsetParent() //得到真正的offsetParent
offset = this.offset() // 得到正确的offsetParent
if (offsetParent[0].tagName !== "HTML") {
parentOffset = offsetParent.offset()
}
parentOffset.top += avalon.css(offsetParent[0], "borderTopWidth", true)
parentOffset.left += avalon.css(offsetParent[0], "borderLeftWidth", true)
// Subtract offsetParent scroll positions
parentOffset.top -= offsetParent.scrollTop()
parentOffset.left -= offsetParent.scrollLeft()
}
return {
top: offset.top - parentOffset.top - avalon.css(elem, "marginTop", true),
left: offset.left - parentOffset.left - avalon.css(elem, "marginLeft", true)
}
},
offsetParent: function () {
var offsetParent = this[0].offsetParent
while (offsetParent && avalon.css(offsetParent, "position") === "static") {
offsetParent = offsetParent.offsetParent;
}
return avalon(offsetParent || root)
},
bind: function (type, fn, phase) {
if (this[0]) { //此方法不会链
return avalon.bind(this[0], type, fn, phase)
}
},
unbind: function (type, fn, phase) {
if (this[0]) {
avalon.unbind(this[0], type, fn, phase)
}
return this
},
val: function (value) {
var node = this[0]
if (node && node.nodeType === 1) {
var get = arguments.length === 0
var access = get ? ":get" : ":set"
var fn = valHooks[getValType(node) + access]
if (fn) {
var val = fn(node, value)
} else if (get) {
return (node.value || "").replace(/\r/g, "")
} else {
node.value = value
}
}
return get ? val : this
}
})
function parseData(data) {
try {
if (typeof data === "object")
return data
data = data === "true" ? true :
data === "false" ? false :
data === "null" ? null : +data + "" === data ? +data : rbrace.test(data) ? avalon.parseJSON(data) : data
} catch (e) {}
return data
}
var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/,
rvalidchars = /^[\],:{}\s]*$/,
rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,
rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g
avalon.parseJSON = window.JSON ? JSON.parse : function (data) {
if (typeof data === "string") {
data = data.trim();
if (data) {
if (rvalidchars.test(data.replace(rvalidescape, "@")
.replace(rvalidtokens, "]")
.replace(rvalidbraces, ""))) {
return (new Function("return " + data))() // jshint ignore:line
}
}
avalon.error("Invalid JSON: " + data)
}
return data
}
avalon.fireDom = function (elem, type, opts) {
if (DOC.createEvent) {
var hackEvent = DOC.createEvent("Events");
hackEvent.initEvent(type, true, true, opts)
avalon.mix(hackEvent, opts)
elem.dispatchEvent(hackEvent)
} else if (root.contains(elem)) {//IE6-8触发事件必须保证在DOM树中,否则报"SCRIPT16389: 未指明的错误"
hackEvent = DOC.createEventObject()
avalon.mix(hackEvent, opts)
elem.fireEvent("on" + type, hackEvent)
}
}
//生成avalon.fn.scrollLeft, avalon.fn.scrollTop方法
avalon.each({
scrollLeft: "pageXOffset",
scrollTop: "pageYOffset"
}, function (method, prop) {
avalon.fn[method] = function (val) {
var node = this[0] || {},
win = getWindow(node),
top = method === "scrollTop"
if (!arguments.length) {
return win ? (prop in win) ? win[prop] : root[method] : node[method]
} else {
if (win) {
win.scrollTo(!top ? val : avalon(win).scrollLeft(), top ? val : avalon(win).scrollTop())
} else {
node[method] = val
}
}
}
})
function getWindow(node) {
return node.window && node.document ? node : node.nodeType === 9 ? node.defaultView || node.parentWindow : false;
}
//=============================css相关=======================
var cssHooks = avalon.cssHooks = {}
var prefixes = ["", "-webkit-", "-o-", "-moz-", "-ms-"]
var cssMap = {
"float": W3C ? "cssFloat" : "styleFloat"
}
avalon.cssNumber = oneObject("animationIterationCount,columnCount,order,flex,flexGrow,flexShrink,fillOpacity,fontWeight,lineHeight,opacity,orphans,widows,zIndex,zoom")
avalon.cssName = function (name, host, camelCase) {
if (cssMap[name]) {
return cssMap[name]
}
host = host || root.style
for (var i = 0, n = prefixes.length; i < n; i++) {
camelCase = camelize(prefixes[i] + name)
if (camelCase in host) {
return (cssMap[name] = camelCase)
}
}
return null
}
cssHooks["@:set"] = function (node, name, value) {
try { //node.style.width = NaN;node.style.width = "xxxxxxx";node.style.width = undefine 在旧式IE下会抛异常
node.style[name] = value
} catch (e) {}
}
if (window.getComputedStyle) {
cssHooks["@:get"] = function (node, name) {
if (!node || !node.style) {
throw new Error("getComputedStyle要求传入一个节点 " + node)
}
var ret, styles = getComputedStyle(node, null)
if (styles) {
ret = name === "filter" ? styles.getPropertyValue(name) : styles[name]
if (ret === "") {
ret = node.style[name] //其他浏览器需要我们手动取内联样式
}
}
return ret
}
cssHooks["opacity:get"] = function (node) {
var ret = cssHooks["@:get"](node, "opacity")
return ret === "" ? "1" : ret
}
} else {
var rnumnonpx = /^-?(?:\d*\.)?\d+(?!px)[^\d\s]+$/i
var rposition = /^(top|right|bottom|left)$/
var ralpha = /alpha\([^)]+\)/i
var ropactiy = /(opacity|\d(\d|\.)*)/g
var ie8 = !!window.XDomainRequest
var border = {
thin: ie8 ? '1px' : '2px',
medium: ie8 ? '3px' : '4px',
thick: ie8 ? '5px' : '6px'
}
cssHooks["@:get"] = function (node, name) {
//取得精确值不过它有可能是带em,pc,mm,pt,%等单位
var currentStyle = node.currentStyle
var ret = currentStyle[name]
if ((rnumnonpx.test(ret) && !rposition.test(ret))) {
//①保存原有的style.left, runtimeStyle.left,
var style = node.style,
left = style.left,
rsLeft = node.runtimeStyle.left
//②由于③处的style.left = xxx会影响到currentStyle.left
//因此把它currentStyle.left放到runtimeStyle.left
//runtimeStyle.left拥有最高优先级不会style.left影响
node.runtimeStyle.left = currentStyle.left
//③将精确值赋给到style.left然后通过IE的另一个私有属性 style.pixelLeft
//得到单位为px的结果fontSize的分支见http://bugs.jquery.com/ticket/760
style.left = name === 'fontSize' ? '1em' : (ret || 0)
ret = style.pixelLeft + "px"
//④还原 style.leftruntimeStyle.left
style.left = left
node.runtimeStyle.left = rsLeft
}
if (ret === "medium") {
name = name.replace("Width", "Style")
//border width 默认值为medium即使其为0"
if (currentStyle[name] === "none") {
ret = "0px"
}
}
return ret === "" ? "auto" : border[ret] || ret
}
cssHooks["opacity:set"] = function (node, name, value) {
var style = node.style
var opacity = Number(value) <= 1 ? 'alpha(opacity=' + value * 100 + ')' : ''
var filter = style.filter || ''
style.zoom = 1
//不能使用以下方式设置透明度
//node.filters.alpha.opacity = value * 100
style.filter = (ralpha.test(filter) ?
filter.replace(ralpha, opacity) :
filter + ' ' + opacity).trim()
if (!style.filter) {
style.removeAttribute('filter')
}
}
cssHooks["opacity:get"] = function (node) {
var match = node.style.filter.match(ropactiy) || []
var ret = false
for (var i = 0, el; el = match[i++];) {
if (el === 'opacity') {
ret = true
} else if (ret) {
return (el / 100) + ''
}
}
return '1'
}
}
"top,left".replace(rword, function (name) {
cssHooks[name + ":get"] = function (node) {
var computed = cssHooks["@:get"](node, name)
return /px$/.test(computed) ? computed :
avalon(node).position()[name] + "px"
}
})
var cssShow = {
position: "absolute",
visibility: "hidden",
display: "block"
}
var rdisplayswap = /^(none|table(?!-c[ea]).+)/
function showHidden(node, array) {
//http://www.cnblogs.com/rubylouvre/archive/2012/10/27/2742529.html
if (node.offsetWidth <= 0) { //opera.offsetWidth可能小于0
if (rdisplayswap.test(cssHooks["@:get"](node, "display"))) {
var obj = {
node: node
}
for (var name in cssShow) {
obj[name] = node.style[name]
node.style[name] = cssShow[name]
}
array.push(obj)
}
var parent = node.parentNode
if (parent && parent.nodeType === 1) {
showHidden(parent, array)
}
}
}
"Width,Height".replace(rword, function (name) { //fix 481
var method = name.toLowerCase(),
clientProp = "client" + name,
scrollProp = "scroll" + name,
offsetProp = "offset" + name
cssHooks[method + ":get"] = function (node, which, override) {
var boxSizing = -4
if (typeof override === "number") {
boxSizing = override
}
which = name === "Width" ? ["Left", "Right"] : ["Top", "Bottom"]
var ret = node[offsetProp] // border-box 0
if (boxSizing === 2) { // margin-box 2
return ret + avalon.css(node, "margin" + which[0], true) + avalon.css(node, "margin" + which[1], true)
}
if (boxSizing < 0) { // padding-box -2
ret = ret - avalon.css(node, "border" + which[0] + "Width", true) - avalon.css(node, "border" + which[1] + "Width", true)
}
if (boxSizing === -4) { // content-box -4
ret = ret - avalon.css(node, "padding" + which[0], true) - avalon.css(node, "padding" + which[1], true)
}
return ret
}
cssHooks[method + "&get"] = function (node) {
var hidden = [];
showHidden(node, hidden);
var val = cssHooks[method + ":get"](node)
for (var i = 0, obj; obj = hidden[i++];) {
node = obj.node
for (var n in obj) {
if (typeof obj[n] === "string") {
node.style[n] = obj[n]
}
}
}
return val;
}
avalon.fn[method] = function (value) { //会忽视其display
var node = this[0]
if (arguments.length === 0) {
if (node.setTimeout) { //取得窗口尺寸
return node["inner" + name] ||
node.document.documentElement[clientProp] ||
node.document.body[clientProp] //IE6下前两个分别为undefined,0
}
if (node.nodeType === 9) { //取得页面尺寸
var doc = node.documentElement
//FF chrome html.scrollHeight< body.scrollHeight
//IE 标准模式 : html.scrollHeight> body.scrollHeight
//IE 怪异模式 : html.scrollHeight 最大等于可视窗口多一点?
return Math.max(node.body[scrollProp], doc[scrollProp], node.body[offsetProp], doc[offsetProp], doc[clientProp])
}
return cssHooks[method + "&get"](node)
} else {
return this.css(method, value)
}
}
avalon.fn["inner" + name] = function () {
return cssHooks[method + ":get"](this[0], void 0, -2)
}
avalon.fn["outer" + name] = function (includeMargin) {
return cssHooks[method + ":get"](this[0], void 0, includeMargin === true ? 2 : 0)
}
})
avalon.fn.offset = function () { //取得距离页面左右角的坐标
var node = this[0],
box = {
left: 0,
top: 0
}
if (!node || !node.tagName || !node.ownerDocument) {
return box
}
var doc = node.ownerDocument,
body = doc.body,
root = doc.documentElement,
win = doc.defaultView || doc.parentWindow
if (!avalon.contains(root, node)) {
return box
}
//http://hkom.blog1.fc2.com/?mode=m&no=750 body的偏移量是不包含margin的
//我们可以通过getBoundingClientRect来获得元素相对于client的rect.
//http://msdn.microsoft.com/en-us/library/ms536433.aspx
if (node.getBoundingClientRect) {
box = node.getBoundingClientRect() // BlackBerry 5, iOS 3 (original iPhone)
}
//chrome/IE6: body.scrollTop, firefox/other: root.scrollTop
var clientTop = root.clientTop || body.clientTop,
clientLeft = root.clientLeft || body.clientLeft,
scrollTop = Math.max(win.pageYOffset || 0, root.scrollTop, body.scrollTop),
scrollLeft = Math.max(win.pageXOffset || 0, root.scrollLeft, body.scrollLeft)
// 把滚动距离加到left,top中去。
// IE一些版本中会自动为HTML元素加上2px的border我们需要去掉它
// http://msdn.microsoft.com/en-us/library/ms533564(VS.85).aspx
return {
top: box.top + scrollTop - clientTop,
left: box.left + scrollLeft - clientLeft
}
}
//==================================val相关============================
function getValType(elem) {
var ret = elem.tagName.toLowerCase()
return ret === "input" && /checkbox|radio/.test(elem.type) ? "checked" : ret
}
var roption = /^<option(?:\s+\w+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+))?)*\s+value[\s=]/i
var valHooks = {
"option:get": IEVersion ? function (node) {
//在IE11及W3C如果没有指定value那么node.value默认为node.text存在trim作但IE9-10则是取innerHTML(没trim操作)
//specified并不可靠因此通过分析outerHTML判定用户有没有显示定义value
return roption.test(node.outerHTML) ? node.value : node.text.trim()
} : function (node) {
return node.value
},
"select:get": function (node, value) {
var option, options = node.options,
index = node.selectedIndex,
getter = valHooks["option:get"],
one = node.type === "select-one" || index < 0,
values = one ? null : [],
max = one ? index + 1 : options.length,
i = index < 0 ? max : one ? index : 0
for (; i < max; i++) {
option = options[i]
//旧式IE在reset后不会改变selected需要改用i === index判定
//我们过滤所有disabled的option元素但在safari5下如果设置select为disable那么其所有孩子都disable
//因此当一个元素为disable需要检测其是否显式设置了disable及其父节点的disable情况
if ((option.selected || i === index) && !option.disabled) {
value = getter(option)
if (one) {
return value
}
//收集所有selected值组成数组返回
values.push(value)
}
}
return values
},
"select:set": function (node, values, optionSet) {
values = [].concat(values) //强制转换为数组
var getter = valHooks["option:get"]
for (var i = 0, el; el = node.options[i++];) {
if ((el.selected = values.indexOf(getter(el)) > -1)) {
optionSet = true
}
}
if (!optionSet) {
node.selectedIndex = -1
}
}
}
var keyMap = {}
var keys = ["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"].join(",")
keys.replace(/\w+/g, function (a) {
keyMap[a] = true
})
var ridentStart = /[a-z_$]/i
var rwhiteSpace = /[\s\uFEFF\xA0]/
function getIdent(input, lastIndex) {
var result = []
var subroutine = !!lastIndex
lastIndex = lastIndex || 0
//将表达式中的标识符抽取出来
var state = "unknown"
var variable = ""
for (var i = 0; i < input.length; i++) {
var c = input.charAt(i)
if (c === "'" || c === '"') {//字符串开始
if (state === "unknown") {
state = c
} else if (state === c) {//字符串结束
state = "unknown"
}
} else if (c === "\\") {
if (state === "'" || state === '"') {
i++
}
} else if (ridentStart.test(c)) {//碰到标识符
if (state === "unknown") {
state = "variable"
variable = c
} else if (state === "maybePath") {
variable = result.pop()
variable += "." + c
state = "variable"
} else if (state === "variable") {
variable += c
}
} else if (/\w/.test(c)) {
if (state === "variable") {
variable += c
}
} else if (c === ".") {
if (state === "variable") {
if (variable) {
result.push(variable)
variable = ""
state = "maybePath"
}
}
} else if (c === "[") {
if (state === "variable" || state === "maybePath") {
if (variable) {//如果前面存在变量,收集它
result.push(variable)
variable = ""
}
var lastLength = result.length
var last = result[lastLength - 1]
var innerResult = getIdent(input.slice(i), i)
if (innerResult.length) {//如果括号中存在变量,那么这里添加通配符
result[lastLength - 1] = last + ".*"
result = innerResult.concat(result)
} else { //如果括号中的东西是确定的,直接转换为其子属性
var content = input.slice(i + 1, innerResult.i)
try {
var text = (scpCompile(["return " + content]))()
result[lastLength - 1] = last + "[" + text + "]"
} catch (e) {
}
}
state = "maybePath"//]后面可能还接东西
i = innerResult.i
}
} else if (c === "]") {
if (subroutine) {
result.i = i + lastIndex
addVar(result, variable)
return result
}
} else if (rwhiteSpace.test(c) && c !== "\r" && c !== "\n") {
if (state === "variable") {
if (addVar(result, variable)) {
state = "maybePath" // aaa . bbb 这样的情况
}
variable = ""
}
} else {
addVar(result, variable)
state = "unknown"
variable = ""
}
}
addVar(result, variable)
return result
}
function addVar(array, element) {
if (element && !keyMap[element]) {
array.push(element)
return true
}
}
function addAssign(vars, vmodel, name, binding) {
var ret = [],
prefix = " = " + name + "."
for (var i = vars.length, prop; prop = vars[--i]; ) {
//修改prop格式 arr[0].pro1 ==> arr.0.pro1,
var arr, a ,oldprop = prop
if( prop.indexOf('[') >= 0 ){
prop = prop.replace('[','.').replace(']','')
}
arr = prop.split(".")
var first = arr[0]
while (a = arr.shift()) {
if (vmodel.hasOwnProperty(a)) {
ret.push(first + prefix + first)
//bugfix:https://github.com/RubyLouvre/avalon/issues/1682
//对于由于expr中可能存在不可达的语句如j5son[0].cn1 ==='' || j5son[0].cn2 ===''造成当cn2改变时dom未修改
//解决办法对于vars数组里的所有变量都取一次值保证可以触发依赖收集。但不影响表达式的结果
//bugfix:https://github.com/RubyLouvre/avalon/issues/1742
//考虑如果绑定表达式中包含变量,会生成'obj.*.property'这样的表达式
//会造成var x = obj.*.property;语句编译失败。
//表达式中包含变量的不生成附值语句
if(oldprop.indexOf('.*')<0)
ret.push(generateID('_nousevar_' + i) + ' = '+ oldprop)
binding.observers.push({
v: vmodel,
p: prop
})
vars.splice(i, 1)
} else {
break
}
}
}
return ret
}
var rproxy = /(\$proxy\$[a-z]+)\d+$/
var variablePool = new Cache(218)
//缓存求值函数,以便多次利用
var evaluatorPool = new Cache(128)
function getVars(expr) {
expr = expr.trim()
var ret = variablePool.get(expr)
if (ret) {
return ret.concat()
}
var array = getIdent(expr)
var uniq = {}
var result = []
for (var i = 0, el; el = array[i++]; ) {
if (!uniq[el]) {
uniq[el] = 1
result.push(el)
}
}
return variablePool.put(expr, result).concat()
}
function parseExpr(expr, vmodels, binding) {
var filters = binding.filters
if (typeof filters === "string" && filters.trim() && !binding._filters) {
binding._filters = parseFilter(filters.trim())
}
var vars = getVars(expr)
var expose = new Date() - 0
var assigns = []
var names = []
var args = []
binding.observers = []
for (var i = 0, sn = vmodels.length; i < sn; i++) {
if (vars.length) {
var name = "vm" + expose + "_" + i
names.push(name)
args.push(vmodels[i])
assigns.push.apply(assigns, addAssign(vars, vmodels[i], name, binding))
}
}
binding.args = args
var dataType = binding.type
var exprId = vmodels.map(function (el) {
return String(el.$id).replace(rproxy, "$1")
}) + expr + dataType
var getter = evaluatorPool.get(exprId) //直接从缓存,免得重复生成
if (getter) {
binding.getter = getter
// https://github.com/RubyLouvre/avalon/issues/1833
if (dataType === "duplex") {
var setter = evaluatorPool.get(exprId + "setter")
if(setter){
binding.setter = setter.apply(setter, binding.args)
return getter
}
}else{
return getter
}
}
if (!assigns.length) {
assigns.push("fix" + expose)
}
if (dataType === "duplex") {
var nameOne = {}
assigns.forEach(function (a) {
var arr = a.split("=")
nameOne[arr[0].trim()] = arr[1].trim()
})
expr = expr.replace(/[\$\w]+/, function (a) {
return nameOne[a] ? nameOne[a] : a
})
/* jshint ignore:start */
var fn2 = scpCompile(names.concat("'use strict';" +
"return function(vvv){" + expr + " = vvv\n}\n"))
/* jshint ignore:end */
evaluatorPool.put(exprId + "setter", fn2)
binding.setter = fn2.apply(fn2, binding.args)
}
if (dataType === "on") { //事件绑定
if (expr.indexOf("(") === -1) {
expr += ".call(this, $event)"
} else {
expr = expr.replace("(", ".call(this,")
}
names.push("$event")
expr = "\nreturn " + expr + ";" //IE全家 Function("return ")出错需要Function("return ;")
var lastIndex = expr.lastIndexOf("\nreturn")
var header = expr.slice(0, lastIndex)
var footer = expr.slice(lastIndex)
expr = header + "\n" + footer
} else {
expr = "\nreturn " + expr + ";" //IE全家 Function("return ")出错需要Function("return ;")
}
var assignstr = []
avalon.each(assigns,function(idx,el){
// 这里跟上面 //bugfix:https://github.com/RubyLouvre/avalon/issues/1682 是相对应的。
if(el.indexOf('_nousevar_')>-1){
//子属性还没有创建,这里避免报错
assignstr.push("try{var " + el + "}catch(e){}")
}
else{
assignstr.push("var " + el )
}
});
/* jshint ignore:start */
getter = scpCompile(names.concat("'use strict';\n" + assignstr.join(";\n") + expr))
/* jshint ignore:end */
return evaluatorPool.put(exprId, getter)
}
function normalizeExpr(code) {
var hasExpr = rexpr.test(code) //比如ms-class="width{{w}}"的情况
if (hasExpr) {
var array = scanExpr(code)
if (array.length === 1) {
return array[0].expr
}
return array.map(function (el) {
return el.type ? "(" + el.expr + ")" : quote(el.expr)
}).join(" + ")
} else {
return code
}
}
avalon.normalizeExpr = normalizeExpr
avalon.parseExprProxy = parseExpr
var rthimRightParentheses = /\)\s*$/
var rthimOtherParentheses = /\)\s*\|/g
var rquoteFilterName = /\|\s*([$\w]+)/g
var rpatchBracket = /"\s*\["/g
var rthimLeftParentheses = /"\s*\(/g
function parseFilter(filters) {
filters = filters
.replace(rthimRightParentheses, "")//处理最后的小括号
.replace(rthimOtherParentheses, function () {//处理其他小括号
return "],|"
})
.replace(rquoteFilterName, function (a, b) { //处理|及它后面的过滤器的名字
return "[" + quote(b)
})
.replace(rpatchBracket, function () {
return '"],["'
})
.replace(rthimLeftParentheses, function () {
return '",'
}) + "]"
/* jshint ignore:start */
return scpCompile(["return [" + filters + "]"])()
/* jshint ignore:end */
}
/*********************************************************************
* 编译系统 *
**********************************************************************/
var Escapes = {
92: "\\\\",
34: '\\"',
8: "\\b",
12: "\\f",
10: "\\n",
13: "\\r",
9: "\\t"
}
// Internal: Converts `value` into a zero-padded string such that its
// length is at least equal to `width`. The `width` must be <= 6.
var leadingZeroes = "000000"
var toPaddedString = function (width, value) {
// The `|| 0` expression is necessary to work around a bug in
// Opera <= 7.54u2 where `0 == -0`, but `String(-0) !== "0"`.
return (leadingZeroes + (value || 0)).slice(-width)
};
var unicodePrefix = "\\u00"
var escapeChar = function (character) {
var charCode = character.charCodeAt(0), escaped = Escapes[charCode]
if (escaped) {
return escaped
}
return unicodePrefix + toPaddedString(2, charCode.toString(16))
};
var reEscape = /[\x00-\x1f\x22\x5c]/g
function _quote(value) {
reEscape.lastIndex = 0
return '"' + ( reEscape.test(value)? String(value).replace(reEscape, escapeChar) : value ) + '"'
}
var quote = typeof JSON !== 'undefined' ? JSON.stringify : _quote
/*********************************************************************
* 扫描系统 *
**********************************************************************/
avalon.scan = function (elem, vmodel) {
elem = elem || root
var vmodels = vmodel ? [].concat(vmodel) : []
scanTag(elem, vmodels)
}
//http://www.w3.org/TR/html5/syntax.html#void-elements
var stopScan = oneObject("area,base,basefont,br,col,command,embed,hr,img,input,link,meta,param,source,track,wbr,noscript,script,style,textarea".toUpperCase())
function checkScan(elem, callback, innerHTML) {
var id = setTimeout(function () {
var currHTML = elem.innerHTML
clearTimeout(id)
if (currHTML === innerHTML) {
callback()
} else {
checkScan(elem, callback, currHTML)
}
})
}
function createSignalTower(elem, vmodel) {
var id = elem.getAttribute("avalonctrl") || vmodel.$id
elem.setAttribute("avalonctrl", id)
if (vmodel.$events) {
vmodel.$events.expr = elem.tagName + '[avalonctrl="' + id + '"]'
}
}
function getBindingCallback(elem, name, vmodels) {
var callback = elem.getAttribute(name)
if (callback) {
for (var i = 0, vm; vm = vmodels[i++]; ) {
if (vm.hasOwnProperty(callback) && typeof vm[callback] === "function") {
return vm[callback]
}
}
}
}
function executeBindings(bindings, vmodels) {
for (var i = 0, binding; binding = bindings[i++]; ) {
binding.vmodels = vmodels
directives[binding.type].init(binding)
avalon.injectBinding(binding)
if (binding.getter && binding.element.nodeType === 1) { //移除数据绑定,防止被二次解析
//chrome使用removeAttributeNode移除不存在的特性节点时会报错 https://github.com/RubyLouvre/avalon/issues/99
binding.element.removeAttribute(binding.name)
}
}
bindings.length = 0
}
//https://github.com/RubyLouvre/avalon/issues/636
var mergeTextNodes = IEVersion && window.MutationObserver ? function (elem) {
var node = elem.firstChild, text
while (node) {
var aaa = node.nextSibling
if (node.nodeType === 3) {
if (text) {
text.nodeValue += node.nodeValue
elem.removeChild(node)
} else {
text = node
}
} else {
text = null
}
node = aaa
}
} : 0
var roneTime = /^\s*::/
var rmsAttr = /ms-(\w+)-?(.*)/
var events = oneObject("animationend,blur,change,input,click,dblclick,focus,keydown,keypress,keyup,mousedown,mouseenter,mouseleave,mousemove,mouseout,mouseover,mouseup,scan,scroll,submit")
var obsoleteAttrs = oneObject("value,title,alt,checked,selected,disabled,readonly,enabled,href,src")
function bindingSorter(a, b) {
return a.priority - b.priority
}
var rnoCollect = /^(ms-\S+|data-\S+|on[a-z]+|id|style|class)$/
var ronattr = /^on\-[\w-]+$/
function getOptionsFromTag(elem, vmodels) {
var attributes = elem.attributes
var ret = {}
for (var i = 0, attr; attr = attributes[i++]; ) {
var name = attr.name
if (attr.specified && !rnoCollect.test(name)) {
var camelizeName = camelize(attr.name)
if (/^on\-[\w-]+$/.test(name)) {
ret[camelizeName] = getBindingCallback(elem, name, vmodels)
} else {
ret[camelizeName] = parseData(attr.value)
}
}
}
return ret
}
function scanAttr(elem, vmodels, match) {
var scanNode = true
if (vmodels.length) {
var attributes = getAttributes ? getAttributes(elem) : elem.attributes
var bindings = []
var uniq = {}
for (var i = 0, attr; attr = attributes[i++]; ) {
var name = attr.name
if (uniq[name]) {//IE8下ms-repeat,ms-with BUG
continue
}
uniq[name] = 1
if (attr.specified) {
if (match = name.match(rmsAttr)) {
//如果是以指定前缀命名的
var type = match[1]
var param = match[2] || ""
var value = attr.value
if (events[type]) {
param = type
type = "on"
} else if (obsoleteAttrs[type]) {
param = type
type = "attr"
name = "ms-" + type + "-" + param
log("warning!请改用" + name + "代替" + attr.name + "!")
}
if (directives[type]) {
var newValue = value.replace(roneTime, "")
var oneTime = value !== newValue
var binding = {
type: type,
param: param,
element: elem,
name: name,
expr: newValue,
oneTime: oneTime,
uuid: "_" + (++bindingID),
//chrome与firefox下Number(param)得到的值不一样 #855
priority: (directives[type].priority || type.charCodeAt(0) * 10) + (Number(param.replace(/\D/g, "")) || 0)
}
if (type === "html" || type === "text") {
var filters = getToken(value).filters
binding.expr = binding.expr.replace(filters, "")
binding.filters = filters.replace(rhasHtml, function () {
binding.type = "html"
binding.group = 1
return ""
}).trim() // jshint ignore:line
} else if (type === "duplex") {
var hasDuplex = name
} else if (name === "ms-if-loop") {
binding.priority += 100
} else if (name === "ms-attr-value") {
var hasAttrValue = name
}
bindings.push(binding)
}
}
}
}
if (bindings.length) {
bindings.sort(bindingSorter)
//http://bugs.jquery.com/ticket/7071
//在IE下对VML读取type属性,会让此元素所有属性都变成<Failed>
if (hasDuplex && hasAttrValue && elem.nodeName === "INPUT" && elem.type === "text") {
log("warning!一个控件不能同时定义ms-attr-value与" + hasDuplex)
}
for (i = 0; binding = bindings[i]; i++) {
type = binding.type
if (rnoscanAttrBinding.test(type)) {
return executeBindings(bindings.slice(0, i + 1), vmodels)
} else if (scanNode) {
scanNode = !rnoscanNodeBinding.test(type)
}
}
executeBindings(bindings, vmodels)
}
}
if (scanNode && !stopScan[elem.tagName] && (isWidget(elem) ? elem.msResolved : 1)) {
mergeTextNodes && mergeTextNodes(elem)
scanNodeList(elem, vmodels) //扫描子孙元素
}
}
var rnoscanAttrBinding = /^if|widget|repeat$/
var rnoscanNodeBinding = /^each|with|html|include$/
//IE67下在循环绑定中一个节点如果是通过cloneNode得到自定义属性的specified为false无法进入里面的分支
//但如果我们去掉scanAttr中的attr.specified检测一个元素会有80+个特性节点(因为它不区分固有属性与自定义属性),很容易卡死页面
if (!W3C) {
var attrPool = new Cache(512)
var rattrs = /\s+([^=\s]+)(?:=("[^"]*"|'[^']*'|[^\s>]+))?/g,
rquote = /^['"]/,
rtag = /<\w+\b(?:(["'])[^"]*?(\1)|[^>])*>/i,
ramp = /&amp;/g
//IE6-8解析HTML5新标签会将它分解两个元素节点与一个文本节点
//<body><section>ddd</section></body>
// window.onload = function() {
// var body = document.body
// for (var i = 0, el; el = body.children[i++]; ) {
// avalon.log(el.outerHTML)
// }
// }
//依次输出<SECTION>, </SECTION>
var getAttributes = function (elem) {
var html = elem.outerHTML
//处理IE6-8解析HTML5新标签的情况及<br>等半闭合标签outerHTML为空的情况
if (html.slice(0, 2) === "</" || !html.trim()) {
return []
}
var str = html.match(rtag)[0]
if(str.slice(-1)===">")
str = str.slice(0,-1)
var attributes = [],
k, v
var ret = attrPool.get(str)
if (ret) {
return ret
}
while (k = rattrs.exec(str)) {
v = k[2]
if (v) {
v = (rquote.test(v) ? v.slice(1, -1) : v).replace(ramp, "&")
}
var name = k[1].toLowerCase()
var binding = {
name: name,
specified: true,
value: v || ""
}
attributes.push(binding)
}
return attrPool.put(str, attributes)
}
}
function scanNodeList(parent, vmodels) {
var nodes = avalon.slice(parent.childNodes)
scanNodeArray(nodes, vmodels)
}
function scanNodeArray(nodes, vmodels) {
function _delay_component(name) {
setTimeout(function () {
avalon.component(name)
})
}
for (var i = 0, node; node = nodes[i++]; ) {
switch (node.nodeType) {
case 1:
var elem = node
if (!elem.msResolved && elem.parentNode && elem.parentNode.nodeType === 1) {
var library = isWidget(elem)
if (library) {
var widget = elem.localName ? elem.localName.replace(library + ":", "") : elem.nodeName
var fullName = library + ":" + camelize(widget)
componentQueue.push({
library: library,
element: elem,
fullName: fullName,
widget: widget,
vmodels: vmodels,
name: "widget"
})
if (avalon.components[fullName]) {
//确保所有ms-attr-name扫描完再处理
_delay_component(fullName)
}
}
}
scanTag(node, vmodels) //扫描元素节点
if (node.msHasEvent) {
avalon.fireDom(node, "datasetchanged", {
bubble: node.msHasEvent
})
}
break
case 3:
if (rexpr.test(node.nodeValue)) {
scanText(node, vmodels, i) //扫描文本节点
}
break
}
}
}
function scanTag(elem, vmodels, node) {
//扫描顺序 ms-skip(0) --> ms-important(1) --> ms-controller(2) --> ms-if(10) --> ms-repeat(100)
//--> ms-if-loop(110) --> ms-attr(970) ...--> ms-each(1400)-->ms-with(1500)--〉ms-duplex(2000)垫后
var a = elem.getAttribute("ms-skip")
//#360 在旧式IE中 Object标签在引入Flash等资源时,可能出现没有getAttributeNode,innerHTML的情形
if (!elem.getAttributeNode) {
return log("warning " + elem.tagName + " no getAttributeNode method")
}
var b = elem.getAttributeNode("ms-important")
var c = elem.getAttributeNode("ms-controller")
if (typeof a === "string") {
return
} else if (node = b || c) {
var newVmodel = avalon.vmodels[node.value]
if (!newVmodel) {
return
}
//ms-important不包含父VMms-controller相反
vmodels = node === b ? [newVmodel] : [newVmodel].concat(vmodels)
var name = node.name
elem.removeAttribute(name) //removeAttributeNode不会刷新[ms-controller]样式规则
avalon(elem).removeClass(name)
createSignalTower(elem, newVmodel)
}
scanAttr(elem, vmodels) //扫描特性节点
if (newVmodel) {
setTimeout(function () {
newVmodel.$fire("ms-scan-end", elem)
})
}
}
var rhasHtml = /\|\s*html(?:\b|$)/,
r11a = /\|\|/g,
rlt = /&lt;/g,
rgt = /&gt;/g,
rstringLiteral = /(['"])(\\\1|.)+?\1/g,
rline = /\r?\n/g
function getToken(value) {
if (value.indexOf("|") > 0) {
var scapegoat = value.replace(rstringLiteral, function (_) {
return Array(_.length + 1).join("1") // jshint ignore:line
})
var index = scapegoat.replace(r11a, "\u1122\u3344").indexOf("|") //干掉所有短路或
if (index > -1) {
return {
type: "text",
filters: value.slice(index).trim(),
expr: value.slice(0, index)
}
}
}
return {
type: "text",
expr: value,
filters: ""
}
}
function scanExpr(str) {
var tokens = [],
value, start = 0,
stop
do {
stop = str.indexOf(openTag, start)
if (stop === -1) {
break
}
value = str.slice(start, stop)
if (value) { // {{ 左边的文本
tokens.push({
expr: value
})
}
start = stop + openTag.length
stop = str.indexOf(closeTag, start)
if (stop === -1) {
break
}
value = str.slice(start, stop)
if (value) { //处理{{ }}插值表达式
tokens.push(getToken(value.replace(rline,"")))
}
start = stop + closeTag.length
} while (1)
value = str.slice(start)
if (value) { //}} 右边的文本
tokens.push({
expr: value
})
}
return tokens
}
function scanText(textNode, vmodels, index) {
var bindings = [],
tokens = scanExpr(textNode.data)
if (tokens.length) {
for (var i = 0, token; token = tokens[i++];) {
var node = DOC.createTextNode(token.expr) //将文本转换为文本节点,并替换原来的文本节点
if (token.type) {
token.expr = token.expr.replace(roneTime, function () {
token.oneTime = true
return ""
}) // jshint ignore:line
token.element = node
token.filters = token.filters.replace(rhasHtml, function () {
token.type = "html"
return ""
}) // jshint ignore:line
token.pos = index * 1000 + i
bindings.push(token) //收集带有插值表达式的文本
}
avalonFragment.appendChild(node)
}
textNode.parentNode.replaceChild(avalonFragment, textNode)
if (bindings.length)
executeBindings(bindings, vmodels)
}
}
//使用来自游戏界的双缓冲技术,减少对视图的冗余刷新
var Buffer = function () {
this.queue = []
}
Buffer.prototype = {
render: function (isAnimate) {
if (!this.locked) {
this.locked = isAnimate ? root.offsetHeight + 10 : 1
var me = this
avalon.nextTick(function () {
me.flush()
})
}
},
flush: function () {
for (var i = 0, sub; sub = this.queue[i++]; ) {
sub.update && sub.update()
}
this.locked = 0
this.queue = []
}
}
var buffer = new Buffer()
var componentQueue = []
var widgetList = []
var componentHooks = {
$construct: function () {
return avalon.mix.apply(null, arguments)
},
$ready: noop,
$init: noop,
$dispose: noop,
$container: null,
$childReady: noop,
$replace: false,
$extend: null,
$$template: function (str) {
return str
}
}
avalon.components = {}
avalon.component = function (name, opts) {
if (opts) {
avalon.components[name] = avalon.mix({}, componentHooks, opts)
}
for (var i = 0, obj; obj = componentQueue[i]; i++) {
if (name === obj.fullName) {
componentQueue.splice(i, 1)
i--;
(function (host, hooks, elem, widget) {
//如果elem已从Document里移除,直接返回
//issuse : https://github.com/RubyLouvre/avalon2/issues/40
if (!avalon.contains(DOC, elem) || elem.msResolved) {
avalon.Array.remove(componentQueue, host)
return
}
var dependencies = 1
var library = host.library
var global = avalon.libraries[library] || componentHooks
//===========收集各种配置=======
if (elem.getAttribute("ms-attr-identifier")) {
//如果还没有解析完,就延迟一下 #1155
return
}
var elemOpts = getOptionsFromTag(elem, host.vmodels)
var vmOpts = getOptionsFromVM(host.vmodels, elemOpts.config || host.fullName)
var $id = elemOpts.$id || elemOpts.identifier || generateID(widget)
delete elemOpts.config
delete elemOpts.$id
delete elemOpts.identifier
var componentDefinition = {}
var parentHooks = avalon.components[hooks.$extend]
if (parentHooks) {
avalon.mix(true, componentDefinition, parentHooks)
componentDefinition = parentHooks.$construct.call(elem, componentDefinition, {}, {})
} else {
avalon.mix(true, componentDefinition, hooks)
}
componentDefinition = avalon.components[name].$construct.call(elem, componentDefinition, vmOpts, elemOpts)
componentDefinition.$refs = {}
componentDefinition.$id = $id
//==========构建VM=========
var keepSlot = componentDefinition.$slot
var keepReplace = componentDefinition.$replace
var keepContainer = componentDefinition.$container
var keepTemplate = componentDefinition.$template
delete componentDefinition.$slot
delete componentDefinition.$replace
delete componentDefinition.$container
delete componentDefinition.$construct
var vmodel = avalon.define(componentDefinition) || {}
elem.msResolved = 1 //防止二进扫描此元素
vmodel.$init(vmodel, elem)
global.$init(vmodel, elem)
var nodes = elem.childNodes
//收集插入点
var slots = {}, snode
for (var s = 0, el; el = nodes[s++]; ) {
var type = el.nodeType === 1 && el.getAttribute("slot") || keepSlot
if (type) {
if (slots[type]) {
slots[type].push(el)
} else {
slots[type] = [el]
}
}
}
if (vmodel.$$template) {
avalon.clearHTML(elem)
elem.innerHTML = vmodel.$$template(keepTemplate)
}
for (s in slots) {
if (vmodel.hasOwnProperty(s)) {
var ss = slots[s]
if (ss.length) {
var fragment = avalonFragment.cloneNode(true)
for (var ns = 0; snode = ss[ns++]; ) {
fragment.appendChild(snode)
}
vmodel[s] = fragment
}
slots[s] = null
}
}
slots = null
var child = elem.children[0] || elem.firstChild
if (keepReplace) {
elem.parentNode.replaceChild(child, elem)
child.msResolved = 1
var cssText = elem.style.cssText
var className = elem.className
elem = host.element = child
elem.style.cssText += ";"+ cssText
if (className) {
avalon(elem).addClass(className)
}
}
if (keepContainer) {
keepContainer.appendChild(elem)
}
avalon.fireDom(elem, "datasetchanged",
{library: library, vm: vmodel, childReady: 1})
var children = 0
var removeFn = avalon.bind(elem, "datasetchanged", function (e) {
if (e.childReady && e.library === library) {
dependencies += e.childReady
if (vmodel !== e.vm) {
vmodel.$refs[e.vm.$id] = e.vm
if (e.childReady === -1) {
children++
vmodel.$childReady(vmodel, elem, e)
}
e.stopPropagation()
}
}
if (dependencies === 0) {
var id1 = setTimeout(function () {
clearTimeout(id1)
vmodel.$ready(vmodel, elem, host.vmodels)
global.$ready(vmodel, elem, host.vmodels)
}, children ? Math.max(children * 17, 100) : 17)
avalon.unbind(elem, "datasetchanged", removeFn)
//==================
host.rollback = function () {
try {
vmodel.$dispose(vmodel, elem)
global.$dispose(vmodel, elem)
} catch (e) {
}
delete avalon.vmodels[vmodel.$id]
}
injectDisposeQueue(host, widgetList)
if (window.chrome) {
elem.addEventListener("DOMNodeRemovedFromDocument", function () {
setTimeout(rejectDisposeQueue)
})
}
}
})
scanTag(elem, [vmodel].concat(host.vmodels))
avalon.vmodels[vmodel.$id] = vmodel
if (!elem.childNodes.length) {
avalon.fireDom(elem, "datasetchanged", {library: library, vm: vmodel, childReady: -1})
} else {
var id2 = setTimeout(function () {
clearTimeout(id2)
avalon.fireDom(elem, "datasetchanged", {library: library, vm: vmodel, childReady: -1})
}, 17)
}
})(obj, avalon.components[name], obj.element, obj.widget)// jshint ignore:line
}
}
}
function getOptionsFromVM(vmodels, pre) {
if (pre) {
for (var i = 0, v; v = vmodels[i++]; ) {
if (v.hasOwnProperty(pre) && typeof v[pre] === "object") {
var vmOptions = v[pre]
return vmOptions.$model || vmOptions
break
}
}
}
return {}
}
avalon.libraries = []
avalon.library = function (name, opts) {
if (DOC.namespaces) {
DOC.namespaces.add(name, 'http://www.w3.org/1999/xhtml');
}
avalon.libraries[name] = avalon.mix({
$init: noop,
$ready: noop,
$dispose: noop
}, opts || {})
}
avalon.library("ms")
/*
broswer nodeName scopeName localName
IE9 ONI:BUTTON oni button
IE10 ONI:BUTTON undefined oni:button
IE8 button oni undefined
chrome ONI:BUTTON undefined oni:button
*/
function isWidget(el) { //如果为自定义标签,返回UI库的名字
if (el.scopeName && el.scopeName !== "HTML") {
return el.scopeName
}
var fullName = el.nodeName.toLowerCase()
var index = fullName.indexOf(":")
if (index > 0) {
return fullName.slice(0, index)
}
}
//各种MVVM框架在大型表格下的性能测试
// https://github.com/RubyLouvre/avalon/issues/859
var bools = ["autofocus,autoplay,async,allowTransparency,checked,controls",
"declare,disabled,defer,defaultChecked,defaultSelected",
"contentEditable,isMap,loop,multiple,noHref,noResize,noShade",
"open,readOnly,selected"
].join(",")
var boolMap = {}
bools.replace(rword, function (name) {
boolMap[name.toLowerCase()] = name
})
var propMap = {//属性名映射
"accept-charset": "acceptCharset",
"char": "ch",
"charoff": "chOff",
"class": "className",
"for": "htmlFor",
"http-equiv": "httpEquiv"
}
var anomaly = ["accessKey,bgColor,cellPadding,cellSpacing,codeBase,codeType,colSpan",
"dateTime,defaultValue,frameBorder,longDesc,maxLength,marginWidth,marginHeight",
"rowSpan,tabIndex,useMap,vSpace,valueType,vAlign"
].join(",")
anomaly.replace(rword, function (name) {
propMap[name.toLowerCase()] = name
})
var attrDir = avalon.directive("attr", {
init: function (binding) {
//{{aaa}} --> aaa
//{{aaa}}/bbb.html --> (aaa) + "/bbb.html"
binding.expr = normalizeExpr(binding.expr.trim())
if (binding.type === "include") {
var elem = binding.element
effectBinding(elem, binding)
binding.includeRendered = getBindingCallback(elem, "data-include-rendered", binding.vmodels)
binding.includeLoaded = getBindingCallback(elem, "data-include-loaded", binding.vmodels)
var outer = binding.includeReplace = !!avalon(elem).data("includeReplace")
if (avalon(elem).data("includeCache")) {
binding.templateCache = {}
}
binding.start = DOC.createComment("ms-include")
binding.end = DOC.createComment("ms-include-end")
if (outer) {
binding.element = binding.end
binding._element = elem
elem.parentNode.insertBefore(binding.start, elem)
elem.parentNode.insertBefore(binding.end, elem.nextSibling)
} else {
elem.insertBefore(binding.start, elem.firstChild)
elem.appendChild(binding.end)
}
}
},
update: function (val) {
var elem = this.element
var attrName = this.param
//这模块在1.5.9被重构了
if (attrName.indexOf('data-') === 0 || rsvg.test(elem)) {
elem.setAttribute(attrName, val)
} else {
var propName = propMap[attrName] || attrName
if (typeof elem[propName] === 'boolean') {
elem[propName] = !!val
//布尔属性必须使用el.xxx = true|false方式设值
//如果为false, IE全系列下相当于setAttribute(xxx,''),
//会影响到样式,需要进一步处理
}
if (val === false) {//移除属性
elem.removeAttribute(propName)
return
}
//IE6中classNamme, htmlFor等无法检测它们为内建属性 
if(!W3C && /[A-Z]/.test(propName)){
elem[propName] = val + ''
return
}
//SVG只能使用setAttribute(xxx, yyy), VML只能使用node.xxx = yyy ,
//HTML的固有属性必须node.xxx = yyy
var isInnate = (!W3C && isVML(elem)) ? true :
isInnateProps(elem.nodeName, attrName)
/* istanbul ignore next */
if (isInnate) {
if (attrName === 'href' || attrName === 'src') {
val = String(val).replace(/&amp;/g, '&') //处理IE67自动转义的问题
}
elem[propName] = val + ''
} else {
elem.setAttribute(attrName, val)
}
}
}
})
var innateMap = {}
function isInnateProps(nodeName, attrName) {
var key = nodeName + ":" + attrName
if (key in innateMap) {
return innateMap[key]
}
return innateMap[key] = (attrName in document.createElement(nodeName))
}
//这几个指令都可以使用插值表达式如ms-src="aaa/{{b}}/{{c}}.html"
"title,alt,src,value,css,include,href".replace(rword, function (name) {
directives[name] = attrDir
})
//根据VM的属性值或表达式的值切换类名ms-class="xxx yyy zzz:flag"
//http://www.cnblogs.com/rubylouvre/archive/2012/12/17/2818540.html
avalon.directive("class", {
init: function (binding) {
var oldStyle = binding.param
var method = binding.type
if (!oldStyle || isFinite(oldStyle)) {
binding.param = "" //去掉数字
directives.effect.init(binding)
} else {
log('ms-' + method + '-xxx="yyy"这种用法已经过时,请使用ms-' + method + '="xxx:yyy"')
binding.expr = '[' + quote(oldStyle) + "," + binding.expr + "]"
binding.oldStyle = oldStyle
}
if (method === "hover" || method === "active") { //确保只绑定一次
if (!binding.hasBindEvent) {
var elem = binding.element
var $elem = avalon(elem)
var activate = "mouseenter" //在移出移入时切换类名
var abandon = "mouseleave"
if (method === "active") { //在聚焦失焦中切换类名
elem.tabIndex = elem.tabIndex || -1
activate = "mousedown"
abandon = "mouseup"
var fn0 = $elem.bind("mouseleave", function () {
binding.toggleClass && $elem.removeClass(binding.newClass)
})
}
}
var fn1 = $elem.bind(activate, function () {
binding.toggleClass && $elem.addClass(binding.newClass)
})
var fn2 = $elem.bind(abandon, function () {
binding.toggleClass && $elem.removeClass(binding.newClass)
})
binding.rollback = function () {
$elem.unbind("mouseleave", fn0)
$elem.unbind(activate, fn1)
$elem.unbind(abandon, fn2)
}
binding.hasBindEvent = true
}
},
update: function (arr) {
var binding = this
var $elem = avalon(this.element)
binding.newClass = arr[0]
binding.toggleClass = !!arr[1]
if (binding.oldClass && binding.newClass !== binding.oldClass) {
$elem.removeClass(binding.oldClass)
}
binding.oldClass = binding.newClass
if (binding.type === "class") {
if (binding.oldStyle) {
$elem.toggleClass(binding.oldStyle, !!arr[1])
} else {
$elem.toggleClass(binding.newClass, binding.toggleClass)
}
}
}
})
"hover,active".replace(rword, function (name) {
directives[name] = directives["class"]
})
//ms-controller绑定已经在scanTag 方法中实现
avalon.directive("css", {
init: directives.attr.init,
update: function (val) {
avalon(this.element).css(this.param, val)
}
})
avalon.directive("data", {
priority: 100,
update: function (val) {
var elem = this.element
var key = "data-" + this.param
if (val && typeof val === "object") {
elem[key] = val
} else {
elem.setAttribute(key, String(val))
}
}
})
//双工绑定
var rduplexType = /^(?:checkbox|radio)$/
var rduplexParam = /^(?:radio|checked)$/
var rnoduplexInput = /^(file|button|reset|submit|checkbox|radio|range)$/
var duplexBinding = avalon.directive("duplex", {
priority: 2000,
init: function (binding, hasCast) {
var elem = binding.element
var vmodels = binding.vmodels
binding.changed = getBindingCallback(elem, "data-duplex-changed", vmodels) || noop
var params = []
var casting = oneObject("string,number,boolean,checked")
if (elem.type === "radio" && binding.param === "") {
binding.param = "checked"
}
binding.param.replace(rw20g, function (name) {
if (rduplexType.test(elem.type) && rduplexParam.test(name)) {
if (name === "radio")
log("ms-duplex-radio已经更名为ms-duplex-checked")
name = "checked"
binding.isChecked = true
binding.xtype = "radio"
}
if (name === "bool") {
name = "boolean"
log("ms-duplex-bool已经更名为ms-duplex-boolean")
} else if (name === "text") {
name = "string"
log("ms-duplex-text已经更名为ms-duplex-string")
}
if (casting[name]) {
hasCast = true
}
avalon.Array.ensure(params, name)
})
if (!hasCast) {
params.push("string")
}
binding.param = params.join("-")
if (!binding.xtype) {
binding.xtype = elem.tagName === "SELECT" ? "select" :
elem.type === "checkbox" ? "checkbox" :
elem.type === "radio" ? "radio" :
/^change/.test(elem.getAttribute("data-duplex-event")) ? "change" :
"input"
}
//===================绑定事件======================
var bound = binding.bound = function (type, callback) {
if (elem.addEventListener) {
elem.addEventListener(type, callback, false)
} else {
elem.attachEvent("on" + type, callback)
}
var old = binding.rollback
binding.rollback = function () {
elem.avalonSetter = null
avalon.unbind(elem, type, callback)
old && old()
}
}
function callback(value) {
binding.changed.call(this, value, binding)
}
var composing = false
function compositionStart() {
composing = true
}
function compositionEnd() {
composing = false
setTimeout(updateVModel)
}
var updateVModel = function (e) {
var val = elem.value //防止递归调用形成死循环
if (composing || val === binding.oldValue || binding.pipe === null) //处理中文输入法在minlengh下引发的BUG
return
var lastValue = binding.pipe(val, binding, "get")
try {
binding.oldValue = val
binding.setter(lastValue)
callback.call(elem, lastValue)
} catch (ex) {
log(ex)
}
}
switch (binding.xtype) {
case "radio":
binding.bound("click", function () {
var lastValue = binding.pipe(elem.value, binding, "get")
try {
binding.setter(lastValue)
callback.call(elem, lastValue)
} catch (ex) {
log(ex)
}
})
break
case "checkbox":
bound(W3C ? "change" : "click", function () {
var method = elem.checked ? "ensure" : "remove"
var array = binding.getter.apply(0, binding.vmodels)
if (!Array.isArray(array)) {
log("ms-duplex应用于checkbox上要对应一个数组")
array = [array]
}
var val = binding.pipe(elem.value, binding, "get")
avalon.Array[method](array, val)
callback.call(elem, array)
})
break
case "change":
bound("change", updateVModel)
break
case "input":
if (!IEVersion) { // W3C
bound("input", updateVModel)
//非IE浏览器才用这个
bound("compositionstart", compositionStart)
bound("compositionend", compositionEnd)
bound("DOMAutoComplete", updateVModel)
} else {
// IE下通过selectionchange事件监听IE9+点击input右边的X的清空行为及粘贴剪切删除行为
if (IEVersion > 8) {
if (IEVersion === 9) {
//IE9删除字符后再失去焦点不会同步 #1167
bound("keyup", updateVModel)
}
bound("input", updateVModel) //IE9使用propertychange无法监听中文输入改动
} else {
//onpropertychange事件无法区分是程序触发还是用户触发
//IE6-8下第一次修改时不会触发,需要使用keydown或selectionchange修正
bound("propertychange", function (e) {
if (e.propertyName === "value") {
updateVModel()
}
})
}
bound("dragend", function () {
setTimeout(function () {
updateVModel()
}, 17)
})
//http://www.cnblogs.com/rubylouvre/archive/2013/02/17/2914604.html
//http://www.matts411.com/post/internet-explorer-9-oninput/
}
break
case "select":
bound("change", function () {
var val = avalon(elem).val() //字符串或字符串数组
if (Array.isArray(val)) {
val = val.map(function (v) {
return binding.pipe(v, binding, "get")
})
} else {
val = binding.pipe(val, binding, "get")
}
if (val + "" !== binding.oldValue) {
try {
binding.setter(val)
} catch (ex) {
log(ex)
}
}
})
bound("datasetchanged", function (e) {
if (e.bubble === "selectDuplex") {
var value = binding._value
var curValue = Array.isArray(value) ? value.map(String) : value + ""
avalon(elem).val(curValue)
elem.oldValue = curValue + ""
callback.call(elem, curValue)
}
})
break
}
if (binding.xtype === "input" && !rnoduplexInput.test(elem.type)) {
if (elem.type !== "hidden") {
bound("focus", function () {
elem.msFocus = true
})
bound("blur", function () {
elem.msFocus = false
})
}
elem.avalonSetter = updateVModel //#765
watchValueInTimer(function () {
if (avalon.contains(root, elem)) {
if (!this.msFocus) {
updateVModel()
}
} else if (!elem.msRetain) {
return false
}
})
}
},
update: function (value) {
var elem = this.element, binding = this, curValue
if (!this.init) {
for (var i in avalon.vmodels) {
var v = avalon.vmodels[i]
v.$fire("avalon-ms-duplex-init", binding)
}
var cpipe = binding.pipe || (binding.pipe = pipe)
cpipe(null, binding, "init")
this.init = 1
}
switch (this.xtype) {
case "input":
case "change":
curValue = this.pipe(value, this, "set") //fix #673
if (curValue !== this.oldValue) {
var fixCaret = false
if (elem.msFocus) {
try {
var pos = getCaret(elem)
if (pos.start === pos.end) {
pos = pos.start
fixCaret = true
}
} catch (e) {
}
}
elem.value = binding.oldValue = curValue
if (fixCaret) {
setCaret(elem, pos, pos)
}
}
break
case "radio":
curValue = binding.isChecked ? !!value : value + "" === elem.value
if (IEVersion === 6) {
setTimeout(function () {
//IE8 checkbox, radio是使用defaultChecked控制选中状态
//并且要先设置defaultChecked后设置checked
//并且必须设置延迟
elem.defaultChecked = curValue
elem.checked = curValue
}, 31)
} else {
elem.checked = curValue
}
break
case "checkbox":
var array = [].concat(value) //强制转换为数组
curValue = this.pipe(elem.value, this, "get")
elem.checked = array.indexOf(curValue) > -1
break
case "select":
//必须变成字符串后才能比较
binding._value = value
if (!elem.msHasEvent) {
elem.msHasEvent = "selectDuplex"
//必须等到其孩子准备好才触发
} else {
avalon.fireDom(elem, "datasetchanged", {
bubble: elem.msHasEvent
})
}
break
}
}
})
if (IEVersion) {
avalon.bind(DOC, "selectionchange", function (e) {
var el = DOC.activeElement || {}
if (!el.msFocus && el.avalonSetter) {
el.avalonSetter()
}
})
}
function fixNull(val) {
return val == null ? "" : val
}
avalon.duplexHooks = {
checked: {
get: function (val, binding) {
return !binding.oldValue
}
},
string: {
get: function (val) { //同步到VM
return val
},
set: fixNull
},
"boolean": {
get: function (val) {
return val === "true"
},
set: fixNull
},
number: {
get: function (val, binding) {
var number = parseFloat(val + "")
if (-val === -number) {
return number
}
var arr = /strong|medium|weak/.exec(binding.element.getAttribute("data-duplex-number")) || ["medium"]
switch (arr[0]) {
case "strong":
return 0
case "medium":
return val === "" ? "" : 0
case "weak":
return val
}
},
set: fixNull
}
}
function pipe(val, binding, action) {
binding.param.replace(rw20g, function (name) {
var hook = avalon.duplexHooks[name]
if (hook && typeof hook[action] === "function") {
val = hook[action](val, binding)
}
})
return val
}
var TimerID, ribbon = []
avalon.tick = function (fn) {
if (ribbon.push(fn) === 1) {
TimerID = setInterval(ticker, 60)
}
}
function ticker() {
for (var n = ribbon.length - 1; n >= 0; n--) {
var el = ribbon[n]
if (el() === false) {
ribbon.splice(n, 1)
}
}
if (!ribbon.length) {
clearInterval(TimerID)
}
}
var watchValueInTimer = noop
new function () { // jshint ignore:line
try { //#272 IE9-IE11, firefox
var setters = {}
var aproto = HTMLInputElement.prototype
var bproto = HTMLTextAreaElement.prototype
function newSetter(value) { // jshint ignore:line
setters[this.tagName].call(this, value)
if (!this.msFocus && this.avalonSetter) {
this.avalonSetter()
}
}
var inputProto = HTMLInputElement.prototype
Object.getOwnPropertyNames(inputProto) //故意引发IE6-8等浏览器报错
setters["INPUT"] = Object.getOwnPropertyDescriptor(aproto, "value").set
Object.defineProperty(aproto, "value", {
set: newSetter
})
setters["TEXTAREA"] = Object.getOwnPropertyDescriptor(bproto, "value").set
Object.defineProperty(bproto, "value", {
set: newSetter
})
} catch (e) {
//在chrome 43中 ms-duplex终于不需要使用定时器实现双向绑定了
// http://updates.html5rocks.com/2015/04/DOM-attributes-now-on-the-prototype
// https://docs.google.com/document/d/1jwA8mtClwxI-QJuHT7872Z0pxpZz8PBkf2bGAbsUtqs/edit?pli=1
watchValueInTimer = avalon.tick
}
} // jshint ignore:line
function getCaret(ctrl) {
var start = NaN, end = NaN
if (ctrl.setSelectionRange) {
start = ctrl.selectionStart
end = ctrl.selectionEnd
} else if (document.selection && document.selection.createRange) {
var range = document.selection.createRange()
start = 0 - range.duplicate().moveStart('character', -100000)
end = start + range.text.length
}
return {
start: start,
end: end
}
}
function setCaret(ctrl, begin, end) {
if (!ctrl.value || ctrl.readOnly)
return
if (ctrl.createTextRange) {//IE6-8
var range = ctrl.createTextRange()
range.collapse(true)
range.moveStart("character", begin)
range.select()
} else {
ctrl.selectionStart = begin
ctrl.selectionEnd = end
}
}
avalon.directive("effect", {
priority: 5,
init: function (binding) {
var text = binding.expr,
className,
rightExpr
var colonIndex = text.replace(rexprg, function (a) {
return a.replace(/./g, "0")
}).indexOf(":") //取得第一个冒号的位置
if (colonIndex === -1) { // 比如 ms-class/effect="aaa bbb ccc" 的情况
className = text
rightExpr = true
} else { // 比如 ms-class/effect-1="ui-state-active:checked" 的情况
className = text.slice(0, colonIndex)
rightExpr = text.slice(colonIndex + 1)
}
if (!rexpr.test(text)) {
className = quote(className)
} else {
className = normalizeExpr(className)
}
binding.expr = "[" + className + "," + rightExpr + "]"
},
update: function (arr) {
var name = arr[0]
var elem = this.element
if (elem.getAttribute("data-effect-name") === name) {
return
} else {
elem.removeAttribute("data-effect-driver")
}
var inlineStyles = elem.style
var computedStyles = window.getComputedStyle ? window.getComputedStyle(elem) : null
var useAni = false
if (computedStyles && (supportTransition || supportAnimation)) {
//如果支持CSS动画
var duration = inlineStyles[transitionDuration] || computedStyles[transitionDuration]
if (duration && duration !== '0s') {
elem.setAttribute("data-effect-driver", "t")
useAni = true
}
if (!useAni) {
duration = inlineStyles[animationDuration] || computedStyles[animationDuration]
if (duration && duration !== '0s') {
elem.setAttribute("data-effect-driver", "a")
useAni = true
}
}
}
if (!useAni) {
if (avalon.effects[name]) {
elem.setAttribute("data-effect-driver", "j")
useAni = true
}
}
if (useAni) {
elem.setAttribute("data-effect-name", name)
}
}
})
avalon.effects = {}
avalon.effect = function (name, callbacks) {
avalon.effects[name] = callbacks
}
var supportTransition = false
var supportAnimation = false
var transitionEndEvent
var animationEndEvent
var transitionDuration = avalon.cssName("transition-duration")
var animationDuration = avalon.cssName("animation-duration")
new function () {// jshint ignore:line
var checker = {
'TransitionEvent': 'transitionend',
'WebKitTransitionEvent': 'webkitTransitionEnd',
'OTransitionEvent': 'oTransitionEnd',
'otransitionEvent': 'otransitionEnd'
}
var tran
//有的浏览器同时支持私有实现与标准写法比如webkit支持前两种Opera支持1、3、4
for (var name in checker) {
if (window[name]) {
tran = checker[name]
break;
}
try {
var a = document.createEvent(name);
tran = checker[name]
break;
} catch (e) {
}
}
if (typeof tran === "string") {
supportTransition = true
transitionEndEvent = tran
}
//大致上有两种选择
//IE10+, Firefox 16+ & Opera 12.1+: animationend
//Chrome/Safari: webkitAnimationEnd
//http://blogs.msdn.com/b/davrous/archive/2011/12/06/introduction-to-css3-animat ions.aspx
//IE10也可以使用MSAnimationEnd监听但是回调里的事件 type依然为animationend
// el.addEventListener("MSAnimationEnd", function(e) {
// alert(e.type)// animationend
// })
checker = {
'AnimationEvent': 'animationend',
'WebKitAnimationEvent': 'webkitAnimationEnd'
}
var ani;
for (name in checker) {
if (window[name]) {
ani = checker[name];
break;
}
}
if (typeof ani === "string") {
supportTransition = true
animationEndEvent = ani
}
}()
var effectPool = []//重复利用动画实例
function effectFactory(el, opts) {
if (!el || el.nodeType !== 1) {
return null
}
if (opts) {
var name = opts.effectName
var driver = opts.effectDriver
} else {
name = el.getAttribute("data-effect-name")
driver = el.getAttribute("data-effect-driver")
}
if (!name || !driver) {
return null
}
var instance = effectPool.pop() || new Effect()
instance.el = el
instance.driver = driver
instance.useCss = driver !== "j"
if (instance.useCss) {
opts && avalon(el).addClass(opts.effectClass)
instance.cssEvent = driver === "t" ? transitionEndEvent : animationEndEvent
}
instance.name = name
instance.callbacks = avalon.effects[name] || {}
return instance
}
function effectBinding(elem, binding) {
var name = elem.getAttribute("data-effect-name")
if (name) {
binding.effectName = name
binding.effectDriver = elem.getAttribute("data-effect-driver")
var stagger = +elem.getAttribute("data-effect-stagger")
binding.effectLeaveStagger = +elem.getAttribute("data-effect-leave-stagger") || stagger
binding.effectEnterStagger = +elem.getAttribute("data-effect-enter-stagger") || stagger
binding.effectClass = elem.className || NaN
}
}
function upperFirstChar(str) {
return str.replace(/^[\S]/g, function (m) {
return m.toUpperCase()
})
}
var effectBuffer = new Buffer()
function Effect() {
}//动画实例,做成类的形式,是为了共用所有原型方法
Effect.prototype = {
contrustor: Effect,
enterClass: function () {
return getEffectClass(this, "enter")
},
leaveClass: function () {
return getEffectClass(this, "leave")
},
// 共享一个函数
actionFun: function (name, before, after) {
if (document.hidden) {
return
}
var me = this
var el = me.el
var isLeave = name === "leave"
name = isLeave ? "leave" : "enter"
var oppositeName = isLeave ? "enter" : "leave"
callEffectHook(me, "abort" + upperFirstChar(oppositeName))
callEffectHook(me, "before" + upperFirstChar(name))
if (!isLeave)
before(el) //这里可能做插入DOM树的操作,因此必须在修改类名前执行
var cssCallback = function (cancel) {
el.removeEventListener(me.cssEvent, me.cssCallback)
if (isLeave) {
before(el) //这里可能做移出DOM树操作,因此必须位于动画之后
avalon(el).removeClass(me.cssClass)
} else {
if (me.driver === "a") {
avalon(el).removeClass(me.cssClass)
}
}
if (cancel !== true) {
callEffectHook(me, "after" + upperFirstChar(name))
after && after(el)
}
me.dispose()
}
if (me.useCss) {
if (me.cssCallback) { //如果leave动画还没有完成,立即完成
me.cssCallback(true)
}
me.cssClass = getEffectClass(me, name)
me.cssCallback = cssCallback
me.update = function () {
el.addEventListener(me.cssEvent, me.cssCallback)
if (!isLeave && me.driver === "t") {//transtion延迟触发
avalon(el).removeClass(me.cssClass)
}
}
avalon(el).addClass(me.cssClass)//animation会立即触发
effectBuffer.render(true)
effectBuffer.queue.push(me)
} else {
callEffectHook(me, name, cssCallback)
}
},
enter: function (before, after) {
this.actionFun.apply(this, ["enter"].concat(avalon.slice(arguments)))
},
leave: function (before, after) {
this.actionFun.apply(this, ["leave"].concat(avalon.slice(arguments)))
},
dispose: function () {//销毁与回收到池子中
this.update = this.cssCallback = null
if (effectPool.unshift(this) > 100) {
effectPool.pop()
}
}
}
function getEffectClass(instance, type) {
var a = instance.callbacks[type + "Class"]
if (typeof a === "string")
return a
if (typeof a === "function")
return a()
return instance.name + "-" + type
}
function callEffectHook(effect, name, cb) {
var hook = effect.callbacks[name]
if (hook) {
hook.call(effect, effect.el, cb)
}
}
var applyEffect = function (el, dir/*[before, [after, [opts]]]*/) {
var args = aslice.call(arguments, 0)
if (typeof args[2] !== "function") {
args.splice(2, 0, noop)
}
if (typeof args[3] !== "function") {
args.splice(3, 0, noop)
}
var before = args[2]
var after = args[3]
var opts = args[4]
var effect = effectFactory(el, opts)
if (!effect) {
before()
after()
return false
} else {
var method = dir ? 'enter' : 'leave'
effect[method](before, after)
}
}
avalon.mix(avalon.effect, {
apply: applyEffect,
append: function (el, parent, after, opts) {
return applyEffect(el, 1, function () {
parent.appendChild(el)
}, after, opts)
},
before: function (el, target, after, opts) {
return applyEffect(el, 1, function () {
target.parentNode.insertBefore(el, target)
}, after, opts)
},
remove: function (el, parent, after, opts) {
return applyEffect(el, 0, function () {
if (el.parentNode === parent)
parent.removeChild(el)
}, after, opts)
}
})
avalon.directive("html", {
update: function (val) {
var binding = this
var elem = this.element
var isHtmlFilter = elem.nodeType !== 1
var parent = isHtmlFilter ? elem.parentNode : elem
if (!parent)
return
val = val == null ? "" : val
if (elem.nodeType === 3) {
var signature = generateID("html")
parent.insertBefore(DOC.createComment(signature), elem)
binding.element = DOC.createComment(signature + ":end")
parent.replaceChild(binding.element, elem)
elem = binding.element
}
if (typeof val !== "object") {//string, number, boolean
var fragment = avalon.parseHTML(String(val))
} else if (val.nodeType === 11) { //将val转换为文档碎片
fragment = val
} else if (val.nodeType === 1 || val.item) {
var nodes = val.nodeType === 1 ? val.childNodes : val.item
fragment = avalonFragment.cloneNode(true)
while (nodes[0]) {
fragment.appendChild(nodes[0])
}
}
nodes = avalon.slice(fragment.childNodes)
//插入占位符, 如果是过滤器,需要有节制地移除指定的数量,如果是html指令,直接清空
if (isHtmlFilter) {
var endValue = elem.nodeValue.slice(0, -4)
while (true) {
var node = elem.previousSibling
if (!node || node.nodeType === 8 && node.nodeValue === endValue) {
break
} else {
parent.removeChild(node)
}
}
parent.insertBefore(fragment, elem)
} else {
avalon.clearHTML(elem).appendChild(fragment)
}
scanNodeArray(nodes, binding.vmodels)
}
})
avalon.directive("if", {
priority: 10,
update: function (val) {
var binding = this
var elem = this.element
var stamp = binding.stamp = +new Date()
var par
var after = function () {
if (stamp !== binding.stamp)
return
binding.recoverNode = null
}
if (binding.recoverNode)
binding.recoverNode() // 还原现场,有移动节点的都需要还原现场
try {
if (!elem.parentNode)
return
par = elem.parentNode
} catch (e) {
return
}
if (val) { //插回DOM树
function alway() {// jshint ignore:line
if (elem.getAttribute(binding.name)) {
elem.removeAttribute(binding.name)
scanAttr(elem, binding.vmodels)
}
binding.rollback = null
}
if (elem.nodeType === 8) {
var keep = binding.keep
var hasEffect = avalon.effect.apply(keep, 1, function () {
if (stamp !== binding.stamp)
return
elem.parentNode.replaceChild(keep, elem)
elem = binding.element = keep //这时可能为null
if (keep.getAttribute("_required")) {//#1044
elem.required = true
elem.removeAttribute("_required")
}
if (elem.querySelectorAll) {
avalon.each(elem.querySelectorAll("[_required=true]"), function (el) {
el.required = true
el.removeAttribute("_required")
})
}
alway()
}, after)
hasEffect = hasEffect === false
}
if (!hasEffect)
alway()
} else { //移出DOM树并用注释节点占据原位置
if (elem.nodeType === 1) {
if (elem.required === true) {
elem.required = false
elem.setAttribute("_required", "true")
}
try {//如果不支持querySelectorAll或:required,可以直接无视
avalon.each(elem.querySelectorAll(":required"), function (el) {
elem.required = false
el.setAttribute("_required", "true")
})
} catch (e) {
}
var node = binding.element = DOC.createComment("ms-if"),
pos = elem.nextSibling
binding.recoverNode = function () {
binding.recoverNode = null
if (node.parentNode !== par) {
par.insertBefore(node, pos)
binding.keep = elem
}
}
avalon.effect.apply(elem, 0, function () {
binding.recoverNode = null
if (stamp !== binding.stamp)
return
elem.parentNode.replaceChild(node, elem)
//解决 当使用ms-if条件为常量表达式时 if节点泄漏
//https://github.com/RubyLouvre/avalon2/issues/78
//通过判断binding.getter()调用是否抛异常来判断ms-if的表达式是否为常量。
try{
if(!binding.getter()){ //如果binding.expr是常量binding.getter调用会返回false不需要将elem移动到ifGroup
binding.keep = elem //元素节点
binding.rollback = noop
}else{
//never been here
log('directive:ms-if: never been here.')
}
}catch(e){ //如果是vm属性绑定binding.getter调用会抛异常. 把节点移到ifGroup
binding.keep = elem //元素节点
ifGroup.appendChild(elem)
binding.rollback = function () {
if (elem.parentNode === ifGroup) {
ifGroup.removeChild(elem)
}
}
}
}, after)
}
}
}
})
//ms-important绑定已经在scanTag 方法中实现
var rnoscripts = /<noscript.*?>(?:[\s\S]+?)<\/noscript>/img
var rnoscriptText = /<noscript.*?>([\s\S]+?)<\/noscript>/im
var getXHR = function () {
return new (window.XMLHttpRequest || ActiveXObject)("Microsoft.XMLHTTP") // jshint ignore:line
}
//将所有远程加载的模板,以字符串形式存放到这里
var templatePool = avalon.templateCache = {}
function getTemplateContainer(binding, id, text) {
var div = binding.templateCache && binding.templateCache[id]
if (div) {
var dom = DOC.createDocumentFragment(),
firstChild
while (firstChild = div.firstChild) {
dom.appendChild(firstChild)
}
return dom
}
return avalon.parseHTML(text)
}
function nodesToFrag(nodes) {
var frag = DOC.createDocumentFragment()
for (var i = 0, len = nodes.length; i < len; i++) {
frag.appendChild(nodes[i])
}
return frag
}
avalon.directive("include", {
init: directives.attr.init,
update: function (val) {
var binding = this
var elem = this.element
var vmodels = binding.vmodels
var rendered = binding.includeRendered
var effectClass = binding.effectName && binding.effectClass // 是否开启动画
var templateCache = binding.templateCache // 是否data-include-cache
var outer = binding.includeReplace // 是否data-include-replace
var loaded = binding.includeLoaded
var target = outer ? elem.parentNode : elem
var _ele = binding._element // data-include-replace binding.element === binding.end
binding.recoverNodes = binding.recoverNodes || avalon.noop
var scanTemplate = function (text) {
var _stamp = binding._stamp = +(new Date()) // 过滤掉频繁操作
if (loaded) {
var newText = loaded.apply(target, [text].concat(vmodels))
if (typeof newText === "string")
text = newText
}
if (rendered) {
checkScan(target, function () {
rendered.call(target)
}, NaN)
}
var lastID = binding.includeLastID || "_default" // 默认
binding.includeLastID = val
var leaveEl = templateCache && templateCache[lastID] || DOC.createElement(elem.tagName || binding._element.tagName) // 创建一个离场元素
if (effectClass) {
leaveEl.className = effectClass
target.insertBefore(leaveEl, binding.start) // 插入到start之前防止被错误的移动
}
// cache or animate移动节点
(templateCache || {})[lastID] = leaveEl
var fragOnDom = binding.recoverNodes() // 恢复动画中的节点
if (fragOnDom) {
target.insertBefore(fragOnDom, binding.end)
}
while (true) {
var node = binding.start.nextSibling
if (node && node !== leaveEl && node !== binding.end) {
leaveEl.appendChild(node)
} else {
break
}
}
// 元素退场
avalon.effect.remove(leaveEl, target, function () {
if (templateCache) { // write cache
if (_stamp === binding._stamp)
ifGroup.appendChild(leaveEl)
}
}, binding)
var enterEl = target,
before = avalon.noop,
after = avalon.noop
var fragment = getTemplateContainer(binding, val, text)
var nodes = avalon.slice(fragment.childNodes)
if (outer && effectClass) {
enterEl = _ele
enterEl.innerHTML = "" // 清空
enterEl.setAttribute("ms-skip", "true")
target.insertBefore(enterEl, binding.end.nextSibling) // 插入到bingding.end之后避免被错误的移动
before = function () {
enterEl.insertBefore(fragment, null) // 插入节点
}
after = function () {
binding.recoverNodes = avalon.noop
if (_stamp === binding._stamp) {
fragment = nodesToFrag(nodes)
target.insertBefore(fragment, binding.end) // 插入真实element
scanNodeArray(nodes, vmodels)
}
if (enterEl.parentNode === target)
target.removeChild(enterEl) // 移除入场动画元素
}
binding.recoverNodes = function () {
binding.recoverNodes = avalon.noop
return nodesToFrag(nodes)
}
} else {
before = function () {//新添加元素的动画
target.insertBefore(fragment, binding.end)
scanNodeArray(nodes, vmodels)
}
}
avalon.effect.apply(enterEl, "enter", before, after)
}
if (binding.param === "src") {
if (typeof templatePool[val] === "string") {
avalon.nextTick(function () {
scanTemplate(templatePool[val])
})
} else if (Array.isArray(templatePool[val])) { //#805 防止在循环绑定中发出许多相同的请求
templatePool[val].push(scanTemplate)
} else {
var xhr = getXHR()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
var s = xhr.status
if (s >= 200 && s < 300 || s === 304 || s === 1223) {
var text = xhr.responseText
for (var f = 0, fn; fn = templatePool[val][f++]; ) {
fn(text)
}
templatePool[val] = text
}else{
log("ms-include load ["+ val +"] error")
}
}
}
templatePool[val] = [scanTemplate]
xhr.open("GET", val, true)
if ("withCredentials" in xhr) {
xhr.withCredentials = true
}
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest")
xhr.send(null)
}
} else {
//IE系列与够新的标准浏览器支持通过ID取得元素firefox14+
//http://tjvantoll.com/2012/07/19/dom-element-references-as-global-variables/
var el = val && val.nodeType === 1 ? val : DOC.getElementById(val)
if (el) {
if (el.tagName === "NOSCRIPT" && !(el.innerHTML || el.fixIE78)) { //IE7-8 innerText,innerHTML都无法取得其内容IE6能取得其innerHTML
xhr = getXHR() //IE9-11与chrome的innerHTML会得到转义的内容它们的innerText可以
xhr.open("GET", location, false)
xhr.send(null)
//http://bbs.csdn.net/topics/390349046?page=1#post-393492653
var noscripts = DOC.getElementsByTagName("noscript")
var array = (xhr.responseText || "").match(rnoscripts) || []
var n = array.length
for (var i = 0; i < n; i++) {
var tag = noscripts[i]
if (tag) { //IE6-8中noscript标签的innerHTML,innerText是只读的
tag.style.display = "none" //http://haslayout.net/css/noscript-Ghost-Bug
tag.fixIE78 = (array[i].match(rnoscriptText) || ["", "&nbsp;"])[1]
}
}
}
avalon.nextTick(function () {
scanTemplate(el.fixIE78 || el.value || el.innerText || el.innerHTML)
})
}
}
}
})
var rdash = /\(([^)]*)\)/
var onDir = avalon.directive("on", {
priority: 3000,
init: function (binding) {
var value = binding.expr
binding.type = "on"
var eventType = binding.param.replace(/-\d+$/, "") // ms-on-mousemove-10
if (typeof onDir[eventType + "Hook"] === "function") {
onDir[eventType + "Hook"](binding)
}
if (value.indexOf("(") > 0 && value.indexOf(")") > -1) {
var matched = (value.match(rdash) || ["", ""])[1].trim()
if (matched === "" || matched === "$event") { // aaa() aaa($event)当成aaa处理
value = value.replace(rdash, "")
}
}
binding.expr = value
},
update: function (callback) {
var binding = this
var elem = this.element
callback = function (e) {
var fn = binding.getter || noop
return fn.apply(this, binding.args.concat(e))
}
var eventType = binding.param.replace(/-\d+$/, "") // ms-on-mousemove-10
if (eventType === "scan") {
callback.call(elem, {
type: eventType
})
} else if (typeof binding.specialBind === "function") {
binding.specialBind(elem, callback)
} else {
var removeFn = avalon.bind(elem, eventType, callback)
}
binding.rollback = function () {
if (typeof binding.specialUnbind === "function") {
binding.specialUnbind()
} else {
avalon.unbind(elem, eventType, removeFn)
}
}
}
})
avalon.directive("repeat", {
priority: 90,
init: function (binding) {
var type = binding.type
binding.cache = {} //用于存放代理VM
binding.enterCount = 0
var elem = binding.element
if (elem.nodeType === 1) {
elem.removeAttribute(binding.name)
effectBinding(elem, binding)
binding.param = binding.param || "el"
binding.sortedCallback = getBindingCallback(elem, "data-with-sorted", binding.vmodels)
var rendered = getBindingCallback(elem, "data-" + type + "-rendered", binding.vmodels)
var signature = generateID(type)
var start = DOC.createComment(signature + ":start")
var end = binding.element = DOC.createComment(signature + ":end")
binding.signature = signature
binding.start = start
binding.template = avalonFragment.cloneNode(false)
if (type === "repeat") {
var parent = elem.parentNode
parent.replaceChild(end, elem)
parent.insertBefore(start, end)
binding.template.appendChild(elem)
} else {
while (elem.firstChild) {
binding.template.appendChild(elem.firstChild)
}
elem.appendChild(start)
elem.appendChild(end)
parent = elem
}
binding.element = end
if (rendered) {
var removeFn = avalon.bind(parent, "datasetchanged", function () {
rendered.apply(parent, parent.args)
avalon.unbind(parent, "datasetchanged", removeFn)
parent.msRendered = rendered
})
}
}
},
update: function (value, oldValue) {
var binding = this
var xtype = this.xtype
this.enterCount += 1
var init = !oldValue
if (init) {
binding.$outer = {}
var check0 = "$key"
var check1 = "$val"
if (xtype === "array") {
check0 = "$first"
check1 = "$last"
}
for (var i = 0, v; v = binding.vmodels[i++]; ) {
if (v.hasOwnProperty(check0) && v.hasOwnProperty(check1)) {
binding.$outer = v
break
}
}
}
var track = this.track
if (binding.sortedCallback) { //如果有回调,则让它们排序
var keys2 = binding.sortedCallback.call(parent, track)
if (keys2 && Array.isArray(keys2)) {
track = keys2
}
}
var action = "move"
binding.$repeat = value
var fragments = []
var transation = init && avalonFragment.cloneNode(false)
var proxies = []
var param = this.param
var retain = avalon.mix({}, this.cache)
var elem = this.element
var length = track.length
var parent = elem.parentNode
//检查新元素数量
var newCount = 0
for (i = 0; i < length; i++) {
var keyOrId = track[i]
if (!retain[keyOrId])
newCount++
}
var oldCount = 0
for (i in retain){
oldCount++
}
var clear = (!length || newCount === length) && oldCount > 10 //当全部是新元素,且移除元素较多(10)时使用clear
if (clear){
var kill = elem.previousSibling
var start = binding.start
while(kill !== start)
{
parent.removeChild(kill)
kill = elem.previousSibling
}
}
for (i = 0; i < length; i++) {
keyOrId = track[i] //array为随机数, object 为keyName
var proxy = retain[keyOrId]
if (!proxy) {
proxy = getProxyVM(this)
proxy.$up = null
if (xtype === "array") {
action = "add"
proxy.$id = keyOrId
var valueItem = value[i]
proxy[param] = valueItem //index
if (Object(valueItem) === valueItem) {
valueItem.$ups = valueItem.$ups || {}
valueItem.$ups[param] = proxy
}
} else {
action = "append"
proxy.$key = keyOrId
proxy.$val = value[keyOrId] //key
proxy[param] = { $key: proxy.$key, $val: proxy.$val }
}
this.cache[keyOrId] = proxy
var node = proxy.$anchor || (proxy.$anchor = elem.cloneNode(false))
node.nodeValue = this.signature
shimController(binding, transation, proxy, fragments, init && !binding.effectDriver)
decorateProxy(proxy, binding, xtype)
} else {
// if (xtype === "array") {
// proxy[param] = value[i]
// }
fragments.push({})
retain[keyOrId] = true
}
//重写proxy
if (this.enterCount === 1) {//防止多次进入,导致位置不对
proxy.$active = false
proxy.$oldIndex = proxy.$index
proxy.$active = true
proxy.$index = i
}
if (xtype === "array") {
proxy.$first = i === 0
proxy.$last = i === length - 1
// proxy[param] = value[i]
} else {
proxy.$val = toJson(value[keyOrId]) //这里是处理vm.object = newObject的情况
}
proxies.push(proxy)
}
this.proxies = proxies
if (init && !binding.effectDriver) {
parent.insertBefore(transation, elem)
fragments.forEach(function (fragment) {
scanNodeArray(fragment.nodes || [], fragment.vmodels)
//if(fragment.vmodels.length > 2)
fragment.nodes = fragment.vmodels = null
})// jshint ignore:line
} else {
var staggerIndex = binding.staggerIndex = 0
for (keyOrId in retain) {
if (retain[keyOrId] !== true) {
action = "del"
!clear && removeItem(retain[keyOrId].$anchor, binding,true)
// 相当于delete binding.cache[key]
proxyRecycler(this.cache, keyOrId, param)
retain[keyOrId] = null
}
}
for (i = 0; i < length; i++) {
proxy = proxies[i]
keyOrId = xtype === "array" ? proxy.$id : proxy.$key
var pre = proxies[i - 1]
var preEl = pre ? pre.$anchor : binding.start
if (!retain[keyOrId]) {//如果还没有插入到DOM树,进行插入动画
(function (fragment, preElement) {
var nodes = fragment.nodes
var vmodels = fragment.vmodels
if (nodes) {
staggerIndex = mayStaggerAnimate(binding.effectEnterStagger, function () {
parent.insertBefore(fragment.content, preElement.nextSibling)
scanNodeArray(nodes, vmodels)
!init && animateRepeat(nodes, 1, binding)
}, staggerIndex)
}
fragment.nodes = fragment.vmodels = null
})(fragments[i], preEl)// jshint ignore:line
} else if (proxy.$index !== proxy.$oldIndex) {//进行移动动画
(function (proxy2, preElement) {
staggerIndex = mayStaggerAnimate(binding.effectEnterStagger, function () {
var curNode = removeItem(proxy2.$anchor)
var inserted = avalon.slice(curNode.childNodes)
parent.insertBefore(curNode, preElement.nextSibling)
animateRepeat(inserted, 1, binding)
}, staggerIndex)
})(proxy, preEl)// jshint ignore:line
}
}
}
if (!value.$track) {//如果是非监控对象,那么就将其$events清空,阻止其持续监听
for (keyOrId in this.cache) {
proxyRecycler(this.cache, keyOrId, param)
}
}
//repeat --> duplex
(function (args) {
parent.args = args
if (parent.msRendered) {//第一次事件触发,以后直接调用
parent.msRendered.apply(parent, args)
}
})(kernel.newWatch ? arguments : [action]);
var id = setTimeout(function () {
clearTimeout(id)
//触发上层的select回调及自己的rendered回调
avalon.fireDom(parent, "datasetchanged", {
bubble: parent.msHasEvent
})
})
this.enterCount -= 1
}
})
"with,each".replace(rword, function (name) {
directives[name] = avalon.mix({}, directives.repeat, {
priority: 1400
})
})
function animateRepeat(nodes, isEnter, binding) {
for (var i = 0, node; node = nodes[i++]; ) {
if (node.className === binding.effectClass) {
avalon.effect.apply(node, isEnter, noop, noop, binding)
}
}
}
function mayStaggerAnimate(staggerTime, callback, index) {
if (staggerTime) {
setTimeout(callback, (++index) * staggerTime)
} else {
callback()
}
return index
}
function removeItem(node, binding, flagRemove) {
var fragment = avalonFragment.cloneNode(false)
var last = node
var breakText = last.nodeValue
var staggerIndex = binding && Math.max(+binding.staggerIndex, 0)
var nodes = avalon.slice(last.parentNode.childNodes)
var index = nodes.indexOf(last)
while (true) {
var pre = nodes[--index] //node.previousSibling
if (!pre || String(pre.nodeValue).indexOf(breakText) === 0) {
break
}
if (!flagRemove && binding && (pre.className === binding.effectClass)) {
node = pre;
(function (cur) {
binding.staggerIndex = mayStaggerAnimate(binding.effectLeaveStagger, function () {
avalon.effect.apply(cur, 0, noop, function () {
fragment.appendChild(cur)
}, binding)
}, staggerIndex)
})(pre);// jshint ignore:line
} else {
fragment.insertBefore(pre, fragment.firstChild)
}
}
fragment.appendChild(last)
return fragment
}
function shimController(data, transation, proxy, fragments, init) {
var content = data.template.cloneNode(true)
var nodes = avalon.slice(content.childNodes)
content.appendChild(proxy.$anchor)
init && transation.appendChild(content)
var itemName = data.param || "el"
var valueItem = proxy[itemName], nv
nv = [proxy].concat(data.vmodels)
var fragment = {
nodes: nodes,
vmodels: nv,
content: content
}
fragments.push(fragment)
}
// {} --> {xx: 0, yy: 1, zz: 2} add
// {xx: 0, yy: 1, zz: 2} --> {xx: 0, yy: 1, zz: 2, uu: 3}
// [xx: 0, yy: 1, zz: 2} --> {xx: 0, zz: 1, yy: 2}
function getProxyVM(binding) {
var agent = binding.xtype === "object" ? withProxyAgent : eachProxyAgent
var proxy = agent(binding)
var node = proxy.$anchor || (proxy.$anchor = binding.element.cloneNode(false))
node.nodeValue = binding.signature
proxy.$outer = binding.$outer
return proxy
}
function decorateProxy(proxy, binding, type) {
if (type === "array") {
proxy.$remove = function () {
binding.$repeat.removeAt(proxy.$index)
}
var param = binding.param
proxy.$watch(param, function (a) {
var index = proxy.$index
binding.$repeat[index] = a
})
} else {
proxy.$watch("$val", function fn(a) {
binding.$repeat[proxy.$key] = a
})
}
}
var eachProxyPool = []
function eachProxyAgent(data, proxy) {
var itemName = data.param || "el"
for (var i = 0, n = eachProxyPool.length; i < n; i++) {
var candidate = eachProxyPool[i]
if (candidate && candidate.hasOwnProperty(itemName)) {
eachProxyPool.splice(i, 1)
proxy = candidate
break
}
}
if (!proxy) {
proxy = eachProxyFactory(itemName)
}
return proxy
}
function eachProxyFactory(itemName) {
var source = {
$outer: {},
$index: 0,
$oldIndex: 0,
$anchor: null,
//-----
$first: false,
$last: false,
$remove: avalon.noop
}
source[itemName] = NaN
var force = {
$last: 1,
$first: 1,
$index: 1
}
force[itemName] = 1
var proxy = modelFactory(source, {
force: force
})
proxy.$id = generateID("$proxy$each")
return proxy
}
var withProxyPool = []
function withProxyAgent(data) {
var itemName = data.param || "el"
return withProxyPool.pop() || withProxyFactory(itemName)
}
function withProxyFactory(itemName) {
var source = {
$key: "",
$val: NaN,
$index: 0,
$oldIndex: 0,
$outer: {},
$anchor: null
}
source[itemName] = NaN
var force = {
$key: 1,
$val: 1,
$index: 1
}
force[itemName] = 1
var proxy = modelFactory(source, {
force: force
})
proxy.$id = generateID("$proxy$with")
return proxy
}
function proxyRecycler(cache, key, param) {
var proxy = cache[key]
if (proxy) {
var proxyPool = proxy.$id.indexOf("$proxy$each") === 0 ? eachProxyPool : withProxyPool
proxy.$outer = {}
for (var i in proxy.$events) {
var a = proxy.$events[i]
if (Array.isArray(a)) {
a.length = 0
if (i === param) {
proxy[param] = NaN
} else if (i === "$val") {
proxy.$val = NaN
}
}
}
if (proxyPool.unshift(proxy) > kernel.maxRepeatSize) {
proxyPool.pop()
}
delete cache[key]
}
}
/*********************************************************************
* 各种指令 *
**********************************************************************/
//ms-skip绑定已经在scanTag 方法中实现
avalon.directive("text", {
update: function (value) {
var elem = this.element
value = value == null ? "" : value //不在页面上显示undefined null
if (elem.nodeType === 3) { //绑定在文本节点上
try { //IE对游离于DOM树外的节点赋值会报错
elem.data = value
} catch (e) {
}
} else { //绑定在特性节点上
if ("textContent" in elem) {
elem.textContent = value
} else {
elem.innerText = value
}
}
}
})
function parseDisplay(nodeName, val) {
//用于取得此类标签的默认display值
var key = "_" + nodeName
if (!parseDisplay[key]) {
var node = DOC.createElement(nodeName)
root.appendChild(node)
if (W3C) {
val = getComputedStyle(node, null).display
} else {
val = node.currentStyle.display
}
root.removeChild(node)
parseDisplay[key] = val
}
return parseDisplay[key]
}
avalon.parseDisplay = parseDisplay
avalon.directive("visible", {
init: function (binding) {
effectBinding(binding.element, binding)
},
update: function (val) {
var binding = this, elem = this.element, stamp
var noEffect = !this.effectName
if (!this.stamp) {
stamp = this.stamp = +new Date()
if (val) {
elem.style.display = binding.display || ""
if (avalon(elem).css("display") === "none") {
elem.style.display = binding.display = parseDisplay(elem.nodeName)
}
} else {
elem.style.display = "none"
}
return
}
stamp = this.stamp = +new Date()
if (val) {
avalon.effect.apply(elem, 1, function () {
if (stamp !== binding.stamp)
return
var driver = elem.getAttribute("data-effect-driver") || "a"
if (noEffect) {//不用动画时走这里
elem.style.display = binding.display || ""
}
// "a", "t"
if (driver === "a" || driver === "t") {
if (avalon(elem).css("display") === "none") {
elem.style.display = binding.display || parseDisplay(elem.nodeName)
}
}
})
} else {
avalon.effect.apply(elem, 0, function () {
if (stamp !== binding.stamp)
return
elem.style.display = "none"
})
}
}
})
/*********************************************************************
* 自带过滤器 *
**********************************************************************/
var rscripts = /<script[^>]*>([\S\s]*?)<\/script\s*>/gim
var ron = /\s+(on[^=\s]+)(?:=("[^"]*"|'[^']*'|[^\s>]+))?/g
var ropen = /<\w+\b(?:(["'])[^"]*?(\1)|[^>])*>/ig
var rsanitize = {
a: /\b(href)\=("javascript[^"]*"|'javascript[^']*')/ig,
img: /\b(src)\=("javascript[^"]*"|'javascript[^']*')/ig,
form: /\b(action)\=("javascript[^"]*"|'javascript[^']*')/ig
}
var rsurrogate = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g
var rnoalphanumeric = /([^\#-~| |!])/g;
function numberFormat(number, decimals, point, thousands) {
//form http://phpjs.org/functions/number_format/
//number 必需,要格式化的数字
//decimals 可选,规定多少个小数位。
//point 可选,规定用作小数点的字符串(默认为 . )。
//thousands 可选,规定用作千位分隔符的字符串(默认为 , ),如果设置了该参数,那么所有其他参数都是必需的。
number = (number + '')
.replace(/[^0-9+\-Ee.]/g, '')
var n = !isFinite(+number) ? 0 : +number,
prec = !isFinite(+decimals) ? 3 : Math.abs(decimals),
sep = thousands || ",",
dec = point || ".",
s = '',
toFixedFix = function(n, prec) {
var k = Math.pow(10, prec)
return '' + (Math.round(n * k) / k)
.toFixed(prec)
}
// Fix for IE parseFloat(0.55).toFixed(0) = 0;
s = (prec ? toFixedFix(n, prec) : '' + Math.round(n))
.split('.')
if (s[0].length > 3) {
s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, sep)
}
if ((s[1] || '')
.length < prec) {
s[1] = s[1] || ''
s[1] += new Array(prec - s[1].length + 1)
.join('0')
}
return s.join(dec)
}
var filters = avalon.filters = {
uppercase: function(str) {
return str.toUpperCase()
},
lowercase: function(str) {
return str.toLowerCase()
},
truncate: function(str, length, truncation) {
//length新字符串长度truncation新字符串的结尾的字段,返回新字符串
length = length || 30
truncation = typeof truncation === "string" ? truncation : "..."
return str.length > length ? str.slice(0, length - truncation.length) + truncation : String(str)
},
$filter: function(val) {
for (var i = 1, n = arguments.length; i < n; i++) {
var array = arguments[i]
var fn = avalon.filters[array[0]]
if (typeof fn === "function") {
var arr = [val].concat(array.slice(1))
val = fn.apply(null, arr)
}
}
return val
},
camelize: camelize,
//https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
// <a href="javasc&NewLine;ript&colon;alert('XSS')">chrome</a>
// <a href="data:text/html;base64, PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KDEpPg==">chrome</a>
// <a href="jav ascript:alert('XSS');">IE67chrome</a>
// <a href="jav&#x09;ascript:alert('XSS');">IE67chrome</a>
// <a href="jav&#x0A;ascript:alert('XSS');">IE67chrome</a>
sanitize: function(str) {
return str.replace(rscripts, "").replace(ropen, function(a, b) {
var match = a.toLowerCase().match(/<(\w+)\s/)
if (match) { //处理a标签的href属性img标签的src属性form标签的action属性
var reg = rsanitize[match[1]]
if (reg) {
a = a.replace(reg, function(s, name, value) {
var quote = value.charAt(0)
return name + "=" + quote + "javascript:void(0)" + quote// jshint ignore:line
})
}
}
return a.replace(ron, " ").replace(/\s+/g, " ") //移除onXXX事件
})
},
escape: function(str) {
//将字符串经过 str 转义得到适合在页面中显示的内容, 例如替换 < 为 &lt
return String(str).
replace(/&/g, '&amp;').
replace(rsurrogate, function(value) {
var hi = value.charCodeAt(0)
var low = value.charCodeAt(1)
return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';'
}).
replace(rnoalphanumeric, function(value) {
return '&#' + value.charCodeAt(0) + ';'
}).
replace(/</g, '&lt;').
replace(/>/g, '&gt;')
},
currency: function(amount, symbol, fractionSize) {
return (symbol || "\u00a5") + numberFormat(amount, isFinite(fractionSize) ? fractionSize : 2)
},
number: numberFormat
}
/*
'yyyy': 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010)
'yy': 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10)
'y': 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199)
'MMMM': Month in year (January-December)
'MMM': Month in year (Jan-Dec)
'MM': Month in year, padded (01-12)
'M': Month in year (1-12)
'dd': Day in month, padded (01-31)
'd': Day in month (1-31)
'EEEE': Day in Week,(Sunday-Saturday)
'EEE': Day in Week, (Sun-Sat)
'HH': Hour in day, padded (00-23)
'H': Hour in day (0-23)
'hh': Hour in am/pm, padded (01-12)
'h': Hour in am/pm, (1-12)
'mm': Minute in hour, padded (00-59)
'm': Minute in hour (0-59)
'ss': Second in minute, padded (00-59)
's': Second in minute (0-59)
'a': am/pm marker
'Z': 4 digit (+sign) representation of the timezone offset (-1200-+1200)
format string can also be one of the following predefined localizable formats:
'medium': equivalent to 'MMM d, y h:mm:ss a' for en_US locale (e.g. Sep 3, 2010 12:05:08 pm)
'short': equivalent to 'M/d/yy h:mm a' for en_US locale (e.g. 9/3/10 12:05 pm)
'fullDate': equivalent to 'EEEE, MMMM d,y' for en_US locale (e.g. Friday, September 3, 2010)
'longDate': equivalent to 'MMMM d, y' for en_US locale (e.g. September 3, 2010
'mediumDate': equivalent to 'MMM d, y' for en_US locale (e.g. Sep 3, 2010)
'shortDate': equivalent to 'M/d/yy' for en_US locale (e.g. 9/3/10)
'mediumTime': equivalent to 'h:mm:ss a' for en_US locale (e.g. 12:05:08 pm)
'shortTime': equivalent to 'h:mm a' for en_US locale (e.g. 12:05 pm)
*/
new function() {// jshint ignore:line
function toInt(str) {
return parseInt(str, 10) || 0
}
function padNumber(num, digits, trim) {
var neg = ""
if (num < 0) {
neg = '-'
num = -num
}
num = "" + num
while (num.length < digits)
num = "0" + num
if (trim)
num = num.substr(num.length - digits)
return neg + num
}
function dateGetter(name, size, offset, trim) {
return function(date) {
var value = date["get" + name]()
if (offset > 0 || value > -offset)
value += offset
if (value === 0 && offset === -12) {
value = 12
}
return padNumber(value, size, trim)
}
}
function dateStrGetter(name, shortForm) {
return function(date, formats) {
var value = date["get" + name]()
var get = (shortForm ? ("SHORT" + name) : name).toUpperCase()
return formats[get][value]
}
}
function timeZoneGetter(date) {
var zone = -1 * date.getTimezoneOffset()
var paddedZone = (zone >= 0) ? "+" : ""
paddedZone += padNumber(Math[zone > 0 ? "floor" : "ceil"](zone / 60), 2) + padNumber(Math.abs(zone % 60), 2)
return paddedZone
}
//取得上午下午
function ampmGetter(date, formats) {
return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]
}
var DATE_FORMATS = {
yyyy: dateGetter("FullYear", 4),
yy: dateGetter("FullYear", 2, 0, true),
y: dateGetter("FullYear", 1),
MMMM: dateStrGetter("Month"),
MMM: dateStrGetter("Month", true),
MM: dateGetter("Month", 2, 1),
M: dateGetter("Month", 1, 1),
dd: dateGetter("Date", 2),
d: dateGetter("Date", 1),
HH: dateGetter("Hours", 2),
H: dateGetter("Hours", 1),
hh: dateGetter("Hours", 2, -12),
h: dateGetter("Hours", 1, -12),
mm: dateGetter("Minutes", 2),
m: dateGetter("Minutes", 1),
ss: dateGetter("Seconds", 2),
s: dateGetter("Seconds", 1),
sss: dateGetter("Milliseconds", 3),
EEEE: dateStrGetter("Day"),
EEE: dateStrGetter("Day", true),
a: ampmGetter,
Z: timeZoneGetter
}
var rdateFormat = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/
var raspnetjson = /^\/Date\((\d+)\)\/$/
filters.date = function(date, format) {
var locate = filters.date.locate,
text = "",
parts = [],
fn, match
format = format || "mediumDate"
format = locate[format] || format
if (typeof date === "string") {
if (/^\d+$/.test(date)) {
date = toInt(date)
} else if (raspnetjson.test(date)) {
date = +RegExp.$1
} else {
var trimDate = date.trim()
var dateArray = [0, 0, 0, 0, 0, 0, 0]
var oDate = new Date(0)
//取得年月日
trimDate = trimDate.replace(/^(\d+)\D(\d+)\D(\d+)/, function(_, a, b, c) {
var array = c.length === 4 ? [c, a, b] : [a, b, c]
dateArray[0] = toInt(array[0]) //年
dateArray[1] = toInt(array[1]) - 1 //月
dateArray[2] = toInt(array[2]) //日
return ""
})
var dateSetter = oDate.setFullYear
var timeSetter = oDate.setHours
trimDate = trimDate.replace(/[T\s](\d+):(\d+):?(\d+)?\.?(\d)?/, function(_, a, b, c, d) {
dateArray[3] = toInt(a) //小时
dateArray[4] = toInt(b) //分钟
dateArray[5] = toInt(c) //秒
if (d) { //毫秒
dateArray[6] = Math.round(parseFloat("0." + d) * 1000)
}
return ""
})
var tzHour = 0
var tzMin = 0
trimDate = trimDate.replace(/Z|([+-])(\d\d):?(\d\d)/, function(z, symbol, c, d) {
dateSetter = oDate.setUTCFullYear
timeSetter = oDate.setUTCHours
if (symbol) {
tzHour = toInt(symbol + c)
tzMin = toInt(symbol + d)
}
return ""
})
dateArray[3] -= tzHour
dateArray[4] -= tzMin
dateSetter.apply(oDate, dateArray.slice(0, 3))
timeSetter.apply(oDate, dateArray.slice(3))
date = oDate
}
}
if (typeof date === "number") {
date = new Date(date)
}
if (avalon.type(date) !== "date") {
return
}
while (format) {
match = rdateFormat.exec(format)
if (match) {
parts = parts.concat(match.slice(1))
format = parts.pop()
} else {
parts.push(format)
format = null
}
}
parts.forEach(function(value) {
fn = DATE_FORMATS[value]
text += fn ? fn(date, locate) : value.replace(/(^'|'$)/g, "").replace(/''/g, "'")
})
return text
}
var locate = {
AMPMS: {
0: "上午",
1: "下午"
},
DAY: {
0: "星期日",
1: "星期一",
2: "星期二",
3: "星期三",
4: "星期四",
5: "星期五",
6: "星期六"
},
MONTH: {
0: "1月",
1: "2月",
2: "3月",
3: "4月",
4: "5月",
5: "6月",
6: "7月",
7: "8月",
8: "9月",
9: "10月",
10: "11月",
11: "12月"
},
SHORTDAY: {
"0": "周日",
"1": "周一",
"2": "周二",
"3": "周三",
"4": "周四",
"5": "周五",
"6": "周六"
},
fullDate: "y年M月d日EEEE",
longDate: "y年M月d日",
medium: "yyyy-M-d H:mm:ss",
mediumDate: "yyyy-M-d",
mediumTime: "H:mm:ss",
"short": "yy-M-d ah:mm",
shortDate: "yy-M-d",
shortTime: "ah:mm"
}
locate.SHORTMONTH = locate.MONTH
filters.date.locate = locate
}// jshint ignore:line
/*********************************************************************
* AMD加载器 *
**********************************************************************/
//https://www.devbridge.com/articles/understanding-amd-requirejs/
//http://maxogden.com/nested-dependencies.html
var modules = avalon.modules = {
"domReady!": {
exports: avalon,
state: 3
},
"avalon": {
exports: avalon,
state: 4
}
}
//Object(modules[id]).state拥有如下值
// undefined 没有定义
// 1(send) 已经发出请求
// 2(loading) 已经被执行但还没有执行完成在这个阶段define方法会被执行
// 3(loaded) 执行完毕通过onload/onreadystatechange回调判定在这个阶段checkDeps方法会执行
// 4(execute) 其依赖也执行完毕, 值放到exports对象上在这个阶段fireFactory方法会执行
modules.exports = modules.avalon
var otherRequire = window.require
var otherDefine = window.define
var innerRequire
plugins.loader = function (builtin) {
var flag = innerRequire && builtin
window.require = flag ? innerRequire : otherRequire
window.define = flag ? innerRequire.define : otherDefine
}
new function () { // jshint ignore:line
var loadings = [] //正在加载中的模块列表
var factorys = [] //放置define方法的factory函数
var rjsext = /\.js$/i
var rquery = /(\?[^#]*)$/
function makeRequest(name, config) {
//1. 去掉资源前缀
var res = "js"
name = name.replace(/^(\w+)\!/, function (a, b) {
res = b
return ""
})
if (res === "ready") {
log("debug: ready!已经被废弃请使用domReady!")
res = "domReady"
}
//2. 去掉querystring, hash
var query = ""
name = name.replace(rquery, function (a) {
query = a
return ""
})
//3. 去掉扩展名
var suffix = "." + res
var ext = /js|css/.test(suffix) ? suffix : ""
name = name.replace(/\.[a-z0-9]+$/g, function (a) {
if (a === suffix) {
ext = a
return ""
} else {
return a
}
})
var req = avalon.mix({
query: query,
ext: ext,
res: res,
name: name,
toUrl: toUrl
}, config)
req.toUrl(name)
return req
}
function fireRequest(req) {
var name = req.name
var res = req.res
//1. 如果该模块已经发出请求,直接返回
var module = modules[name]
var urlNoQuery = name && req.urlNoQuery
if (module && module.state >= 1) {
return name
}
module = modules[urlNoQuery]
if (module && module.state >= 3) {
innerRequire(module.deps || [], module.factory, urlNoQuery)
return urlNoQuery
}
if (name && !module) {
module = modules[urlNoQuery] = {
id: urlNoQuery,
state: 1 //send
}
var wrap = function (obj) {
resources[res] = obj//标识该插件已注册
obj.load(name, req, function (a) {
if (arguments.length && a !== void 0) {
module.exports = a
}
module.state = 4
checkDeps()
})
}
if (!resources[res]) {//如果资源插件不存在,先加载插件
innerRequire([res], wrap)
} else {
wrap(resources[res])//使用资源插件的load方法加载我们的模块
}
}
return name ? urlNoQuery : res + "!"
}
//核心API之一 require
var requireQueue = []
var isUserFirstRequire = false
innerRequire = avalon.require = function (array, factory, parentUrl, defineConfig) {
if (!isUserFirstRequire) {
requireQueue.push(avalon.slice(arguments))
if (arguments.length <= 2) {
isUserFirstRequire = true
var queue = requireQueue.splice(0, requireQueue.length),
args
while (args = queue.shift()) {
innerRequire.apply(null, args)
}
}
return
}
if (!Array.isArray(array)) {
avalon.error("require方法的第一个参数应为数组 " + array)
}
var deps = [] // 放置所有依赖项的完整路径
var uniq = {}
var id = parentUrl || "callback" + setTimeout("1") // jshint ignore:line
defineConfig = defineConfig || {}
defineConfig.baseUrl = kernel.baseUrl
var isBuilt = !!defineConfig.built
if (parentUrl) {
defineConfig.parentUrl = parentUrl.substr(0, parentUrl.lastIndexOf("/"))
defineConfig.mapUrl = parentUrl.replace(rjsext, "")
}
if (isBuilt) {
var req = makeRequest(defineConfig.defineName, defineConfig)
id = req.urlNoQuery
} else {
array.forEach(function (name) {
var req = makeRequest(name, defineConfig)
var url = fireRequest(req) //加载资源,并返回该资源的完整地址
if (url) {
if (!uniq[url]) {
deps.push(url)
uniq[url] = "司徒正美" //去重
}
}
})
}
var module = modules[id]
if (!module || module.state !== 4) {
modules[id] = {
id: id,
deps: isBuilt ? array.concat() : deps,
factory: factory || noop,
state: 3
}
}
if (!module) {
//如果此模块是定义在另一个JS文件中, 那必须等该文件加载完毕, 才能放到检测列队中
loadings.push(id)
}
checkDeps()
}
//核心API之二 require
innerRequire.define = function (name, deps, factory) { //模块名,依赖列表,模块本身
if (typeof name !== "string") {
factory = deps
deps = name
name = "anonymous"
}
if (!Array.isArray(deps)) {
factory = deps
deps = []
}
var config = {
built: !isUserFirstRequire, //用r.js打包后,所有define方法会放到require方法之前()
defineName: name
}
var args = [deps, factory, config]
factory.require = function (url) {
args.splice(2, 0, url)
if (modules[url]) {
modules[url].state = 3 //loaded
var isCycle = false
try {
isCycle = checkCycle(modules[url].deps, url)
} catch (e) {
}
if (isCycle) {
avalon.error(url + "模块与之前的模块存在循环依赖请不要直接用script标签引入" + url + "模块")
}
}
delete factory.require //释放内存
innerRequire.apply(null, args) //0,1,2 --> 1,2,0
}
//根据标准,所有遵循W3C标准的浏览器,script标签会按标签的出现顺序执行。
//老的浏览器中,加载也是按顺序的:一个文件下载完成后,才开始下载下一个文件。
//较新的浏览器中IE8+ 、FireFox3.5+ 、Chrome4+ 、Safari4+),为了减小请求时间以优化体验,
//下载可以是并行的,但是执行顺序还是按照标签出现的顺序。
//但如果script标签是动态插入的, 就未必按照先请求先执行的原则了,目测只有firefox遵守
//唯一比较一致的是,IE10+及其他标准浏览器,一旦开始解析脚本, 就会一直堵在那里,直接脚本解析完毕
//亦即先进入loading阶段的script标签(模块)必然会先进入loaded阶段
var url = config.built ? "unknown" : getCurrentScript()
if (url) {
var module = modules[url]
if (module) {
module.state = 2
}
factory.require(url)
} else { //合并前后的safari合并后的IE6-9走此分支
factorys.push(factory)
}
}
//核心API之三 require.config(settings)
innerRequire.config = kernel
//核心API之四 define.amd 标识其符合AMD规范
innerRequire.define.amd = modules
//==========================对用户配置项进行再加工==========================
var allpaths = kernel["orig.paths"] = {}
var allmaps = kernel["orig.map"] = {}
var allpackages = kernel["packages"] = []
var allargs = kernel["orig.args"] = {}
avalon.mix(plugins, {
paths: function (hash) {
avalon.mix(allpaths, hash)
kernel.paths = makeIndexArray(allpaths)
},
map: function (hash) {
avalon.mix(allmaps, hash)
var list = makeIndexArray(allmaps, 1, 1)
avalon.each(list, function (_, item) {
item.val = makeIndexArray(item.val)
})
kernel.map = list
},
packages: function (array) {
array = array.concat(allpackages)
var uniq = {}
var ret = []
for (var i = 0, pkg; pkg = array[i++]; ) {
pkg = typeof pkg === "string" ? {
name: pkg
} : pkg
var name = pkg.name
if (!uniq[name]) {
var url = joinPath(pkg.location || name, pkg.main || "main")
url = url.replace(rjsext, "")
ret.push(pkg)
uniq[name] = pkg.location = url
pkg.reg = makeMatcher(name)
}
}
kernel.packages = ret.sort()
},
urlArgs: function (hash) {
if (typeof hash === "string") {
hash = {
"*": hash
}
}
avalon.mix(allargs, hash)
kernel.urlArgs = makeIndexArray(allargs, 1)
},
baseUrl: function (url) {
if (!isAbsUrl(url)) {
var baseElement = head.getElementsByTagName("base")[0]
if (baseElement) {
head.removeChild(baseElement)
}
var node = DOC.createElement("a")
node.href = url
url = getFullUrl(node, "href")
if (baseElement) {
head.insertBefore(baseElement, head.firstChild)
}
}
if (url.length > 3)
kernel.baseUrl = url
},
shim: function (obj) {
for (var i in obj) {
var value = obj[i]
if (Array.isArray(value)) {
value = obj[i] = {
deps: value
}
}
if (!value.exportsFn && (value.exports || value.init)) {
value.exportsFn = makeExports(value)
}
}
kernel.shim = obj
}
})
//==============================内部方法=================================
function checkCycle(deps, nick) {
//检测是否存在循环依赖
for (var i = 0, id; id = deps[i++]; ) {
if (modules[id].state !== 4 &&
(id === nick || checkCycle(modules[id].deps, nick))) {
return true
}
}
}
function checkFail(node, onError, fuckIE) {
var id = trimQuery(node.src) //检测是否死链
node.onload = node.onreadystatechange = node.onerror = null
if (onError || (fuckIE && modules[id] && !modules[id].state)) {
setTimeout(function () {
head.removeChild(node)
node = null // 处理旧式IE下的循环引用问题
})
log("debug: 加载 " + id + " 失败" + onError + " " + (!modules[id].state))
} else {
return true
}
}
function checkDeps() {
//检测此模块的依赖是否都执行完毕,是则执行自身
loop: for (var i = loadings.length, id; id = loadings[--i]; ) {
var obj = modules[id],
deps = obj.deps
if (!deps)
continue
for (var j = 0, key; key = deps[j]; j++) {
if (Object(modules[key]).state !== 4) {
continue loop
}
}
//如果deps是空对象或者其依赖的模块的状态都是4
if (obj.state !== 4) {
loadings.splice(i, 1) //必须先移除再安装防止在IE下DOM树建完后手动刷新页面会多次执行它
fireFactory(obj.id, obj.deps, obj.factory)
checkDeps() //如果成功,则再执行一次,以防有些模块就差本模块没有执行
}
}
}
var rreadyState = /complete|loaded/
function loadJS(url, id, callback) {
//通过script节点加载目标模块
var node = DOC.createElement("script")
var supportLoad = "onload" in node
var onEvent = supportLoad ? "onload" : "onreadystatechange"
function onload() {
var factory = factorys.pop()//处理safari早期版本
factory && factory.require(id)
if (callback) {
callback()
}
if (checkFail(node, false, !supportLoad)) {
log("debug: 已成功加载 " + url)
id && loadings.push(id)
checkDeps()
}
}
node[onEvent] = supportLoad ? onload : function () {
if (rreadyState.test(node.readyState)) {
onload()
}
}
node.onerror = function () {
checkFail(node, true)
}
node.className = subscribers //让getCurrentScript只处理类名为subscribers的script节点
node.src = url //插入到head的第一个节点前防止IE6下head标签没闭合前使用appendChild抛错
head.insertBefore(node, head.firstChild) //chrome下第二个参数不能为null
log("debug: 正准备加载 " + url) //更重要的是IE6下可以收窄getCurrentScript的寻找范围
}
var resources = innerRequire.plugins = {
//三大常用资源插件 js!, css!, text!, domReady!
domReady: {
load: noop
},
js: {
load: function (name, req, onLoad) {
var url = req.url
var id = req.urlNoQuery
var shim = kernel.shim[name.replace(rjsext, "")]
if (shim) { //shim机制
innerRequire(shim.deps || [], function () {
var args = avalon.slice(arguments)
loadJS(url, id, function () {
onLoad(shim.exportsFn ? shim.exportsFn.apply(0, args) : void 0)
})
})
} else {
loadJS(url, id)
}
}
},
css: {
load: function (name, req, onLoad) {
var url = req.url
var node = DOC.createElement("link")
node.rel = "stylesheet"
node.href = url
head.insertBefore(node, head.firstChild)
log("debug: 已成功加载 " + url)
onLoad()
}
},
text: {
load: function (name, req, onLoad) {
var url = req.url
var xhr = getXHR()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
var status = xhr.status;
if (status > 399 && status < 600) {
avalon.error(url + " 对应资源不存在或没有开启 CORS")
} else {
log("debug: 已成功加载 " + url)
onLoad(xhr.responseText)
}
}
}
var time = "_=" + (new Date() - 0)
var _url = url.indexOf("?") === -1 ? url + "?" + time : url + "&" + time
xhr.open("GET", _url, true)
if ("withCredentials" in xhr) { //这是处理跨域
xhr.withCredentials = true
}
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest") //告诉后端这是AJAX请求
xhr.send()
log("debug: 正准备加载 " + url)
}
}
}
innerRequire.checkDeps = checkDeps
function trimQuery(url) {
return (url || "").replace(rquery, "")
}
function isAbsUrl(path) {
//http://stackoverflow.com/questions/10687099/how-to-test-if-a-url-string-is-absolute-or-relative
return /^(?:[a-z]+:)?\/\//i.test(String(path))
}
function getFullUrl(node, src) {
return "1" [0] ? node[src] : node.getAttribute(src, 4)
}
function getCurrentScript() {
// inspireb by https://github.com/samyk/jiagra/blob/master/jiagra.js
var stack
try {
a.b.c() //强制报错,以便捕获e.stack
} catch (e) { //safari5的sourceURLfirefox的fileName它们的效果与e.stack不一样
stack = e.stack
if (!stack && window.opera) {
//opera 9没有e.stack,但有e.Backtrace,但不能直接取得,需要对e对象转字符串进行抽取
stack = (String(e).match(/of linked script \S+/g) || []).join(" ")
}
}
if (stack) {
/**e.stack最后一行在所有支持的浏览器大致如下:
*chrome23:
* at http://113.93.50.63/data.js:4:1
*firefox17:
*@http://113.93.50.63/query.js:4
*opera12:http://www.oldapps.com/opera.php?system=Windows_XP
*@http://113.93.50.63/data.js:4
*IE10:
* at Global code (http://113.93.50.63/data.js:4:1)
* //firefox4+ 可以用document.currentScript
*/
stack = stack.split(/[@ ]/g).pop() //取得最后一行,最后一个空格或@之后的部分
stack = stack[0] === "(" ? stack.slice(1, -1) : stack.replace(/\s/, "") //去掉换行符
return trimQuery(stack.replace(/(:\d+)?:\d+$/i, "")) //去掉行号与或许存在的出错字符起始位置
}
var nodes = head.getElementsByTagName("script") //只在head标签中寻找
for (var i = nodes.length, node; node = nodes[--i]; ) {
if (node.className === subscribers && node.readyState === "interactive") {
var url = getFullUrl(node, "src")
return node.className = trimQuery(url)
}
}
}
var rcallback = /^callback\d+$/
function fireFactory(id, deps, factory) {
var module = Object(modules[id])
module.state = 4
for (var i = 0, array = [], d; d = deps[i++]; ) {
if (d === "exports") {
var obj = module.exports || (module.exports = {})
array.push(obj)
} else {
array.push(modules[d].exports)
}
}
try {
var ret = factory.apply(window, array)
} catch (e) {
log("执行[" + id + "]模块的factory抛错 ", e)
}
if (ret !== void 0) {
modules[id].exports = ret
}
modules[id].state = 4
if (rcallback.test(id)) {
delete modules[id]
}
delete module.factory
return ret
}
function toUrl(id) {
if (id.indexOf(this.res + "!") === 0) {
id = id.slice(this.res.length + 1) //处理define("css!style",[], function(){})的情况
}
var url = id
//1. 是否命中paths配置项
var usePath = 0
var baseUrl = this.baseUrl
var rootUrl = this.parentUrl || baseUrl
eachIndexArray(id, kernel.paths, function (value, key) {
url = url.replace(key, value)
usePath = 1
})
//2. 是否命中packages配置项
if (!usePath) {
eachIndexArray(id, kernel.packages, function (value, key, item) {
url = url.replace(item.name, item.location)
})
}
//3. 是否命中map配置项
if (this.mapUrl) {
eachIndexArray(this.mapUrl, kernel.map, function (array) {
eachIndexArray(url, array, function (mdValue, mdKey) {
url = url.replace(mdKey, mdValue)
rootUrl = baseUrl
})
})
}
var ext = this.ext
if (ext && usePath && url.slice(-ext.length) === ext) {
url = url.slice(0, -ext.length)
}
//4. 转换为绝对路径
if (!isAbsUrl(url)) {
rootUrl = this.built || /^\w/.test(url) ? baseUrl : rootUrl
url = joinPath(rootUrl, url)
}
//5. 还原扩展名query
var urlNoQuery = url + ext
url = urlNoQuery + this.query
urlNoQuery = url.replace(rquery, function (a) {
this.query = a
return ""
})
//6. 处理urlArgs
eachIndexArray(id, kernel.urlArgs, function (value) {
url += (url.indexOf("?") === -1 ? "?" : "&") + value;
})
this.url = url
return this.urlNoQuery = urlNoQuery
}
function makeIndexArray(hash, useStar, part) {
//创建一个经过特殊算法排好序的数组
var index = hash2array(hash, useStar, part)
index.sort(descSorterByName)
return index
}
function makeMatcher(prefix) {
return new RegExp('^' + prefix + '(/|$)')
}
function makeExports(value) {
return function () {
var ret
if (value.init) {
ret = value.init.apply(window, arguments)
}
return ret || (value.exports && getGlobal(value.exports))
}
}
function hash2array(hash, useStar, part) {
var array = [];
for (var key in hash) {
if (ohasOwn.call(hash, key)) {
var item = {
name: key,
val: hash[key]
}
array.push(item)
item.reg = key === "*" && useStar ? /^/ : makeMatcher(key)
if (part && key !== "*") {
item.reg = new RegExp('\/' + key.replace(/^\//, "") + '(/|$)')
}
}
}
return array
}
function eachIndexArray(moduleID, array, matcher) {
array = array || []
for (var i = 0, el; el = array[i++]; ) {
if (el.reg.test(moduleID)) {
matcher(el.val, el.name, el)
return false
}
}
}
// 根据元素的name项进行数组字符数逆序的排序函数
function descSorterByName(a, b) {
var aaa = a.name
var bbb = b.name
if (bbb === "*") {
return -1
}
if (aaa === "*") {
return 1
}
return bbb.length - aaa.length
}
var rdeuce = /\/\w+\/\.\./
function joinPath(a, b) {
if (a.charAt(a.length - 1) !== "/") {
a += "/"
}
if (b.slice(0, 2) === "./") { //相对于兄弟路径
return a + b.slice(2)
}
if (b.slice(0, 2) === "..") { //相对于父路径
a += b
while (rdeuce.test(a)) {
a = a.replace(rdeuce, "")
}
return a
}
if (b.slice(0, 1) === "/") {
return a + b.slice(1)
}
return a + b
}
function getGlobal(value) {
if (!value) {
return value
}
var g = window
value.split(".").forEach(function (part) {
g = g[part]
})
return g
}
var mainNode = DOC.scripts[DOC.scripts.length - 1]
var dataMain = mainNode.getAttribute("data-main")
if (dataMain) {
plugins.baseUrl(dataMain)
var href = kernel.baseUrl
kernel.baseUrl = href.slice(0, href.lastIndexOf("/") + 1)
loadJS(href.replace(rjsext, "") + ".js")
} else {
var loaderUrl = trimQuery(getFullUrl(mainNode, "src"))
kernel.baseUrl = loaderUrl.slice(0, loaderUrl.lastIndexOf("/") + 1)
}
} // jshint ignore:line
/*********************************************************************
* DOMReady *
**********************************************************************/
var readyList = [],
isReady
var fireReady = function (fn) {
isReady = true
var require = avalon.require
if (require && require.checkDeps) {
modules["domReady!"].state = 4
require.checkDeps()
}
while (fn = readyList.shift()) {
fn(avalon)
}
}
function doScrollCheck() {
try { //IE下通过doScrollCheck检测DOM树是否建完
root.doScroll("left")
fireReady()
} catch (e) {
setTimeout(doScrollCheck)
}
}
if (DOC.readyState === "complete") {
setTimeout(fireReady) //如果在domReady之外加载
} else if (W3C) {
DOC.addEventListener("DOMContentLoaded", fireReady)
} else {
DOC.attachEvent("onreadystatechange", function () {
if (DOC.readyState === "complete") {
fireReady()
}
})
try {
var isTop = window.frameElement === null
} catch (e) {}
if (root.doScroll && isTop && window.external) { //fix IE iframe BUG
doScrollCheck()
}
}
avalon.bind(window, "load", fireReady)
avalon.ready = function (fn) {
if (!isReady) {
readyList.push(fn)
} else {
fn(avalon)
}
}
avalon.config({
loader: true
})
avalon.ready(function () {
avalon.scan(DOC.body)
})
// Register as a named AMD module, since avalon can be concatenated with other
// files that may use define, but not via a proper concatenation script that
// understands anonymous AMD modules. A named AMD is safest and most robust
// way to register. Lowercase avalon is used because AMD module names are
// derived from file names, and Avalon is normally delivered in a lowercase
// file name. Do this after creating the global so that if an AMD module wants
// to call noConflict to hide this version of avalon, it will work.
// Note that for maximum portability, libraries that are not avalon should
// declare themselves as anonymous modules, and avoid setting a global if an
// AMD loader is present. avalon is a special case. For more information, see
// https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon
if (typeof define === "function" && define.amd) {
define("avalon", [], function() {
return avalon
})
}
// Map over avalon in case of overwrite
var _avalon = window.avalon
avalon.noConflict = function(deep) {
if (deep && window.avalon === avalon) {
window.avalon = _avalon
}
return avalon
}
// Expose avalon identifiers, even in AMD
// and CommonJS for browser emulators
if (noGlobal === void 0) {
window.avalon = avalon
}
return avalon
}));