1 /*==================================================
\r
2 Copyright (c) 2013-2015 司徒正美 and other contributors
\r
3 http://www.cnblogs.com/rubylouvre/
\r
4 https://github.com/RubyLouvre
\r
5 http://weibo.com/jslouvre/
\r
7 Released under the MIT license
\r
8 avalon.js 1.45 built in 2015.7.17
\r
9 support IE6+ and other browsers
\r
10 ==================================================*/
\r
11 (function(global, factory) {
\r
13 if (typeof module === "object" && typeof module.exports === "object") {
\r
14 // For CommonJS and CommonJS-like environments where a proper `window`
\r
15 // is present, execute the factory and get avalon.
\r
16 // For environments that do not have a `window` with a `document`
\r
17 // (such as Node.js), expose a factory as module.exports.
\r
18 // This accentuates the need for the creation of a real `window`.
\r
19 // e.g. var avalon = require("avalon")(window);
\r
20 module.exports = global.document ? factory(global, true) : function(w) {
\r
22 throw new Error("Avalon requires a window with a document")
\r
30 // Pass this if window is not defined yet
\r
31 }(typeof window !== "undefined" ? window : this, function(window, noGlobal){
\r
33 /*********************************************************************
\r
35 **********************************************************************/
\r
36 var expose = new Date() - 0
\r
37 //http://stackoverflow.com/questions/7290086/javascript-use-strict-and-nicks-find-global-function
\r
38 var DOC = window.document
\r
39 var head = DOC.getElementsByTagName("head")[0] //HEAD元素
\r
40 var ifGroup = head.insertBefore(document.createElement("avalon"), head.firstChild) //避免IE6 base标签BUG
\r
41 ifGroup.innerHTML = "X<style id='avalonStyle'>.avalonHide{ display: none!important }</style>"
\r
42 ifGroup.setAttribute("ms-skip", "1")
\r
43 ifGroup.className = "avalonHide"
\r
44 var rnative = /\[native code\]/ //判定是否原生函数
\r
46 if (window.console && avalon.config.debug) {
\r
47 // http://stackoverflow.com/questions/8785624/how-to-safely-wrap-console-log
\r
48 Function.apply.call(console.log, console, arguments)
\r
53 var subscribers = "$" + expose
\r
54 var otherRequire = window.require
\r
55 var otherDefine = window.define
\r
57 var stopRepeatAssign = false
\r
58 var rword = /[^, ]+/g //切割字符串为一个个小块,以空格或豆号分开它们,结合replace实现字符串的forEach
\r
59 var rcomplexType = /^(?:object|array)$/
\r
60 var rsvg = /^\[object SVG\w*Element\]$/
\r
61 var rwindow = /^\[object (?:Window|DOMWindow|global)\]$/
\r
62 var oproto = Object.prototype
\r
63 var ohasOwn = oproto.hasOwnProperty
\r
64 var serialize = oproto.toString
\r
65 var ap = Array.prototype
\r
66 var aslice = ap.slice
\r
67 var Registry = {} //将函数曝光到此对象上,方便访问器收集依赖
\r
68 var W3C = window.dispatchEvent
\r
69 var root = DOC.documentElement
\r
70 var avalonFragment = DOC.createDocumentFragment()
\r
71 var cinerator = DOC.createElement("div")
\r
73 "Boolean Number String Function Array Date RegExp Object Error".replace(rword, function (name) {
\r
74 class2type["[object " + name + "]"] = name.toLowerCase()
\r
82 function oneObject(array, val) {
\r
83 if (typeof array === "string") {
\r
84 array = array.match(rword) || []
\r
87 value = val !== void 0 ? val : 1
\r
88 for (var i = 0, n = array.length; i < n; i++) {
\r
89 result[array[i]] = value
\r
94 //生成UUID http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
\r
95 var generateID = function (prefix) {
\r
96 prefix = prefix || "avalon"
\r
97 return String(Math.random() + Math.random()).replace(/\d\.\d{4}/, prefix)
\r
100 if (window.VBArray) {
\r
101 var mode = document.documentMode
\r
102 return mode ? mode : window.XMLHttpRequest ? 7 : 6
\r
107 var IEVersion = IE()
\r
109 avalon = function (el) { //创建jQuery式的无new 实例化结构
\r
110 return new avalon.init(el)
\r
113 avalon.profile = function () {
\r
114 if (window.console && avalon.config.profile) {
\r
115 Function.apply.call(console.log, console, arguments)
\r
119 /*视浏览器情况采用最快的异步回调*/
\r
120 avalon.nextTick = new function () {// jshint ignore:line
\r
121 var tickImmediate = window.setImmediate
\r
122 var tickObserver = window.MutationObserver
\r
123 var tickPost = W3C && window.postMessage
\r
124 if (tickImmediate) {
\r
125 return tickImmediate.bind(window)
\r
129 function callback() {
\r
130 var n = queue.length
\r
131 for (var i = 0; i < n; i++) {
\r
134 queue = queue.slice(n)
\r
137 if (tickObserver) {
\r
138 var node = document.createTextNode("avalon")
\r
139 new tickObserver(callback).observe(node, {characterData: true})// jshint ignore:line
\r
140 return function (fn) {
\r
142 node.data = Math.random()
\r
147 window.addEventListener("message", function (e) {
\r
148 var source = e.source
\r
149 if ((source === window || source === null) && e.data === "process-tick") {
\r
150 e.stopPropagation()
\r
155 return function (fn) {
\r
157 window.postMessage('process-tick', '*')
\r
161 return function (fn) {
\r
164 }// jshint ignore:line
\r
165 /*********************************************************************
\r
167 **********************************************************************/
\r
168 avalon.init = function (el) {
\r
169 this[0] = this.element = el
\r
171 avalon.fn = avalon.prototype = avalon.init.prototype
\r
173 avalon.type = function (obj) { //取得目标的类型
\r
177 // 早期的webkit内核浏览器实现了已废弃的ecma262v4标准,可以将正则字面量当作函数使用,因此typeof在判定正则时会返回function
\r
178 return typeof obj === "object" || typeof obj === "function" ?
\r
179 class2type[serialize.call(obj)] || "object" :
\r
183 var isFunction = typeof alert === "object" ? function (fn) {
\r
185 return /^\s*\bfunction\b/.test(fn + "")
\r
189 } : function (fn) {
\r
190 return serialize.call(fn) === "[object Function]"
\r
192 avalon.isFunction = isFunction
\r
194 avalon.isWindow = function (obj) {
\r
197 // 利用IE678 window == document为true,document == window竟然为false的神奇特性
\r
198 // 标准浏览器及IE9,IE10等使用 正则检测
\r
199 return obj == obj.document && obj.document != obj //jshint ignore:line
\r
202 function isWindow(obj) {
\r
203 return rwindow.test(serialize.call(obj))
\r
205 if (isWindow(window)) {
\r
206 avalon.isWindow = isWindow
\r
209 for (enu in avalon({})) {
\r
212 var enumerateBUG = enu !== "0" //IE6下为true, 其他为false
\r
213 /*判定是否是一个朴素的javascript对象(Object),不是DOM对象,不是BOM对象,不是自定义类的实例*/
\r
214 avalon.isPlainObject = function (obj, key) {
\r
215 if (!obj || avalon.type(obj) !== "object" || obj.nodeType || avalon.isWindow(obj)) {
\r
218 try { //IE内置对象没有constructor
\r
219 if (obj.constructor && !ohasOwn.call(obj, "constructor") && !ohasOwn.call(obj.constructor.prototype, "isPrototypeOf")) {
\r
222 } catch (e) { //IE8 9会在这里抛错
\r
225 if (enumerateBUG) {
\r
227 return ohasOwn.call(obj, key)
\r
232 return key === void 0 || ohasOwn.call(obj, key)
\r
234 if (rnative.test(Object.getPrototypeOf)) {
\r
235 avalon.isPlainObject = function (obj) {
\r
236 // 简单的 typeof obj === "object"检测,会致使用isPlainObject(window)在opera下通不过
\r
237 return serialize.call(obj) === "[object Object]" && Object.getPrototypeOf(obj) === oproto
\r
240 //与jQuery.extend方法,可用于浅拷贝,深拷贝
\r
241 avalon.mix = avalon.fn.mix = function () {
\r
242 var options, name, src, copy, copyIsArray, clone,
\r
243 target = arguments[0] || {},
\r
245 length = arguments.length,
\r
248 // 如果第一个参数为布尔,判定是否深拷贝
\r
249 if (typeof target === "boolean") {
\r
251 target = arguments[1] || {}
\r
256 if (typeof target !== "object" && !isFunction(target)) {
\r
260 //如果只有一个参数,那么新成员添加于mix所在的对象上
\r
261 if (i === length) {
\r
266 for (; i < length; i++) {
\r
268 if ((options = arguments[i]) != null) {
\r
269 for (name in options) {
\r
272 copy = options[name] //当options为VBS对象时报错
\r
278 if (target === copy) {
\r
281 if (deep && copy && (avalon.isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))) {
\r
284 copyIsArray = false
\r
285 clone = src && Array.isArray(src) ? src : []
\r
288 clone = src && avalon.isPlainObject(src) ? src : {}
\r
291 target[name] = avalon.mix(deep, clone, copy)
\r
292 } else if (copy !== void 0) {
\r
293 target[name] = copy
\r
301 function _number(a, len) { //用于模拟slice, splice的效果
\r
302 a = Math.floor(a) || 0
\r
303 return a < 0 ? Math.max(len + a, 0) : Math.min(a, len);
\r
307 subscribers: subscribers,
\r
311 slice: W3C ? function (nodes, start, end) {
\r
312 return aslice.call(nodes, start, end)
\r
313 } : function (nodes, start, end) {
\r
315 var len = nodes.length
\r
316 if (end === void 0)
\r
318 if (typeof end === "number" && isFinite(end)) {
\r
319 start = _number(start, len)
\r
320 end = _number(end, len)
\r
321 for (var i = start; i < end; ++i) {
\r
322 ret[i - start] = nodes[i]
\r
328 /*如果不用Error对象封装一下,str在控制台下可能会乱码*/
\r
329 error: function (str, e) {
\r
330 throw (e || Error)(str)
\r
332 /*将一个以空格或逗号隔开的字符串或数组,转换成一个键值都为1的对象*/
\r
333 oneObject: oneObject,
\r
334 /* avalon.range(10)
\r
335 => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
\r
336 avalon.range(1, 11)
\r
337 => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
\r
338 avalon.range(0, 30, 5)
\r
339 => [0, 5, 10, 15, 20, 25]
\r
340 avalon.range(0, -10, -1)
\r
341 => [0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
\r
344 range: function (start, end, step) { // 用于生成整数数组
\r
351 length = Math.max(0, Math.ceil((end - start) / step)),
\r
352 result = new Array(length)
\r
353 while (++index < length) {
\r
354 result[index] = start
\r
361 bind: function(el, type, fn, phase) {
\r
362 var hooks = avalon.eventHooks
\r
363 var hook = hooks[type]
\r
364 if (typeof hook === "object") {
\r
367 fn = hook.deel(el, type, fn, phase)
\r
370 var callback = W3C ? fn : function(e) {
\r
371 fn.call(el, fixEvent(e));
\r
374 el.addEventListener(type, callback, !!phase)
\r
376 el.attachEvent("on" + type, callback)
\r
381 unbind: function(el, type, fn, phase) {
\r
382 var hooks = avalon.eventHooks
\r
383 var hook = hooks[type]
\r
384 var callback = fn || noop
\r
385 if (typeof hook === "object") {
\r
388 fn = hook.deel(el, type, fn, false)
\r
392 el.removeEventListener(type, callback, !!phase)
\r
394 el.detachEvent("on" + type, callback)
\r
398 css: function (node, name, value) {
\r
399 if (node instanceof avalon) {
\r
402 var prop = /[_-]/.test(name) ? camelize(name) : name, fn
\r
403 name = avalon.cssName(prop) || prop
\r
404 if (value === void 0 || typeof value === "boolean") { //获取样式
\r
405 fn = cssHooks[prop + ":get"] || cssHooks["@:get"]
\r
406 if (name === "background") {
\r
407 name = "backgroundColor"
\r
409 var val = fn(node, name)
\r
410 return value === true ? parseFloat(val) || 0 : val
\r
411 } else if (value === "") { //请除样式
\r
412 node.style[name] = ""
\r
414 if (value == null || value !== value) {
\r
417 if (isFinite(value) && !avalon.cssNumber[prop]) {
\r
420 fn = cssHooks[prop + ":set"] || cssHooks["@:set"]
\r
421 fn(node, name, value)
\r
424 /*遍历数组与对象,回调的第一个参数为索引或键名,第二个或元素或键值*/
\r
425 each: function (obj, fn) {
\r
426 if (obj) { //排除null, undefined
\r
428 if (isArrayLike(obj)) {
\r
429 for (var n = obj.length; i < n; i++) {
\r
430 if (fn(i, obj[i]) === false)
\r
435 if (obj.hasOwnProperty(i) && fn(i, obj[i]) === false) {
\r
442 //收集元素的data-{{prefix}}-*属性,并转换为对象
\r
443 getWidgetData: function (elem, prefix) {
\r
444 var raw = avalon(elem).data()
\r
446 for (var i in raw) {
\r
447 if (i.indexOf(prefix) === 0) {
\r
448 result[i.replace(prefix, "").replace(/\w/, function (a) {
\r
449 return a.toLowerCase()
\r
456 /*只有当前数组不存在此元素时只添加它*/
\r
457 ensure: function (target, item) {
\r
458 if (target.indexOf(item) === -1) {
\r
459 return target.push(item)
\r
462 /*移除数组中指定位置的元素,返回布尔表示成功与否*/
\r
463 removeAt: function (target, index) {
\r
464 return !!target.splice(index, 1).length
\r
466 /*移除数组中第一个匹配传参的那个元素,返回布尔表示成功与否*/
\r
467 remove: function (target, item) {
\r
468 var index = target.indexOf(item)
\r
470 return avalon.Array.removeAt(target, index)
\r
476 var bindingHandlers = avalon.bindingHandlers = {}
\r
477 var bindingExecutors = avalon.bindingExecutors = {}
\r
479 /*判定是否类数组,如节点集合,纯数组,arguments与拥有非负整数的length属性的纯JS对象*/
\r
480 function isArrayLike(obj) {
\r
484 if (n === (n >>> 0)) { //检测length属性是否为非负整数
\r
485 var type = serialize.call(obj).slice(8, -1)
\r
486 if (/(?:regexp|string|function|window|global)$/i.test(type))
\r
488 if (type === "Array")
\r
491 if ({}.propertyIsEnumerable.call(obj, "length") === false) { //如果是原生对象
\r
492 return /^\s?function/.test(obj.item || obj.callee)
\r
495 } catch (e) { //IE的NodeList直接抛错
\r
496 return !obj.window //IE6-8 window
\r
503 // https://github.com/rsms/js-lru
\r
504 var Cache = new function() {// jshint ignore:line
\r
505 function LRU(maxLength) {
\r
507 this.limit = maxLength
\r
508 this.head = this.tail = void 0
\r
512 var p = LRU.prototype
\r
514 p.put = function(key, value) {
\r
519 this._keymap[key] = entry
\r
521 this.tail.newer = entry
\r
522 entry.older = this.tail
\r
527 if (this.size === this.limit) {
\r
535 p.shift = function() {
\r
536 var entry = this.head
\r
538 this.head = this.head.newer
\r
542 this._keymap[entry.key] = void 0
\r
545 p.get = function(key) {
\r
546 var entry = this._keymap[key]
\r
547 if (entry === void 0)
\r
549 if (entry === this.tail) {
\r
552 // HEAD--------------TAIL
\r
554 // <--- add direction --
\r
557 if (entry === this.head) {
\r
558 this.head = entry.newer
\r
560 entry.newer.older = entry.older // C <-- E.
\r
563 entry.older.newer = entry.newer // C. --> E
\r
565 entry.newer = void 0 // D --x
\r
566 entry.older = this.tail // D. --> E
\r
568 this.tail.newer = entry // E. <-- D
\r
574 }// jshint ignore:line
\r
576 /*********************************************************************
\r
577 * javascript 底层补丁 *
\r
578 **********************************************************************/
\r
579 if (!"司徒正美".trim) {
\r
580 var rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g
\r
581 String.prototype.trim = function () {
\r
582 return this.replace(rtrim, "")
\r
585 var hasDontEnumBug = !({
\r
587 }).propertyIsEnumerable('toString'),
\r
588 hasProtoEnumBug = (function () {
\r
589 }).propertyIsEnumerable('prototype'),
\r
596 "propertyIsEnumerable",
\r
599 dontEnumsLength = dontEnums.length;
\r
600 if (!Object.keys) {
\r
601 Object.keys = function (object) { //ecma262v5 15.2.3.14
\r
603 var skipProto = hasProtoEnumBug && typeof object === "function"
\r
604 if (typeof object === "string" || (object && object.callee)) {
\r
605 for (var i = 0; i < object.length; ++i) {
\r
606 theKeys.push(String(i))
\r
609 for (var name in object) {
\r
610 if (!(skipProto && name === "prototype") && ohasOwn.call(object, name)) {
\r
611 theKeys.push(String(name))
\r
616 if (hasDontEnumBug) {
\r
617 var ctor = object.constructor,
\r
618 skipConstructor = ctor && ctor.prototype === object
\r
619 for (var j = 0; j < dontEnumsLength; j++) {
\r
620 var dontEnum = dontEnums[j]
\r
621 if (!(skipConstructor && dontEnum === "constructor") && ohasOwn.call(object, dontEnum)) {
\r
622 theKeys.push(dontEnum)
\r
629 if (!Array.isArray) {
\r
630 Array.isArray = function (a) {
\r
631 return serialize.call(a) === "[object Array]"
\r
636 Function.prototype.bind = function (scope) {
\r
637 if (arguments.length < 2 && scope === void 0)
\r
641 return function () {
\r
644 for (i = 1; i < argv.length; i++)
\r
646 for (i = 0; i < arguments.length; i++)
\r
647 args.push(arguments[i])
\r
648 return fn.apply(scope, args)
\r
653 function iterator(vars, body, ret) {
\r
654 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
\r
655 /* jshint ignore:start */
\r
656 return Function("fn,scope", fun)
\r
657 /* jshint ignore:end */
\r
659 if (!rnative.test([].map)) {
\r
661 //定位操作,返回数组中第一个等于给定参数的元素的索引值。
\r
662 indexOf: function (item, index) {
\r
663 var n = this.length,
\r
668 if (this[i] === item)
\r
673 lastIndexOf: function (item, index) {
\r
674 var n = this.length,
\r
675 i = index == null ? n - 1 : index
\r
677 i = Math.max(0, n + i)
\r
678 for (; i >= 0; i--)
\r
679 if (this[i] === item)
\r
683 //迭代操作,将数组的元素挨个儿传入一个函数中执行。Prototype.js的对应名字为each。
\r
684 forEach: iterator("", '_', ""),
\r
685 //迭代类 在数组中的每个项上运行一个函数,如果此函数的值为真,则此元素作为新数组的元素收集起来,并返回新数组
\r
686 filter: iterator('r=[],j=0,', 'if(_)r[j++]=this[i]', 'return r'),
\r
687 //收集操作,将数组的元素挨个儿传入一个函数中执行,然后把它们的返回值组成一个新数组返回。Prototype.js的对应名字为collect。
\r
688 map: iterator('r=[],', 'r[i]=_', 'return r'),
\r
689 //只要数组中有一个元素满足条件(放进给定函数返回true),那么它就返回true。Prototype.js的对应名字为any。
\r
690 some: iterator("", 'if(_)return true', 'return false'),
\r
691 //只有数组中的元素都满足条件(放进给定函数返回true),它才返回true。Prototype.js的对应名字为all。
\r
692 every: iterator("", 'if(!_)return false', 'return true')
\r
695 /*********************************************************************
\r
697 **********************************************************************/
\r
699 function fixContains(root, el) {
\r
700 try { //IE6-8,游离于DOM树外的文本节点,访问parentNode有时会抛错
\r
701 while ((el = el.parentNode))
\r
709 avalon.contains = fixContains
\r
710 //IE6-11的文档对象没有contains
\r
711 if (!DOC.contains) {
\r
712 DOC.contains = function (b) {
\r
713 return fixContains(DOC, b)
\r
717 function outerHTML() {
\r
718 return new XMLSerializer().serializeToString(this)
\r
721 if (window.SVGElement) {
\r
722 //safari5+是把contains方法放在Element.prototype上而不是Node.prototype
\r
723 if (!DOC.createTextNode("x").contains) {
\r
724 Node.prototype.contains = function (arg) {//IE6-8没有Node对象
\r
725 return !!(this.compareDocumentPosition(arg) & 16)
\r
728 var svgns = "http://www.w3.org/2000/svg"
\r
729 var svg = DOC.createElementNS(svgns, "svg")
\r
730 svg.innerHTML = '<circle cx="50" cy="50" r="40" fill="red" />'
\r
731 if (!rsvg.test(svg.firstChild)) { // #409
\r
732 function enumerateNode(node, targetNode) {// jshint ignore:line
\r
733 if (node && node.childNodes) {
\r
734 var nodes = node.childNodes
\r
735 for (var i = 0, el; el = nodes[i++]; ) {
\r
737 var svg = DOC.createElementNS(svgns,
\r
738 el.tagName.toLowerCase())
\r
739 ap.forEach.call(el.attributes, function (attr) {
\r
740 svg.setAttribute(attr.name, attr.value) //复制属性
\r
741 })// jshint ignore:line
\r
743 enumerateNode(el, svg)
\r
744 targetNode.appendChild(svg)
\r
749 Object.defineProperties(SVGElement.prototype, {
\r
750 "outerHTML": {//IE9-11,firefox不支持SVG元素的innerHTML,outerHTML属性
\r
752 configurable: true,
\r
754 set: function (html) {
\r
755 var tagName = this.tagName.toLowerCase(),
\r
756 par = this.parentNode,
\r
757 frag = avalon.parseHTML(html)
\r
759 if (tagName === "svg") {
\r
760 par.insertBefore(frag, this)
\r
763 var newFrag = DOC.createDocumentFragment()
\r
764 enumerateNode(frag, newFrag)
\r
765 par.insertBefore(newFrag, this)
\r
767 par.removeChild(this)
\r
772 configurable: true,
\r
774 var s = this.outerHTML
\r
775 var ropen = new RegExp("<" + this.nodeName + '\\b(?:(["\'])[^"]*?(\\1)|[^>])*>', "i")
\r
776 var rclose = new RegExp("<\/" + this.nodeName + ">$", "i")
\r
777 return s.replace(ropen, "").replace(rclose, "")
\r
779 set: function (html) {
\r
780 if (avalon.clearHTML) {
\r
781 avalon.clearHTML(this)
\r
782 var frag = avalon.parseHTML(html)
\r
783 enumerateNode(frag, this)
\r
790 if (!root.outerHTML && window.HTMLElement) { //firefox 到11时才有outerHTML
\r
791 HTMLElement.prototype.__defineGetter__("outerHTML", outerHTML);
\r
795 //============================= event binding =======================
\r
796 var rmouseEvent = /^(?:mouse|contextmenu|drag)|click/
\r
797 function fixEvent(event) {
\r
799 for (var i in event) {
\r
802 var target = ret.target = event.srcElement
\r
803 if (event.type.indexOf("key") === 0) {
\r
804 ret.which = event.charCode != null ? event.charCode : event.keyCode
\r
805 } else if (rmouseEvent.test(event.type)) {
\r
806 var doc = target.ownerDocument || DOC
\r
807 var box = doc.compatMode === "BackCompat" ? doc.body : doc.documentElement
\r
808 ret.pageX = event.clientX + (box.scrollLeft >> 0) - (box.clientLeft >> 0)
\r
809 ret.pageY = event.clientY + (box.scrollTop >> 0) - (box.clientTop >> 0)
\r
810 ret.wheelDeltaY = ret.wheelDelta
\r
811 ret.wheelDeltaX = 0
\r
813 ret.timeStamp = new Date() - 0
\r
814 ret.originalEvent = event
\r
815 ret.preventDefault = function () { //阻止默认行为
\r
816 event.returnValue = false
\r
818 ret.stopPropagation = function () { //阻止事件在DOM树中的传播
\r
819 event.cancelBubble = true
\r
824 var eventHooks = avalon.eventHooks
\r
825 //针对firefox, chrome修正mouseenter, mouseleave
\r
826 if (!("onmouseenter" in root)) {
\r
828 mouseenter: "mouseover",
\r
829 mouseleave: "mouseout"
\r
830 }, function (origType, fixType) {
\r
831 eventHooks[origType] = {
\r
833 deel: function (elem, _, fn) {
\r
834 return function (e) {
\r
835 var t = e.relatedTarget
\r
836 if (!t || (t !== elem && !(elem.compareDocumentPosition(t) & 16))) {
\r
839 return fn.call(elem, e)
\r
846 //针对IE9+, w3c修正animationend
\r
848 AnimationEvent: "animationend",
\r
849 WebKitAnimationEvent: "webkitAnimationEnd"
\r
850 }, function (construct, fixType) {
\r
851 if (window[construct] && !eventHooks.animationend) {
\r
852 eventHooks.animationend = {
\r
858 if (!("oninput" in DOC.createElement("input"))) {
\r
859 eventHooks.input = {
\r
860 type: "propertychange",
\r
861 deel: function (elem, _, fn) {
\r
862 return function (e) {
\r
863 if (e.propertyName === "value") {
\r
865 return fn.call(elem, e)
\r
871 if (DOC.onmousewheel === void 0) {
\r
872 /* IE6-11 chrome mousewheel wheelDetla 下 -120 上 120
\r
873 firefox DOMMouseScroll detail 下3 上-3
\r
874 firefox wheel detlaY 下3 上-3
\r
875 IE9-11 wheel deltaY 下40 上-40
\r
876 chrome wheel deltaY 下100 上-100 */
\r
877 var fixWheelType = DOC.onwheel !== void 0 ? "wheel" : "DOMMouseScroll"
\r
878 var fixWheelDelta = fixWheelType === "wheel" ? "deltaY" : "detail"
\r
879 eventHooks.mousewheel = {
\r
880 type: fixWheelType,
\r
881 deel: function (elem, _, fn) {
\r
882 return function (e) {
\r
883 e.wheelDeltaY = e.wheelDelta = e[fixWheelDelta] > 0 ? -120 : 120
\r
885 if (Object.defineProperty) {
\r
886 Object.defineProperty(e, "type", {
\r
887 value: "mousewheel"
\r
898 /*********************************************************************
\r
900 **********************************************************************/
\r
902 function kernel(settings) {
\r
903 for (var p in settings) {
\r
904 if (!ohasOwn.call(settings, p))
\r
906 var val = settings[p]
\r
907 if (typeof kernel.plugins[p] === "function") {
\r
908 kernel.plugins[p](val)
\r
909 } else if (typeof kernel[p] === "object") {
\r
910 avalon.mix(kernel[p], val)
\r
917 var openTag, closeTag, rexpr, rexprg, rbind, rregexp = /[-.*+?^${}()|[\]\/\\]/g
\r
919 function escapeRegExp(target) {
\r
920 //http://stevenlevithan.com/regex/xregexp/
\r
921 //将字符串安全格式化为正则表达式的源码
\r
922 return (target + "").replace(rregexp, "\\$&")
\r
926 loader: function (builtin) {
\r
927 var flag = innerRequire && builtin
\r
928 window.require = flag ? innerRequire : otherRequire
\r
929 window.define = flag ? innerRequire.define : otherDefine
\r
931 interpolate: function (array) {
\r
933 closeTag = array[1]
\r
934 if (openTag === closeTag) {
\r
935 throw new SyntaxError("openTag!==closeTag")
\r
936 var test = openTag + "test" + closeTag
\r
937 cinerator.innerHTML = test
\r
938 if (cinerator.innerHTML !== test && cinerator.innerHTML.indexOf("<") > -1) {
\r
939 throw new SyntaxError("此定界符不合法")
\r
941 cinerator.innerHTML = ""
\r
943 var o = escapeRegExp(openTag),
\r
944 c = escapeRegExp(closeTag)
\r
945 rexpr = new RegExp(o + "(.*?)" + c)
\r
946 rexprg = new RegExp(o + "(.*?)" + c, "g")
\r
947 rbind = new RegExp(o + ".*?" + c + "|\\sms-")
\r
951 kernel.debug = true
\r
952 kernel.plugins = plugins
\r
953 kernel.plugins['interpolate'](["{{", "}}"])
\r
956 kernel.maxRepeatSize = 100
\r
957 avalon.config = kernel
\r
958 var ravalon = /(\w+)\[(avalonctrl)="(\S+)"\]/
\r
959 var findNodes = DOC.querySelectorAll ? function(str) {
\r
960 return DOC.querySelectorAll(str)
\r
961 } : function(str) {
\r
962 var match = str.match(ravalon)
\r
963 var all = DOC.getElementsByTagName(match[1])
\r
965 for (var i = 0, el; el = all[i++]; ) {
\r
966 if (el.getAttribute(match[2]) === match[3]) {
\r
972 /*********************************************************************
\r
974 **********************************************************************/
\r
976 $watch: function (type, callback) {
\r
977 if (typeof callback === "function") {
\r
978 var callbacks = this.$events[type]
\r
980 callbacks.push(callback)
\r
982 this.$events[type] = [callback]
\r
984 } else { //重新开始监听此VM的第一重简单属性的变动
\r
985 this.$events = this.$watch.backup
\r
989 $unwatch: function (type, callback) {
\r
990 var n = arguments.length
\r
991 if (n === 0) { //让此VM的所有$watch回调无效化
\r
992 this.$watch.backup = this.$events
\r
994 } else if (n === 1) {
\r
995 this.$events[type] = []
\r
997 var callbacks = this.$events[type] || []
\r
998 var i = callbacks.length
\r
1000 if (callbacks[i] === callback) {
\r
1001 return callbacks.splice(i, 1)
\r
1007 $fire: function (type) {
\r
1008 var special, i, v, callback
\r
1009 if (/^(\w+)!(\S+)$/.test(type)) {
\r
1010 special = RegExp.$1
\r
1013 var events = this.$events
\r
1016 var args = aslice.call(arguments, 1)
\r
1017 var detail = [type].concat(args)
\r
1018 if (special === "all") {
\r
1019 for (i in avalon.vmodels) {
\r
1020 v = avalon.vmodels[i]
\r
1022 v.$fire.apply(v, detail)
\r
1025 } else if (special === "up" || special === "down") {
\r
1026 var elements = events.expr ? findNodes(events.expr) : []
\r
1027 if (elements.length === 0)
\r
1029 for (i in avalon.vmodels) {
\r
1030 v = avalon.vmodels[i]
\r
1032 if (v.$events.expr) {
\r
1033 var eventNodes = findNodes(v.$events.expr)
\r
1034 if (eventNodes.length === 0) {
\r
1037 //循环两个vmodel中的节点,查找匹配(向上匹配或者向下匹配)的节点并设置标识
\r
1038 /* jshint ignore:start */
\r
1039 ap.forEach.call(eventNodes, function (node) {
\r
1040 ap.forEach.call(elements, function (element) {
\r
1041 var ok = special === "down" ? element.contains(node) : //向下捕获
\r
1042 node.contains(element) //向上冒泡
\r
1044 node._avalon = v //符合条件的加一个标识
\r
1048 /* jshint ignore:end */
\r
1052 var nodes = DOC.getElementsByTagName("*") //实现节点排序
\r
1054 ap.forEach.call(nodes, function (el) {
\r
1056 alls.push(el._avalon)
\r
1058 el.removeAttribute("_avalon")
\r
1061 if (special === "up") {
\r
1064 for (i = 0; callback = alls[i++]; ) {
\r
1065 if (callback.$fire.apply(callback, detail) === false) {
\r
1070 var callbacks = events[type] || []
\r
1071 var all = events.$all || []
\r
1072 for (i = 0; callback = callbacks[i++]; ) {
\r
1073 if (isFunction(callback))
\r
1074 callback.apply(this, args)
\r
1076 for (i = 0; callback = all[i++]; ) {
\r
1077 if (isFunction(callback))
\r
1078 callback.apply(this, arguments)
\r
1084 /*********************************************************************
\r
1086 **********************************************************************/
\r
1087 //avalon最核心的方法的两个方法之一(另一个是avalon.scan),返回一个ViewModel(VM)
\r
1088 var VMODELS = avalon.vmodels = {} //所有vmodel都储存在这里
\r
1089 avalon.define = function (id, factory) {
\r
1090 var $id = id.$id || id
\r
1092 log("warning: vm必须指定$id")
\r
1094 if (VMODELS[$id]) {
\r
1095 log("warning: " + $id + " 已经存在于avalon.vmodels中")
\r
1097 if (typeof id === "object") {
\r
1098 var model = modelFactory(id)
\r
1103 factory(scope) //得到所有定义
\r
1105 model = modelFactory(scope) //偷天换日,将scope换为model
\r
1106 stopRepeatAssign = true
\r
1108 stopRepeatAssign = false
\r
1111 return VMODELS[$id] = model
\r
1115 var $$skipArray = String("$id,$watch,$unwatch,$fire,$events,$model,$skipArray,$proxy,$reinitialize,$propertyNames").match(rword)
\r
1116 var defineProperty = Object.defineProperty
\r
1117 var canHideOwn = true
\r
1118 //如果浏览器不支持ecma262v5的Object.defineProperties或者存在BUG,比如IE8
\r
1119 //标准浏览器使用__defineGetter__, __defineSetter__实现
\r
1121 defineProperty({}, "_", {
\r
1124 var defineProperties = Object.defineProperties
\r
1126 canHideOwn = false
\r
1129 function modelFactory(source, $special, $model) {
\r
1130 if (Array.isArray(source)) {
\r
1131 var arr = source.concat()
\r
1133 var collection = arrayFactory(source)
\r
1134 collection.pushArray(arr)
\r
1137 //0 null undefined || Node || VModel(fix IE6-8 createWithProxy $val: val引发的BUG)
\r
1138 if (!source || source.nodeType > 0 || (source.$id && source.$events)) {
\r
1141 var $skipArray = Array.isArray(source.$skipArray) ? source.$skipArray : []
\r
1142 $skipArray.$special = $special || {} //强制要监听的属性
\r
1143 var $vmodel = {} //要返回的对象, 它在IE6-8下可能被偷龙转凤
\r
1144 $model = $model || {} //vmodels.$model属性
\r
1145 var $events = {} //vmodel.$events属性
\r
1146 var accessors = {} //监控属性
\r
1148 $$skipArray.forEach(function (name) {
\r
1149 delete source[name]
\r
1151 var names = Object.keys(source)
\r
1152 /* jshint ignore:start */
\r
1153 names.forEach(function (name, accessor) {
\r
1154 var val = source[name]
\r
1155 $model[name] = val
\r
1156 if (isObservable(name, val, $skipArray)) {
\r
1158 $events[name] = []
\r
1159 var valueType = avalon.type(val)
\r
1161 if (valueType === "object" && isFunction(val.get) && Object.keys(val).length <= 2) {
\r
1162 accessor = makeComputedAccessor(name, val)
\r
1163 computed.push(accessor)
\r
1164 } else if (rcomplexType.test(valueType)) {
\r
1165 accessor = makeComplexAccessor(name, val, valueType, $events[name])
\r
1167 accessor = makeSimpleAccessor(name, val)
\r
1169 accessors[name] = accessor
\r
1172 /* jshint ignore:end */
\r
1174 $vmodel = defineProperties($vmodel, descriptorFactory(accessors), source) //生成一个空的ViewModel
\r
1175 for (var i = 0; i < names.length; i++) {
\r
1176 var name = names[i]
\r
1177 if (!accessors[name]) {
\r
1178 $vmodel[name] = source[name]
\r
1181 //添加$id, $model, $events, $watch, $unwatch, $fire
\r
1182 $vmodel.$propertyNames = names.join("­")
\r
1183 $vmodel.$id = generateID()
\r
1184 $vmodel.$model = $model
\r
1185 $vmodel.$events = $events
\r
1186 for (i in EventBus) {
\r
1187 var fn = EventBus[i]
\r
1188 if (!W3C) { //在IE6-8下,VB对象的方法里的this并不指向自身,需要用bind处理一下
\r
1189 fn = fn.bind($vmodel)
\r
1194 Object.defineProperty($vmodel, "hasOwnProperty", hasOwnDescriptor)
\r
1196 /* jshint ignore:start */
\r
1197 $vmodel.hasOwnProperty = function (name) {
\r
1198 return name in $vmodel.$model
\r
1200 /* jshint ignore:end */
\r
1203 $vmodel.$reinitialize = function () {
\r
1204 computed.forEach(function (accessor) {
\r
1205 delete accessor._value
\r
1206 delete accessor.oldArgs
\r
1207 accessor.digest = function () {
\r
1208 accessor.call($vmodel)
\r
1210 dependencyDetection.begin({
\r
1211 callback: function (vm, dependency) {//dependency为一个accessor
\r
1212 var name = dependency._name
\r
1213 if (dependency !== accessor) {
\r
1214 var list = vm.$events[name]
\r
1215 injectDependency(list, accessor.digest)
\r
1220 accessor.get.call($vmodel)
\r
1222 dependencyDetection.end()
\r
1226 $vmodel.$reinitialize()
\r
1230 var hasOwnDescriptor = {
\r
1231 value: function (name) {
\r
1232 return name in this.$model
\r
1235 enumerable: false,
\r
1236 configurable: true
\r
1239 function makeSimpleAccessor(name, value) {
\r
1240 function accessor(value) {
\r
1241 var oldValue = accessor._value
\r
1242 if (arguments.length > 0) {
\r
1243 if (!stopRepeatAssign && !isEqual(value, oldValue)) {
\r
1244 accessor.updateValue(this, value)
\r
1245 accessor.notify(this, value, oldValue)
\r
1249 dependencyDetection.collectDependency(this, accessor)
\r
1253 accessorFactory(accessor, name)
\r
1254 accessor._value = value
\r
1259 function makeComputedAccessor(name, options) {
\r
1260 function accessor(value) {//计算属性
\r
1261 var oldValue = accessor._value
\r
1262 var init = ("_value" in accessor)
\r
1263 if (arguments.length > 0) {
\r
1264 if (stopRepeatAssign) {
\r
1267 if (typeof accessor.set === "function") {
\r
1268 if (accessor.oldArgs !== value) {
\r
1269 accessor.oldArgs = value
\r
1270 var $events = this.$events
\r
1271 var lock = $events[name]
\r
1272 $events[name] = [] //清空回调,防止内部冒泡而触发多次$fire
\r
1273 accessor.set.call(this, value)
\r
1274 $events[name] = lock
\r
1275 value = accessor.get.call(this)
\r
1276 if (value !== oldValue) {
\r
1277 accessor.updateValue(this, value)
\r
1278 accessor.notify(this, value, oldValue) //触发$watch回调
\r
1284 //将依赖于自己的高层访问器或视图刷新函数(以绑定对象形式)放到自己的订阅数组中
\r
1285 //将自己注入到低层访问器的订阅数组中
\r
1286 value = accessor.get.call(this)
\r
1287 accessor.updateValue(this, value)
\r
1288 if (init && oldValue !== value) {
\r
1289 accessor.notify(this, value, oldValue) //触发$watch回调
\r
1294 accessor.set = options.set
\r
1295 accessor.get = options.get
\r
1296 accessorFactory(accessor, name)
\r
1301 function makeComplexAccessor(name, initValue, valueType, list) {
\r
1302 function accessor(value) {
\r
1303 var oldValue = accessor._value
\r
1305 var son = accessor._vmodel
\r
1306 if (arguments.length > 0) {
\r
1307 if (stopRepeatAssign) {
\r
1310 if (valueType === "array") {
\r
1311 var a = son, b = value,
\r
1316 a.splice(bn, an - bn)
\r
1317 } else if (bn > an) {
\r
1318 a.push.apply(a, b.slice(an))
\r
1320 var n = Math.min(an, bn)
\r
1321 for (var i = 0; i < n; i++) {
\r
1326 } else if (valueType === "object") {
\r
1327 var newPropertyNames = Object.keys(value).join("­")
\r
1328 if (son.$propertyNames === newPropertyNames) {
\r
1329 for (i in value) {
\r
1333 var sson = accessor._vmodel = modelFactory(value)
\r
1334 var sevent = sson.$events
\r
1335 var oevent = son.$events
\r
1336 for (var i in sevent) {
\r
1337 var arr = sevent[i]
\r
1338 if (Array.isArray(arr)) {
\r
1339 arr = arr.concat(oevent[i])
\r
1342 sevent[subscribers] = oevent[subscribers]
\r
1343 sson.$proxy = son.$proxy
\r
1347 accessor.updateValue(this, son.$model)
\r
1348 accessor.notify(this, this._value, oldValue)
\r
1351 dependencyDetection.collectDependency(this, accessor)
\r
1355 accessorFactory(accessor, name)
\r
1356 var son = accessor._vmodel = modelFactory(initValue)
\r
1357 son.$events[subscribers] = list
\r
1361 function globalUpdateValue(vmodel, value) {
\r
1362 vmodel.$model[this._name] = this._value = value
\r
1365 function globalNotify(vmodel, value, oldValue) {
\r
1366 var name = this._name
\r
1367 var array = vmodel.$events[name] //刷新值
\r
1369 fireDependencies(array) //同步视图
\r
1370 EventBus.$fire.call(vmodel, name, value, oldValue) //触发$watch回调
\r
1374 function accessorFactory(accessor, name) {
\r
1375 accessor._name = name
\r
1376 //同时更新_value与model
\r
1377 accessor.updateValue = globalUpdateValue
\r
1378 accessor.notify = globalNotify
\r
1382 var isEqual = Object.is || function (v1, v2) {
\r
1383 if (v1 === 0 && v2 === 0) {
\r
1384 return 1 / v1 === 1 / v2
\r
1385 } else if (v1 !== v1) {
\r
1392 function isObservable(name, value, $skipArray) {
\r
1393 if (isFunction(value) || value && value.nodeType) {
\r
1396 if ($skipArray.indexOf(name) !== -1) {
\r
1399 var $special = $skipArray.$special
\r
1400 if (name && name.charAt(0) === "$" && !$special[name]) {
\r
1406 var descriptorFactory = W3C ? function (obj) {
\r
1407 var descriptors = {}
\r
1408 for (var i in obj) {
\r
1409 descriptors[i] = {
\r
1413 configurable: true
\r
1416 return descriptors
\r
1417 } : function (a) {
\r
1421 //===================修复浏览器对Object.defineProperties的支持=================
\r
1422 if (!canHideOwn) {
\r
1423 if ("__defineGetter__" in avalon) {
\r
1424 defineProperty = function (obj, prop, desc) {
\r
1425 if ('value' in desc) {
\r
1426 obj[prop] = desc.value
\r
1428 if ("get" in desc) {
\r
1429 obj.__defineGetter__(prop, desc.get)
\r
1431 if ('set' in desc) {
\r
1432 obj.__defineSetter__(prop, desc.set)
\r
1436 defineProperties = function (obj, descs) {
\r
1437 for (var prop in descs) {
\r
1438 if (descs.hasOwnProperty(prop)) {
\r
1439 defineProperty(obj, prop, descs[prop])
\r
1446 var VBClassPool = {}
\r
1447 window.execScript([// jshint ignore:line
\r
1448 "Function parseVB(code)",
\r
1449 "\tExecuteGlobal(code)",
\r
1450 "End Function" //转换一段文本为VB代码
\r
1451 ].join("\n"), "VBScript")
\r
1452 function VBMediator(instance, accessors, name, value) {// jshint ignore:line
\r
1453 var accessor = accessors[name]
\r
1454 if (arguments.length === 4) {
\r
1455 accessor.call(instance, value)
\r
1457 return accessor.call(instance)
\r
1460 defineProperties = function (name, accessors, properties) {
\r
1461 // jshint ignore:line
\r
1464 "\r\n\tPrivate [__data__], [__proxy__]",
\r
1465 "\tPublic Default Function [__const__](d, p)",
\r
1466 "\t\tSet [__data__] = d: set [__proxy__] = p",
\r
1467 "\t\tSet [__const__] = Me", //链式调用
\r
1469 //添加普通属性,因为VBScript对象不能像JS那样随意增删属性,必须在这里预先定义好
\r
1470 for (name in properties) {
\r
1471 if (!accessors.hasOwnProperty(name)) {
\r
1472 buffer.push("\tPublic [" + name + "]")
\r
1475 $$skipArray.forEach(function (name) {
\r
1476 if (!accessors.hasOwnProperty(name)) {
\r
1477 buffer.push("\tPublic [" + name + "]")
\r
1480 buffer.push("\tPublic [" + 'hasOwnProperty' + "]")
\r
1482 for (name in accessors) {
\r
1484 //由于不知对方会传入什么,因此set, let都用上
\r
1485 "\tPublic Property Let [" + name + "](val" + expose + ")", //setter
\r
1486 "\t\tCall [__proxy__](Me,[__data__], \"" + name + "\", val" + expose + ")",
\r
1488 "\tPublic Property Set [" + name + "](val" + expose + ")", //setter
\r
1489 "\t\tCall [__proxy__](Me,[__data__], \"" + name + "\", val" + expose + ")",
\r
1491 "\tPublic Property Get [" + name + "]", //getter
\r
1492 "\tOn Error Resume Next", //必须优先使用set语句,否则它会误将数组当字符串返回
\r
1493 "\t\tSet[" + name + "] = [__proxy__](Me,[__data__],\"" + name + "\")",
\r
1494 "\tIf Err.Number <> 0 Then",
\r
1495 "\t\t[" + name + "] = [__proxy__](Me,[__data__],\"" + name + "\")",
\r
1497 "\tOn Error Goto 0",
\r
1502 buffer.push("End Class")
\r
1503 var body = buffer.join("\r\n")
\r
1504 var className =VBClassPool[body]
\r
1506 className = generateID("VBClass")
\r
1507 window.parseVB("Class " + className + body)
\r
1509 "Function " + className + "Factory(a, b)", //创建实例并传入两个关键的参数
\r
1511 "\tSet o = (New " + className + ")(a, b)",
\r
1512 "\tSet " + className + "Factory = o",
\r
1515 VBClassPool[body] = className
\r
1517 var ret = window[className + "Factory"](accessors, VBMediator) //得到其产品
\r
1518 return ret //得到其产品
\r
1523 /*********************************************************************
\r
1524 * 监控数组(与ms-each, ms-repeat配合使用) *
\r
1525 **********************************************************************/
\r
1527 function arrayFactory(model) {
\r
1529 array.$id = generateID()
\r
1530 array.$model = model //数据模型
\r
1531 array.$events = {}
\r
1532 array.$events[subscribers] = []
\r
1533 array._ = modelFactory({
\r
1534 length: model.length
\r
1536 array._.$watch("length", function (a, b) {
\r
1537 array.$fire("length", a, b)
\r
1539 for (var i in EventBus) {
\r
1540 array[i] = EventBus[i]
\r
1542 avalon.mix(array, arrayPrototype)
\r
1546 function mutateArray(method, pos, n, index, method2, pos2, n2) {
\r
1547 var oldLen = this.length, loop = 2
\r
1551 /* jshint ignore:start */
\r
1552 var array = this.$model.slice(pos, pos + n).map(function (el) {
\r
1553 if (rcomplexType.test(avalon.type(el))) {
\r
1554 return el.$id ? el : modelFactory(el, 0, el)
\r
1559 /* jshint ignore:end */
\r
1560 _splice.apply(this, [pos, 0].concat(array))
\r
1561 this._fire("add", pos, n)
\r
1564 var ret = this._splice(pos, n)
\r
1565 this._fire("del", pos, n)
\r
1576 this._fire("index", index)
\r
1577 if (this.length !== oldLen) {
\r
1578 this._.length = this.length
\r
1583 var _splice = ap.splice
\r
1584 var arrayPrototype = {
\r
1586 _fire: function (method, a, b) {
\r
1587 fireDependencies(this.$events[subscribers], method, a, b)
\r
1589 size: function () { //取得数组长度,这个函数可以同步视图,length不能
\r
1590 return this._.length
\r
1592 pushArray: function (array) {
\r
1593 var m = array.length, n = this.length
\r
1595 ap.push.apply(this.$model, array)
\r
1596 mutateArray.call(this, "add", n, m, Math.max(0, n - 1))
\r
1600 push: function () {
\r
1601 //http://jsperf.com/closure-with-arguments
\r
1603 var i, n = arguments.length
\r
1604 for (i = 0; i < n; i++) {
\r
1605 array[i] = arguments[i]
\r
1607 return this.pushArray(array)
\r
1609 unshift: function () {
\r
1610 var m = arguments.length, n = this.length
\r
1612 ap.unshift.apply(this.$model, arguments)
\r
1613 mutateArray.call(this, "add", 0, m, 0)
\r
1615 return m + n //IE67的unshift不会返回长度
\r
1617 shift: function () {
\r
1618 if (this.length) {
\r
1619 var el = this.$model.shift()
\r
1620 mutateArray.call(this, "del", 0, 1, 0)
\r
1621 return el //返回被移除的元素
\r
1624 pop: function () {
\r
1625 var n = this.length
\r
1627 var el = this.$model.pop()
\r
1628 mutateArray.call(this, "del", n - 1, 1, Math.max(0, n - 2))
\r
1629 return el //返回被移除的元素
\r
1632 splice: function (start) {
\r
1633 var m = arguments.length, args = [], change
\r
1634 var removed = _splice.apply(this.$model, arguments)
\r
1635 if (removed.length) { //如果用户删掉了元素
\r
1636 args.push("del", start, removed.length, 0)
\r
1639 if (m > 2) { //如果用户添加了元素
\r
1641 args.splice(3, 1, 0, "add", start, m - 2)
\r
1643 args.push("add", start, m - 2, 0)
\r
1647 if (change) { //返回被移除的元素
\r
1648 return mutateArray.apply(this, args)
\r
1653 contains: function (el) { //判定是否包含
\r
1654 return this.indexOf(el) !== -1
\r
1656 remove: function (el) { //移除第一个等于给定值的元素
\r
1657 return this.removeAt(this.indexOf(el))
\r
1659 removeAt: function (index) { //移除指定索引上的元素
\r
1661 this.$model.splice(index, 1)
\r
1662 return mutateArray.call(this, "del", index, 1, 0)
\r
1666 clear: function () {
\r
1667 this.$model.length = this.length = this._.length = 0 //清空数组
\r
1668 this._fire("clear", 0)
\r
1671 removeAll: function (all) { //移除N个元素
\r
1672 if (Array.isArray(all)) {
\r
1673 for (var i = this.length - 1; i >= 0; i--) {
\r
1674 if (all.indexOf(this[i]) !== -1) {
\r
1678 } else if (typeof all === "function") {
\r
1679 for ( i = this.length - 1; i >= 0; i--) {
\r
1689 ensure: function (el) {
\r
1690 if (!this.contains(el)) { //只有不存在才push
\r
1695 set: function (index, val) {
\r
1697 var valueType = avalon.type(val)
\r
1698 if (val && val.$model) {
\r
1701 var target = this[index]
\r
1702 if (valueType === "object") {
\r
1703 for (var i in val) {
\r
1704 if (target.hasOwnProperty(i)) {
\r
1705 target[i] = val[i]
\r
1708 } else if (valueType === "array") {
\r
1709 target.clear().push.apply(target, val)
\r
1710 } else if (target !== val) {
\r
1712 this.$model[index] = val
\r
1713 this._fire("set", index, val)
\r
1719 //相当于原来bindingExecutors.repeat 的index分支
\r
1720 function resetIndex(array, pos) {
\r
1721 var last = array.length - 1
\r
1722 for (var el; el = array[pos]; pos++) {
\r
1724 el.$first = pos === 0
\r
1725 el.$last = pos === last
\r
1729 function sortByIndex(array, indexes) {
\r
1731 for (var i = 0, n = indexes.length; i < n; i++) {
\r
1732 map[i] = array[i] // preserve
\r
1733 var j = indexes[i]
\r
1738 array[i] = array[j]
\r
1743 "sort,reverse".replace(rword, function (method) {
\r
1744 arrayPrototype[method] = function () {
\r
1745 var newArray = this.$model//这是要排序的新数组
\r
1746 var oldArray = newArray.concat() //保持原来状态的旧数组
\r
1747 var mask = Math.random()
\r
1750 ap[method].apply(newArray, arguments) //排序
\r
1751 for (var i = 0, n = oldArray.length; i < n; i++) {
\r
1752 var neo = newArray[i]
\r
1753 var old = oldArray[i]
\r
1754 if (isEqual(neo, old)) {
\r
1757 var index = oldArray.indexOf(neo)
\r
1758 indexes.push(index)//得到新数组的每个元素在旧数组对应的位置
\r
1759 oldArray[index] = mask //屏蔽已经找过的元素
\r
1764 sortByIndex(this, indexes)
\r
1765 // sortByIndex(this.$proxy, indexes)
\r
1766 this._fire("move", indexes)
\r
1767 this._fire("index", 0)
\r
1774 /*********************************************************************
\r
1776 **********************************************************************/
\r
1778 var dependencyDetection = (function () {
\r
1779 var outerFrames = []
\r
1782 begin: function (accessorObject) {
\r
1783 //accessorObject为一个拥有callback的对象
\r
1784 outerFrames.push(currentFrame)
\r
1785 currentFrame = accessorObject
\r
1787 end: function () {
\r
1788 currentFrame = outerFrames.pop()
\r
1790 collectDependency: function (vmodel, accessor) {
\r
1791 if (currentFrame) {
\r
1792 //被dependencyDetection.begin调用
\r
1793 currentFrame.callback(vmodel, accessor);
\r
1798 //将绑定对象注入到其依赖项的订阅数组中
\r
1799 var ronduplex = /^(duplex|on)$/
\r
1800 avalon.injectBinding = function (data) {
\r
1801 var valueFn = data.evaluator
\r
1802 if (valueFn) { //如果是求值函数
\r
1803 dependencyDetection.begin({
\r
1804 callback: function (vmodel, dependency) {
\r
1805 injectDependency(vmodel.$events[dependency._name], data)
\r
1809 var value = ronduplex.test(data.type) ? data : valueFn.apply(0, data.args)
\r
1810 if(value === void 0){
\r
1811 delete data.evaluator
\r
1813 data.handler(value, data.element, data)
\r
1815 //log("warning:exception throwed in [avalon.injectBinding] " + e)
\r
1816 delete data.evaluator
\r
1817 var node = data.element
\r
1818 if (node.nodeType === 3) {
\r
1819 var parent = node.parentNode
\r
1820 if (kernel.commentInterpolate) {
\r
1821 parent.replaceChild(DOC.createComment(data.value), node)
\r
1823 node.data = openTag + (data.oneTime ? "::" : "") + data.value + closeTag
\r
1827 dependencyDetection.end()
\r
1832 //将依赖项(比它高层的访问器或构建视图刷新函数的绑定对象)注入到订阅者数组
\r
1833 function injectDependency(list, data) {
\r
1836 if (list && avalon.Array.ensure(list, data) && data.element) {
\r
1837 injectDisposeQueue(data, list)
\r
1841 //通知依赖于这个访问器的订阅者更新自身
\r
1842 function fireDependencies(list) {
\r
1843 if (list && list.length) {
\r
1844 if (new Date() - beginTime > 444 && typeof list[0] === "object") {
\r
1845 rejectDisposeQueue()
\r
1847 var args = aslice.call(arguments, 1)
\r
1848 for (var i = list.length, fn; fn = list[--i]; ) {
\r
1849 var el = fn.element
\r
1850 if (el && el.parentNode) {
\r
1852 var valueFn = fn.evaluator
\r
1854 fn.handler.apply(fn, args) //处理监控数组的方法
\r
1855 }else if("$repeat" in fn || !valueFn ){//如果没有eval,先eval
\r
1856 bindingHandlers[fn.type](fn, fn.vmodels)
\r
1857 } else if (fn.type !== "on") { //事件绑定只能由用户触发,不能由程序触发
\r
1858 var value = valueFn.apply(0, fn.args || [])
\r
1859 fn.handler(value, el, fn)
\r
1866 /*********************************************************************
\r
1868 **********************************************************************/
\r
1869 var disposeCount = 0
\r
1870 var disposeQueue = avalon.$$subscribers = []
\r
1871 var beginTime = new Date()
\r
1873 var uuid2Node = {}
\r
1874 function getUid(obj, makeID) { //IE9+,标准浏览器
\r
1875 if (!obj.uuid && !makeID) {
\r
1876 obj.uuid = ++disposeCount
\r
1877 uuid2Node[obj.uuid] = obj
\r
1881 function getNode(uuid) {
\r
1882 return uuid2Node[uuid]
\r
1885 function injectDisposeQueue(data, list) {
\r
1886 var elem = data.element
\r
1888 if (elem.nodeType !== 1) {
\r
1889 data.uuid = data.type + (data.pos || 0) + "-" + getUid(elem.parentNode)
\r
1891 data.uuid = data.name + "-" + getUid(elem)
\r
1894 var lists = data.lists || (data.lists = [])
\r
1895 avalon.Array.ensure(lists, list)
\r
1896 list.$uuid = list.$uuid || generateID()
\r
1897 if (!disposeQueue[data.uuid]) {
\r
1898 disposeQueue[data.uuid] = 1
\r
1899 disposeQueue.push(data)
\r
1903 function rejectDisposeQueue(data) {
\r
1904 if (avalon.optimize)
\r
1906 var i = disposeQueue.length
\r
1909 var iffishTypes = {}
\r
1911 //对页面上所有绑定对象进行分门别类, 只检测个数发生变化的类型
\r
1912 while (data = disposeQueue[--i]) {
\r
1913 var type = data.type
\r
1914 if (newInfo[type]) {
\r
1918 allTypes.push(type)
\r
1922 allTypes.forEach(function (type) {
\r
1923 if (oldInfo[type] !== newInfo[type]) {
\r
1924 iffishTypes[type] = 1
\r
1930 while (data = disposeQueue[--i]) {
\r
1931 if (!data.element)
\r
1933 if (iffishTypes[data.type] && shouldDispose(data.element)) { //如果它没有在DOM树
\r
1934 disposeQueue.splice(i, 1)
\r
1935 delete disposeQueue[data.uuid]
\r
1936 delete uuid2Node[data.element.uuid]
\r
1937 var lists = data.lists
\r
1938 for (var k = 0, list; list = lists[k++]; ) {
\r
1939 avalon.Array.remove(lists, list)
\r
1940 avalon.Array.remove(list, data)
\r
1947 beginTime = new Date()
\r
1950 function disposeData(data) {
\r
1951 data.element = null
\r
1952 data.rollback && data.rollback()
\r
1953 for (var key in data) {
\r
1958 function shouldDispose(el) {
\r
1959 try {//IE下,如果文本节点脱离DOM树,访问parentNode会报错
\r
1960 if (!el.parentNode) {
\r
1967 return el.msRetain ? 0 : (el.nodeType === 1 ? !root.contains(el) : !avalon.contains(root, el))
\r
1970 /************************************************************************
\r
1971 * HTML处理(parseHTML, innerHTML, clearHTML) *
\r
1972 ************************************************************************/
\r
1973 // We have to close these tags to support XHTML
\r
1975 area: [1, "<map>", "</map>"],
\r
1976 param: [1, "<object>", "</object>"],
\r
1977 col: [2, "<table><colgroup>", "</colgroup></table>"],
\r
1978 legend: [1, "<fieldset>", "</fieldset>"],
\r
1979 option: [1, "<select multiple='multiple'>", "</select>"],
\r
1980 thead: [1, "<table>", "</table>"],
\r
1981 tr: [2, "<table>", "</table>"],
\r
1982 td: [3, "<table><tr>", "</tr></table>"],
\r
1983 g: [1, '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">', '</svg>'],
\r
1984 //IE6-8在用innerHTML生成节点时,不能直接创建no-scope元素与HTML5的新标签
\r
1985 _default: W3C ? [0, "", ""] : [1, "X<div>", "</div>"] //div可以不用闭合
\r
1987 tagHooks.th = tagHooks.td
\r
1988 tagHooks.optgroup = tagHooks.option
\r
1989 tagHooks.tbody = tagHooks.tfoot = tagHooks.colgroup = tagHooks.caption = tagHooks.thead
\r
1990 String("circle,defs,ellipse,image,line,path,polygon,polyline,rect,symbol,text,use").replace(rword, function (tag) {
\r
1991 tagHooks[tag] = tagHooks.g //处理SVG
\r
1993 var rtagName = /<([\w:]+)/ //取得其tagName
\r
1994 var rxhtml = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig
\r
1995 var rcreate = W3C ? /[^\d\D]/ : /(<(?:script|link|style|meta|noscript))/ig
\r
1996 var scriptTypes = oneObject(["", "text/javascript", "text/ecmascript", "application/ecmascript", "application/javascript"])
\r
1997 var rnest = /<(?:tb|td|tf|th|tr|col|opt|leg|cap|area)/ //需要处理套嵌关系的标签
\r
1998 var script = DOC.createElement("script")
\r
1999 var rhtml = /<|&#?\w+;/
\r
2000 avalon.parseHTML = function (html) {
\r
2001 var fragment = avalonFragment.cloneNode(false)
\r
2002 if (typeof html !== "string") {
\r
2005 if (!rhtml.test(html)) {
\r
2006 fragment.appendChild(DOC.createTextNode(html))
\r
2009 html = html.replace(rxhtml, "<$1></$2>").trim()
\r
2010 var tag = (rtagName.exec(html) || ["", ""])[1].toLowerCase(),
\r
2012 wrap = tagHooks[tag] || tagHooks._default,
\r
2013 wrapper = cinerator,
\r
2015 if (!W3C) { //fix IE
\r
2016 html = html.replace(rcreate, "<br class=msNoScope>$1") //在link style script等标签之前添加一个补丁
\r
2018 wrapper.innerHTML = wrap[1] + html + wrap[2]
\r
2019 var els = wrapper.getElementsByTagName("script")
\r
2020 if (els.length) { //使用innerHTML生成的script节点不会发出请求与执行text属性
\r
2021 for (var i = 0, el; el = els[i++]; ) {
\r
2022 if (scriptTypes[el.type]) {
\r
2024 neo = script.cloneNode(false) //FF不能省略参数
\r
2025 ap.forEach.call(el.attributes, function (attr) {
\r
2026 if (attr && attr.specified) {
\r
2027 neo[attr.name] = attr.value //复制其属性
\r
2028 neo.setAttribute(attr.name, attr.value)
\r
2030 }) // jshint ignore:line
\r
2031 neo.text = el.text
\r
2032 el.parentNode.replaceChild(neo, el) //替换节点
\r
2036 if (!W3C) { //fix IE
\r
2037 var target = wrap[1] === "X<div>" ? wrapper.lastChild.firstChild : wrapper.lastChild
\r
2038 if (target && target.tagName === "TABLE" && tag !== "tbody") {
\r
2039 //IE6-7处理 <thead> --> <thead>,<tbody>
\r
2040 //<tfoot> --> <tfoot>,<tbody>
\r
2041 //<table> --> <table><tbody></table>
\r
2042 for (els = target.childNodes, i = 0; el = els[i++]; ) {
\r
2043 if (el.tagName === "TBODY" && !el.innerHTML) {
\r
2044 target.removeChild(el)
\r
2049 els = wrapper.getElementsByTagName("br")
\r
2050 var n = els.length
\r
2051 while (el = els[--n]) {
\r
2052 if (el.className === "msNoScope") {
\r
2053 el.parentNode.removeChild(el)
\r
2056 for (els = wrapper.all, i = 0; el = els[i++]; ) { //fix VML
\r
2062 //移除我们为了符合套嵌关系而添加的标签
\r
2063 for (i = wrap[0]; i--; wrapper = wrapper.lastChild) {
\r
2065 while (firstChild = wrapper.firstChild) { // 将wrapper上的节点转移到文档碎片上!
\r
2066 fragment.appendChild(firstChild)
\r
2071 function isVML(src) {
\r
2072 var nodeName = src.nodeName
\r
2073 return nodeName.toLowerCase() === nodeName && src.scopeName && src.outerText === ""
\r
2076 function fixVML(node) {
\r
2077 if (node.currentStyle.behavior !== "url(#default#VML)") {
\r
2078 node.style.behavior = "url(#default#VML)"
\r
2079 node.style.display = "inline-block"
\r
2080 node.style.zoom = 1 //hasLayout
\r
2083 avalon.innerHTML = function (node, html) {
\r
2084 if (!W3C && (!rcreate.test(html) && !rnest.test(html))) {
\r
2086 node.innerHTML = html
\r
2091 var a = this.parseHTML(html)
\r
2092 this.clearHTML(node).appendChild(a)
\r
2094 avalon.clearHTML = function (node) {
\r
2095 node.textContent = ""
\r
2096 while (node.firstChild) {
\r
2097 node.removeChild(node.firstChild)
\r
2102 /*********************************************************************
\r
2103 * avalon的原型方法定义区 *
\r
2104 **********************************************************************/
\r
2106 function hyphen(target) {
\r
2108 return target.replace(/([a-z\d])([A-Z]+)/g, "$1-$2").toLowerCase()
\r
2111 function camelize(target) {
\r
2112 //提前判断,提高getStyle等的效率
\r
2113 if (!target || target.indexOf("-") < 0 && target.indexOf("_") < 0) {
\r
2117 return target.replace(/[-_][^-_]/g, function(match) {
\r
2118 return match.charAt(1).toUpperCase()
\r
2122 var fakeClassListMethods = {
\r
2123 _toString: function() {
\r
2124 var node = this.node
\r
2125 var cls = node.className
\r
2126 var str = typeof cls === "string" ? cls : cls.baseVal
\r
2127 return str.split(/\s+/).join(" ")
\r
2129 _contains: function(cls) {
\r
2130 return (" " + this + " ").indexOf(" " + cls + " ") > -1
\r
2132 _add: function(cls) {
\r
2133 if (!this.contains(cls)) {
\r
2134 this._set(this + " " + cls)
\r
2137 _remove: function(cls) {
\r
2138 this._set((" " + this + " ").replace(" " + cls + " ", " "))
\r
2140 __set: function(cls) {
\r
2142 var node = this.node
\r
2143 if (rsvg.test(node)) {
\r
2144 //SVG元素的className是一个对象 SVGAnimatedString { baseVal="", animVal=""},只能通过set/getAttribute操作
\r
2145 node.setAttribute("class", cls)
\r
2147 node.className = cls
\r
2149 } //toggle存在版本差异,因此不使用它
\r
2152 function fakeClassList(node) {
\r
2153 if (!("classList" in node)) {
\r
2154 node.classList = {
\r
2157 for (var k in fakeClassListMethods) {
\r
2158 node.classList[k.slice(1)] = fakeClassListMethods[k]
\r
2161 return node.classList
\r
2165 "add,remove".replace(rword, function(method) {
\r
2166 avalon.fn[method + "Class"] = function(cls) {
\r
2168 //https://developer.mozilla.org/zh-CN/docs/Mozilla/Firefox/Releases/26
\r
2169 if (cls && typeof cls === "string" && el && el.nodeType === 1) {
\r
2170 cls.replace(/\S+/g, function(c) {
\r
2171 fakeClassList(el)[method](c)
\r
2178 hasClass: function(cls) {
\r
2179 var el = this[0] || {}
\r
2180 return el.nodeType === 1 && fakeClassList(el).contains(cls)
\r
2182 toggleClass: function(value, stateVal) {
\r
2183 var className, i = 0
\r
2184 var classNames = String(value).split(/\s+/)
\r
2185 var isBool = typeof stateVal === "boolean"
\r
2186 while ((className = classNames[i++])) {
\r
2187 var state = isBool ? stateVal : !this.hasClass(className)
\r
2188 this[state ? "addClass" : "removeClass"](className)
\r
2192 attr: function(name, value) {
\r
2193 if (arguments.length === 2) {
\r
2194 this[0].setAttribute(name, value)
\r
2197 return this[0].getAttribute(name)
\r
2200 data: function(name, value) {
\r
2201 name = "data-" + hyphen(name || "")
\r
2202 switch (arguments.length) {
\r
2204 this.attr(name, value)
\r
2207 var val = this.attr(name)
\r
2208 return parseData(val)
\r
2211 ap.forEach.call(this[0].attributes, function(attr) {
\r
2214 if (!name.indexOf("data-")) {
\r
2215 name = camelize(name.slice(5))
\r
2216 ret[name] = parseData(attr.value)
\r
2223 removeData: function(name) {
\r
2224 name = "data-" + hyphen(name)
\r
2225 this[0].removeAttribute(name)
\r
2228 css: function(name, value) {
\r
2229 if (avalon.isPlainObject(name)) {
\r
2230 for (var i in name) {
\r
2231 avalon.css(this, i, name[i])
\r
2234 var ret = avalon.css(this, name, value)
\r
2236 return ret !== void 0 ? ret : this
\r
2238 position: function() {
\r
2239 var offsetParent, offset,
\r
2248 if (this.css("position") === "fixed") {
\r
2249 offset = elem.getBoundingClientRect()
\r
2251 offsetParent = this.offsetParent() //得到真正的offsetParent
\r
2252 offset = this.offset() // 得到正确的offsetParent
\r
2253 if (offsetParent[0].tagName !== "HTML") {
\r
2254 parentOffset = offsetParent.offset()
\r
2256 parentOffset.top += avalon.css(offsetParent[0], "borderTopWidth", true)
\r
2257 parentOffset.left += avalon.css(offsetParent[0], "borderLeftWidth", true)
\r
2259 // Subtract offsetParent scroll positions
\r
2260 parentOffset.top -= offsetParent.scrollTop()
\r
2261 parentOffset.left -= offsetParent.scrollLeft()
\r
2264 top: offset.top - parentOffset.top - avalon.css(elem, "marginTop", true),
\r
2265 left: offset.left - parentOffset.left - avalon.css(elem, "marginLeft", true)
\r
2268 offsetParent: function() {
\r
2269 var offsetParent = this[0].offsetParent
\r
2270 while (offsetParent && avalon.css(offsetParent, "position") === "static") {
\r
2271 offsetParent = offsetParent.offsetParent;
\r
2273 return avalon(offsetParent || root)
\r
2275 bind: function(type, fn, phase) {
\r
2276 if (this[0]) { //此方法不会链
\r
2277 return avalon.bind(this[0], type, fn, phase)
\r
2280 unbind: function(type, fn, phase) {
\r
2282 avalon.unbind(this[0], type, fn, phase)
\r
2286 val: function(value) {
\r
2287 var node = this[0]
\r
2288 if (node && node.nodeType === 1) {
\r
2289 var get = arguments.length === 0
\r
2290 var access = get ? ":get" : ":set"
\r
2291 var fn = valHooks[getValType(node) + access]
\r
2293 var val = fn(node, value)
\r
2295 return (node.value || "").replace(/\r/g, "")
\r
2297 node.value = value
\r
2300 return get ? val : this
\r
2304 function parseData(data) {
\r
2306 if (typeof data === "object")
\r
2308 data = data === "true" ? true :
\r
2309 data === "false" ? false :
\r
2310 data === "null" ? null : +data + "" === data ? +data : rbrace.test(data) ? avalon.parseJSON(data) : data
\r
2314 var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/,
\r
2315 rvalidchars = /^[\],:{}\s]*$/,
\r
2316 rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
\r
2317 rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,
\r
2318 rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g
\r
2319 avalon.parseJSON = window.JSON ? JSON.parse : function(data) {
\r
2320 if (typeof data === "string") {
\r
2321 data = data.trim();
\r
2323 if (rvalidchars.test(data.replace(rvalidescape, "@")
\r
2324 .replace(rvalidtokens, "]")
\r
2325 .replace(rvalidbraces, ""))) {
\r
2326 return (new Function("return " + data))() // jshint ignore:line
\r
2329 avalon.error("Invalid JSON: " + data)
\r
2334 //生成avalon.fn.scrollLeft, avalon.fn.scrollTop方法
\r
2336 scrollLeft: "pageXOffset",
\r
2337 scrollTop: "pageYOffset"
\r
2338 }, function(method, prop) {
\r
2339 avalon.fn[method] = function(val) {
\r
2340 var node = this[0] || {}, win = getWindow(node),
\r
2341 top = method === "scrollTop"
\r
2342 if (!arguments.length) {
\r
2343 return win ? (prop in win) ? win[prop] : root[method] : node[method]
\r
2346 win.scrollTo(!top ? val : avalon(win).scrollLeft(), top ? val : avalon(win).scrollTop())
\r
2348 node[method] = val
\r
2354 function getWindow(node) {
\r
2355 return node.window && node.document ? node : node.nodeType === 9 ? node.defaultView || node.parentWindow : false;
\r
2357 //=============================css相关=======================
\r
2358 var cssHooks = avalon.cssHooks = {}
\r
2359 var prefixes = ["", "-webkit-", "-o-", "-moz-", "-ms-"]
\r
2361 "float": W3C ? "cssFloat" : "styleFloat"
\r
2363 avalon.cssNumber = oneObject("columnCount,order,fillOpacity,fontWeight,lineHeight,opacity,orphans,widows,zIndex,zoom")
\r
2365 avalon.cssName = function(name, host, camelCase) {
\r
2366 if (cssMap[name]) {
\r
2367 return cssMap[name]
\r
2369 host = host || root.style
\r
2370 for (var i = 0, n = prefixes.length; i < n; i++) {
\r
2371 camelCase = camelize(prefixes[i] + name)
\r
2372 if (camelCase in host) {
\r
2373 return (cssMap[name] = camelCase)
\r
2378 cssHooks["@:set"] = function(node, name, value) {
\r
2379 try { //node.style.width = NaN;node.style.width = "xxxxxxx";node.style.width = undefine 在旧式IE下会抛异常
\r
2380 node.style[name] = value
\r
2383 if (window.getComputedStyle) {
\r
2384 cssHooks["@:get"] = function(node, name) {
\r
2385 if (!node || !node.style) {
\r
2386 throw new Error("getComputedStyle要求传入一个节点 " + node)
\r
2388 var ret, styles = getComputedStyle(node, null)
\r
2390 ret = name === "filter" ? styles.getPropertyValue(name) : styles[name]
\r
2392 ret = node.style[name] //其他浏览器需要我们手动取内联样式
\r
2397 cssHooks["opacity:get"] = function(node) {
\r
2398 var ret = cssHooks["@:get"](node, "opacity")
\r
2399 return ret === "" ? "1" : ret
\r
2402 var rnumnonpx = /^-?(?:\d*\.)?\d+(?!px)[^\d\s]+$/i
\r
2403 var rposition = /^(top|right|bottom|left)$/
\r
2404 var ralpha = /alpha\([^)]*\)/i
\r
2405 var ie8 = !! window.XDomainRequest
\r
2406 var salpha = "DXImageTransform.Microsoft.Alpha"
\r
2408 thin: ie8 ? '1px' : '2px',
\r
2409 medium: ie8 ? '3px' : '4px',
\r
2410 thick: ie8 ? '5px' : '6px'
\r
2412 cssHooks["@:get"] = function(node, name) {
\r
2413 //取得精确值,不过它有可能是带em,pc,mm,pt,%等单位
\r
2414 var currentStyle = node.currentStyle
\r
2415 var ret = currentStyle[name]
\r
2416 if ((rnumnonpx.test(ret) && !rposition.test(ret))) {
\r
2417 //①,保存原有的style.left, runtimeStyle.left,
\r
2418 var style = node.style,
\r
2419 left = style.left,
\r
2420 rsLeft = node.runtimeStyle.left
\r
2421 //②由于③处的style.left = xxx会影响到currentStyle.left,
\r
2422 //因此把它currentStyle.left放到runtimeStyle.left,
\r
2423 //runtimeStyle.left拥有最高优先级,不会style.left影响
\r
2424 node.runtimeStyle.left = currentStyle.left
\r
2425 //③将精确值赋给到style.left,然后通过IE的另一个私有属性 style.pixelLeft
\r
2426 //得到单位为px的结果;fontSize的分支见http://bugs.jquery.com/ticket/760
\r
2427 style.left = name === 'fontSize' ? '1em' : (ret || 0)
\r
2428 ret = style.pixelLeft + "px"
\r
2429 //④还原 style.left,runtimeStyle.left
\r
2431 node.runtimeStyle.left = rsLeft
\r
2433 if (ret === "medium") {
\r
2434 name = name.replace("Width", "Style")
\r
2435 //border width 默认值为medium,即使其为0"
\r
2436 if (currentStyle[name] === "none") {
\r
2440 return ret === "" ? "auto" : border[ret] || ret
\r
2442 cssHooks["opacity:set"] = function(node, name, value) {
\r
2443 var style = node.style
\r
2444 var opacity = isFinite(value) && value <= 1 ? "alpha(opacity=" + value * 100 + ")" : ""
\r
2445 var filter = style.filter || "";
\r
2448 //node.filters.alpha.opacity = value * 100
\r
2449 style.filter = (ralpha.test(filter) ?
\r
2450 filter.replace(ralpha, opacity) :
\r
2451 filter + " " + opacity).trim()
\r
2452 if (!style.filter) {
\r
2453 style.removeAttribute("filter")
\r
2456 cssHooks["opacity:get"] = function(node) {
\r
2457 //这是最快的获取IE透明值的方式,不需要动用正则了!
\r
2458 var alpha = node.filters.alpha || node.filters[salpha],
\r
2459 op = alpha && alpha.enabled ? alpha.opacity : 100
\r
2460 return (op / 100) + "" //确保返回的是字符串
\r
2464 "top,left".replace(rword, function(name) {
\r
2465 cssHooks[name + ":get"] = function(node) {
\r
2466 var computed = cssHooks["@:get"](node, name)
\r
2467 return /px$/.test(computed) ? computed :
\r
2468 avalon(node).position()[name] + "px"
\r
2473 position: "absolute",
\r
2474 visibility: "hidden",
\r
2478 var rdisplayswap = /^(none|table(?!-c[ea]).+)/
\r
2480 function showHidden(node, array) {
\r
2481 //http://www.cnblogs.com/rubylouvre/archive/2012/10/27/2742529.html
\r
2482 if (node.offsetWidth <= 0) { //opera.offsetWidth可能小于0
\r
2483 if (rdisplayswap.test(cssHooks["@:get"](node, "display"))) {
\r
2487 for (var name in cssShow) {
\r
2488 obj[name] = node.style[name]
\r
2489 node.style[name] = cssShow[name]
\r
2493 var parent = node.parentNode
\r
2494 if (parent && parent.nodeType === 1) {
\r
2495 showHidden(parent, array)
\r
2499 "Width,Height".replace(rword, function(name) { //fix 481
\r
2500 var method = name.toLowerCase(),
\r
2501 clientProp = "client" + name,
\r
2502 scrollProp = "scroll" + name,
\r
2503 offsetProp = "offset" + name
\r
2504 cssHooks[method + ":get"] = function(node, which, override) {
\r
2505 var boxSizing = -4
\r
2506 if (typeof override === "number") {
\r
2507 boxSizing = override
\r
2509 which = name === "Width" ? ["Left", "Right"] : ["Top", "Bottom"]
\r
2510 var ret = node[offsetProp] // border-box 0
\r
2511 if (boxSizing === 2) { // margin-box 2
\r
2512 return ret + avalon.css(node, "margin" + which[0], true) + avalon.css(node, "margin" + which[1], true)
\r
2514 if (boxSizing < 0) { // padding-box -2
\r
2515 ret = ret - avalon.css(node, "border" + which[0] + "Width", true) - avalon.css(node, "border" + which[1] + "Width", true)
\r
2517 if (boxSizing === -4) { // content-box -4
\r
2518 ret = ret - avalon.css(node, "padding" + which[0], true) - avalon.css(node, "padding" + which[1], true)
\r
2522 cssHooks[method + "&get"] = function(node) {
\r
2524 showHidden(node, hidden);
\r
2525 var val = cssHooks[method + ":get"](node)
\r
2526 for (var i = 0, obj; obj = hidden[i++];) {
\r
2528 for (var n in obj) {
\r
2529 if (typeof obj[n] === "string") {
\r
2530 node.style[n] = obj[n]
\r
2536 avalon.fn[method] = function(value) { //会忽视其display
\r
2537 var node = this[0]
\r
2538 if (arguments.length === 0) {
\r
2539 if (node.setTimeout) { //取得窗口尺寸,IE9后可以用node.innerWidth /innerHeight代替
\r
2540 return node["inner" + name] || node.document.documentElement[clientProp]
\r
2542 if (node.nodeType === 9) { //取得页面尺寸
\r
2543 var doc = node.documentElement
\r
2544 //FF chrome html.scrollHeight< body.scrollHeight
\r
2545 //IE 标准模式 : html.scrollHeight> body.scrollHeight
\r
2546 //IE 怪异模式 : html.scrollHeight 最大等于可视窗口多一点?
\r
2547 return Math.max(node.body[scrollProp], doc[scrollProp], node.body[offsetProp], doc[offsetProp], doc[clientProp])
\r
2549 return cssHooks[method + "&get"](node)
\r
2551 return this.css(method, value)
\r
2554 avalon.fn["inner" + name] = function() {
\r
2555 return cssHooks[method + ":get"](this[0], void 0, -2)
\r
2557 avalon.fn["outer" + name] = function(includeMargin) {
\r
2558 return cssHooks[method + ":get"](this[0], void 0, includeMargin === true ? 2 : 0)
\r
2561 avalon.fn.offset = function() { //取得距离页面左右角的坐标
\r
2562 var node = this[0],
\r
2567 if (!node || !node.tagName || !node.ownerDocument) {
\r
2570 var doc = node.ownerDocument,
\r
2572 root = doc.documentElement,
\r
2573 win = doc.defaultView || doc.parentWindow
\r
2574 if (!avalon.contains(root, node)) {
\r
2577 //http://hkom.blog1.fc2.com/?mode=m&no=750 body的偏移量是不包含margin的
\r
2578 //我们可以通过getBoundingClientRect来获得元素相对于client的rect.
\r
2579 //http://msdn.microsoft.com/en-us/library/ms536433.aspx
\r
2580 if (node.getBoundingClientRect) {
\r
2581 box = node.getBoundingClientRect() // BlackBerry 5, iOS 3 (original iPhone)
\r
2583 //chrome/IE6: body.scrollTop, firefox/other: root.scrollTop
\r
2584 var clientTop = root.clientTop || body.clientTop,
\r
2585 clientLeft = root.clientLeft || body.clientLeft,
\r
2586 scrollTop = Math.max(win.pageYOffset || 0, root.scrollTop, body.scrollTop),
\r
2587 scrollLeft = Math.max(win.pageXOffset || 0, root.scrollLeft, body.scrollLeft)
\r
2588 // 把滚动距离加到left,top中去。
\r
2589 // IE一些版本中会自动为HTML元素加上2px的border,我们需要去掉它
\r
2590 // http://msdn.microsoft.com/en-us/library/ms533564(VS.85).aspx
\r
2592 top: box.top + scrollTop - clientTop,
\r
2593 left: box.left + scrollLeft - clientLeft
\r
2597 //==================================val相关============================
\r
2599 function getValType(elem) {
\r
2600 var ret = elem.tagName.toLowerCase()
\r
2601 return ret === "input" && /checkbox|radio/.test(elem.type) ? "checked" : ret
\r
2603 var roption = /^<option(?:\s+\w+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+))?)*\s+value[\s=]/i
\r
2605 "option:get": IEVersion ? function(node) {
\r
2606 //在IE11及W3C,如果没有指定value,那么node.value默认为node.text(存在trim作),但IE9-10则是取innerHTML(没trim操作)
\r
2607 //specified并不可靠,因此通过分析outerHTML判定用户有没有显示定义value
\r
2608 return roption.test(node.outerHTML) ? node.value : node.text.trim()
\r
2609 } : function(node) {
\r
2612 "select:get": function(node, value) {
\r
2613 var option, options = node.options,
\r
2614 index = node.selectedIndex,
\r
2615 getter = valHooks["option:get"],
\r
2616 one = node.type === "select-one" || index < 0,
\r
2617 values = one ? null : [],
\r
2618 max = one ? index + 1 : options.length,
\r
2619 i = index < 0 ? max : one ? index : 0
\r
2620 for (; i < max; i++) {
\r
2621 option = options[i]
\r
2622 //旧式IE在reset后不会改变selected,需要改用i === index判定
\r
2623 //我们过滤所有disabled的option元素,但在safari5下,如果设置select为disable,那么其所有孩子都disable
\r
2624 //因此当一个元素为disable,需要检测其是否显式设置了disable及其父节点的disable情况
\r
2625 if ((option.selected || i === index) && !option.disabled) {
\r
2626 value = getter(option)
\r
2630 //收集所有selected值组成数组返回
\r
2631 values.push(value)
\r
2636 "select:set": function(node, values, optionSet) {
\r
2637 values = [].concat(values) //强制转换为数组
\r
2638 var getter = valHooks["option:get"]
\r
2639 for (var i = 0, el; el = node.options[i++];) {
\r
2640 if ((el.selected = values.indexOf(getter(el)) > -1)) {
\r
2645 node.selectedIndex = -1
\r
2650 /*********************************************************************
\r
2652 **********************************************************************/
\r
2662 var quote = window.JSON && JSON.stringify || function(str) {
\r
2663 return '"' + str.replace(/[\\\"\x00-\x1f]/g, function(a) {
\r
2665 return typeof c === 'string' ? c :
\r
2666 '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
\r
2671 "break,case,catch,continue,debugger,default,delete,do,else,false",
\r
2672 "finally,for,function,if,in,instanceof,new,null,return,switch,this",
\r
2673 "throw,true,try,typeof,var,void,while,with", /* 关键字*/
\r
2674 "abstract,boolean,byte,char,class,const,double,enum,export,extends",
\r
2675 "final,float,goto,implements,import,int,interface,long,native",
\r
2676 "package,private,protected,public,short,static,super,synchronized",
\r
2677 "throws,transient,volatile", /*保留字*/
\r
2678 "arguments,let,yield,undefined" /* ECMA 5 - use strict*/].join(",")
\r
2679 var rrexpstr = /\/\*[\w\W]*?\*\/|\/\/[^\n]*\n|\/\/[^\n]*$|"(?:[^"\\]|\\[\w\W])*"|'(?:[^'\\]|\\[\w\W])*'|[\s\t\n]*\.[\s\t\n]*[$\w\.]+/g
\r
2680 var rsplit = /[^\w$]+/g
\r
2681 var rkeywords = new RegExp(["\\b" + keywords.replace(/,/g, '\\b|\\b') + "\\b"].join('|'), 'g')
\r
2682 var rnumber = /\b\d[^,]*/g
\r
2683 var rcomma = /^,+|,+$/g
\r
2684 var variablePool = new Cache(512)
\r
2685 var getVariables = function (code) {
\r
2686 var key = "," + code.trim()
\r
2687 var ret = variablePool.get(key)
\r
2692 .replace(rrexpstr, "")
\r
2693 .replace(rsplit, ",")
\r
2694 .replace(rkeywords, "")
\r
2695 .replace(rnumber, "")
\r
2696 .replace(rcomma, "")
\r
2698 return variablePool.put(key, uniqSet(match))
\r
2702 function addAssign(vars, scope, name, data) {
\r
2704 prefix = " = " + name + "."
\r
2705 for (var i = vars.length, prop; prop = vars[--i]; ) {
\r
2706 if (scope.hasOwnProperty(prop)) {
\r
2707 ret.push(prop + prefix + prop)
\r
2708 data.vars.push(prop)
\r
2709 if (data.type === "duplex") {
\r
2710 vars.get = name + "." + prop
\r
2718 function uniqSet(array) {
\r
2721 for (var i = 0; i < array.length; i++) {
\r
2723 var id = el && typeof el.$id === "string" ? el.$id : el
\r
2724 if (!unique[id]) {
\r
2725 unique[id] = ret.push(el)
\r
2731 var evaluatorPool = new Cache(128)
\r
2733 var rduplex = /\w\[.*\]|\w\.\w/
\r
2734 var rproxy = /(\$proxy\$[a-z]+)\d+$/
\r
2735 var rthimRightParentheses = /\)\s*$/
\r
2736 var rthimOtherParentheses = /\)\s*\|/g
\r
2737 var rquoteFilterName = /\|\s*([$\w]+)/g
\r
2738 var rpatchBracket = /"\s*\["/g
\r
2739 var rthimLeftParentheses = /"\s*\(/g
\r
2740 function parseFilter(val, filters) {
\r
2742 .replace(rthimRightParentheses, "")//处理最后的小括号
\r
2743 .replace(rthimOtherParentheses, function () {//处理其他小括号
\r
2746 .replace(rquoteFilterName, function (a, b) { //处理|及它后面的过滤器的名字
\r
2747 return "[" + quote(b)
\r
2749 .replace(rpatchBracket, function () {
\r
2752 .replace(rthimLeftParentheses, function () {
\r
2755 return "return avalon.filters.$filter(" + val + ", " + filters + ")"
\r
2758 function parseExpr(code, scopes, data) {
\r
2759 var dataType = data.type
\r
2760 var filters = data.filters || ""
\r
2761 var exprId = scopes.map(function (el) {
\r
2762 return String(el.$id).replace(rproxy, "$1")
\r
2763 }) + code + dataType + filters
\r
2764 var vars = getVariables(code).concat(),
\r
2769 //args 是一个对象数组, names 是将要生成的求值函数的参数
\r
2770 scopes = uniqSet(scopes)
\r
2772 for (var i = 0, sn = scopes.length; i < sn; i++) {
\r
2773 if (vars.length) {
\r
2774 var name = "vm" + expose + "_" + i
\r
2776 args.push(scopes[i])
\r
2777 assigns.push.apply(assigns, addAssign(vars, scopes[i], name, data))
\r
2780 if (!assigns.length && dataType === "duplex") {
\r
2783 if (dataType !== "duplex" && (code.indexOf("||") > -1 || code.indexOf("&&") > -1)) {
\r
2784 //https://github.com/RubyLouvre/avalon/issues/583
\r
2785 data.vars.forEach(function (v) {
\r
2786 var reg = new RegExp("\\b" + v + "(?:\\.\\w+|\\[\\w+\\])+", "ig")
\r
2787 code = code.replace(reg, function (_) {
\r
2788 var c = _.charAt(v.length)
\r
2789 var r = IEVersion ? code.slice(arguments[1] + _.length) : RegExp.rightContext
\r
2790 var method = /^\s*\(/.test(r)
\r
2791 if (c === "." || c === "[" || method) {//比如v为aa,我们只匹配aa.bb,aa[cc],不匹配aaa.xxx
\r
2792 var name = "var" + String(Math.random()).replace(/^0\./, "")
\r
2793 if (method) {//array.size()
\r
2794 var array = _.split(".")
\r
2795 if (array.length > 2) {
\r
2796 var last = array.pop()
\r
2797 assigns.push(name + " = " + array.join("."))
\r
2798 return name + "." + last
\r
2803 assigns.push(name + " = " + _)
\r
2811 //---------------args----------------
\r
2813 //---------------cache----------------
\r
2815 var fn = evaluatorPool.get(exprId) //直接从缓存,免得重复生成
\r
2817 data.evaluator = fn
\r
2820 prefix = assigns.join(", ")
\r
2822 prefix = "var " + prefix
\r
2824 if (/\S/.test(filters)) { //文本绑定,双工绑定才有过滤器
\r
2825 if (!/text|html/.test(data.type)) {
\r
2826 throw Error("ms-" + data.type + "不支持过滤器")
\r
2828 code = "\nvar ret" + expose + " = " + code + ";\r\n"
\r
2829 code += parseFilter("ret" + expose, filters)
\r
2830 } else if (dataType === "duplex") { //双工绑定
\r
2831 var _body = "'use strict';\nreturn function(vvv){\n\t" +
\r
2833 ";\n\tif(!arguments.length){\n\t\treturn " +
\r
2835 "\n\t}\n\t" + (!rduplex.test(code) ? vars.get : code) +
\r
2838 fn = Function.apply(noop, names.concat(_body))
\r
2839 data.evaluator = evaluatorPool.put(exprId, fn)
\r
2841 log("debug: parse error," + e.message)
\r
2844 } else if (dataType === "on") { //事件绑定
\r
2845 if (code.indexOf("(") === -1) {
\r
2846 code += ".call(this, $event)"
\r
2848 code = code.replace("(", ".call(this,")
\r
2850 names.push("$event")
\r
2851 code = "\nreturn " + code + ";" //IE全家 Function("return ")出错,需要Function("return ;")
\r
2852 var lastIndex = code.lastIndexOf("\nreturn")
\r
2853 var header = code.slice(0, lastIndex)
\r
2854 var footer = code.slice(lastIndex)
\r
2855 code = header + "\n" + footer
\r
2857 code = "\nreturn " + code + ";" //IE全家 Function("return ")出错,需要Function("return ;")
\r
2860 fn = Function.apply(noop, names.concat("'use strict';\n" + prefix + code))
\r
2861 data.evaluator = evaluatorPool.put(exprId, fn)
\r
2863 log("debug: parse error," + e.message)
\r
2865 vars = assigns = names = null //释放内存
\r
2870 //parseExpr的智能引用代理
\r
2872 function parseExprProxy(code, scopes, data, tokens, noRegister) {
\r
2873 if (Array.isArray(tokens)) {
\r
2874 code = tokens.map(function (el) {
\r
2875 return el.expr ? "(" + el.value + ")" : quote(el.value)
\r
2878 parseExpr(code, scopes, data)
\r
2879 if (data.evaluator && !noRegister) {
\r
2880 data.handler = bindingExecutors[data.handlerName || data.type]
\r
2882 //这里非常重要,我们通过判定视图刷新函数的element是否在DOM树决定
\r
2884 avalon.injectBinding(data)
\r
2887 avalon.parseExprProxy = parseExprProxy
\r
2888 /*********************************************************************
\r
2890 **********************************************************************/
\r
2892 avalon.scan = function(elem, vmodel) {
\r
2893 elem = elem || root
\r
2894 var vmodels = vmodel ? [].concat(vmodel) : []
\r
2895 scanTag(elem, vmodels)
\r
2898 //http://www.w3.org/TR/html5/syntax.html#void-elements
\r
2899 var stopScan = oneObject("area,base,basefont,br,col,command,embed,hr,img,input,link,meta,param,source,track,wbr,noscript,script,style,textarea".toUpperCase())
\r
2901 function checkScan(elem, callback, innerHTML) {
\r
2902 var id = setTimeout(function() {
\r
2903 var currHTML = elem.innerHTML
\r
2905 if (currHTML === innerHTML) {
\r
2908 checkScan(elem, callback, currHTML)
\r
2914 function createSignalTower(elem, vmodel) {
\r
2915 var id = elem.getAttribute("avalonctrl") || vmodel.$id
\r
2916 elem.setAttribute("avalonctrl", id)
\r
2917 vmodel.$events.expr = elem.tagName + '[avalonctrl="' + id + '"]'
\r
2920 var getBindingCallback = function(elem, name, vmodels) {
\r
2921 var callback = elem.getAttribute(name)
\r
2923 for (var i = 0, vm; vm = vmodels[i++]; ) {
\r
2924 if (vm.hasOwnProperty(callback) && typeof vm[callback] === "function") {
\r
2925 return vm[callback]
\r
2931 function executeBindings(bindings, vmodels) {
\r
2932 for (var i = 0, data; data = bindings[i++]; ) {
\r
2933 data.vmodels = vmodels
\r
2934 bindingHandlers[data.type](data, vmodels)
\r
2935 if (data.evaluator && data.element && data.element.nodeType === 1) { //移除数据绑定,防止被二次解析
\r
2936 //chrome使用removeAttributeNode移除不存在的特性节点时会报错 https://github.com/RubyLouvre/avalon/issues/99
\r
2937 data.element.removeAttribute(data.name)
\r
2940 bindings.length = 0
\r
2943 //https://github.com/RubyLouvre/avalon/issues/636
\r
2944 var mergeTextNodes = IEVersion && window.MutationObserver ? function (elem) {
\r
2945 var node = elem.firstChild, text
\r
2947 var aaa = node.nextSibling
\r
2948 if (node.nodeType === 3) {
\r
2950 text.nodeValue += node.nodeValue
\r
2951 elem.removeChild(node)
\r
2961 var roneTime = /^\s*::/
\r
2962 var rmsAttr = /ms-(\w+)-?(.*)/
\r
2963 var priorityMap = {
\r
2974 var events = oneObject("animationend,blur,change,input,click,dblclick,focus,keydown,keypress,keyup,mousedown,mouseenter,mouseleave,mousemove,mouseout,mouseover,mouseup,scan,scroll,submit")
\r
2975 var obsoleteAttrs = oneObject("value,title,alt,checked,selected,disabled,readonly,enabled")
\r
2976 function bindingSorter(a, b) {
\r
2977 return a.priority - b.priority
\r
2980 function scanAttr(elem, vmodels, match) {
\r
2981 var scanNode = true
\r
2982 if (vmodels.length) {
\r
2983 var attributes = getAttributes ? getAttributes(elem) : elem.attributes
\r
2987 for (var i = 0, attr; attr = attributes[i++]; ) {
\r
2988 if (attr.specified) {
\r
2989 if (match = attr.name.match(rmsAttr)) {
\r
2991 var type = match[1]
\r
2992 var param = match[2] || ""
\r
2993 var value = attr.value
\r
2994 var name = attr.name
\r
2995 if (events[type]) {
\r
2998 } else if (obsoleteAttrs[type]) {
\r
2999 if (type === "enabled") {//吃掉ms-enabled绑定,用ms-disabled代替
\r
3000 log("warning!ms-enabled或ms-attr-enabled已经被废弃")
\r
3002 value = "!(" + value + ")"
\r
3006 name = "ms-" + type + "-"+ param
\r
3007 fixAttrs.push([attr.name, name, value])
\r
3009 msData[name] = value
\r
3010 if (typeof bindingHandlers[type] === "function") {
\r
3011 var newValue = value.replace(roneTime, "")
\r
3012 var oneTime = value !== newValue
\r
3020 uuid: name+"-"+getUid(elem),
\r
3021 //chrome与firefox下Number(param)得到的值不一样 #855
\r
3022 priority: (priorityMap[type] || type.charCodeAt(0) * 10 )+ (Number(param.replace(/\D/g, "")) || 0)
\r
3024 if (type === "html" || type === "text") {
\r
3025 var token = getToken(value)
\r
3026 avalon.mix(binding, token)
\r
3027 binding.filters = binding.filters.replace(rhasHtml, function () {
\r
3028 binding.type = "html"
\r
3031 })// jshint ignore:line
\r
3032 } else if (type === "duplex") {
\r
3033 var hasDuplex = name
\r
3034 } else if (name === "ms-if-loop") {
\r
3035 binding.priority += 100
\r
3037 bindings.push(binding)
\r
3038 if (type === "widget") {
\r
3039 elem.msData = elem.msData || msData
\r
3045 if (bindings.length) {
\r
3046 bindings.sort(bindingSorter)
\r
3047 fixAttrs.forEach(function (arr) {
\r
3048 log("warning!请改用" + arr[1] + "代替" + arr[0] + "!")
\r
3049 elem.removeAttribute(arr[0])
\r
3050 elem.setAttribute(arr[1], arr[2])
\r
3052 //http://bugs.jquery.com/ticket/7071
\r
3053 //在IE下对VML读取type属性,会让此元素所有属性都变成<Failed>
\r
3055 if (msData["ms-attr-checked"]) {
\r
3056 log("warning!一个控件不能同时定义ms-attr-checked与" + hasDuplex)
\r
3058 if (msData["ms-attr-value"]) {
\r
3059 log("warning!一个控件不能同时定义ms-attr-value与" + hasDuplex)
\r
3062 for (i = 0; binding = bindings[i]; i++) {
\r
3063 type = binding.type
\r
3064 if (rnoscanAttrBinding.test(type)) {
\r
3065 return executeBindings(bindings.slice(0, i + 1), vmodels)
\r
3066 } else if (scanNode) {
\r
3067 scanNode = !rnoscanNodeBinding.test(type)
\r
3070 executeBindings(bindings, vmodels)
\r
3073 if (scanNode && !stopScan[elem.tagName] && rbind.test(elem.innerHTML.replace(rlt, "<").replace(rgt, ">"))) {
\r
3074 mergeTextNodes && mergeTextNodes(elem)
\r
3075 scanNodeList(elem, vmodels) //扫描子孙元素
\r
3078 var rnoscanAttrBinding = /^if|widget|repeat$/
\r
3079 var rnoscanNodeBinding = /^each|with|html|include$/
\r
3080 //IE67下,在循环绑定中,一个节点如果是通过cloneNode得到,自定义属性的specified为false,无法进入里面的分支,
\r
3081 //但如果我们去掉scanAttr中的attr.specified检测,一个元素会有80+个特性节点(因为它不区分固有属性与自定义属性),很容易卡死页面
\r
3083 var attrPool = new Cache(512)
\r
3084 var rattrs = /\s+(ms-[^=\s]+)(?:=("[^"]*"|'[^']*'|[^\s>]+))?/g,
\r
3086 rtag = /<\w+\b(?:(["'])[^"]*?(\1)|[^>])*>/i,
\r
3088 //IE6-8解析HTML5新标签,会将它分解两个元素节点与一个文本节点
\r
3089 //<body><section>ddd</section></body>
\r
3090 // window.onload = function() {
\r
3091 // var body = document.body
\r
3092 // for (var i = 0, el; el = body.children[i++]; ) {
\r
3093 // avalon.log(el.outerHTML)
\r
3096 //依次输出<SECTION>, </SECTION>
\r
3097 var getAttributes = function (elem) {
\r
3098 var html = elem.outerHTML
\r
3099 //处理IE6-8解析HTML5新标签的情况,及<br>等半闭合标签outerHTML为空的情况
\r
3100 if (html.slice(0, 2) === "</" || !html.trim()) {
\r
3103 var str = html.match(rtag)[0]
\r
3104 var attributes = [],
\r
3107 var ret = attrPool.get(str)
\r
3111 while (k = rattrs.exec(str)) {
\r
3114 v = (rquote.test(v) ? v.slice(1, -1) : v).replace(ramp, "&")
\r
3116 var name = k[1].toLowerCase()
\r
3117 match = name.match(rmsAttr)
\r
3123 attributes.push(binding)
\r
3125 return attrPool.put(str, attributes)
\r
3129 function scanNodeList(parent, vmodels) {
\r
3130 var nodes = avalon.slice(parent.childNodes)
\r
3131 scanNodeArray(nodes, vmodels)
\r
3134 function scanNodeArray(nodes, vmodels) {
\r
3135 for (var i = 0, node; node = nodes[i++];) {
\r
3136 switch (node.nodeType) {
\r
3138 scanTag(node, vmodels) //扫描元素节点
\r
3139 if (node.msCallback) {
\r
3141 node.msCallback = void 0
\r
3145 if(rexpr.test(node.nodeValue)){
\r
3146 scanText(node, vmodels, i) //扫描文本节点
\r
3154 function scanTag(elem, vmodels, node) {
\r
3155 //扫描顺序 ms-skip(0) --> ms-important(1) --> ms-controller(2) --> ms-if(10) --> ms-repeat(100)
\r
3156 //--> ms-if-loop(110) --> ms-attr(970) ...--> ms-each(1400)-->ms-with(1500)--〉ms-duplex(2000)垫后
\r
3157 var a = elem.getAttribute("ms-skip")
\r
3158 //#360 在旧式IE中 Object标签在引入Flash等资源时,可能出现没有getAttributeNode,innerHTML的情形
\r
3159 if (!elem.getAttributeNode) {
\r
3160 return log("warning " + elem.tagName + " no getAttributeNode method")
\r
3162 var b = elem.getAttributeNode("ms-important")
\r
3163 var c = elem.getAttributeNode("ms-controller")
\r
3164 if (typeof a === "string") {
\r
3166 } else if (node = b || c) {
\r
3167 var newVmodel = avalon.vmodels[node.value]
\r
3171 //ms-important不包含父VM,ms-controller相反
\r
3172 vmodels = node === b ? [newVmodel] : [newVmodel].concat(vmodels)
\r
3173 var name = node.name
\r
3174 elem.removeAttribute(name) //removeAttributeNode不会刷新[ms-controller]样式规则
\r
3175 avalon(elem).removeClass(name)
\r
3176 createSignalTower(elem, newVmodel)
\r
3178 scanAttr(elem, vmodels) //扫描特性节点
\r
3180 var rhasHtml = /\|\s*html(?:\b|$)/,
\r
3184 rstringLiteral = /(['"])(\\\1|.)+?\1/g
\r
3185 function getToken(value) {
\r
3186 if (value.indexOf("|") > 0) {
\r
3187 var scapegoat = value.replace(rstringLiteral, function (_) {
\r
3188 return Array(_.length + 1).join("1")// jshint ignore:line
\r
3190 var index = scapegoat.replace(r11a, "\u1122\u3344").indexOf("|") //干掉所有短路或
\r
3193 filters: value.slice(index),
\r
3194 value: value.slice(0, index),
\r
3206 function scanExpr(str) {
\r
3211 stop = str.indexOf(openTag, start)
\r
3212 if (stop === -1) {
\r
3215 value = str.slice(start, stop)
\r
3216 if (value) { // {{ 左边的文本
\r
3223 start = stop + openTag.length
\r
3224 stop = str.indexOf(closeTag, start)
\r
3225 if (stop === -1) {
\r
3228 value = str.slice(start, stop)
\r
3229 if (value) { //处理{{ }}插值表达式
\r
3230 tokens.push(getToken(value, start))
\r
3232 start = stop + closeTag.length
\r
3234 value = str.slice(start)
\r
3235 if (value) { //}} 右边的文本
\r
3245 function scanText(textNode, vmodels, index) {
\r
3247 tokens = scanExpr(textNode.data)
\r
3248 if (tokens.length) {
\r
3249 for (var i = 0; token = tokens[i++]; ) {
\r
3250 var node = DOC.createTextNode(token.value) //将文本转换为文本节点,并替换原来的文本节点
\r
3252 token.value = token.value.replace(roneTime, function () {
\r
3253 token.oneTime = true
\r
3256 token.type = "text"
\r
3257 token.element = node
\r
3258 token.filters = token.filters.replace(rhasHtml, function (a, b,c) {
\r
3259 token.type = "html"
\r
3261 })// jshint ignore:line
\r
3262 token.pos = index * 1000 + i
\r
3263 bindings.push(token) //收集带有插值表达式的文本
\r
3265 avalonFragment.appendChild(node)
\r
3267 textNode.parentNode.replaceChild(avalonFragment, textNode)
\r
3268 if (bindings.length)
\r
3269 executeBindings(bindings, vmodels)
\r
3273 var bools = ["autofocus,autoplay,async,allowTransparency,checked,controls",
\r
3274 "declare,disabled,defer,defaultChecked,defaultSelected",
\r
3275 "contentEditable,isMap,loop,multiple,noHref,noResize,noShade",
\r
3276 "open,readOnly,selected"
\r
3279 bools.replace(rword, function(name) {
\r
3280 boolMap[name.toLowerCase()] = name
\r
3283 var propMap = { //属性名映射
\r
3284 "accept-charset": "acceptCharset",
\r
3286 "charoff": "chOff",
\r
3287 "class": "className",
\r
3289 "http-equiv": "httpEquiv"
\r
3292 var anomaly = ["accessKey,bgColor,cellPadding,cellSpacing,codeBase,codeType,colSpan",
\r
3293 "dateTime,defaultValue,frameBorder,longDesc,maxLength,marginWidth,marginHeight",
\r
3294 "rowSpan,tabIndex,useMap,vSpace,valueType,vAlign"
\r
3296 anomaly.replace(rword, function(name) {
\r
3297 propMap[name.toLowerCase()] = name
\r
3300 var rnoscripts = /<noscript.*?>(?:[\s\S]+?)<\/noscript>/img
\r
3301 var rnoscriptText = /<noscript.*?>([\s\S]+?)<\/noscript>/im
\r
3303 var getXHR = function() {
\r
3304 return new(window.XMLHttpRequest || ActiveXObject)("Microsoft.XMLHTTP") // jshint ignore:line
\r
3307 var templatePool = avalon.templateCache = {}
\r
3309 bindingHandlers.attr = function(data, vmodels) {
\r
3310 var text = data.value.trim(),
\r
3312 if (text.indexOf(openTag) > -1 && text.indexOf(closeTag) > 2) {
\r
3314 if (rexpr.test(text) && RegExp.rightContext === "" && RegExp.leftContext === "") {
\r
3319 if (data.type === "include") {
\r
3320 var elem = data.element
\r
3321 data.includeRendered = getBindingCallback(elem, "data-include-rendered", vmodels)
\r
3322 data.includeLoaded = getBindingCallback(elem, "data-include-loaded", vmodels)
\r
3323 var outer = data.includeReplace = !! avalon(elem).data("includeReplace")
\r
3324 if (avalon(elem).data("includeCache")) {
\r
3325 data.templateCache = {}
\r
3327 data.startInclude = DOC.createComment("ms-include")
\r
3328 data.endInclude = DOC.createComment("ms-include-end")
\r
3330 data.element = data.startInclude
\r
3331 elem.parentNode.insertBefore(data.startInclude, elem)
\r
3332 elem.parentNode.insertBefore(data.endInclude, elem.nextSibling)
\r
3334 elem.insertBefore(data.startInclude, elem.firstChild)
\r
3335 elem.appendChild(data.endInclude)
\r
3338 data.handlerName = "attr" //handleName用于处理多种绑定共用同一种bindingExecutor的情况
\r
3339 parseExprProxy(text, vmodels, data, (simple ? 0 : scanExpr(data.value)))
\r
3342 bindingExecutors.attr = function(val, elem, data) {
\r
3343 var method = data.type,
\r
3344 attrName = data.param
\r
3345 if (method === "css") {
\r
3346 avalon(elem).css(attrName, val)
\r
3347 } else if (method === "attr") {
\r
3349 // ms-attr-class="xxx" vm.xxx="aaa bbb ccc"将元素的className设置为aaa bbb ccc
\r
3350 // ms-attr-class="xxx" vm.xxx=false 清空元素的所有类名
\r
3351 // ms-attr-name="yyy" vm.yyy="ooo" 为元素设置name属性
\r
3352 var toRemove = (val === false) || (val === null) || (val === void 0)
\r
3354 if (!W3C && propMap[attrName]) { //旧式IE下需要进行名字映射
\r
3355 attrName = propMap[attrName]
\r
3357 var bool = boolMap[attrName]
\r
3358 if (typeof elem[bool] === "boolean") {
\r
3359 elem[bool] = !! val //布尔属性必须使用el.xxx = true|false方式设值
\r
3360 if (!val) { //如果为false, IE全系列下相当于setAttribute(xxx,''),会影响到样式,需要进一步处理
\r
3365 return elem.removeAttribute(attrName)
\r
3367 //SVG只能使用setAttribute(xxx, yyy), VML只能使用elem.xxx = yyy ,HTML的固有属性必须elem.xxx = yyy
\r
3368 var isInnate = rsvg.test(elem) ? false : (DOC.namespaces && isVML(elem)) ? true : attrName in elem.cloneNode(false)
\r
3370 elem[attrName] = val+""
\r
3372 elem.setAttribute(attrName, val)
\r
3374 } else if (method === "include" && val) {
\r
3375 var vmodels = data.vmodels
\r
3376 var rendered = data.includeRendered
\r
3377 var loaded = data.includeLoaded
\r
3378 var replace = data.includeReplace
\r
3379 var target = replace ? elem.parentNode : elem
\r
3380 var scanTemplate = function(text) {
\r
3382 var newText = loaded.apply(target, [text].concat(vmodels))
\r
3383 if (typeof newText === "string")
\r
3387 checkScan(target, function() {
\r
3388 rendered.call(target)
\r
3391 var lastID = data.includeLastID
\r
3392 if (data.templateCache && lastID && lastID !== val) {
\r
3393 var lastTemplate = data.templateCache[lastID]
\r
3394 if (!lastTemplate) {
\r
3395 lastTemplate = data.templateCache[lastID] = DOC.createElement("div")
\r
3396 ifGroup.appendChild(lastTemplate)
\r
3399 data.includeLastID = val
\r
3401 var node = data.startInclude.nextSibling
\r
3402 if (node && node !== data.endInclude) {
\r
3403 target.removeChild(node)
\r
3405 lastTemplate.appendChild(node)
\r
3410 var dom = getTemplateNodes(data, val, text)
\r
3411 var nodes = avalon.slice(dom.childNodes)
\r
3412 target.insertBefore(dom, data.endInclude)
\r
3413 scanNodeArray(nodes, vmodels)
\r
3416 if (data.param === "src") {
\r
3417 if (typeof templatePool[val] === "string") {
\r
3418 avalon.nextTick(function() {
\r
3419 scanTemplate(templatePool[val])
\r
3421 } else if (Array.isArray(templatePool[val])) { //#805 防止在循环绑定中发出许多相同的请求
\r
3422 templatePool[val].push(scanTemplate)
\r
3424 var xhr = getXHR()
\r
3425 xhr.onreadystatechange = function() {
\r
3426 if (xhr.readyState === 4) {
\r
3427 var s = xhr.status
\r
3428 if (s >= 200 && s < 300 || s === 304 || s === 1223) {
\r
3429 var text = xhr.responseText
\r
3430 for (var f = 0, fn; fn = templatePool[val][f++];) {
\r
3433 templatePool[val] = text
\r
3437 templatePool[val] = [scanTemplate]
\r
3438 xhr.open("GET", val, true)
\r
3439 if ("withCredentials" in xhr) {
\r
3440 xhr.withCredentials = true
\r
3442 xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest")
\r
3446 //IE系列与够新的标准浏览器支持通过ID取得元素(firefox14+)
\r
3447 //http://tjvantoll.com/2012/07/19/dom-element-references-as-global-variables/
\r
3448 var el = val && val.nodeType === 1 ? val : DOC.getElementById(val)
\r
3450 if (el.tagName === "NOSCRIPT" && !(el.innerHTML || el.fixIE78)) { //IE7-8 innerText,innerHTML都无法取得其内容,IE6能取得其innerHTML
\r
3451 xhr = getXHR() //IE9-11与chrome的innerHTML会得到转义的内容,它们的innerText可以
\r
3452 xhr.open("GET", location, false) //谢谢Nodejs 乱炖群 深圳-纯属虚构
\r
3454 //http://bbs.csdn.net/topics/390349046?page=1#post-393492653
\r
3455 var noscripts = DOC.getElementsByTagName("noscript")
\r
3456 var array = (xhr.responseText || "").match(rnoscripts) || []
\r
3457 var n = array.length
\r
3458 for (var i = 0; i < n; i++) {
\r
3459 var tag = noscripts[i]
\r
3460 if (tag) { //IE6-8中noscript标签的innerHTML,innerText是只读的
\r
3461 tag.style.display = "none" //http://haslayout.net/css/noscript-Ghost-Bug
\r
3462 tag.fixIE78 = (array[i].match(rnoscriptText) || ["", " "])[1]
\r
3466 avalon.nextTick(function() {
\r
3467 scanTemplate(el.fixIE78 || el.value || el.innerText || el.innerHTML)
\r
3472 if (!root.hasAttribute && typeof val === "string" && (method === "src" || method === "href")) {
\r
3473 val = val.replace(/&/g, "&") //处理IE67自动转义的问题
\r
3475 elem[method] = val
\r
3476 if (window.chrome && elem.tagName === "EMBED") {
\r
3477 var parent = elem.parentNode //#525 chrome1-37下embed标签动态设置src不能发生请求
\r
3478 var comment = document.createComment("ms-src")
\r
3479 parent.replaceChild(comment, elem)
\r
3480 parent.replaceChild(elem, comment)
\r
3485 function getTemplateNodes(data, id, text) {
\r
3486 var div = data.templateCache && data.templateCache[id]
\r
3488 var dom = DOC.createDocumentFragment(),
\r
3490 while (firstChild = div.firstChild) {
\r
3491 dom.appendChild(firstChild)
\r
3495 return avalon.parseHTML(text)
\r
3498 //这几个指令都可以使用插值表达式,如ms-src="aaa/{{b}}/{{c}}.html"
\r
3499 "title,alt,src,value,css,include,href".replace(rword, function(name) {
\r
3500 bindingHandlers[name] = bindingHandlers.attr
\r
3502 //根据VM的属性值或表达式的值切换类名,ms-class="xxx yyy zzz:flag"
\r
3503 //http://www.cnblogs.com/rubylouvre/archive/2012/12/17/2818540.html
\r
3504 bindingHandlers["class"] = function(data, vmodels) {
\r
3505 var oldStyle = data.param,
\r
3506 text = data.value,
\r
3508 data.handlerName = "class"
\r
3509 if (!oldStyle || isFinite(oldStyle)) {
\r
3510 data.param = "" //去掉数字
\r
3511 var noExpr = text.replace(rexprg, function(a) {
\r
3512 return a.replace(/./g, "0")
\r
3513 //return Math.pow(10, a.length - 1) //将插值表达式插入10的N-1次方来占位
\r
3515 var colonIndex = noExpr.indexOf(":") //取得第一个冒号的位置
\r
3516 if (colonIndex === -1) { // 比如 ms-class="aaa bbb ccc" 的情况
\r
3517 var className = text
\r
3518 } else { // 比如 ms-class-1="ui-state-active:checked" 的情况
\r
3519 className = text.slice(0, colonIndex)
\r
3520 rightExpr = text.slice(colonIndex + 1)
\r
3521 parseExpr(rightExpr, vmodels, data) //决定是添加还是删除
\r
3522 if (!data.evaluator) {
\r
3523 log("debug: ms-class '" + (rightExpr || "").trim() + "' 不存在于VM中")
\r
3526 data._evaluator = data.evaluator
\r
3527 data._args = data.args
\r
3530 var hasExpr = rexpr.test(className) //比如ms-class="width{{w}}"的情况
\r
3532 data.immobileClass = className
\r
3534 parseExprProxy("", vmodels, data, (hasExpr ? scanExpr(className) : 0))
\r
3536 data.immobileClass = data.oldStyle = data.param
\r
3537 parseExprProxy(text, vmodels, data)
\r
3541 bindingExecutors["class"] = function(val, elem, data) {
\r
3542 var $elem = avalon(elem),
\r
3543 method = data.type
\r
3544 if (method === "class" && data.oldStyle) { //如果是旧风格
\r
3545 $elem.toggleClass(data.oldStyle, !! val)
\r
3548 data.toggleClass = data._evaluator ? !! data._evaluator.apply(elem, data._args) : true
\r
3549 data.newClass = data.immobileClass || val
\r
3550 if (data.oldClass && data.newClass !== data.oldClass) {
\r
3551 $elem.removeClass(data.oldClass)
\r
3553 data.oldClass = data.newClass
\r
3556 $elem.toggleClass(data.newClass, data.toggleClass)
\r
3560 if (!data.hasBindEvent) { //确保只绑定一次
\r
3561 var activate = "mouseenter" //在移出移入时切换类名
\r
3562 var abandon = "mouseleave"
\r
3563 if (method === "active") { //在聚焦失焦中切换类名
\r
3564 elem.tabIndex = elem.tabIndex || -1
\r
3565 activate = "mousedown"
\r
3566 abandon = "mouseup"
\r
3567 var fn0 = $elem.bind("mouseleave", function() {
\r
3568 data.toggleClass && $elem.removeClass(data.newClass)
\r
3571 var fn1 = $elem.bind(activate, function() {
\r
3572 data.toggleClass && $elem.addClass(data.newClass)
\r
3574 var fn2 = $elem.bind(abandon, function() {
\r
3575 data.toggleClass && $elem.removeClass(data.newClass)
\r
3577 data.rollback = function() {
\r
3578 $elem.unbind("mouseleave", fn0)
\r
3579 $elem.unbind(activate, fn1)
\r
3580 $elem.unbind(abandon, fn2)
\r
3582 data.hasBindEvent = true
\r
3589 "hover,active".replace(rword, function(method) {
\r
3590 bindingHandlers[method] = bindingHandlers["class"]
\r
3592 //ms-controller绑定已经在scanTag 方法中实现
\r
3593 //ms-css绑定已由ms-attr绑定实现
\r
3596 // bindingHandlers.data 定义在if.js
\r
3597 bindingExecutors.data = function(val, elem, data) {
\r
3598 var key = "data-" + data.param
\r
3599 if (val && typeof val === "object") {
\r
3602 elem.setAttribute(key, String(val))
\r
3606 var duplexBinding = bindingHandlers.duplex = function(data, vmodels) {
\r
3607 var elem = data.element,
\r
3609 parseExprProxy(data.value, vmodels, data, 0, 1)
\r
3611 data.changed = getBindingCallback(elem, "data-duplex-changed", vmodels) || noop
\r
3612 if (data.evaluator && data.args) {
\r
3614 var casting = oneObject("string,number,boolean,checked")
\r
3615 if (elem.type === "radio" && data.param === "") {
\r
3616 data.param = "checked"
\r
3618 if (elem.msData) {
\r
3619 elem.msData["ms-duplex"] = data.value
\r
3621 data.param.replace(/\w+/g, function(name) {
\r
3622 if (/^(checkbox|radio)$/.test(elem.type) && /^(radio|checked)$/.test(name)) {
\r
3623 if (name === "radio")
\r
3624 log("ms-duplex-radio已经更名为ms-duplex-checked")
\r
3626 data.isChecked = true
\r
3628 if (name === "bool") {
\r
3630 log("ms-duplex-bool已经更名为ms-duplex-boolean")
\r
3631 } else if (name === "text") {
\r
3633 log("ms-duplex-text已经更名为ms-duplex-string")
\r
3635 if (casting[name]) {
\r
3638 avalon.Array.ensure(params, name)
\r
3641 params.push("string")
\r
3643 data.param = params.join("-")
\r
3644 data.bound = function(type, callback) {
\r
3645 if (elem.addEventListener) {
\r
3646 elem.addEventListener(type, callback, false)
\r
3648 elem.attachEvent("on" + type, callback)
\r
3650 var old = data.rollback
\r
3651 data.rollback = function() {
\r
3652 elem.avalonSetter = null
\r
3653 avalon.unbind(elem, type, callback)
\r
3657 for (var i in avalon.vmodels) {
\r
3658 var v = avalon.vmodels[i]
\r
3659 v.$fire("avalon-ms-duplex-init", data)
\r
3661 var cpipe = data.pipe || (data.pipe = pipe)
\r
3662 cpipe(null, data, "init")
\r
3663 var tagName = elem.tagName
\r
3664 duplexBinding[tagName] && duplexBinding[tagName](elem, data.evaluator.apply(null, data.args), data)
\r
3667 //不存在 bindingExecutors.duplex
\r
3669 function fixNull(val) {
\r
3670 return val == null ? "" : val
\r
3672 avalon.duplexHooks = {
\r
3674 get: function(val, data) {
\r
3675 return !data.element.oldValue
\r
3679 get: function(val) { //同步到VM
\r
3685 get: function(val) {
\r
3686 return val === "true"
\r
3691 get: function(val, data) {
\r
3692 var number = parseFloat(val)
\r
3693 if (-val === -number) {
\r
3696 var arr = /strong|medium|weak/.exec(data.element.getAttribute("data-duplex-number")) || ["medium"]
\r
3701 return val === "" ? "" : 0
\r
3710 function pipe(val, data, action, e) {
\r
3711 data.param.replace(/\w+/g, function(name) {
\r
3712 var hook = avalon.duplexHooks[name]
\r
3713 if (hook && typeof hook[action] === "function") {
\r
3714 val = hook[action](val, data)
\r
3720 var TimerID, ribbon = []
\r
3722 avalon.tick = function(fn) {
\r
3723 if (ribbon.push(fn) === 1) {
\r
3724 TimerID = setInterval(ticker, 60)
\r
3728 function ticker() {
\r
3729 for (var n = ribbon.length - 1; n >= 0; n--) {
\r
3730 var el = ribbon[n]
\r
3731 if (el() === false) {
\r
3732 ribbon.splice(n, 1)
\r
3735 if (!ribbon.length) {
\r
3736 clearInterval(TimerID)
\r
3740 var watchValueInTimer = noop
\r
3741 var rmsinput = /text|password|hidden/
\r
3742 new function() { // jshint ignore:line
\r
3743 try { //#272 IE9-IE11, firefox
\r
3745 var aproto = HTMLInputElement.prototype
\r
3746 var bproto = HTMLTextAreaElement.prototype
\r
3747 function newSetter(value) { // jshint ignore:line
\r
3748 setters[this.tagName].call(this, value)
\r
3749 if (rmsinput.test(this.type) && !this.msFocus && this.avalonSetter) {
\r
3750 this.avalonSetter()
\r
3753 var inputProto = HTMLInputElement.prototype
\r
3754 Object.getOwnPropertyNames(inputProto) //故意引发IE6-8等浏览器报错
\r
3755 setters["INPUT"] = Object.getOwnPropertyDescriptor(aproto, "value").set
\r
3757 Object.defineProperty(aproto, "value", {
\r
3760 setters["TEXTAREA"] = Object.getOwnPropertyDescriptor(bproto, "value").set
\r
3761 Object.defineProperty(bproto, "value", {
\r
3765 //在chrome 43中 ms-duplex终于不需要使用定时器实现双向绑定了
\r
3766 // http://updates.html5rocks.com/2015/04/DOM-attributes-now-on-the-prototype
\r
3767 // https://docs.google.com/document/d/1jwA8mtClwxI-QJuHT7872Z0pxpZz8PBkf2bGAbsUtqs/edit?pli=1
\r
3768 watchValueInTimer = avalon.tick
\r
3770 } // jshint ignore:line
\r
3772 avalon.bind(DOC, "selectionchange", function(e) {
\r
3773 var el = DOC.activeElement
\r
3774 if (el && typeof el.avalonSetter === "function") {
\r
3780 //处理radio, checkbox, text, textarea, password
\r
3781 duplexBinding.INPUT = function(element, evaluator, data) {
\r
3782 var $type = element.type,
\r
3783 bound = data.bound,
\r
3784 $elem = avalon(element),
\r
3787 function callback(value) {
\r
3788 data.changed.call(this, value, data)
\r
3791 function compositionStart() {
\r
3795 function compositionEnd() {
\r
3798 //当value变化时改变model的值
\r
3799 var updateVModel = function() {
\r
3800 if (composing) //处理中文输入法在minlengh下引发的BUG
\r
3802 var val = element.oldValue = element.value //防止递归调用形成死循环
\r
3803 var lastValue = data.pipe(val, data, "get")
\r
3804 if ($elem.data("duplexObserve") !== false) {
\r
3805 evaluator(lastValue)
\r
3806 callback.call(element, lastValue)
\r
3807 if ($elem.data("duplex-focus")) {
\r
3808 avalon.nextTick(function() {
\r
3814 //当model变化时,它就会改变value的值
\r
3815 data.handler = function() {
\r
3816 var val = data.pipe(evaluator(), data, "set") + "" //fix #673
\r
3817 if (val !== element.oldValue) {
\r
3818 element.value = val
\r
3821 if (data.isChecked || $type === "radio") {
\r
3822 var IE6 = IEVersion === 6
\r
3823 updateVModel = function() {
\r
3824 if ($elem.data("duplexObserve") !== false) {
\r
3825 var lastValue = data.pipe(element.value, data, "get")
\r
3826 evaluator(lastValue)
\r
3827 callback.call(element, lastValue)
\r
3830 data.handler = function() {
\r
3831 var val = evaluator()
\r
3832 var checked = data.isChecked ? !! val : val + "" === element.value
\r
3833 element.oldValue = checked
\r
3835 setTimeout(function() {
\r
3836 //IE8 checkbox, radio是使用defaultChecked控制选中状态,
\r
3837 //并且要先设置defaultChecked后设置checked
\r
3839 element.defaultChecked = checked
\r
3840 element.checked = checked
\r
3843 element.checked = checked
\r
3846 bound("click", updateVModel)
\r
3847 } else if ($type === "checkbox") {
\r
3848 updateVModel = function() {
\r
3849 if ($elem.data("duplexObserve") !== false) {
\r
3850 var method = element.checked ? "ensure" : "remove"
\r
3851 var array = evaluator()
\r
3852 if (!Array.isArray(array)) {
\r
3853 log("ms-duplex应用于checkbox上要对应一个数组")
\r
3856 var val = data.pipe(element.value, data, "get")
\r
3857 avalon.Array[method](array, val)
\r
3858 callback.call(element, array)
\r
3862 data.handler = function() {
\r
3863 var array = [].concat(evaluator()) //强制转换为数组
\r
3864 var val = data.pipe(element.value, data, "get")
\r
3865 element.checked = array.indexOf(val) > -1
\r
3867 bound(W3C ? "change" : "click", updateVModel)
\r
3869 var events = element.getAttribute("data-duplex-event") || "input"
\r
3870 if (element.attributes["data-event"]) {
\r
3871 log("data-event指令已经废弃,请改用data-duplex-event")
\r
3874 function delay(e) { // jshint ignore:line
\r
3875 setTimeout(function() {
\r
3879 events.replace(rword, function(name) {
\r
3882 if (!IEVersion) { // W3C
\r
3883 bound("input", updateVModel)
\r
3885 bound("compositionstart", compositionStart)
\r
3886 bound("compositionend", compositionEnd)
\r
3887 bound("DOMAutoComplete", updateVModel)
\r
3888 } else { //onpropertychange事件无法区分是程序触发还是用户触发
\r
3889 // IE下通过selectionchange事件监听IE9+点击input右边的X的清空行为,及粘贴,剪切,删除行为
\r
3890 if (IEVersion > 8) {
\r
3891 bound("input", updateVModel) //IE9使用propertychange无法监听中文输入改动
\r
3893 bound("propertychange", function(e) { //IE6-8下第一次修改时不会触发,需要使用keydown或selectionchange修正
\r
3894 if (e.propertyName === "value") {
\r
3899 bound("dragend", delay)
\r
3900 //http://www.cnblogs.com/rubylouvre/archive/2013/02/17/2914604.html
\r
3901 //http://www.matts411.com/post/internet-explorer-9-oninput/
\r
3905 bound(name, updateVModel)
\r
3909 bound("focus", function() {
\r
3910 element.msFocus = true
\r
3912 bound("blur", function() {
\r
3913 element.msFocus = false
\r
3916 if (rmsinput.test($type)) {
\r
3917 watchValueInTimer(function() {
\r
3918 if (root.contains(element)) {
\r
3919 if (!element.msFocus && element.oldValue !== element.value) {
\r
3922 } else if (!element.msRetain) {
\r
3928 element.avalonSetter = updateVModel //#765
\r
3931 element.oldValue = element.value
\r
3932 avalon.injectBinding(data)
\r
3933 callback.call(element, element.value)
\r
3935 duplexBinding.TEXTAREA = duplexBinding.INPUT
\r
3936 duplexBinding.SELECT = function(element, evaluator, data) {
\r
3937 var $elem = avalon(element)
\r
3939 function updateVModel() {
\r
3940 if ($elem.data("duplexObserve") !== false) {
\r
3941 var val = $elem.val() //字符串或字符串数组
\r
3942 if (Array.isArray(val)) {
\r
3943 val = val.map(function(v) {
\r
3944 return data.pipe(v, data, "get")
\r
3947 val = data.pipe(val, data, "get")
\r
3949 if (val + "" !== element.oldValue) {
\r
3952 data.changed.call(element, val, data)
\r
3955 data.handler = function() {
\r
3956 var val = evaluator()
\r
3957 val = val && val.$model || val
\r
3958 if (Array.isArray(val)) {
\r
3959 if (!element.multiple) {
\r
3960 log("ms-duplex在<select multiple=true>上要求对应一个数组")
\r
3963 if (element.multiple) {
\r
3964 log("ms-duplex在<select multiple=false>不能对应一个数组")
\r
3968 val = Array.isArray(val) ? val.map(String) : val + ""
\r
3969 if (val + "" !== element.oldValue) {
\r
3971 element.oldValue = val + ""
\r
3974 data.bound("change", updateVModel)
\r
3975 element.msCallback = function() {
\r
3976 avalon.injectBinding(data)
\r
3977 data.changed.call(element, evaluator(), data)
\r
3980 // bindingHandlers.html 定义在if.js
\r
3981 bindingExecutors.html = function (val, elem, data) {
\r
3982 var isHtmlFilter = elem.nodeType !== 1
\r
3983 var parent = isHtmlFilter ? elem.parentNode : elem
\r
3986 val = val == null ? "" : val
\r
3987 if (data.oldText !== val) {
\r
3988 data.oldText = val
\r
3992 if (elem.nodeType === 3) {
\r
3993 var signature = generateID("html")
\r
3994 parent.insertBefore(DOC.createComment(signature), elem)
\r
3995 data.element = DOC.createComment(signature + ":end")
\r
3996 parent.replaceChild(data.element, elem)
\r
3997 elem = data.element
\r
3999 if (typeof val !== "object") {//string, number, boolean
\r
4000 var fragment = avalon.parseHTML(String(val))
\r
4001 } else if (val.nodeType === 11) { //将val转换为文档碎片
\r
4003 } else if (val.nodeType === 1 || val.item) {
\r
4004 var nodes = val.nodeType === 1 ? val.childNodes : val.item
\r
4005 fragment = avalonFragment.cloneNode(true)
\r
4006 while (nodes[0]) {
\r
4007 fragment.appendChild(nodes[0])
\r
4011 nodes = avalon.slice(fragment.childNodes)
\r
4012 //插入占位符, 如果是过滤器,需要有节制地移除指定的数量,如果是html指令,直接清空
\r
4013 if (isHtmlFilter) {
\r
4014 var endValue = elem.nodeValue.slice(0, -4)
\r
4016 var node = elem.previousSibling
\r
4017 if (!node || node.nodeType === 8 && node.nodeValue === endValue) {
\r
4020 parent.removeChild(node)
\r
4023 parent.insertBefore(fragment, elem)
\r
4025 avalon.clearHTML(elem).appendChild(fragment)
\r
4027 scanNodeArray(nodes, data.vmodels)
\r
4029 bindingHandlers["if"] =
\r
4030 bindingHandlers.data =
\r
4031 bindingHandlers.text =
\r
4032 bindingHandlers.html =
\r
4033 function(data, vmodels) {
\r
4034 parseExprProxy(data.value, vmodels, data)
\r
4037 bindingExecutors["if"] = function(val, elem, data) {
\r
4039 if(!elem.parentNode) return
\r
4040 } catch(e) {return}
\r
4041 if (val) { //插回DOM树
\r
4042 if (elem.nodeType === 8) {
\r
4043 elem.parentNode.replaceChild(data.template, elem)
\r
4044 // animate.enter(data.template, elem.parentNode)
\r
4045 elem = data.element = data.template //这时可能为null
\r
4047 if (elem.getAttribute(data.name)) {
\r
4048 elem.removeAttribute(data.name)
\r
4049 scanAttr(elem, data.vmodels)
\r
4051 data.rollback = null
\r
4052 } else { //移出DOM树,并用注释节点占据原位置
\r
4053 if (elem.nodeType === 1) {
\r
4054 var node = data.element = DOC.createComment("ms-if")
\r
4055 elem.parentNode.replaceChild(node, elem)
\r
4056 // animate.leave(elem, node.parentNode, node)
\r
4057 data.template = elem //元素节点
\r
4058 ifGroup.appendChild(elem)
\r
4059 data.rollback = function() {
\r
4060 if (elem.parentNode === ifGroup) {
\r
4061 ifGroup.removeChild(elem)
\r
4067 //ms-important绑定已经在scanTag 方法中实现
\r
4068 //ms-include绑定已由ms-attr绑定实现
\r
4070 var rdash = /\(([^)]*)\)/
\r
4071 bindingHandlers.on = function(data, vmodels) {
\r
4072 var value = data.value
\r
4074 var eventType = data.param.replace(/-\d+$/, "") // ms-on-mousemove-10
\r
4075 if (typeof bindingHandlers.on[eventType + "Hook"] === "function") {
\r
4076 bindingHandlers.on[eventType + "Hook"](data)
\r
4078 if (value.indexOf("(") > 0 && value.indexOf(")") > -1) {
\r
4079 var matched = (value.match(rdash) || ["", ""])[1].trim()
\r
4080 if (matched === "" || matched === "$event") { // aaa() aaa($event)当成aaa处理
\r
4081 value = value.replace(rdash, "")
\r
4084 parseExprProxy(value, vmodels, data)
\r
4087 bindingExecutors.on = function(callback, elem, data) {
\r
4088 callback = function(e) {
\r
4089 var fn = data.evaluator || noop
\r
4090 return fn.apply(this, data.args.concat(e))
\r
4092 var eventType = data.param.replace(/-\d+$/, "") // ms-on-mousemove-10
\r
4093 if (eventType === "scan") {
\r
4094 callback.call(elem, {
\r
4097 } else if (typeof data.specialBind === "function") {
\r
4098 data.specialBind(elem, callback)
\r
4100 var removeFn = avalon.bind(elem, eventType, callback)
\r
4102 data.rollback = function() {
\r
4103 if (typeof data.specialUnbind === "function") {
\r
4104 data.specialUnbind()
\r
4106 avalon.unbind(elem, eventType, removeFn)
\r
4110 bindingHandlers.repeat = function (data, vmodels) {
\r
4111 var type = data.type
\r
4112 parseExprProxy(data.value, vmodels, data, 0, 1)
\r
4114 var freturn = false
\r
4116 var $repeat = data.$repeat = data.evaluator.apply(0, data.args || [])
\r
4117 var xtype = avalon.type($repeat)
\r
4118 if (xtype !== "object" && xtype !== "array") {
\r
4120 avalon.log("warning:" + data.value + "只能是对象或数组")
\r
4125 var arr = data.value.split(".") || []
\r
4126 if (arr.length > 1) {
\r
4129 for (var i = 0, v; v = vmodels[i++]; ) {
\r
4130 if (v && v.hasOwnProperty(n)) {
\r
4131 var events = v[n].$events || {}
\r
4132 events[subscribers] = events[subscribers] || []
\r
4133 events[subscribers].push(data)
\r
4139 var elem = data.element
\r
4140 if (elem.nodeType === 1) {
\r
4141 elem.removeAttribute(data.name)
\r
4142 data.sortedCallback = getBindingCallback(elem, "data-with-sorted", vmodels)
\r
4143 data.renderedCallback = getBindingCallback(elem, "data-" + type + "-rendered", vmodels)
\r
4144 var signature = generateID(type)
\r
4145 var start = DOC.createComment(signature)
\r
4146 var end = DOC.createComment(signature + ":end")
\r
4147 data.signature = signature
\r
4148 data.template = avalonFragment.cloneNode(false)
\r
4149 if (type === "repeat") {
\r
4150 var parent = elem.parentNode
\r
4151 parent.replaceChild(end, elem)
\r
4152 parent.insertBefore(start, end)
\r
4153 data.template.appendChild(elem)
\r
4155 while (elem.firstChild) {
\r
4156 data.template.appendChild(elem.firstChild)
\r
4158 elem.appendChild(start)
\r
4159 elem.appendChild(end)
\r
4161 data.element = end
\r
4162 data.handler = bindingExecutors.repeat
\r
4163 data.rollback = function () {
\r
4164 var elem = data.element
\r
4167 data.handler("clear")
\r
4176 var check0 = "$key"
\r
4177 var check1 = "$val"
\r
4178 if (Array.isArray($repeat)) {
\r
4183 for (i = 0; v = vmodels[i++]; ) {
\r
4184 if (v.hasOwnProperty(check0) && v.hasOwnProperty(check1)) {
\r
4189 var $events = $repeat.$events
\r
4190 var $list = ($events || {})[subscribers]
\r
4191 injectDependency($list, data)
\r
4192 if (xtype === "object") {
\r
4194 $repeat.$proxy || ($repeat.$proxy = {})
\r
4195 data.handler("append", $repeat)
\r
4196 } else if ($repeat.length) {
\r
4197 data.handler("add", 0, $repeat.length)
\r
4201 bindingExecutors.repeat = function (method, pos, el) {
\r
4202 if (!method && this.$with) {
\r
4204 var flag = "update"
\r
4207 var data = this, start, fragment
\r
4208 var end = data.element
\r
4209 var comments = getComments(data)
\r
4210 var parent = end.parentNode
\r
4211 var proxies = data.proxies
\r
4212 var transation = avalonFragment.cloneNode(false)
\r
4214 case "add": //在pos位置后添加el数组(pos为插入位置,el为要插入的个数)
\r
4216 var fragments = []
\r
4217 for (var i = pos; i < n; i++) {
\r
4218 var proxy = eachProxyAgent(i, data)
\r
4219 proxies.splice(i, 0, proxy)
\r
4220 shimController(data, transation, proxy, fragments)
\r
4222 var now = new Date() - 0
\r
4223 avalon.optimize = avalon.optimize || now
\r
4224 for (i = 0; fragment = fragments[i++]; ) {
\r
4225 scanNodeArray(fragment.nodes, fragment.vmodels)
\r
4226 fragment.nodes = fragment.vmodels = null
\r
4228 if (avalon.optimize === now) {
\r
4229 avalon.optimize = null
\r
4231 parent.insertBefore(transation, comments[pos] || end)
\r
4232 avalon.profile("插入操作花费了 " + (new Date - now))
\r
4234 case "del": //将pos后的el个元素删掉(pos, el都是数字)
\r
4235 sweepNodes(comments[pos], comments[pos + el] || end)
\r
4236 var removed = proxies.splice(pos, el)
\r
4237 recycleProxies(removed, "each")
\r
4240 start = comments[0]
\r
4242 sweepNodes(start, end)
\r
4244 parent.insertBefore(start, end)
\r
4247 recycleProxies(proxies, "each")
\r
4250 start = comments[0]
\r
4252 var signature = start.nodeValue
\r
4256 sweepNodes(start, end, function () {
\r
4257 room.unshift(this)
\r
4258 if (this.nodeValue === signature) {
\r
4259 rooms.unshift(room)
\r
4263 sortByIndex(rooms, pos)
\r
4264 sortByIndex(proxies, pos)
\r
4265 while (room = rooms.shift()) {
\r
4266 while (node = room.shift()) {
\r
4267 transation.appendChild(node)
\r
4270 parent.insertBefore(transation, end)
\r
4273 case "index": //将proxies中的第pos个起的所有元素重新索引
\r
4274 var last = proxies.length - 1
\r
4275 for (; el = proxies[pos]; pos++) {
\r
4277 el.$first = pos === 0
\r
4278 el.$last = pos === last
\r
4281 case "set": //将proxies中的第pos个元素的VM设置为el(pos为数字,el任意)
\r
4282 proxy = proxies[pos]
\r
4284 fireDependencies(proxy.$events[data.param || "el"])
\r
4288 var object = data.$repeat //原来第2参数, 被循环对象
\r
4289 var oldProxy = object.$proxy //代理对象组成的hash
\r
4291 now = new Date() - 0
\r
4292 avalon.optimize = avalon.optimize || now
\r
4293 if (flag === "update") {
\r
4294 if (!data.evaluator) {
\r
4295 parseExprProxy(data.value, data.vmodels, data, 0, 1)
\r
4297 object = data.$repeat = data.evaluator.apply(0, data.args || [])
\r
4298 object.$proxy = oldProxy
\r
4300 var pool = object.$proxy || {}
\r
4302 var nodes = data.element.parentNode.childNodes
\r
4304 for (i = 0; node = nodes[i++]; ) {
\r
4305 if (node.nodeValue === data.signature) {
\r
4307 } else if (node.nodeValue === data.signature + ":end") {
\r
4311 removed.push(node)
\r
4315 var indexNode = [], item
\r
4316 var keyIndex = data.keyIndex || (data.keyIndex = {})
\r
4318 for ( i = 0; i < removed.length; i++) {
\r
4320 if (el.nodeValue === data.signature) {
\r
4321 item = avalonFragment.cloneNode(false)
\r
4322 indexNode.push(item)
\r
4324 item.appendChild(el)
\r
4328 for (var key in object) { //当前对象的所有键名
\r
4329 if (object.hasOwnProperty(key) && key !== "hasOwnProperty" && key !== "$proxy") {
\r
4334 for (var i = 0; key = keys[i++]; ) {
\r
4335 if (!pool.hasOwnProperty(key)) {//添加缺失的代理VM
\r
4336 pool[key] = withProxyAgent(pool[key], key, data)
\r
4338 pool[key].$val = object[key]
\r
4342 for ( key in pool) {
\r
4343 if (keys.indexOf(key) === -1) {//删除没用的代理VM
\r
4344 proxyRecycler(pool[key], withProxyPool) //去掉之前的代理VM
\r
4348 var fragments = []
\r
4349 var renderKeys = keys //需要渲染到DOM树去的键名
\r
4350 var end = data.element
\r
4351 if (data.sortedCallback) { //如果有回调,则让它们排序
\r
4352 var keys2 = data.sortedCallback.call(parent, keys)
\r
4353 if (keys2 && Array.isArray(keys2)) {
\r
4354 renderKeys = keys2
\r
4358 for (i = 0; i < renderKeys.length; i++) {
\r
4359 key = renderKeys[i]
\r
4360 if (typeof keyIndex[key] === "number") {
\r
4361 transation.appendChild(indexNode[keyIndex[key]])
\r
4362 fragments.push({})
\r
4364 shimController(data, transation, pool[key], fragments)
\r
4368 for (i = 0; i < renderKeys.length; i++) {
\r
4369 keyIndex[renderKeys[i]] = i
\r
4372 for (i = 0; fragment = fragments[i++]; ) {
\r
4373 if (fragment.nodes) {
\r
4374 scanNodeArray(fragment.nodes, fragment.vmodels)
\r
4375 fragment.nodes = fragment.vmodels = null
\r
4378 if (avalon.optimize === now) {
\r
4379 avalon.optimize = null
\r
4381 parent.insertBefore(transation, end)
\r
4382 avalon.profile("插入操作花费了 " + (new Date - now))
\r
4385 if (!data.$repeat || data.$repeat.hasOwnProperty("$lock")) //IE6-8 VBScript对象会报错, 有时候data.$repeat不存在
\r
4387 if (method === "clear")
\r
4389 var callback = data.renderedCallback || noop,
\r
4391 if (parent.oldValue && parent.tagName === "SELECT") { //fix #503
\r
4392 avalon(parent).val(parent.oldValue.split(","))
\r
4394 callback.apply(parent, args)
\r
4397 "with,each".replace(rword, function (name) {
\r
4398 bindingHandlers[name] = bindingHandlers.repeat
\r
4401 function shimController(data, transation, proxy, fragments) {
\r
4402 var content = data.template.cloneNode(true)
\r
4403 var nodes = avalon.slice(content.childNodes)
\r
4404 content.insertBefore(DOC.createComment(data.signature), content.firstChild)
\r
4405 transation.appendChild(content)
\r
4406 var nv = [proxy].concat(data.vmodels)
\r
4411 fragments.push(fragment)
\r
4414 function getComments(data) {
\r
4416 var nodes = data.element.parentNode.childNodes
\r
4417 for(var i= 0, node; node = nodes[i++];){
\r
4418 if(node.nodeValue === data.signature){
\r
4420 }else if(node.nodeValue === data.signature+":end"){
\r
4428 //移除掉start与end之间的节点(保留end)
\r
4429 function sweepNodes(start, end, callback) {
\r
4431 var node = end.previousSibling
\r
4434 node.parentNode.removeChild(node)
\r
4435 callback && callback.call(node)
\r
4436 if (node === start) {
\r
4442 // 为ms-each,ms-with, ms-repeat会创建一个代理VM,
\r
4443 // 通过它们保持一个下上文,让用户能调用$index,$first,$last,$remove,$key,$val,$outer等属性与方法
\r
4444 // 所有代理VM的产生,消费,收集,存放通过xxxProxyFactory,xxxProxyAgent, recycleProxies,xxxProxyPool实现
\r
4445 var withProxyPool = []
\r
4446 function withProxyFactory() {
\r
4447 var proxy = modelFactory({
\r
4452 get: function () {
\r
4453 return this.$host[this.$key]
\r
4455 set: function (val) {
\r
4456 this.$host[this.$key] = val
\r
4462 proxy.$id = generateID("$proxy$with")
\r
4466 function withProxyAgent(proxy, key, data) {
\r
4467 proxy = proxy || withProxyPool.pop()
\r
4469 proxy = withProxyFactory()
\r
4471 proxy.$reinitialize()
\r
4473 var host = data.$repeat
\r
4475 proxy.$host = host
\r
4476 proxy.$outer = data.$outer
\r
4477 if (host.$events) {
\r
4478 proxy.$events.$val = host.$events[key]
\r
4480 proxy.$events = {}
\r
4486 function recycleProxies(proxies) {
\r
4487 eachProxyRecycler(proxies)
\r
4489 function eachProxyRecycler(proxies) {
\r
4490 proxies.forEach(function (proxy) {
\r
4491 proxyRecycler(proxy, eachProxyPool)
\r
4493 proxies.length = 0
\r
4497 var eachProxyPool = []
\r
4498 function eachProxyFactory(name) {
\r
4505 $remove: avalon.noop
\r
4508 get: function () {
\r
4509 var e = this.$events
\r
4510 var array = e.$index
\r
4511 e.$index = e[name] //#817 通过$index为el收集依赖
\r
4513 return this.$host[this.$index]
\r
4518 set: function (val) {
\r
4520 var e = this.$events
\r
4521 var array = e.$index
\r
4523 this.$host.set(this.$index, val)
\r
4534 var proxy = modelFactory(source, second)
\r
4535 proxy.$id = generateID("$proxy$each")
\r
4539 function eachProxyAgent(index, data) {
\r
4540 var param = data.param || "el",
\r
4542 for (var i = 0, n = eachProxyPool.length; i < n; i++) {
\r
4543 var candidate = eachProxyPool[i]
\r
4544 if (candidate && candidate.hasOwnProperty(param)) {
\r
4546 eachProxyPool.splice(i, 1)
\r
4550 proxy = eachProxyFactory(param)
\r
4552 var host = data.$repeat
\r
4553 var last = host.length - 1
\r
4554 proxy.$index = index
\r
4555 proxy.$first = index === 0
\r
4556 proxy.$last = index === last
\r
4557 proxy.$host = host
\r
4558 proxy.$outer = data.$outer
\r
4559 proxy.$remove = function () {
\r
4560 return host.removeAt(proxy.$index)
\r
4566 function proxyRecycler(proxy, proxyPool) {
\r
4567 for (var i in proxy.$events) {
\r
4568 if (Array.isArray(proxy.$events[i])) {
\r
4569 proxy.$events[i].forEach(function (data) {
\r
4570 if (typeof data === "object")
\r
4572 })// jshint ignore:line
\r
4573 proxy.$events[i].length = 0
\r
4576 proxy.$host = proxy.$outer = {}
\r
4577 if (proxyPool.unshift(proxy) > kernel.maxRepeatSize) {
\r
4581 /*********************************************************************
\r
4583 **********************************************************************/
\r
4584 //ms-skip绑定已经在scanTag 方法中实现
\r
4585 // bindingHandlers.text 定义在if.js
\r
4586 bindingExecutors.text = function(val, elem) {
\r
4587 val = val == null ? "" : val //不在页面上显示undefined null
\r
4588 if (elem.nodeType === 3) { //绑定在文本节点上
\r
4589 try { //IE对游离于DOM树外的节点赋值会报错
\r
4592 } else { //绑定在特性节点上
\r
4593 if ("textContent" in elem) {
\r
4594 elem.textContent = val
\r
4596 elem.innerText = val
\r
4600 function parseDisplay(nodeName, val) {
\r
4601 //用于取得此类标签的默认display值
\r
4602 var key = "_" + nodeName
\r
4603 if (!parseDisplay[key]) {
\r
4604 var node = DOC.createElement(nodeName)
\r
4605 root.appendChild(node)
\r
4607 val = getComputedStyle(node, null).display
\r
4609 val = node.currentStyle.display
\r
4611 root.removeChild(node)
\r
4612 parseDisplay[key] = val
\r
4614 return parseDisplay[key]
\r
4617 avalon.parseDisplay = parseDisplay
\r
4619 bindingHandlers.visible = function(data, vmodels) {
\r
4620 var elem = data.element
\r
4621 var display = elem.style.display
\r
4622 if(display === "none"){
\r
4623 display = parseDisplay(elem.nodeName)
\r
4625 data.display = display
\r
4626 parseExprProxy(data.value, vmodels, data)
\r
4629 bindingExecutors.visible = function(val, elem, data) {
\r
4630 elem.style.display = val ? data.display : "none"
\r
4632 bindingHandlers.widget = function(data, vmodels) {
\r
4633 var args = data.value.match(rword)
\r
4634 var elem = data.element
\r
4635 var widget = args[0]
\r
4637 if (!id || id === "$") { //没有定义或为$时,取组件名+随机数
\r
4638 id = generateID(widget)
\r
4640 var optName = args[2] || widget //没有定义,取组件名
\r
4641 var constructor = avalon.ui[widget]
\r
4642 if (typeof constructor === "function") { //ms-widget="tabs,tabsAAA,optname"
\r
4643 vmodels = elem.vmodels || vmodels
\r
4644 for (var i = 0, v; v = vmodels[i++];) {
\r
4645 if (v.hasOwnProperty(optName) && typeof v[optName] === "object") {
\r
4646 var vmOptions = v[optName]
\r
4647 vmOptions = vmOptions.$model || vmOptions
\r
4652 var wid = vmOptions[widget + "Id"]
\r
4653 if (typeof wid === "string") {
\r
4654 log("warning!不再支持" + widget + "Id")
\r
4658 //抽取data-tooltip-text、data-tooltip-attr属性,组成一个配置对象
\r
4659 var widgetData = avalon.getWidgetData(elem, widget)
\r
4660 data.value = [widget, id, optName].join(",")
\r
4661 data[widget + "Id"] = id
\r
4662 data.evaluator = noop
\r
4663 elem.msData["ms-widget-id"] = id
\r
4664 var options = data[widget + "Options"] = avalon.mix({}, constructor.defaults, vmOptions || {}, widgetData)
\r
4665 elem.removeAttribute("ms-widget")
\r
4666 var vmodel = constructor(elem, data, vmodels) || {} //防止组件不返回VM
\r
4668 avalon.vmodels[id] = vmodel
\r
4669 createSignalTower(elem, vmodel)
\r
4671 vmodel.$init(function() {
\r
4672 avalon.scan(elem, [vmodel].concat(vmodels))
\r
4673 if (typeof options.onInit === "function") {
\r
4674 options.onInit.call(elem, vmodel, options, vmodels)
\r
4678 data.rollback = function() {
\r
4680 vmodel.widgetElement = null
\r
4684 delete avalon.vmodels[vmodel.$id]
\r
4686 injectDisposeQueue(data, widgetList)
\r
4687 if (window.chrome) {
\r
4688 elem.addEventListener("DOMNodeRemovedFromDocument", function() {
\r
4689 setTimeout(rejectDisposeQueue)
\r
4693 avalon.scan(elem, vmodels)
\r
4695 } else if (vmodels.length) { //如果该组件还没有加载,那么保存当前的vmodels
\r
4696 elem.vmodels = vmodels
\r
4699 var widgetList = []
\r
4700 //不存在 bindingExecutors.widget
\r
4701 /*********************************************************************
\r
4703 **********************************************************************/
\r
4704 var rscripts = /<script[^>]*>([\S\s]*?)<\/script\s*>/gim
\r
4705 var ron = /\s+(on[^=\s]+)(?:=("[^"]*"|'[^']*'|[^\s>]+))?/g
\r
4706 var ropen = /<\w+\b(?:(["'])[^"]*?(\1)|[^>])*>/ig
\r
4708 a: /\b(href)\=("javascript[^"]*"|'javascript[^']*')/ig,
\r
4709 img: /\b(src)\=("javascript[^"]*"|'javascript[^']*')/ig,
\r
4710 form: /\b(action)\=("javascript[^"]*"|'javascript[^']*')/ig
\r
4712 var rsurrogate = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g
\r
4713 var rnoalphanumeric = /([^\#-~| |!])/g;
\r
4715 function numberFormat(number, decimals, point, thousands) {
\r
4716 //form http://phpjs.org/functions/number_format/
\r
4717 //number 必需,要格式化的数字
\r
4718 //decimals 可选,规定多少个小数位。
\r
4719 //point 可选,规定用作小数点的字符串(默认为 . )。
\r
4720 //thousands 可选,规定用作千位分隔符的字符串(默认为 , ),如果设置了该参数,那么所有其他参数都是必需的。
\r
4721 number = (number + '')
\r
4722 .replace(/[^0-9+\-Ee.]/g, '')
\r
4723 var n = !isFinite(+number) ? 0 : +number,
\r
4724 prec = !isFinite(+decimals) ? 3 : Math.abs(decimals),
\r
4725 sep = thousands || ",",
\r
4726 dec = point || ".",
\r
4728 toFixedFix = function(n, prec) {
\r
4729 var k = Math.pow(10, prec)
\r
4730 return '' + (Math.round(n * k) / k)
\r
4733 // Fix for IE parseFloat(0.55).toFixed(0) = 0;
\r
4734 s = (prec ? toFixedFix(n, prec) : '' + Math.round(n))
\r
4736 if (s[0].length > 3) {
\r
4737 s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, sep)
\r
4742 s[1] += new Array(prec - s[1].length + 1)
\r
4745 return s.join(dec)
\r
4749 var filters = avalon.filters = {
\r
4750 uppercase: function(str) {
\r
4751 return str.toUpperCase()
\r
4753 lowercase: function(str) {
\r
4754 return str.toLowerCase()
\r
4756 truncate: function(str, length, truncation) {
\r
4757 //length,新字符串长度,truncation,新字符串的结尾的字段,返回新字符串
\r
4758 length = length || 30
\r
4759 truncation = typeof truncation === "string" ? truncation : "..."
\r
4760 return str.length > length ? str.slice(0, length - truncation.length) + truncation : String(str)
\r
4762 $filter: function(val) {
\r
4763 for (var i = 1, n = arguments.length; i < n; i++) {
\r
4764 var array = arguments[i]
\r
4765 var fn = avalon.filters[array.shift()]
\r
4766 if (typeof fn === "function") {
\r
4767 var arr = [val].concat(array)
\r
4768 val = fn.apply(null, arr)
\r
4773 camelize: camelize,
\r
4774 //https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
\r
4775 // <a href="javasc
ript:alert('XSS')">chrome</a>
\r
4776 // <a href="data:text/html;base64, PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KDEpPg==">chrome</a>
\r
4777 // <a href="jav ascript:alert('XSS');">IE67chrome</a>
\r
4778 // <a href="jav	ascript:alert('XSS');">IE67chrome</a>
\r
4779 // <a href="jav
ascript:alert('XSS');">IE67chrome</a>
\r
4780 sanitize: function(str) {
\r
4781 return str.replace(rscripts, "").replace(ropen, function(a, b) {
\r
4782 var match = a.toLowerCase().match(/<(\w+)\s/)
\r
4783 if (match) { //处理a标签的href属性,img标签的src属性,form标签的action属性
\r
4784 var reg = rsanitize[match[1]]
\r
4786 a = a.replace(reg, function(s, name, value) {
\r
4787 var quote = value.charAt(0)
\r
4788 return name + "=" + quote + "javascript:void(0)" + quote// jshint ignore:line
\r
4792 return a.replace(ron, " ").replace(/\s+/g, " ") //移除onXXX事件
\r
4795 escape: function(str) {
\r
4796 //将字符串经过 str 转义得到适合在页面中显示的内容, 例如替换 < 为 <
\r
4797 return String(str).
\r
4798 replace(/&/g, '&').
\r
4799 replace(rsurrogate, function(value) {
\r
4800 var hi = value.charCodeAt(0)
\r
4801 var low = value.charCodeAt(1)
\r
4802 return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';'
\r
4804 replace(rnoalphanumeric, function(value) {
\r
4805 return '&#' + value.charCodeAt(0) + ';'
\r
4807 replace(/</g, '<').
\r
4808 replace(/>/g, '>')
\r
4810 currency: function(amount, symbol, fractionSize) {
\r
4811 return (symbol || "\uFFE5") + numberFormat(amount, isFinite(fractionSize) ? fractionSize : 2)
\r
4813 number: numberFormat
\r
4816 'yyyy': 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010)
\r
4817 'yy': 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10)
\r
4818 'y': 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199)
\r
4819 'MMMM': Month in year (January-December)
\r
4820 'MMM': Month in year (Jan-Dec)
\r
4821 'MM': Month in year, padded (01-12)
\r
4822 'M': Month in year (1-12)
\r
4823 'dd': Day in month, padded (01-31)
\r
4824 'd': Day in month (1-31)
\r
4825 'EEEE': Day in Week,(Sunday-Saturday)
\r
4826 'EEE': Day in Week, (Sun-Sat)
\r
4827 'HH': Hour in day, padded (00-23)
\r
4828 'H': Hour in day (0-23)
\r
4829 'hh': Hour in am/pm, padded (01-12)
\r
4830 'h': Hour in am/pm, (1-12)
\r
4831 'mm': Minute in hour, padded (00-59)
\r
4832 'm': Minute in hour (0-59)
\r
4833 'ss': Second in minute, padded (00-59)
\r
4834 's': Second in minute (0-59)
\r
4836 'Z': 4 digit (+sign) representation of the timezone offset (-1200-+1200)
\r
4837 format string can also be one of the following predefined localizable formats:
\r
4839 'medium': equivalent to 'MMM d, y h:mm:ss a' for en_US locale (e.g. Sep 3, 2010 12:05:08 pm)
\r
4840 'short': equivalent to 'M/d/yy h:mm a' for en_US locale (e.g. 9/3/10 12:05 pm)
\r
4841 'fullDate': equivalent to 'EEEE, MMMM d,y' for en_US locale (e.g. Friday, September 3, 2010)
\r
4842 'longDate': equivalent to 'MMMM d, y' for en_US locale (e.g. September 3, 2010
\r
4843 'mediumDate': equivalent to 'MMM d, y' for en_US locale (e.g. Sep 3, 2010)
\r
4844 'shortDate': equivalent to 'M/d/yy' for en_US locale (e.g. 9/3/10)
\r
4845 'mediumTime': equivalent to 'h:mm:ss a' for en_US locale (e.g. 12:05:08 pm)
\r
4846 'shortTime': equivalent to 'h:mm a' for en_US locale (e.g. 12:05 pm)
\r
4848 new function() {// jshint ignore:line
\r
4849 function toInt(str) {
\r
4850 return parseInt(str, 10) || 0
\r
4853 function padNumber(num, digits, trim) {
\r
4860 while (num.length < digits)
\r
4863 num = num.substr(num.length - digits)
\r
4867 function dateGetter(name, size, offset, trim) {
\r
4868 return function(date) {
\r
4869 var value = date["get" + name]()
\r
4870 if (offset > 0 || value > -offset)
\r
4872 if (value === 0 && offset === -12) {
\r
4875 return padNumber(value, size, trim)
\r
4879 function dateStrGetter(name, shortForm) {
\r
4880 return function(date, formats) {
\r
4881 var value = date["get" + name]()
\r
4882 var get = (shortForm ? ("SHORT" + name) : name).toUpperCase()
\r
4883 return formats[get][value]
\r
4887 function timeZoneGetter(date) {
\r
4888 var zone = -1 * date.getTimezoneOffset()
\r
4889 var paddedZone = (zone >= 0) ? "+" : ""
\r
4890 paddedZone += padNumber(Math[zone > 0 ? "floor" : "ceil"](zone / 60), 2) + padNumber(Math.abs(zone % 60), 2)
\r
4895 function ampmGetter(date, formats) {
\r
4896 return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]
\r
4898 var DATE_FORMATS = {
\r
4899 yyyy: dateGetter("FullYear", 4),
\r
4900 yy: dateGetter("FullYear", 2, 0, true),
\r
4901 y: dateGetter("FullYear", 1),
\r
4902 MMMM: dateStrGetter("Month"),
\r
4903 MMM: dateStrGetter("Month", true),
\r
4904 MM: dateGetter("Month", 2, 1),
\r
4905 M: dateGetter("Month", 1, 1),
\r
4906 dd: dateGetter("Date", 2),
\r
4907 d: dateGetter("Date", 1),
\r
4908 HH: dateGetter("Hours", 2),
\r
4909 H: dateGetter("Hours", 1),
\r
4910 hh: dateGetter("Hours", 2, -12),
\r
4911 h: dateGetter("Hours", 1, -12),
\r
4912 mm: dateGetter("Minutes", 2),
\r
4913 m: dateGetter("Minutes", 1),
\r
4914 ss: dateGetter("Seconds", 2),
\r
4915 s: dateGetter("Seconds", 1),
\r
4916 sss: dateGetter("Milliseconds", 3),
\r
4917 EEEE: dateStrGetter("Day"),
\r
4918 EEE: dateStrGetter("Day", true),
\r
4922 var rdateFormat = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/
\r
4923 var raspnetjson = /^\/Date\((\d+)\)\/$/
\r
4924 filters.date = function(date, format) {
\r
4925 var locate = filters.date.locate,
\r
4929 format = format || "mediumDate"
\r
4930 format = locate[format] || format
\r
4931 if (typeof date === "string") {
\r
4932 if (/^\d+$/.test(date)) {
\r
4933 date = toInt(date)
\r
4934 } else if (raspnetjson.test(date)) {
\r
4937 var trimDate = date.trim()
\r
4938 var dateArray = [0, 0, 0, 0, 0, 0, 0]
\r
4939 var oDate = new Date(0)
\r
4941 trimDate = trimDate.replace(/^(\d+)\D(\d+)\D(\d+)/, function(_, a, b, c) {
\r
4942 var array = c.length === 4 ? [c, a, b] : [a, b, c]
\r
4943 dateArray[0] = toInt(array[0]) //年
\r
4944 dateArray[1] = toInt(array[1]) - 1 //月
\r
4945 dateArray[2] = toInt(array[2]) //日
\r
4948 var dateSetter = oDate.setFullYear
\r
4949 var timeSetter = oDate.setHours
\r
4950 trimDate = trimDate.replace(/[T\s](\d+):(\d+):?(\d+)?\.?(\d)?/, function(_, a, b, c, d) {
\r
4951 dateArray[3] = toInt(a) //小时
\r
4952 dateArray[4] = toInt(b) //分钟
\r
4953 dateArray[5] = toInt(c) //秒
\r
4955 dateArray[6] = Math.round(parseFloat("0." + d) * 1000)
\r
4961 trimDate = trimDate.replace(/Z|([+-])(\d\d):?(\d\d)/, function(z, symbol, c, d) {
\r
4962 dateSetter = oDate.setUTCFullYear
\r
4963 timeSetter = oDate.setUTCHours
\r
4965 tzHour = toInt(symbol + c)
\r
4966 tzMin = toInt(symbol + d)
\r
4971 dateArray[3] -= tzHour
\r
4972 dateArray[4] -= tzMin
\r
4973 dateSetter.apply(oDate, dateArray.slice(0, 3))
\r
4974 timeSetter.apply(oDate, dateArray.slice(3))
\r
4978 if (typeof date === "number") {
\r
4979 date = new Date(date)
\r
4981 if (avalon.type(date) !== "date") {
\r
4985 match = rdateFormat.exec(format)
\r
4987 parts = parts.concat(match.slice(1))
\r
4988 format = parts.pop()
\r
4990 parts.push(format)
\r
4994 parts.forEach(function(value) {
\r
4995 fn = DATE_FORMATS[value]
\r
4996 text += fn ? fn(date, locate) : value.replace(/(^'|'$)/g, "").replace(/''/g, "'")
\r
5037 fullDate: "y年M月d日EEEE",
\r
5038 longDate: "y年M月d日",
\r
5039 medium: "yyyy-M-d H:mm:ss",
\r
5040 mediumDate: "yyyy-M-d",
\r
5041 mediumTime: "H:mm:ss",
\r
5042 "short": "yy-M-d ah:mm",
\r
5043 shortDate: "yy-M-d",
\r
5044 shortTime: "ah:mm"
\r
5046 locate.SHORTMONTH = locate.MONTH
\r
5047 filters.date.locate = locate
\r
5048 }// jshint ignore:line
\r
5049 /*********************************************************************
\r
5051 **********************************************************************/
\r
5052 //https://www.devbridge.com/articles/understanding-amd-requirejs/
\r
5053 //http://maxogden.com/nested-dependencies.html
\r
5054 var modules = avalon.modules = {
\r
5064 //Object(modules[id]).state拥有如下值
\r
5067 // 2(loading) 已经被执行但还没有执行完成,在这个阶段define方法会被执行
\r
5068 // 3(loaded) 执行完毕,通过onload/onreadystatechange回调判定,在这个阶段checkDeps方法会执行
\r
5069 // 4(execute) 其依赖也执行完毕, 值放到exports对象上,在这个阶段fireFactory方法会执行
\r
5070 modules.exports = modules.avalon
\r
5072 new function () {// jshint ignore:line
\r
5073 var loadings = [] //正在加载中的模块列表
\r
5074 var factorys = [] //放置define方法的factory函数
\r
5075 var rjsext = /\.js$/i
\r
5076 function makeRequest(name, config) {
\r
5079 name = name.replace(/^(\w+)\!/, function (a, b) {
\r
5083 if (res === "ready") {
\r
5084 log("debug: ready!已经被废弃,请使用domReady!")
\r
5087 //2. 去掉querystring, hash
\r
5089 name = name.replace(rquery, function (a) {
\r
5094 var suffix = "." + res
\r
5095 var ext = /js|css/.test(suffix) ? suffix : ""
\r
5096 name = name.replace(/\.[a-z0-9]+$/g, function (a) {
\r
5097 if (a === suffix) {
\r
5104 var req = avalon.mix({
\r
5115 function fireRequest(req) {
\r
5116 var name = req.name
\r
5118 //1. 如果该模块已经发出请求,直接返回
\r
5119 var module = modules[name]
\r
5120 var urlNoQuery = name && req.urlNoQuery
\r
5121 if (module && module.state >= 1) {
\r
5124 module = modules[urlNoQuery]
\r
5125 if (module && module.state >= 3) {
\r
5126 innerRequire(module.deps || [], module.factory, urlNoQuery)
\r
5129 if (name && !module) {
\r
5130 module = modules[urlNoQuery] = {
\r
5134 var wrap = function (obj) {
\r
5135 resources[res] = obj
\r
5136 obj.load(name, req, function (a) {
\r
5137 if (arguments.length && a !== void 0) {
\r
5138 module.exports = a
\r
5145 if (!resources[res]) {
\r
5146 innerRequire([res], wrap)
\r
5148 wrap(resources[res])
\r
5151 return name ? urlNoQuery : res + "!"
\r
5155 var requireQueue = []
\r
5156 var isUserFirstRequire = false
\r
5157 innerRequire = avalon.require = function (array, factory, parentUrl, defineConfig) {
\r
5158 if (!isUserFirstRequire) {
\r
5159 requireQueue.push(avalon.slice(arguments))
\r
5160 if (arguments.length <= 2) {
\r
5161 isUserFirstRequire = true
\r
5162 var queue = requireQueue.splice(0, requireQueue.length), args
\r
5163 while (args = queue.shift()) {
\r
5164 innerRequire.apply(null, args)
\r
5170 if (!Array.isArray(array)) {
\r
5171 avalon.error("require方法的第一个参数应为数组 " + array)
\r
5173 var deps = [] // 放置所有依赖项的完整路径
\r
5175 var id = parentUrl || "callback" + setTimeout("1")// jshint ignore:line
\r
5176 defineConfig = defineConfig || {}
\r
5177 defineConfig.baseUrl = kernel.baseUrl
\r
5178 var isBuilt = !!defineConfig.built
\r
5180 defineConfig.parentUrl = parentUrl.substr(0, parentUrl.lastIndexOf("/"))
\r
5181 defineConfig.mapUrl = parentUrl.replace(rjsext, "")
\r
5184 var req = makeRequest(defineConfig.defineName, defineConfig)
\r
5185 id = req.urlNoQuery
\r
5187 array.forEach(function (name) {
\r
5188 var req = makeRequest(name, defineConfig)
\r
5189 var url = fireRequest(req) //加载资源,并返回该资源的完整地址
\r
5193 uniq[url] = "司徒正美" //去重
\r
5199 var module = modules[id]
\r
5200 if (!module || module.state !== 4) {
\r
5203 deps: isBuilt ? array.concat() : deps,
\r
5204 factory: factory || noop,
\r
5209 //如果此模块是定义在另一个JS文件中, 那必须等该文件加载完毕, 才能放到检测列队中
\r
5216 innerRequire.define = function (name, deps, factory) { //模块名,依赖列表,模块本身
\r
5217 if (typeof name !== "string") {
\r
5220 name = "anonymous"
\r
5222 if (!Array.isArray(deps)) {
\r
5227 built: !isUserFirstRequire, //用r.js打包后,所有define会放到requirejs之前
\r
5230 var args = [deps, factory, config]
\r
5231 factory.require = function (url) {
\r
5232 args.splice(2, 0, url)
\r
5233 if (modules[url]) {
\r
5234 modules[url].state = 3 //loaded
\r
5235 var isCycle = false
\r
5237 isCycle = checkCycle(modules[url].deps, url)
\r
5241 avalon.error(url + "模块与之前的模块存在循环依赖,请不要直接用script标签引入" + url + "模块")
\r
5244 delete factory.require //释放内存
\r
5245 innerRequire.apply(null, args) //0,1,2 --> 1,2,0
\r
5247 //根据标准,所有遵循W3C标准的浏览器,script标签会按标签的出现顺序执行。
\r
5248 //老的浏览器中,加载也是按顺序的:一个文件下载完成后,才开始下载下一个文件。
\r
5249 //较新的浏览器中(IE8+ 、FireFox3.5+ 、Chrome4+ 、Safari4+),为了减小请求时间以优化体验,
\r
5250 //下载可以是并行的,但是执行顺序还是按照标签出现的顺序。
\r
5251 //但如果script标签是动态插入的, 就未必按照先请求先执行的原则了,目测只有firefox遵守
\r
5252 //唯一比较一致的是,IE10+及其他标准浏览器,一旦开始解析脚本, 就会一直堵在那里,直接脚本解析完毕
\r
5253 //亦即,先进入loading阶段的script标签(模块)必然会先进入loaded阶段
\r
5254 var url = config.built ? "unknown" : getCurrentScript()
\r
5256 var module = modules[url]
\r
5260 factory.require(url)
\r
5261 } else {//合并前后的safari,合并后的IE6-9走此分支
\r
5262 factorys.push(factory)
\r
5265 //核心API之三 require.config(settings)
\r
5266 innerRequire.config = kernel
\r
5267 //核心API之四 define.amd 标识其符合AMD规范
\r
5268 innerRequire.define.amd = modules
\r
5270 //==========================对用户配置项进行再加工==========================
\r
5271 var allpaths = kernel["orig.paths"] = {}
\r
5272 var allmaps = kernel["orig.map"] = {}
\r
5273 var allpackages = kernel["packages"] = []
\r
5274 var allargs = kernel["orig.args"] = {}
\r
5275 avalon.mix(plugins, {
\r
5276 paths: function (hash) {
\r
5277 avalon.mix(allpaths, hash)
\r
5278 kernel.paths = makeIndexArray(allpaths)
\r
5280 map: function (hash) {
\r
5281 avalon.mix(allmaps, hash)
\r
5282 var list = makeIndexArray(allmaps, 1, 1)
\r
5283 avalon.each(list, function (_, item) {
\r
5284 item.val = makeIndexArray(item.val)
\r
5288 packages: function (array) {
\r
5289 array = array.concat(allpackages)
\r
5292 for (var i = 0, pkg; pkg = array[i++]; ) {
\r
5293 pkg = typeof pkg === "string" ? {name: pkg} : pkg
\r
5294 var name = pkg.name
\r
5295 if (!uniq[name]) {
\r
5296 var url = joinPath(pkg.location || name, pkg.main || "main")
\r
5297 url = url.replace(rjsext, "")
\r
5299 uniq[name] = pkg.location = url
\r
5300 pkg.reg = makeMatcher(name)
\r
5303 kernel.packages = ret.sort()
\r
5305 urlArgs: function (hash) {
\r
5306 if (typeof hash === "string") {
\r
5307 hash = {"*": hash}
\r
5309 avalon.mix(allargs, hash)
\r
5310 kernel.urlArgs = makeIndexArray(allargs, 1)
\r
5312 baseUrl: function (url) {
\r
5313 if (!isAbsUrl(url)) {
\r
5314 var baseElement = head.getElementsByTagName("base")[0]
\r
5315 if (baseElement) {
\r
5316 head.removeChild(baseElement)
\r
5318 var node = DOC.createElement("a")
\r
5320 url = getFullUrl(node, "href")
\r
5321 if (baseElement) {
\r
5322 head.insertBefore(baseElement, head.firstChild)
\r
5325 if (url.length > 3)
\r
5326 kernel.baseUrl = url
\r
5328 shim: function (obj) {
\r
5329 for (var i in obj) {
\r
5330 var value = obj[i]
\r
5331 if (Array.isArray(value)) {
\r
5332 value = obj[i] = {
\r
5336 if (!value.exportsFn && (value.exports || value.init)) {
\r
5337 value.exportsFn = makeExports(value)
\r
5346 //==============================内部方法=================================
\r
5347 function checkCycle(deps, nick) {
\r
5349 for (var i = 0, id; id = deps[i++]; ) {
\r
5350 if (modules[id].state !== 4 &&
\r
5351 (id === nick || checkCycle(modules[id].deps, nick))) {
\r
5357 function checkFail(node, onError, fuckIE) {
\r
5358 var id = trimQuery(node.src) //检测是否死链
\r
5359 node.onload = node.onreadystatechange = node.onerror = null
\r
5360 if (onError || (fuckIE && modules[id] && !modules[id].state)) {
\r
5361 setTimeout(function () {
\r
5362 head.removeChild(node)
\r
5363 node = null // 处理旧式IE下的循环引用问题
\r
5365 log("debug: 加载 " + id + " 失败" + onError + " " + (!modules[id].state))
\r
5371 function checkDeps() {
\r
5372 //检测此JS模块的依赖是否都已安装完毕,是则安装自身
\r
5373 loop: for (var i = loadings.length, id; id = loadings[--i]; ) {
\r
5374 var obj = modules[id],
\r
5378 for (var j = 0, key; key = deps[j]; j++) {
\r
5379 if (Object(modules[key]).state !== 4) {
\r
5383 //如果deps是空对象或者其依赖的模块的状态都是2
\r
5384 if (obj.state !== 4) {
\r
5385 loadings.splice(i, 1) //必须先移除再安装,防止在IE下DOM树建完后手动刷新页面,会多次执行它
\r
5386 fireFactory(obj.id, obj.deps, obj.factory)
\r
5387 checkDeps() //如果成功,则再执行一次,以防有些模块就差本模块没有安装好
\r
5392 var rreadyState = /complete|loaded/
\r
5393 function loadJS(url, id, callback) {
\r
5394 //通过script节点加载目标模块
\r
5395 var node = DOC.createElement("script")
\r
5396 node.className = subscribers //让getCurrentScript只处理类名为subscribers的script节点
\r
5397 var supportLoad = "onload" in node
\r
5398 var onEvent = supportLoad ? "onload" : "onreadystatechange"
\r
5399 function onload() {
\r
5400 var factory = factorys.pop()
\r
5401 factory && factory.require(id)
\r
5405 if (checkFail(node, false, !supportLoad)) {
\r
5406 log("debug: 已成功加载 " + url)
\r
5407 id && loadings.push(id)
\r
5411 var index = 0, loadID
\r
5412 node[onEvent] = supportLoad ? onload : function () {
\r
5413 if (rreadyState.test(node.readyState)) {
\r
5415 if (index === 1) {
\r
5416 loadID = setTimeout(onload, 500)
\r
5418 clearTimeout(loadID)
\r
5423 node.onerror = function () {
\r
5424 checkFail(node, true)
\r
5427 head.insertBefore(node, head.firstChild) //chrome下第二个参数不能为null
\r
5428 node.src = url //插入到head的第一个节点前,防止IE6下head标签没闭合前使用appendChild抛错
\r
5429 log("debug: 正准备加载 " + url) //更重要的是IE6下可以收窄getCurrentScript的寻找范围
\r
5432 var resources = innerRequire.plugins = {
\r
5433 //三大常用资源插件 js!, css!, text!, ready!
\r
5438 load: function (name, req, onLoad) {
\r
5440 var id = req.urlNoQuery
\r
5441 var shim = kernel.shim[name.replace(rjsext, "")]
\r
5442 if (shim) { //shim机制
\r
5443 innerRequire(shim.deps || [], function () {
\r
5444 var args = avalon.slice(arguments)
\r
5445 loadJS(url, id, function () {
\r
5446 onLoad(shim.exportsFn ? shim.exportsFn.apply(0, args) : void 0)
\r
5455 load: function (name, req, onLoad) {
\r
5457 var node = DOC.createElement("link")
\r
5458 node.rel = "stylesheet"
\r
5460 head.insertBefore(node, head.firstChild)
\r
5461 log("debug: 已成功加载 " + url)
\r
5466 load: function (name, req, onLoad) {
\r
5468 var xhr = getXHR()
\r
5469 xhr.onreadystatechange = function () {
\r
5470 if (xhr.readyState === 4) {
\r
5471 var status = xhr.status;
\r
5472 if (status > 399 && status < 600) {
\r
5473 avalon.error(url + " 对应资源不存在或没有开启 CORS")
\r
5475 log("debug: 已成功加载 " + url)
\r
5476 onLoad(xhr.responseText)
\r
5480 var time = "_=" + (new Date() - 0)
\r
5481 var _url = url.indexOf("?") === -1 ? url + "?" + time : url + "&" + time
\r
5482 xhr.open("GET", _url, true)
\r
5483 if ("withCredentials" in xhr) {//这是处理跨域
\r
5484 xhr.withCredentials = true
\r
5486 xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest")//告诉后端这是AJAX请求
\r
5488 log("debug: 正准备加载 " + url)
\r
5492 innerRequire.checkDeps = checkDeps
\r
5494 var rquery = /(\?[^#]*)$/
\r
5495 function trimQuery(url) {
\r
5496 return (url || "").replace(rquery, "")
\r
5499 function isAbsUrl(path) {
\r
5500 //http://stackoverflow.com/questions/10687099/how-to-test-if-a-url-string-is-absolute-or-relative
\r
5501 return /^(?:[a-z]+:)?\/\//i.test(String(path))
\r
5504 function getFullUrl(node, src) {
\r
5505 return"1"[0] ? node[src] : node.getAttribute(src, 4)
\r
5508 function getCurrentScript() {
\r
5509 // inspireb by https://github.com/samyk/jiagra/blob/master/jiagra.js
\r
5512 a.b.c() //强制报错,以便捕获e.stack
\r
5513 } catch (e) { //safari5的sourceURL,firefox的fileName,它们的效果与e.stack不一样
\r
5515 if (!stack && window.opera) {
\r
5516 //opera 9没有e.stack,但有e.Backtrace,但不能直接取得,需要对e对象转字符串进行抽取
\r
5517 stack = (String(e).match(/of linked script \S+/g) || []).join(" ")
\r
5521 /**e.stack最后一行在所有支持的浏览器大致如下:
\r
5523 * at http://113.93.50.63/data.js:4:1
\r
5525 *@http://113.93.50.63/query.js:4
\r
5526 *opera12:http://www.oldapps.com/opera.php?system=Windows_XP
\r
5527 *@http://113.93.50.63/data.js:4
\r
5529 * at Global code (http://113.93.50.63/data.js:4:1)
\r
5530 * //firefox4+ 可以用document.currentScript
\r
5532 stack = stack.split(/[@ ]/g).pop() //取得最后一行,最后一个空格或@之后的部分
\r
5533 stack = stack[0] === "(" ? stack.slice(1, -1) : stack.replace(/\s/, "") //去掉换行符
\r
5534 return trimQuery(stack.replace(/(:\d+)?:\d+$/i, "")) //去掉行号与或许存在的出错字符起始位置
\r
5536 var nodes = head.getElementsByTagName("script") //只在head标签中寻找
\r
5537 for (var i = nodes.length, node; node = nodes[--i]; ) {
\r
5538 if (node.className === subscribers && node.readyState === "interactive") {
\r
5539 var url = getFullUrl(node, "src")
\r
5540 return node.className = trimQuery(url)
\r
5545 var rcallback = /^callback\d+$/
\r
5546 function fireFactory(id, deps, factory) {
\r
5547 var module = Object(modules[id])
\r
5549 for (var i = 0, array = [], d; d = deps[i++]; ) {
\r
5550 if (d === "exports") {
\r
5551 var obj = module.exports || (module.exports = {})
\r
5554 array.push(modules[d].exports)
\r
5558 var ret = factory.apply(window, array)
\r
5560 log("执行[" + id + "]模块的factory抛错: ", e)
\r
5562 if (ret !== void 0) {
\r
5563 module.exports = ret
\r
5565 if (rcallback.test(id)) {
\r
5566 delete modules[id]
\r
5568 delete module.factory
\r
5571 function toUrl(id) {
\r
5572 if (id.indexOf(this.res + "!") === 0) {
\r
5573 id = id.slice(this.res.length + 1) //处理define("css!style",[], function(){})的情况
\r
5578 var baseUrl = this.baseUrl
\r
5579 var rootUrl = this.parentUrl || baseUrl
\r
5580 eachIndexArray(id, kernel.paths, function (value, key) {
\r
5581 url = url.replace(key, value)
\r
5584 //2. 是否命中packages配置项
\r
5586 eachIndexArray(id, kernel.packages, function (value, key, item) {
\r
5587 url = url.replace(item.name, item.location)
\r
5591 if (this.mapUrl) {
\r
5592 eachIndexArray(this.mapUrl, kernel.map, function (array) {
\r
5593 eachIndexArray(url, array, function (mdValue, mdKey) {
\r
5594 url = url.replace(mdKey, mdValue)
\r
5599 var ext = this.ext
\r
5600 if (ext && usePath && url.slice(-ext.length) === ext) {
\r
5601 url = url.slice(0, -ext.length)
\r
5604 if (!isAbsUrl(url)) {
\r
5605 rootUrl = this.built || /^\w/.test(url) ? baseUrl : rootUrl
\r
5606 url = joinPath(rootUrl, url)
\r
5609 var urlNoQuery = url + ext
\r
5610 url = urlNoQuery + this.query
\r
5612 eachIndexArray(id, kernel.urlArgs, function (value) {
\r
5613 url += (url.indexOf("?") === -1 ? "?" : "&") + value;
\r
5616 return this.urlNoQuery = urlNoQuery
\r
5619 function makeIndexArray(hash, useStar, part) {
\r
5620 //创建一个经过特殊算法排好序的数组
\r
5621 var index = hash2array(hash, useStar, part)
\r
5622 index.sort(descSorterByName)
\r
5626 function makeMatcher(prefix) {
\r
5627 return new RegExp('^' + prefix + '(/|$)')
\r
5630 function makeExports(value) {
\r
5631 return function () {
\r
5634 ret = value.init.apply(window, arguments)
\r
5636 return ret || (value.exports && getGlobal(value.exports))
\r
5641 function hash2array(hash, useStar, part) {
\r
5643 for (var key in hash) {
\r
5644 if (ohasOwn.call(hash, key)) {
\r
5650 item.reg = key === "*" && useStar ? /^/ : makeMatcher(key)
\r
5651 if (part && key !== "*") {
\r
5652 item.reg = new RegExp('\/' + key.replace(/^\//, "") + '(/|$)')
\r
5659 function eachIndexArray(moduleID, array, matcher) {
\r
5660 array = array || []
\r
5661 for (var i = 0, el; el = array[i++]; ) {
\r
5662 if (el.reg.test(moduleID)) {
\r
5663 matcher(el.val, el.name, el)
\r
5668 // 根据元素的name项进行数组字符数逆序的排序函数
\r
5669 function descSorterByName(a, b) {
\r
5672 if (bbb === "*") {
\r
5675 if (aaa === "*") {
\r
5678 return bbb.length - aaa.length
\r
5681 var rdeuce = /\/\w+\/\.\./
\r
5682 function joinPath(a, b) {
\r
5683 if (a.charAt(a.length - 1) !== "/") {
\r
5686 if (b.slice(0, 2) === "./") { //相对于兄弟路径
\r
5687 return a + b.slice(2)
\r
5689 if (b.slice(0, 2) === "..") { //相对于父路径
\r
5691 while (rdeuce.test(a)) {
\r
5692 a = a.replace(rdeuce, "")
\r
5696 if (b.slice(0, 1) === "/") {
\r
5697 return a + b.slice(1)
\r
5702 function getGlobal(value) {
\r
5707 value.split(".").forEach(function (part) {
\r
5713 var mainNode = DOC.scripts[DOC.scripts.length - 1]
\r
5714 var dataMain = mainNode.getAttribute("data-main")
\r
5716 plugins.baseUrl(dataMain)
\r
5717 var href = kernel.baseUrl
\r
5718 kernel.baseUrl = href.slice(0, href.lastIndexOf("/") + 1)
\r
5719 loadJS(href.replace(rjsext, "") + ".js")
\r
5721 var loaderUrl = trimQuery(getFullUrl(mainNode, "src"))
\r
5722 kernel.baseUrl = loaderUrl.slice(0, loaderUrl.lastIndexOf("/") + 1)
\r
5724 }// jshint ignore:line
\r
5726 /*********************************************************************
\r
5728 **********************************************************************/
\r
5730 var readyList = [], isReady
\r
5731 var fireReady = function(fn) {
\r
5733 if (innerRequire) {
\r
5734 modules["domReady!"].state = 4
\r
5735 innerRequire.checkDeps()
\r
5737 while(fn = readyList.shift()){
\r
5742 function doScrollCheck() {
\r
5743 try { //IE下通过doScrollCheck检测DOM树是否建完
\r
5744 root.doScroll("left")
\r
5747 setTimeout(doScrollCheck)
\r
5751 if (DOC.readyState === "complete") {
\r
5752 setTimeout(fireReady) //如果在domReady之外加载
\r
5754 DOC.addEventListener("DOMContentLoaded", fireReady)
\r
5756 DOC.attachEvent("onreadystatechange", function() {
\r
5757 if (DOC.readyState === "complete") {
\r
5762 var isTop = window.frameElement === null
\r
5765 if (root.doScroll && isTop && window.external) {//fix IE iframe BUG
\r
5769 avalon.bind(window, "load", fireReady)
\r
5771 avalon.ready = function(fn) {
\r
5773 readyList.push(fn)
\r
5783 avalon.ready(function() {
\r
5784 avalon.scan(DOC.body)
\r
5787 // Register as a named AMD module, since avalon can be concatenated with other
\r
5788 // files that may use define, but not via a proper concatenation script that
\r
5789 // understands anonymous AMD modules. A named AMD is safest and most robust
\r
5790 // way to register. Lowercase avalon is used because AMD module names are
\r
5791 // derived from file names, and Avalon is normally delivered in a lowercase
\r
5792 // file name. Do this after creating the global so that if an AMD module wants
\r
5793 // to call noConflict to hide this version of avalon, it will work.
\r
5795 // Note that for maximum portability, libraries that are not avalon should
\r
5796 // declare themselves as anonymous modules, and avoid setting a global if an
\r
5797 // AMD loader is present. avalon is a special case. For more information, see
\r
5798 // https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon
\r
5799 if (typeof define === "function" && define.amd) {
\r
5800 define("avalon", [], function() {
\r
5804 // Map over avalon in case of overwrite
\r
5805 var _avalon = window.avalon
\r
5806 avalon.noConflict = function(deep) {
\r
5807 if (deep && window.avalon === avalon) {
\r
5808 window.avalon = _avalon
\r
5812 // Expose avalon identifiers, even in AMD
\r
5813 // and CommonJS for browser emulators
\r
5814 if (noGlobal === void 0) {
\r
5815 window.avalon = avalon
\r