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.modern.js 1.43 built in 2015.5.21
\r
9 support IE10+ 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 = Date.now()
\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.head //HEAD元素
\r
40 head.insertAdjacentHTML("afterBegin", '<avalon ms-skip class="avalonHide"><style id="avalonStyle">.avalonHide{ display: none!important }</style></avalon>')
\r
41 var ifGroup = head.firstChild
\r
44 if (avalon.config.debug) {
\r
45 // http://stackoverflow.com/questions/8785624/how-to-safely-wrap-console-log
\r
46 console.log.apply(console, arguments)
\r
51 * Creates a new object without a prototype. This object is useful for lookup without having to
\r
52 * guard against prototypically inherited properties via hasOwnProperty.
\r
54 * Related micro-benchmarks:
\r
55 * - http://jsperf.com/object-create2
\r
56 * - http://jsperf.com/proto-map-lookup/2
\r
57 * - http://jsperf.com/for-in-vs-object-keys2
\r
59 function createMap() {
\r
60 return Object.create(null)
\r
63 var subscribers = "$" + expose
\r
64 var otherRequire = window.require
\r
65 var otherDefine = window.define
\r
67 var stopRepeatAssign = false
\r
68 var rword = /[^, ]+/g //切割字符串为一个个小块,以空格或豆号分开它们,结合replace实现字符串的forEach
\r
69 var rcomplexType = /^(?:object|array)$/
\r
70 var rsvg = /^\[object SVG\w*Element\]$/
\r
71 var rwindow = /^\[object (?:Window|DOMWindow|global)\]$/
\r
72 var oproto = Object.prototype
\r
73 var ohasOwn = oproto.hasOwnProperty
\r
74 var serialize = oproto.toString
\r
75 var ap = Array.prototype
\r
76 var aslice = ap.slice
\r
77 var Registry = {} //将函数曝光到此对象上,方便访问器收集依赖
\r
78 var W3C = window.dispatchEvent
\r
79 var root = DOC.documentElement
\r
80 var hyperspace = DOC.createDocumentFragment()
\r
81 var cinerator = DOC.createElement("div")
\r
83 "Boolean Number String Function Array Date RegExp Object Error".replace(rword, function (name) {
\r
84 class2type["[object " + name + "]"] = name.toLowerCase()
\r
92 function oneObject(array, val) {
\r
93 if (typeof array === "string") {
\r
94 array = array.match(rword) || []
\r
97 value = val !== void 0 ? val : 1
\r
98 for (var i = 0, n = array.length; i < n; i++) {
\r
99 result[array[i]] = value
\r
104 //生成UUID http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
\r
105 var generateID = function (prefix) {
\r
106 prefix = prefix || "avalon"
\r
107 return String(Math.random() + Math.random()).replace(/\d\.\d{4}/, prefix)
\r
111 if (window.VBArray) {
\r
112 var mode = document.documentMode
\r
113 return mode ? mode : window.XMLHttpRequest ? 7 : 6
\r
119 var IEVersion = IE()
\r
121 avalon = function (el) { //创建jQuery式的无new 实例化结构
\r
122 return new avalon.init(el)
\r
125 /*视浏览器情况采用最快的异步回调*/
\r
126 avalon.nextTick = new function () {// jshint ignore:line
\r
127 var tickImmediate = window.setImmediate
\r
128 var tickObserver = window.MutationObserver
\r
129 var tickPost = W3C && window.postMessage
\r
130 if (tickImmediate) {
\r
131 return tickImmediate.bind(window)
\r
136 function callback() {
\r
137 var n = queue.length
\r
138 for (var i = 0; i < n; i++) {
\r
141 queue = queue.slice(n)
\r
144 if (tickObserver) {
\r
145 var node = document.createTextNode("avalon")
\r
146 new tickObserver(callback).observe(node, {characterData: true})// jshint ignore:line
\r
147 return function (fn) {
\r
149 node.data = Math.random()
\r
154 window.addEventListener("message", function (e) {
\r
155 var source = e.source
\r
156 if ((source === window || source === null) && e.data === "process-tick") {
\r
157 e.stopPropagation()
\r
162 return function (fn) {
\r
164 window.postMessage('process-tick', '*')
\r
168 return function (fn) {
\r
171 }// jshint ignore:line
\r
172 /*********************************************************************
\r
174 **********************************************************************/
\r
175 avalon.init = function (el) {
\r
176 this[0] = this.element = el
\r
178 avalon.fn = avalon.prototype = avalon.init.prototype
\r
180 avalon.type = function (obj) { //取得目标的类型
\r
184 // 早期的webkit内核浏览器实现了已废弃的ecma262v4标准,可以将正则字面量当作函数使用,因此typeof在判定正则时会返回function
\r
185 return typeof obj === "object" || typeof obj === "function" ?
\r
186 class2type[serialize.call(obj)] || "object" :
\r
190 var isFunction = function (fn) {
\r
191 return serialize.call(fn) === "[object Function]"
\r
194 avalon.isFunction = isFunction
\r
196 avalon.isWindow = function (obj) {
\r
197 return rwindow.test(serialize.call(obj))
\r
200 /*判定是否是一个朴素的javascript对象(Object),不是DOM对象,不是BOM对象,不是自定义类的实例*/
\r
202 avalon.isPlainObject = function (obj) {
\r
203 // 简单的 typeof obj === "object"检测,会致使用isPlainObject(window)在opera下通不过
\r
204 return serialize.call(obj) === "[object Object]" && Object.getPrototypeOf(obj) === oproto
\r
207 //与jQuery.extend方法,可用于浅拷贝,深拷贝
\r
208 avalon.mix = avalon.fn.mix = function () {
\r
209 var options, name, src, copy, copyIsArray, clone,
\r
210 target = arguments[0] || {},
\r
212 length = arguments.length,
\r
215 // 如果第一个参数为布尔,判定是否深拷贝
\r
216 if (typeof target === "boolean") {
\r
218 target = arguments[1] || {}
\r
223 if (typeof target !== "object" && !isFunction(target)) {
\r
227 //如果只有一个参数,那么新成员添加于mix所在的对象上
\r
228 if (i === length) {
\r
233 for (; i < length; i++) {
\r
235 if ((options = arguments[i]) != null) {
\r
236 for (name in options) {
\r
238 copy = options[name]
\r
240 if (target === copy) {
\r
243 if (deep && copy && (avalon.isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))) {
\r
246 copyIsArray = false
\r
247 clone = src && Array.isArray(src) ? src : []
\r
250 clone = src && avalon.isPlainObject(src) ? src : {}
\r
253 target[name] = avalon.mix(deep, clone, copy)
\r
254 } else if (copy !== void 0) {
\r
255 target[name] = copy
\r
263 function _number(a, len) { //用于模拟slice, splice的效果
\r
264 a = Math.floor(a) || 0
\r
265 return a < 0 ? Math.max(len + a, 0) : Math.min(a, len);
\r
270 subscribers: subscribers,
\r
274 slice: function (nodes, start, end) {
\r
275 return aslice.call(nodes, start, end)
\r
278 /*如果不用Error对象封装一下,str在控制台下可能会乱码*/
\r
279 error: function (str, e) {
\r
280 throw new (e || Error)(str)// jshint ignore:line
\r
282 /*将一个以空格或逗号隔开的字符串或数组,转换成一个键值都为1的对象*/
\r
283 oneObject: oneObject,
\r
284 /* avalon.range(10)
\r
285 => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
\r
286 avalon.range(1, 11)
\r
287 => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
\r
288 avalon.range(0, 30, 5)
\r
289 => [0, 5, 10, 15, 20, 25]
\r
290 avalon.range(0, -10, -1)
\r
291 => [0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
\r
294 range: function (start, end, step) { // 用于生成整数数组
\r
301 length = Math.max(0, Math.ceil((end - start) / step)),
\r
302 result = new Array(length)
\r
303 while (++index < length) {
\r
304 result[index] = start
\r
311 bind: function (el, type, fn, phase) {
\r
312 var hooks = avalon.eventHooks
\r
313 var hook = hooks[type]
\r
314 if (typeof hook === "object") {
\r
317 fn = hook.deel(el, type, fn, phase)
\r
321 el.addEventListener(type, fn, !!phase)
\r
325 unbind: function (el, type, fn, phase) {
\r
326 var hooks = avalon.eventHooks
\r
327 var hook = hooks[type]
\r
328 var callback = fn || noop
\r
329 if (typeof hook === "object") {
\r
332 fn = hook.deel(el, type, fn, false)
\r
335 el.removeEventListener(type, callback, !!phase)
\r
338 css: function (node, name, value) {
\r
339 if (node instanceof avalon) {
\r
342 var prop = /[_-]/.test(name) ? camelize(name) : name, fn
\r
343 name = avalon.cssName(prop) || prop
\r
344 if (value === void 0 || typeof value === "boolean") { //获取样式
\r
345 fn = cssHooks[prop + ":get"] || cssHooks["@:get"]
\r
346 if (name === "background") {
\r
347 name = "backgroundColor"
\r
349 var val = fn(node, name)
\r
350 return value === true ? parseFloat(val) || 0 : val
\r
351 } else if (value === "") { //请除样式
\r
352 node.style[name] = ""
\r
354 if (value == null || value !== value) {
\r
357 if (isFinite(value) && !avalon.cssNumber[prop]) {
\r
360 fn = cssHooks[prop + ":set"] || cssHooks["@:set"]
\r
361 fn(node, name, value)
\r
364 /*遍历数组与对象,回调的第一个参数为索引或键名,第二个或元素或键值*/
\r
365 each: function (obj, fn) {
\r
366 if (obj) { //排除null, undefined
\r
368 if (isArrayLike(obj)) {
\r
369 for (var n = obj.length; i < n; i++) {
\r
370 if (fn(i, obj[i]) === false)
\r
375 if (obj.hasOwnProperty(i) && fn(i, obj[i]) === false) {
\r
382 //收集元素的data-{{prefix}}-*属性,并转换为对象
\r
383 getWidgetData: function (elem, prefix) {
\r
384 var raw = avalon(elem).data()
\r
386 for (var i in raw) {
\r
387 if (i.indexOf(prefix) === 0) {
\r
388 result[i.replace(prefix, "").replace(/\w/, function (a) {
\r
389 return a.toLowerCase()
\r
396 /*只有当前数组不存在此元素时只添加它*/
\r
397 ensure: function (target, item) {
\r
398 if (target.indexOf(item) === -1) {
\r
399 return target.push(item)
\r
402 /*移除数组中指定位置的元素,返回布尔表示成功与否*/
\r
403 removeAt: function (target, index) {
\r
404 return !!target.splice(index, 1).length
\r
406 /*移除数组中第一个匹配传参的那个元素,返回布尔表示成功与否*/
\r
407 remove: function (target, item) {
\r
408 var index = target.indexOf(item)
\r
410 return avalon.Array.removeAt(target, index)
\r
416 var bindingHandlers = avalon.bindingHandlers = {}
\r
417 var bindingExecutors = avalon.bindingExecutors = {}
\r
419 /*判定是否类数组,如节点集合,纯数组,arguments与拥有非负整数的length属性的纯JS对象*/
\r
420 function isArrayLike(obj) {
\r
421 if (obj && typeof obj === "object") {
\r
422 var n = obj.length,
\r
423 str = serialize.call(obj)
\r
424 if (/(Array|List|Collection|Map|Arguments)\]$/.test(str)) {
\r
426 } else if (str === "[object Object]" && n === (n >>> 0)) {
\r
427 return true //由于ecma262v5能修改对象属性的enumerable,因此不能用propertyIsEnumerable来判定了
\r
434 // https://github.com/rsms/js-lru
\r
435 var Cache = new function () {// jshint ignore:line
\r
436 function LRU(maxLength) {
\r
438 this.limit = maxLength
\r
439 this.head = this.tail = void 0
\r
443 var p = LRU.prototype
\r
445 p.put = function (key, value) {
\r
450 this._keymap[key] = entry
\r
452 this.tail.newer = entry
\r
453 entry.older = this.tail
\r
458 if (this.size === this.limit) {
\r
466 p.shift = function () {
\r
467 var entry = this.head
\r
469 this.head = this.head.newer
\r
473 this._keymap[entry.key] = void 0
\r
476 p.get = function (key) {
\r
477 var entry = this._keymap[key]
\r
478 if (entry === void 0)
\r
480 if (entry === this.tail) {
\r
483 // HEAD--------------TAIL
\r
485 // <--- add direction --
\r
488 if (entry === this.head) {
\r
489 this.head = entry.newer
\r
491 entry.newer.older = entry.older // C <-- E.
\r
494 entry.older.newer = entry.newer // C. --> E
\r
496 entry.newer = void 0 // D --x
\r
497 entry.older = this.tail // D. --> E
\r
499 this.tail.newer = entry // E. <-- D
\r
505 }// jshint ignore:line
\r
507 /*********************************************************************
\r
509 **********************************************************************/
\r
510 //safari5+是把contains方法放在Element.prototype上而不是Node.prototype
\r
511 if (!DOC.contains) {
\r
512 Node.prototype.contains = function (arg) {
\r
513 return !!(this.compareDocumentPosition(arg) & 16)
\r
516 avalon.contains = function (root, el) {
\r
518 while ((el = el.parentNode))
\r
527 if (window.SVGElement) {
\r
528 var svgns = "http://www.w3.org/2000/svg"
\r
529 var svg = DOC.createElementNS(svgns, "svg")
\r
530 svg.innerHTML = '<circle cx="50" cy="50" r="40" fill="red" />'
\r
531 if (!rsvg.test(svg.firstChild)) {// #409
\r
532 /* jshint ignore:start */
\r
533 function enumerateNode(node, targetNode) {
\r
534 if (node && node.childNodes) {
\r
535 var nodes = node.childNodes
\r
536 for (var i = 0, el; el = nodes[i++];) {
\r
538 var svg = DOC.createElementNS(svgns,
\r
539 el.tagName.toLowerCase())
\r
541 ap.forEach.call(el.attributes, function (attr) {
\r
542 svg.setAttribute(attr.name, attr.value)
\r
545 enumerateNode(el, svg)
\r
546 targetNode.appendChild(svg)
\r
552 /* jshint ignore:end */
\r
553 Object.defineProperties(SVGElement.prototype, {
\r
554 "outerHTML": {//IE9-11,firefox不支持SVG元素的innerHTML,outerHTML属性
\r
556 configurable: true,
\r
558 return new XMLSerializer().serializeToString(this)
\r
560 set: function (html) {
\r
561 var tagName = this.tagName.toLowerCase(),
\r
562 par = this.parentNode,
\r
563 frag = avalon.parseHTML(html)
\r
565 if (tagName === "svg") {
\r
566 par.insertBefore(frag, this)
\r
569 var newFrag = DOC.createDocumentFragment()
\r
570 enumerateNode(frag, newFrag)
\r
571 par.insertBefore(newFrag, this)
\r
573 par.removeChild(this)
\r
578 configurable: true,
\r
580 var s = this.outerHTML
\r
581 var ropen = new RegExp("<" + this.nodeName + '\\b(?:(["\'])[^"]*?(\\1)|[^>])*>', "i")
\r
582 var rclose = new RegExp("<\/" + this.nodeName + ">$", "i")
\r
583 return s.replace(ropen, "").replace(rclose, "")
\r
585 set: function (html) {
\r
586 if (avalon.clearHTML) {
\r
587 avalon.clearHTML(this)
\r
588 var frag = avalon.parseHTML(html)
\r
589 enumerateNode(frag, this)
\r
596 //========================= event binding ====================
\r
597 var eventHooks = avalon.eventHooks
\r
598 //针对firefox, chrome修正mouseenter, mouseleave(chrome30+)
\r
599 if (!("onmouseenter" in root)) {
\r
601 mouseenter: "mouseover",
\r
602 mouseleave: "mouseout"
\r
603 }, function (origType, fixType) {
\r
604 eventHooks[origType] = {
\r
606 deel: function (elem, _, fn) {
\r
607 return function (e) {
\r
608 var t = e.relatedTarget
\r
609 if (!t || (t !== elem && !(elem.compareDocumentPosition(t) & 16))) {
\r
612 return fn.call(elem, e)
\r
619 //针对IE9+, w3c修正animationend
\r
621 AnimationEvent: "animationend",
\r
622 WebKitAnimationEvent: "webkitAnimationEnd"
\r
623 }, function (construct, fixType) {
\r
624 if (window[construct] && !eventHooks.animationend) {
\r
625 eventHooks.animationend = {
\r
631 if (DOC.onmousewheel === void 0) {
\r
632 /* IE6-11 chrome mousewheel wheelDetla 下 -120 上 120
\r
633 firefox DOMMouseScroll detail 下3 上-3
\r
634 firefox wheel detlaY 下3 上-3
\r
635 IE9-11 wheel deltaY 下40 上-40
\r
636 chrome wheel deltaY 下100 上-100 */
\r
637 eventHooks.mousewheel = {
\r
639 deel: function (elem, _, fn) {
\r
640 return function (e) {
\r
641 e.wheelDeltaY = e.wheelDelta = e.deltaY > 0 ? -120 : 120
\r
643 Object.defineProperty(e, "type", {
\r
644 value: "mousewheel"
\r
652 /*********************************************************************
\r
654 **********************************************************************/
\r
656 function kernel(settings) {
\r
657 for (var p in settings) {
\r
658 if (!ohasOwn.call(settings, p))
\r
660 var val = settings[p]
\r
661 if (typeof kernel.plugins[p] === "function") {
\r
662 kernel.plugins[p](val)
\r
663 } else if (typeof kernel[p] === "object") {
\r
664 avalon.mix(kernel[p], val)
\r
672 var openTag, closeTag, rexpr, rexprg, rbind, rregexp = /[-.*+?^${}()|[\]\/\\]/g
\r
674 function escapeRegExp(target) {
\r
675 //http://stevenlevithan.com/regex/xregexp/
\r
676 //将字符串安全格式化为正则表达式的源码
\r
677 return (target + "").replace(rregexp, "\\$&")
\r
681 loader: function (builtin) {
\r
682 var flag = innerRequire && builtin
\r
683 window.require = flag ? innerRequire : otherRequire
\r
684 window.define = flag ? innerRequire.define : otherDefine
\r
686 interpolate: function (array) {
\r
688 closeTag = array[1]
\r
689 if (openTag === closeTag) {
\r
690 throw new SyntaxError("openTag!==closeTag")
\r
691 } else if (array + "" === "<!--,-->") {
\r
692 kernel.commentInterpolate = true
\r
694 var test = openTag + "test" + closeTag
\r
695 cinerator.innerHTML = test
\r
696 if (cinerator.innerHTML !== test && cinerator.innerHTML.indexOf("<") > -1) {
\r
697 throw new SyntaxError("此定界符不合法")
\r
699 cinerator.innerHTML = ""
\r
701 var o = escapeRegExp(openTag),
\r
702 c = escapeRegExp(closeTag)
\r
703 rexpr = new RegExp(o + "(.*?)" + c)
\r
704 rexprg = new RegExp(o + "(.*?)" + c, "g")
\r
705 rbind = new RegExp(o + ".*?" + c + "|\\sms-")
\r
709 kernel.debug = true
\r
710 kernel.plugins = plugins
\r
711 kernel.plugins['interpolate'](["{{", "}}"])
\r
714 kernel.maxRepeatSize = 100
\r
715 avalon.config = kernel
\r
716 var ravalon = /(\w+)\[(avalonctrl)="(\S+)"\]/
\r
717 var findNodes = function (str) {
\r
718 return DOC.querySelectorAll(str)
\r
720 /*********************************************************************
\r
722 **********************************************************************/
\r
724 $watch: function (type, callback) {
\r
725 if (typeof callback === "function") {
\r
726 var callbacks = this.$events[type]
\r
728 callbacks.push(callback)
\r
730 this.$events[type] = [callback]
\r
732 } else { //重新开始监听此VM的第一重简单属性的变动
\r
733 this.$events = this.$watch.backup
\r
737 $unwatch: function (type, callback) {
\r
738 var n = arguments.length
\r
739 if (n === 0) { //让此VM的所有$watch回调无效化
\r
740 this.$watch.backup = this.$events
\r
742 } else if (n === 1) {
\r
743 this.$events[type] = []
\r
745 var callbacks = this.$events[type] || []
\r
746 var i = callbacks.length
\r
748 if (callbacks[i] === callback) {
\r
749 return callbacks.splice(i, 1)
\r
755 $fire: function (type) {
\r
756 var special, i, v, callback
\r
757 if (/^(\w+)!(\S+)$/.test(type)) {
\r
758 special = RegExp.$1
\r
761 var events = this.$events
\r
764 var args = aslice.call(arguments, 1)
\r
765 var detail = [type].concat(args)
\r
766 if (special === "all") {
\r
767 for (i in avalon.vmodels) {
\r
768 v = avalon.vmodels[i]
\r
770 v.$fire.apply(v, detail)
\r
773 } else if (special === "up" || special === "down") {
\r
774 var elements = events.expr ? findNodes(events.expr) : []
\r
775 if (elements.length === 0)
\r
777 for (i in avalon.vmodels) {
\r
778 v = avalon.vmodels[i]
\r
780 if (v.$events.expr) {
\r
781 var eventNodes = findNodes(v.$events.expr)
\r
782 if (eventNodes.length === 0) {
\r
785 //循环两个vmodel中的节点,查找匹配(向上匹配或者向下匹配)的节点并设置标识
\r
786 /* jshint ignore:start */
\r
787 Array.prototype.forEach.call(eventNodes, function (node) {
\r
788 Array.prototype.forEach.call(elements, function (element) {
\r
789 var ok = special === "down" ? element.contains(node) : //向下捕获
\r
790 node.contains(element) //向上冒泡
\r
792 node._avalon = v //符合条件的加一个标识
\r
796 /* jshint ignore:end */
\r
800 var nodes = DOC.getElementsByTagName("*") //实现节点排序
\r
802 Array.prototype.forEach.call(nodes, function (el) {
\r
804 alls.push(el._avalon)
\r
806 el.removeAttribute("_avalon")
\r
809 if (special === "up") {
\r
812 for (i = 0; callback = alls[i++];) {
\r
813 if (callback.$fire.apply(callback, detail) === false) {
\r
818 var callbacks = events[type] || []
\r
819 var all = events.$all || []
\r
820 for (i = 0; callback = callbacks[i++];) {
\r
821 if (isFunction(callback))
\r
822 callback.apply(this, args)
\r
824 for (i = 0; callback = all[i++];) {
\r
825 if (isFunction(callback))
\r
826 callback.apply(this, arguments)
\r
832 /*********************************************************************
\r
834 **********************************************************************/
\r
835 //avalon最核心的方法的两个方法之一(另一个是avalon.scan),返回一个ViewModel(VM)
\r
836 var VMODELS = avalon.vmodels = createMap() //所有vmodel都储存在这里
\r
837 avalon.define = function (id, factory) {
\r
838 var $id = id.$id || id
\r
840 log("warning: vm必须指定$id")
\r
842 if (VMODELS[$id]) {
\r
843 log("warning: " + $id + " 已经存在于avalon.vmodels中")
\r
845 if (typeof id === "object") {
\r
846 var model = modelFactory(id)
\r
851 factory(scope) //得到所有定义
\r
852 model = modelFactory(scope) //偷天换日,将scope换为model
\r
853 stopRepeatAssign = true
\r
855 stopRepeatAssign = false
\r
858 return VMODELS[$id] = model
\r
862 var $$skipArray = String("$id,$watch,$unwatch,$fire,$events,$model,$skipArray").match(rword)
\r
864 function isObservable(name, value, $skipArray) {
\r
865 if (isFunction(value) || value && value.nodeType) {
\r
868 if ($skipArray.indexOf(name) !== -1) {
\r
871 if ($$skipArray.indexOf(name) !== -1) {
\r
874 var $special = $skipArray.$special
\r
875 if (name && name.charAt(0) === "$" && !$special[name]) {
\r
881 //ms-with,ms-each, ms-repeat绑定生成的代理对象储存池
\r
882 var midway = createMap()
\r
884 function getNewValue(accessor, name, value, $vmodel) {
\r
885 switch (accessor.type) {
\r
887 var getter = accessor.get
\r
888 var setter = accessor.set
\r
889 if (isFunction(setter)) {
\r
890 var $events = $vmodel.$events
\r
891 var lock = $events[name]
\r
892 $events[name] = [] //清空回调,防止内部冒泡而触发多次$fire
\r
893 setter.call($vmodel, value)
\r
894 $events[name] = lock
\r
896 return getter.call($vmodel) //同步$model
\r
899 case 2://对象属性(包括数组与哈希)
\r
900 if (value !== $vmodel.$model[name]) {
\r
901 var svmodel = accessor.svmodel = objectFactory($vmodel, name, value, accessor.valueType)
\r
902 value = svmodel.$model //同步$model
\r
903 var fn = midway[svmodel.$id]
\r
910 function modelFactory(source, $special, $model) {
\r
911 if (Array.isArray(source)) {
\r
912 var arr = source.concat()
\r
914 var collection = Collection(source)// jshint ignore:line
\r
915 collection.pushArray(arr)
\r
918 //0 null undefined || Node || VModel
\r
919 if (!source || source.nodeType > 0 || (source.$id && source.$events)) {
\r
922 if (!Array.isArray(source.$skipArray)) {
\r
923 source.$skipArray = []
\r
925 source.$skipArray.$special = $special || createMap() //强制要监听的属性
\r
926 var $vmodel = {} //要返回的对象, 它在IE6-8下可能被偷龙转凤
\r
927 $model = $model || {} //vmodels.$model属性
\r
928 var $events = createMap() //vmodel.$events属性
\r
929 var watchedProperties = createMap() //监控属性
\r
930 var initCallbacks = [] //初始化才执行的函数
\r
931 for (var i in source) {
\r
932 (function (name, val) {
\r
934 if (!isObservable(name, val, source.$skipArray)) {
\r
939 var valueType = avalon.type(val)
\r
940 var accessor = function (newValue) {
\r
941 var name = accessor._name
\r
943 var $model = $vmodel.$model
\r
944 var oldValue = $model[name]
\r
945 var $events = $vmodel.$events
\r
947 if (arguments.length) {
\r
948 if (stopRepeatAssign) {
\r
951 //计算属性与对象属性需要重新计算newValue
\r
952 if (accessor.type !== 1) {
\r
953 newValue = getNewValue(accessor, name, newValue, $vmodel)
\r
954 if (!accessor.type)
\r
957 if (!isEqual(oldValue, newValue)) {
\r
958 $model[name] = newValue
\r
959 notifySubscribers($events[name]) //同步视图
\r
960 safeFire($vmodel, name, newValue, oldValue) //触发$watch回调
\r
963 if (accessor.type === 0) { //type 0 计算属性 1 监控属性 2 对象属性
\r
964 //计算属性不需要收集视图刷新函数,都是由其他监控属性代劳
\r
965 newValue = accessor.get.call($vmodel)
\r
966 if (oldValue !== newValue) {
\r
967 $model[name] = newValue
\r
969 safeFire($vmodel, name, newValue, oldValue) //触发$watch回调
\r
973 collectSubscribers($events[name]) //收集视图函数
\r
974 return accessor.svmodel || oldValue
\r
979 if (valueType === "object" && isFunction(val.get) && Object.keys(val).length <= 2) {
\r
980 //第1种为计算属性, 因变量,通过其他监控属性触发其改变
\r
981 accessor.set = val.set
\r
982 accessor.get = val.get
\r
984 initCallbacks.push(function () {
\r
986 evaluator: function () {
\r
987 data.type = Math.random(),
\r
988 data.element = null
\r
989 $model[name] = accessor.get.call($vmodel)
\r
992 type: Math.random(),
\r
996 Registry[expose] = data
\r
997 accessor.call($vmodel)
\r
998 delete Registry[expose]
\r
1000 } else if (rcomplexType.test(valueType)) {
\r
1001 //第2种为对象属性,产生子VM与监控数组
\r
1003 accessor.valueType = valueType
\r
1004 initCallbacks.push(function () {
\r
1005 var svmodel = modelFactory(val, 0, $model[name])
\r
1006 accessor.svmodel = svmodel
\r
1007 svmodel.$events[subscribers] = $events[name]
\r
1011 //第3种为监控属性,对应简单的数据类型,自变量
\r
1013 accessor._name = name
\r
1014 watchedProperties[name] = accessor
\r
1015 })(i, source[i])// jshint ignore:line
\r
1018 $$skipArray.forEach(function (name) {
\r
1019 delete source[name]
\r
1020 delete $model[name] //这些特殊属性不应该在$model中出现
\r
1023 $vmodel = Object.defineProperties($vmodel, descriptorFactory(watchedProperties), source) //生成一个空的ViewModel
\r
1024 for (var name in source) {
\r
1025 if (!watchedProperties[name]) {
\r
1026 $vmodel[name] = source[name]
\r
1029 //添加$id, $model, $events, $watch, $unwatch, $fire
\r
1030 $vmodel.$id = generateID()
\r
1031 $vmodel.$model = $model
\r
1032 $vmodel.$events = $events
\r
1033 for (i in EventBus) {
\r
1034 $vmodel[i] = EventBus[i]
\r
1037 Object.defineProperty($vmodel, "hasOwnProperty", {
\r
1038 value: function (name) {
\r
1039 return name in this.$model
\r
1042 enumerable: false,
\r
1043 configurable: true
\r
1046 initCallbacks.forEach(function (cb) { //收集依赖
\r
1053 var isEqual = Object.is || function (v1, v2) {
\r
1054 if (v1 === 0 && v2 === 0) {
\r
1055 return 1 / v1 === 1 / v2
\r
1056 } else if (v1 !== v1) {
\r
1063 function safeFire(a, b, c, d) {
\r
1065 EventBus.$fire.call(a, b, c, d)
\r
1069 var descriptorFactory = function (obj) {
\r
1070 var descriptors = createMap()
\r
1071 for (var i in obj) {
\r
1072 descriptors[i] = {
\r
1076 configurable: true
\r
1079 return descriptors
\r
1083 function objectFactory(parent, name, value, valueType) {
\r
1084 //a为原来的VM, b为新数组或新对象
\r
1085 var son = parent[name]
\r
1086 if (valueType === "array") {
\r
1087 if (!Array.isArray(value) || son === value) {
\r
1088 return son //fix https://github.com/RubyLouvre/avalon/issues/261
\r
1093 son.pushArray(value.concat())
\r
1096 var iterators = parent.$events[name]
\r
1097 var ret = modelFactory(value)
\r
1098 ret.$events[subscribers] = iterators
\r
1099 midway[ret.$id] = function (data) {
\r
1100 while (data = iterators.shift()) {
\r
1102 avalon.nextTick(function () {
\r
1103 var type = el.type
\r
1104 if (type && bindingHandlers[type]) { //#753
\r
1105 el.rollback && el.rollback() //还原 ms-with ms-on
\r
1106 bindingHandlers[type](el, el.vmodels)
\r
1109 })(data)// jshint ignore:line
\r
1111 delete midway[ret.$id]
\r
1117 /*********************************************************************
\r
1118 * 监控数组(与ms-each, ms-repeat配合使用) *
\r
1119 **********************************************************************/
\r
1121 function Collection(model) {
\r
1123 array.$id = generateID()
\r
1124 array.$model = model //数据模型
\r
1125 array.$events = {}
\r
1126 array.$events[subscribers] = []
\r
1127 array._ = modelFactory({
\r
1128 length: model.length
\r
1130 array._.$watch("length", function (a, b) {
\r
1131 array.$fire("length", a, b)
\r
1133 for (var i in EventBus) {
\r
1134 array[i] = EventBus[i]
\r
1140 avalon.mix(array, CollectionPrototype)
\r
1144 function mutateArray(method, pos, n, index, method2, pos2, n2) {
\r
1145 var oldLen = this.length, loop = 2
\r
1149 /* jshint ignore:start */
\r
1151 var array = this.$model.slice(pos, m).map(function (el) {
\r
1152 if (rcomplexType.test(avalon.type(el))) {//转换为VM
\r
1153 return el.$id ? el : modelFactory(el, 0, el)
\r
1158 /* jshint ignore:end */
\r
1159 for (var i = pos; i < m; i++) {//生成代理VM
\r
1160 var proxy = eachProxyAgent(i, this)
\r
1161 this.$proxy.splice(i, 0, proxy)
\r
1163 _splice.apply(this, [pos, 0].concat(array))
\r
1164 this._fire("add", pos, n)
\r
1167 var ret = this._splice(pos, n)
\r
1168 var removed = this.$proxy.splice(pos, n) //回收代理VM
\r
1169 recycleProxies(removed, "each")
\r
1170 this._fire("del", pos, n)
\r
1181 resetIndex(this.$proxy, index)
\r
1182 if (this.length !== oldLen) {
\r
1183 this._.length = this.length
\r
1188 var _splice = ap.splice
\r
1189 var CollectionPrototype = {
\r
1191 _fire: function (method, a, b) {
\r
1192 notifySubscribers(this.$events[subscribers], method, a, b)
\r
1194 size: function () { //取得数组长度,这个函数可以同步视图,length不能
\r
1195 return this._.length
\r
1197 pushArray: function (array) {
\r
1198 var m = array.length, n = this.length
\r
1200 ap.push.apply(this.$model, array)
\r
1201 mutateArray.call(this, "add", n, m, Math.max(0, n - 1))
\r
1205 push: function () {
\r
1206 //http://jsperf.com/closure-with-arguments
\r
1208 var i, n = arguments.length
\r
1209 for (i = 0; i < n; i++) {
\r
1210 array[i] = arguments[i]
\r
1212 return this.pushArray(array)
\r
1214 unshift: function () {
\r
1215 var m = arguments.length, n = this.length
\r
1217 ap.unshift.apply(this.$model, arguments)
\r
1218 mutateArray.call(this, "add", 0, m, 0)
\r
1220 return m + n //IE67的unshift不会返回长度
\r
1222 shift: function () {
\r
1223 if (this.length) {
\r
1224 var el = this.$model.shift()
\r
1225 mutateArray.call(this, "del", 0, 1, 0)
\r
1226 return el //返回被移除的元素
\r
1229 pop: function () {
\r
1230 var n = this.length
\r
1232 var el = this.$model.pop()
\r
1233 mutateArray.call(this, "del", n - 1, 1, Math.max(0, n - 2))
\r
1234 return el //返回被移除的元素
\r
1237 splice: function (start) {
\r
1238 var m = arguments.length, args = [], change
\r
1239 var removed = _splice.apply(this.$model, arguments)
\r
1240 if (removed.length) { //如果用户删掉了元素
\r
1241 args.push("del", start, removed.length, 0)
\r
1244 if (m > 2) { //如果用户添加了元素
\r
1246 args.splice(3, 1, 0, "add", start, m - 2)
\r
1248 args.push("add", start, m - 2, 0)
\r
1252 if (change) { //返回被移除的元素
\r
1253 return mutateArray.apply(this, args)
\r
1258 contains: function (el) { //判定是否包含
\r
1259 return this.indexOf(el) !== -1
\r
1261 remove: function (el) { //移除第一个等于给定值的元素
\r
1262 return this.removeAt(this.indexOf(el))
\r
1264 removeAt: function (index) { //移除指定索引上的元素
\r
1266 this.$model.splice(index, 1)
\r
1267 return mutateArray.call(this, "del", index, 1, 0)
\r
1271 clear: function () {
\r
1272 recycleProxies(this.$proxy, "each")
\r
1273 this.$model.length = this.$proxy.length = this.length = this._.length = 0 //清空数组
\r
1274 this._fire("clear", 0)
\r
1277 removeAll: function (all) { //移除N个元素
\r
1278 if (Array.isArray(all)) {
\r
1279 all.forEach(function (el) {
\r
1282 } else if (typeof all === "function") {
\r
1283 for (var i = this.length - 1; i >= 0; i--) {
\r
1293 ensure: function (el) {
\r
1294 if (!this.contains(el)) { //只有不存在才push
\r
1299 set: function (index, val) {
\r
1301 var valueType = avalon.type(val)
\r
1302 if (val && val.$model) {
\r
1305 var target = this[index]
\r
1306 if (valueType === "object") {
\r
1307 for (var i in val) {
\r
1308 if (target.hasOwnProperty(i)) {
\r
1309 target[i] = val[i]
\r
1312 } else if (valueType === "array") {
\r
1313 target.clear().push.apply(target, val)
\r
1314 } else if (target !== val) {
\r
1316 this.$model[index] = val
\r
1317 var proxy = this.$proxy[index]
\r
1319 notifySubscribers(proxy.$events.el)
\r
1321 // this._fire("set", index, val)
\r
1327 //相当于原来bindingExecutors.repeat 的index分支
\r
1328 function resetIndex(array, pos) {
\r
1329 var last = array.length - 1
\r
1330 for (var el; el = array[pos]; pos++) {
\r
1332 el.$first = pos === 0
\r
1333 el.$last = pos === last
\r
1337 function sortByIndex(array, indexes) {
\r
1339 for (var i = 0, n = indexes.length; i < n; i++) {
\r
1340 map[i] = array[i] // preserve
\r
1341 var j = indexes[i]
\r
1346 array[i] = array[j]
\r
1351 "sort,reverse".replace(rword, function (method) {
\r
1352 CollectionPrototype[method] = function () {
\r
1353 var newArray = this.$model//这是要排序的新数组
\r
1354 var oldArray = newArray.concat() //保持原来状态的旧数组
\r
1355 var mask = Math.random()
\r
1358 ap[method].apply(newArray, arguments) //排序
\r
1359 for (var i = 0, n = oldArray.length; i < n; i++) {
\r
1360 var neo = newArray[i]
\r
1361 var old = oldArray[i]
\r
1362 if (isEqual(neo, old)) {
\r
1365 var index = oldArray.indexOf(neo)
\r
1366 indexes.push(index)//得到新数组的每个元素在旧数组对应的位置
\r
1367 oldArray[index] = mask //屏蔽已经找过的元素
\r
1372 sortByIndex(this, indexes)
\r
1373 sortByIndex(this.$proxy, indexes)
\r
1374 this._fire("move", indexes)
\r
1375 resetIndex(this.$proxy, 0)
\r
1381 /*********************************************************************
\r
1383 **********************************************************************/
\r
1384 var ronduplex = /^(duplex|on)$/
\r
1386 avalon.injectBinding = function (data) {
\r
1387 Registry[expose] = data //暴光此函数,方便collectSubscribers收集
\r
1388 avalon.openComputedCollect = true
\r
1389 var fn = data.evaluator
\r
1390 if (fn) { //如果是求值函数
\r
1392 var c = ronduplex.test(data.type) ? data : fn.apply(0, data.args)
\r
1393 if (!data.noRefresh)
\r
1394 data.handler(c, data.element, data)
\r
1396 //log("warning:exception throwed in [avalon.injectBinding] " + e)
\r
1397 delete data.evaluator
\r
1398 var node = data.element
\r
1399 if (node.nodeType === 3) {
\r
1400 var parent = node.parentNode
\r
1401 if (kernel.commentInterpolate) {
\r
1402 parent.replaceChild(DOC.createComment(data.value), node)
\r
1404 node.data = openTag + data.value + closeTag
\r
1409 avalon.openComputedCollect = false
\r
1410 delete Registry[expose]
\r
1413 function collectSubscribers(list) { //收集依赖于这个访问器的订阅者
\r
1414 var data = Registry[expose]
\r
1415 if (list && data && avalon.Array.ensure(list, data) && data.element) { //只有数组不存在此元素才push进去
\r
1416 addSubscribers(data, list)
\r
1421 function addSubscribers(data, list) {
\r
1422 data.$uuid = data.$uuid || generateID()
\r
1423 list.$uuid = list.$uuid || generateID()
\r
1427 $$uuid: data.$uuid + list.$uuid
\r
1429 if (!$$subscribers[obj.$$uuid]) {
\r
1430 $$subscribers[obj.$$uuid] = 1
\r
1431 $$subscribers.push(obj)
\r
1435 function disposeData(data) {
\r
1436 data.element = null
\r
1437 data.rollback && data.rollback()
\r
1438 for (var key in data) {
\r
1443 function isRemove(el) {
\r
1444 try {//IE下,如果文本节点脱离DOM树,访问parentNode会报错
\r
1445 if (!el.parentNode) {
\r
1451 return el.msRetain ? 0 : (el.nodeType === 1 ? typeof el.sourceIndex === "number" ?
\r
1452 el.sourceIndex === 0 : !root.contains(el) : !avalon.contains(root, el))
\r
1455 var $$subscribers = avalon.$$subscribers = []
\r
1456 var beginTime = new Date()
\r
1459 function removeSubscribers() {
\r
1460 var i = $$subscribers.length
\r
1467 while (obj = $$subscribers[--i]) {
\r
1468 var data = obj.data
\r
1469 var type = data.type
\r
1470 if (newInfo[type]) {
\r
1478 types.forEach(function (type) {
\r
1479 if (oldInfo[type] !== newInfo[type]) {
\r
1480 needTest[type] = 1
\r
1485 //avalon.log("需要检测的个数 " + i)
\r
1487 //avalon.log("有需要移除的元素")
\r
1488 while (obj = $$subscribers[--i]) {
\r
1490 if (data.element === void 0)
\r
1492 if (needTest[data.type] && isRemove(data.element)) { //如果它没有在DOM树
\r
1494 $$subscribers.splice(i, 1)
\r
1495 delete $$subscribers[obj.$$uuid]
\r
1496 avalon.Array.remove(obj.list, data)
\r
1497 //log("debug: remove " + data.type)
\r
1499 obj.data = obj.list = null
\r
1504 // avalon.log("已经移除的个数 " + k)
\r
1505 beginTime = new Date()
\r
1508 function notifySubscribers(list) { //通知依赖于这个访问器的订阅者更新自身
\r
1509 if (list && list.length) {
\r
1510 if (new Date() - beginTime > 444 && typeof list[0] === "object") {
\r
1511 removeSubscribers()
\r
1513 var args = aslice.call(arguments, 1)
\r
1514 for (var i = list.length, fn; fn = list[--i];) {
\r
1515 var el = fn.element
\r
1516 if (el && el.parentNode) {
\r
1518 fn.handler.apply(fn, args) //处理监控数组的方法
\r
1519 } else if (fn.type !== "on") { //事件绑定只能由用户触发,不能由程序触发
\r
1520 var fun = fn.evaluator || noop
\r
1521 fn.handler(fun.apply(0, fn.args || []), el, fn)
\r
1528 /************************************************************************
\r
1529 * HTML处理(parseHTML, innerHTML, clearHTML) *
\r
1530 **************************************************************************/
\r
1532 var tagHooks = new function () {// jshint ignore:line
\r
1533 avalon.mix(this, {
\r
1534 option: DOC.createElement("select"),
\r
1535 thead: DOC.createElement("table"),
\r
1536 td: DOC.createElement("tr"),
\r
1537 area: DOC.createElement("map"),
\r
1538 tr: DOC.createElement("tbody"),
\r
1539 col: DOC.createElement("colgroup"),
\r
1540 legend: DOC.createElement("fieldset"),
\r
1541 _default: DOC.createElement("div"),
\r
1542 "g": DOC.createElementNS("http://www.w3.org/2000/svg", "svg")
\r
1544 this.optgroup = this.option
\r
1545 this.tbody = this.tfoot = this.colgroup = this.caption = this.thead
\r
1547 }// jshint ignore:line
\r
1549 String("circle,defs,ellipse,image,line,path,polygon,polyline,rect,symbol,text,use").replace(rword, function (tag) {
\r
1550 tagHooks[tag] = tagHooks.g //处理SVG
\r
1552 var rtagName = /<([\w:]+)/
\r
1553 var rxhtml = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig
\r
1554 var scriptTypes = oneObject(["", "text/javascript", "text/ecmascript", "application/ecmascript", "application/javascript"])
\r
1555 var script = DOC.createElement("script")
\r
1556 var rhtml = /<|&#?\w+;/
\r
1557 avalon.parseHTML = function (html) {
\r
1558 var fragment = hyperspace.cloneNode(false)
\r
1559 if (typeof html !== "string") {
\r
1562 if (!rhtml.test(html)) {
\r
1563 fragment.appendChild(DOC.createTextNode(html))
\r
1566 html = html.replace(rxhtml, "<$1></$2>").trim()
\r
1567 var tag = (rtagName.exec(html) || ["", ""])[1].toLowerCase(),
\r
1569 wrapper = tagHooks[tag] || tagHooks._default,
\r
1571 wrapper.innerHTML = html
\r
1572 var els = wrapper.getElementsByTagName("script")
\r
1573 if (els.length) { //使用innerHTML生成的script节点不会发出请求与执行text属性
\r
1574 for (var i = 0, el; el = els[i++];) {
\r
1575 if (scriptTypes[el.type]) {
\r
1576 var neo = script.cloneNode(false) //FF不能省略参数
\r
1577 ap.forEach.call(el.attributes, function (attr) {
\r
1578 neo.setAttribute(attr.name, attr.value)
\r
1579 })// jshint ignore:line
\r
1580 neo.text = el.text
\r
1581 el.parentNode.replaceChild(neo, el)
\r
1586 while (firstChild = wrapper.firstChild) { // 将wrapper上的节点转移到文档碎片上!
\r
1587 fragment.appendChild(firstChild)
\r
1592 avalon.innerHTML = function (node, html) {
\r
1593 var a = this.parseHTML(html)
\r
1594 this.clearHTML(node).appendChild(a)
\r
1597 avalon.clearHTML = function (node) {
\r
1598 node.textContent = ""
\r
1599 while (node.firstChild) {
\r
1600 node.removeChild(node.firstChild)
\r
1605 /*********************************************************************
\r
1607 **********************************************************************/
\r
1609 avalon.scan = function (elem, vmodel) {
\r
1610 elem = elem || root
\r
1611 var vmodels = vmodel ? [].concat(vmodel) : []
\r
1612 scanTag(elem, vmodels)
\r
1615 //http://www.w3.org/TR/html5/syntax.html#void-elements
\r
1616 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
1618 function checkScan(elem, callback, innerHTML) {
\r
1619 var id = setTimeout(function () {
\r
1620 var currHTML = elem.innerHTML
\r
1622 if (currHTML === innerHTML) {
\r
1625 checkScan(elem, callback, currHTML)
\r
1631 function createSignalTower(elem, vmodel) {
\r
1632 var id = elem.getAttribute("avalonctrl") || vmodel.$id
\r
1633 elem.setAttribute("avalonctrl", id)
\r
1634 vmodel.$events.expr = elem.tagName + '[avalonctrl="' + id + '"]'
\r
1637 var getBindingCallback = function (elem, name, vmodels) {
\r
1638 var callback = elem.getAttribute(name)
\r
1640 for (var i = 0, vm; vm = vmodels[i++];) {
\r
1641 if (vm.hasOwnProperty(callback) && typeof vm[callback] === "function") {
\r
1642 return vm[callback]
\r
1648 function executeBindings(bindings, vmodels) {
\r
1649 for (var i = 0, data; data = bindings[i++];) {
\r
1650 data.vmodels = vmodels
\r
1651 bindingHandlers[data.type](data, vmodels)
\r
1652 if (data.evaluator && data.element && data.element.nodeType === 1) { //移除数据绑定,防止被二次解析
\r
1653 //chrome使用removeAttributeNode移除不存在的特性节点时会报错 https://github.com/RubyLouvre/avalon/issues/99
\r
1654 data.element.removeAttribute(data.name)
\r
1657 bindings.length = 0
\r
1660 //https://github.com/RubyLouvre/avalon/issues/636
\r
1661 var mergeTextNodes = IEVersion && window.MutationObserver ? function (elem) {
\r
1662 var node = elem.firstChild, text
\r
1664 var aaa = node.nextSibling
\r
1665 if (node.nodeType === 3) {
\r
1667 text.nodeValue += node.nodeValue
\r
1668 elem.removeChild(node)
\r
1679 var rmsAttr = /ms-(\w+)-?(.*)/
\r
1680 var priorityMap = {
\r
1691 var events = oneObject("animationend,blur,change,input,click,dblclick,focus,keydown,keypress,keyup,mousedown,mouseenter,mouseleave,mousemove,mouseout,mouseover,mouseup,scan,scroll,submit")
\r
1692 var obsoleteAttrs = oneObject("value,title,alt,checked,selected,disabled,readonly,enabled")
\r
1694 function bindingSorter(a, b) {
\r
1695 return a.priority - b.priority
\r
1698 function scanAttr(elem, vmodels) {
\r
1699 //防止setAttribute, removeAttribute时 attributes自动被同步,导致for循环出错
\r
1700 var attributes = elem.hasAttributes() ? avalon.slice(elem.attributes) : []
\r
1701 var bindings = [],
\r
1702 msData = createMap(),
\r
1704 for (var i = 0, attr; attr = attributes[i++];) {
\r
1705 if (attr.specified) {
\r
1706 if (match = attr.name.match(rmsAttr)) {
\r
1708 var type = match[1]
\r
1709 var param = match[2] || ""
\r
1710 var value = attr.value
\r
1711 var name = attr.name
\r
1712 msData[name] = value
\r
1713 if (events[type]) {
\r
1716 } else if (obsoleteAttrs[type]) {
\r
1717 log("warning!请改用ms-attr-" + type + "代替ms-" + type + "!")
\r
1718 if (type === "enabled") {//吃掉ms-enabled绑定,用ms-disabled代替
\r
1719 log("warning!ms-enabled或ms-attr-enabled已经被废弃")
\r
1721 value = "!(" + value + ")"
\r
1725 elem.removeAttribute(name)
\r
1726 name = "ms-attr-" + param
\r
1727 elem.setAttribute(name, value)
\r
1729 msData[name] = value
\r
1731 if (typeof bindingHandlers[type] === "function") {
\r
1738 priority: type in priorityMap ? priorityMap[type] : type.charCodeAt(0) * 10 + (Number(param) || 0)
\r
1740 if (type === "html" || type === "text") {
\r
1741 var token = getToken(value)
\r
1742 avalon.mix(binding, token)
\r
1743 binding.filters = binding.filters.replace(rhasHtml, function () {
\r
1744 binding.type = "html"
\r
1747 })// jshint ignore:line
\r
1749 if (name === "ms-if-loop") {
\r
1750 binding.priority += 100
\r
1752 if (vmodels.length) {
\r
1753 bindings.push(binding)
\r
1754 if (type === "widget") {
\r
1755 elem.msData = elem.msData || msData
\r
1762 var control = elem.type
\r
1763 if (control && msData["ms-duplex"]) {
\r
1764 if (msData["ms-attr-checked"] && /radio|checkbox/.test(control)) {
\r
1765 log("warning!" + control + "控件不能同时定义ms-attr-checked与ms-duplex")
\r
1767 if (msData["ms-attr-value"] && /text|password/.test(control)) {
\r
1768 log("warning!" + control + "控件不能同时定义ms-attr-value与ms-duplex")
\r
1771 bindings.sort(bindingSorter)
\r
1772 var scanNode = true
\r
1773 for (i = 0; binding = bindings[i]; i++) {
\r
1774 type = binding.type
\r
1775 if (rnoscanAttrBinding.test(type)) {
\r
1776 return executeBindings(bindings.slice(0, i + 1), vmodels)
\r
1777 } else if (scanNode) {
\r
1778 scanNode = !rnoscanNodeBinding.test(type)
\r
1781 executeBindings(bindings, vmodels)
\r
1782 if (scanNode && !stopScan[elem.tagName] && rbind.test(elem.innerHTML + elem.textContent)) {
\r
1783 mergeTextNodes && mergeTextNodes(elem)
\r
1784 scanNodeList(elem, vmodels) //扫描子孙元素
\r
1788 var rnoscanAttrBinding = /^if|widget|repeat$/
\r
1789 var rnoscanNodeBinding = /^each|with|html|include$/
\r
1791 function scanNodeList(parent, vmodels) {
\r
1792 var node = parent.firstChild
\r
1794 var nextNode = node.nextSibling
\r
1795 scanNode(node, node.nodeType, vmodels)
\r
1800 function scanNodeArray(nodes, vmodels) {
\r
1801 for (var i = 0, node; node = nodes[i++];) {
\r
1802 scanNode(node, node.nodeType, vmodels)
\r
1806 function scanNode(node, nodeType, vmodels) {
\r
1807 if (nodeType === 1) {
\r
1808 scanTag(node, vmodels) //扫描元素节点
\r
1809 if (node.msCallback) {
\r
1811 node.msCallback = void 0
\r
1813 } else if (nodeType === 3 && rexpr.test(node.data)) {
\r
1814 scanText(node, vmodels) //扫描文本节点
\r
1815 } else if (kernel.commentInterpolate && nodeType === 8 && !rexpr.test(node.nodeValue)) {
\r
1816 scanText(node, vmodels) //扫描注释节点
\r
1820 function scanTag(elem, vmodels, node) {
\r
1821 //扫描顺序 ms-skip(0) --> ms-important(1) --> ms-controller(2) --> ms-if(10) --> ms-repeat(100)
\r
1822 //--> ms-if-loop(110) --> ms-attr(970) ...--> ms-each(1400)-->ms-with(1500)--〉ms-duplex(2000)垫后
\r
1823 var a = elem.getAttribute("ms-skip")
\r
1824 var b = elem.getAttributeNode("ms-important")
\r
1825 var c = elem.getAttributeNode("ms-controller")
\r
1826 if (typeof a === "string") {
\r
1828 } else if (node = b || c) {
\r
1829 var newVmodel = avalon.vmodels[node.value]
\r
1833 //ms-important不包含父VM,ms-controller相反
\r
1834 vmodels = node === b ? [newVmodel] : [newVmodel].concat(vmodels)
\r
1835 elem.removeAttribute(node.name) //removeAttributeNode不会刷新[ms-controller]样式规则
\r
1836 elem.classList.remove(node.name)
\r
1837 createSignalTower(elem, newVmodel)
\r
1839 scanAttr(elem, vmodels) //扫描特性节点
\r
1842 var rhasHtml = /\|\s*html\s*/,
\r
1846 rstringLiteral = /(['"])(\\\1|.)+?\1/g
\r
1848 function getToken(value) {
\r
1849 if (value.indexOf("|") > 0) {
\r
1850 var scapegoat = value.replace(rstringLiteral, function (_) {
\r
1851 return Array(_.length + 1).join("1")// jshint ignore:line
\r
1853 var index = scapegoat.replace(r11a, "\u1122\u3344").indexOf("|") //干掉所有短路或
\r
1856 filters: value.slice(index),
\r
1857 value: value.slice(0, index),
\r
1869 function scanExpr(str) {
\r
1874 stop = str.indexOf(openTag, start)
\r
1875 if (stop === -1) {
\r
1878 value = str.slice(start, stop)
\r
1879 if (value) { // {{ 左边的文本
\r
1886 start = stop + openTag.length
\r
1887 stop = str.indexOf(closeTag, start)
\r
1888 if (stop === -1) {
\r
1891 value = str.slice(start, stop)
\r
1892 if (value) { //处理{{ }}插值表达式
\r
1893 tokens.push(getToken(value))
\r
1895 start = stop + closeTag.length
\r
1897 value = str.slice(start)
\r
1898 if (value) { //}} 右边的文本
\r
1908 function scanText(textNode, vmodels) {
\r
1910 if (textNode.nodeType === 8) {
\r
1911 var token = getToken(textNode.nodeValue)
\r
1912 var tokens = [token]
\r
1914 tokens = scanExpr(textNode.data)
\r
1916 if (tokens.length) {
\r
1917 for (var i = 0; token = tokens[i++];) {
\r
1918 var node = DOC.createTextNode(token.value) //将文本转换为文本节点,并替换原来的文本节点
\r
1920 token.type = "text"
\r
1921 token.element = node
\r
1922 token.filters = token.filters.replace(rhasHtml, function () {
\r
1923 token.type = "html"
\r
1926 })// jshint ignore:line
\r
1927 bindings.push(token) //收集带有插值表达式的文本
\r
1929 hyperspace.appendChild(node)
\r
1931 textNode.parentNode.replaceChild(hyperspace, textNode)
\r
1932 if (bindings.length)
\r
1933 executeBindings(bindings, vmodels)
\r
1937 /*********************************************************************
\r
1938 * avalon的原型方法定义区 *
\r
1939 **********************************************************************/
\r
1941 function hyphen(target) {
\r
1943 return target.replace(/([a-z\d])([A-Z]+)/g, "$1-$2").toLowerCase()
\r
1946 function camelize(target) {
\r
1948 if (target.indexOf("-") < 0 && target.indexOf("_") < 0) {
\r
1949 return target //提前判断,提高getStyle等的效率
\r
1951 return target.replace(/[-_][^-_]/g, function (match) {
\r
1952 return match.charAt(1).toUpperCase()
\r
1956 "add,remove".replace(rword, function (method) {
\r
1957 avalon.fn[method + "Class"] = function (cls) {
\r
1959 //https://developer.mozilla.org/zh-CN/docs/Mozilla/Firefox/Releases/26
\r
1960 if (cls && typeof cls === "string" && el && el.nodeType === 1) {
\r
1961 cls.replace(/\S+/g, function (c) {
\r
1962 el.classList[method](c)
\r
1970 hasClass: function (cls) {
\r
1971 var el = this[0] || {} //IE10+, chrome8+, firefox3.6+, safari5.1+,opera11.5+支持classList,chrome24+,firefox26+支持classList2.0
\r
1972 return el.nodeType === 1 && el.classList.contains(cls)
\r
1974 toggleClass: function (value, stateVal) {
\r
1975 var className, i = 0
\r
1976 var classNames = String(value).split(/\s+/)
\r
1977 var isBool = typeof stateVal === "boolean"
\r
1978 while ((className = classNames[i++])) {
\r
1979 var state = isBool ? stateVal : !this.hasClass(className)
\r
1980 this[state ? "addClass" : "removeClass"](className)
\r
1984 attr: function (name, value) {
\r
1985 if (arguments.length === 2) {
\r
1986 this[0].setAttribute(name, value)
\r
1989 return this[0].getAttribute(name)
\r
1992 data: function (name, value) {
\r
1993 name = "data-" + hyphen(name || "")
\r
1994 switch (arguments.length) {
\r
1996 this.attr(name, value)
\r
1999 var val = this.attr(name)
\r
2000 return parseData(val)
\r
2003 ap.forEach.call(this[0].attributes, function (attr) {
\r
2006 if (!name.indexOf("data-")) {
\r
2007 name = camelize(name.slice(5))
\r
2008 ret[name] = parseData(attr.value)
\r
2015 removeData: function (name) {
\r
2016 name = "data-" + hyphen(name)
\r
2017 this[0].removeAttribute(name)
\r
2020 css: function (name, value) {
\r
2021 if (avalon.isPlainObject(name)) {
\r
2022 for (var i in name) {
\r
2023 avalon.css(this, i, name[i])
\r
2026 var ret = avalon.css(this, name, value)
\r
2028 return ret !== void 0 ? ret : this
\r
2030 position: function () {
\r
2031 var offsetParent, offset,
\r
2040 if (this.css("position") === "fixed") {
\r
2041 offset = elem.getBoundingClientRect()
\r
2043 offsetParent = this.offsetParent() //得到真正的offsetParent
\r
2044 offset = this.offset() // 得到正确的offsetParent
\r
2045 if (offsetParent[0].tagName !== "HTML") {
\r
2046 parentOffset = offsetParent.offset()
\r
2048 parentOffset.top += avalon.css(offsetParent[0], "borderTopWidth", true)
\r
2049 parentOffset.left += avalon.css(offsetParent[0], "borderLeftWidth", true)
\r
2050 // Subtract offsetParent scroll positions
\r
2051 parentOffset.top -= offsetParent.scrollTop()
\r
2052 parentOffset.left -= offsetParent.scrollLeft()
\r
2055 top: offset.top - parentOffset.top - avalon.css(elem, "marginTop", true),
\r
2056 left: offset.left - parentOffset.left - avalon.css(elem, "marginLeft", true)
\r
2059 offsetParent: function () {
\r
2060 var offsetParent = this[0].offsetParent
\r
2061 while (offsetParent && avalon.css(offsetParent, "position") === "static") {
\r
2062 offsetParent = offsetParent.offsetParent;
\r
2064 return avalon(offsetParent || root)
\r
2066 bind: function (type, fn, phase) {
\r
2067 if (this[0]) { //此方法不会链
\r
2068 return avalon.bind(this[0], type, fn, phase)
\r
2071 unbind: function (type, fn, phase) {
\r
2073 avalon.unbind(this[0], type, fn, phase)
\r
2077 val: function (value) {
\r
2078 var node = this[0]
\r
2079 if (node && node.nodeType === 1) {
\r
2080 var get = arguments.length === 0
\r
2081 var access = get ? ":get" : ":set"
\r
2082 var fn = valHooks[getValType(node) + access]
\r
2084 var val = fn(node, value)
\r
2086 return (node.value || "").replace(/\r/g, "")
\r
2088 node.value = value
\r
2091 return get ? val : this
\r
2095 if (root.dataset) {
\r
2096 avalon.fn.data = function (name, val) {
\r
2097 name = name && camelize(name)
\r
2098 var dataset = this[0].dataset
\r
2099 switch (arguments.length) {
\r
2101 dataset[name] = val
\r
2104 val = dataset[name]
\r
2105 return parseData(val)
\r
2107 var ret = createMap()
\r
2108 for (name in dataset) {
\r
2109 ret[name] = parseData(dataset[name])
\r
2115 var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/
\r
2116 avalon.parseJSON = JSON.parse
\r
2118 function parseData(data) {
\r
2120 if (typeof data === "object")
\r
2122 data = data === "true" ? true :
\r
2123 data === "false" ? false :
\r
2124 data === "null" ? null : +data + "" === data ? +data : rbrace.test(data) ? JSON.parse(data) : data
\r
2131 scrollLeft: "pageXOffset",
\r
2132 scrollTop: "pageYOffset"
\r
2133 }, function (method, prop) {
\r
2134 avalon.fn[method] = function (val) {
\r
2135 var node = this[0] || {}, win = getWindow(node),
\r
2136 top = method === "scrollTop"
\r
2137 if (!arguments.length) {
\r
2138 return win ? win[prop] : node[method]
\r
2141 win.scrollTo(!top ? val : win[prop], top ? val : win[prop])
\r
2143 node[method] = val
\r
2149 function getWindow(node) {
\r
2150 return node.window && node.document ? node : node.nodeType === 9 ? node.defaultView : false
\r
2153 //=============================css相关==================================
\r
2154 var cssHooks = avalon.cssHooks = createMap()
\r
2155 var prefixes = ["", "-webkit-", "-moz-", "-ms-"] //去掉opera-15的支持
\r
2157 "float": "cssFloat"
\r
2159 avalon.cssNumber = oneObject("columnCount,order,fillOpacity,fontWeight,lineHeight,opacity,orphans,widows,zIndex,zoom")
\r
2161 avalon.cssName = function (name, host, camelCase) {
\r
2162 if (cssMap[name]) {
\r
2163 return cssMap[name]
\r
2165 host = host || root.style
\r
2166 for (var i = 0, n = prefixes.length; i < n; i++) {
\r
2167 camelCase = camelize(prefixes[i] + name)
\r
2168 if (camelCase in host) {
\r
2169 return (cssMap[name] = camelCase)
\r
2174 cssHooks["@:set"] = function (node, name, value) {
\r
2175 node.style[name] = value
\r
2178 cssHooks["@:get"] = function (node, name) {
\r
2179 if (!node || !node.style) {
\r
2180 throw new Error("getComputedStyle要求传入一个节点 " + node)
\r
2182 var ret, computed = getComputedStyle(node)
\r
2184 ret = name === "filter" ? computed.getPropertyValue(name) : computed[name]
\r
2186 ret = node.style[name] //其他浏览器需要我们手动取内联样式
\r
2191 cssHooks["opacity:get"] = function (node) {
\r
2192 var ret = cssHooks["@:get"](node, "opacity")
\r
2193 return ret === "" ? "1" : ret
\r
2196 "top,left".replace(rword, function (name) {
\r
2197 cssHooks[name + ":get"] = function (node) {
\r
2198 var computed = cssHooks["@:get"](node, name)
\r
2199 return /px$/.test(computed) ? computed :
\r
2200 avalon(node).position()[name] + "px"
\r
2204 position: "absolute",
\r
2205 visibility: "hidden",
\r
2208 var rdisplayswap = /^(none|table(?!-c[ea]).+)/
\r
2210 function showHidden(node, array) {
\r
2211 //http://www.cnblogs.com/rubylouvre/archive/2012/10/27/2742529.html
\r
2212 if (node.offsetWidth <= 0) { //opera.offsetWidth可能小于0
\r
2213 var styles = getComputedStyle(node, null)
\r
2214 if (rdisplayswap.test(styles["display"])) {
\r
2218 for (var name in cssShow) {
\r
2219 obj[name] = styles[name]
\r
2220 node.style[name] = cssShow[name]
\r
2224 var parent = node.parentNode
\r
2225 if (parent && parent.nodeType === 1) {
\r
2226 showHidden(parent, array)
\r
2231 "Width,Height".replace(rword, function (name) { //fix 481
\r
2232 var method = name.toLowerCase(),
\r
2233 clientProp = "client" + name,
\r
2234 scrollProp = "scroll" + name,
\r
2235 offsetProp = "offset" + name
\r
2236 cssHooks[method + ":get"] = function (node, which, override) {
\r
2237 var boxSizing = -4
\r
2238 if (typeof override === "number") {
\r
2239 boxSizing = override
\r
2241 which = name === "Width" ? ["Left", "Right"] : ["Top", "Bottom"]
\r
2242 var ret = node[offsetProp] // border-box 0
\r
2243 if (boxSizing === 2) { // margin-box 2
\r
2244 return ret + avalon.css(node, "margin" + which[0], true) + avalon.css(node, "margin" + which[1], true)
\r
2246 if (boxSizing < 0) { // padding-box -2
\r
2247 ret = ret - avalon.css(node, "border" + which[0] + "Width", true) - avalon.css(node, "border" + which[1] + "Width", true)
\r
2249 if (boxSizing === -4) { // content-box -4
\r
2250 ret = ret - avalon.css(node, "padding" + which[0], true) - avalon.css(node, "padding" + which[1], true)
\r
2254 cssHooks[method + "&get"] = function (node) {
\r
2256 showHidden(node, hidden);
\r
2257 var val = cssHooks[method + ":get"](node)
\r
2258 for (var i = 0, obj; obj = hidden[i++];) {
\r
2260 for (var n in obj) {
\r
2261 if (typeof obj[n] === "string") {
\r
2262 node.style[n] = obj[n]
\r
2268 avalon.fn[method] = function (value) { //会忽视其display
\r
2269 var node = this[0]
\r
2270 if (arguments.length === 0) {
\r
2271 if (node.setTimeout) { //取得窗口尺寸,IE9后可以用node.innerWidth /innerHeight代替
\r
2272 return node["inner" + name]
\r
2274 if (node.nodeType === 9) { //取得页面尺寸
\r
2275 var doc = node.documentElement
\r
2276 //FF chrome html.scrollHeight< body.scrollHeight
\r
2277 //IE 标准模式 : html.scrollHeight> body.scrollHeight
\r
2278 //IE 怪异模式 : html.scrollHeight 最大等于可视窗口多一点?
\r
2279 return Math.max(node.body[scrollProp], doc[scrollProp], node.body[offsetProp], doc[offsetProp], doc[clientProp])
\r
2281 return cssHooks[method + "&get"](node)
\r
2283 return this.css(method, value)
\r
2286 avalon.fn["inner" + name] = function () {
\r
2287 return cssHooks[method + ":get"](this[0], void 0, -2)
\r
2289 avalon.fn["outer" + name] = function (includeMargin) {
\r
2290 return cssHooks[method + ":get"](this[0], void 0, includeMargin === true ? 2 : 0)
\r
2293 avalon.fn.offset = function () { //取得距离页面左右角的坐标
\r
2294 var node = this[0]
\r
2296 var rect = node.getBoundingClientRect()
\r
2297 // Make sure element is not hidden (display: none) or disconnected
\r
2298 // https://github.com/jquery/jquery/pull/2043/files#r23981494
\r
2299 if (rect.width || rect.height || node.getClientRects().length) {
\r
2300 var doc = node.ownerDocument
\r
2301 var root = doc.documentElement
\r
2302 var win = doc.defaultView
\r
2304 top: rect.top + win.pageYOffset - root.clientTop,
\r
2305 left: rect.left + win.pageXOffset - root.clientLeft
\r
2315 //=============================val相关=======================
\r
2317 function getValType(elem) {
\r
2318 var ret = elem.tagName.toLowerCase()
\r
2319 return ret === "input" && /checkbox|radio/.test(elem.type) ? "checked" : ret
\r
2323 "select:get": function (node, value) {
\r
2324 var option, options = node.options,
\r
2325 index = node.selectedIndex,
\r
2326 one = node.type === "select-one" || index < 0,
\r
2327 values = one ? null : [],
\r
2328 max = one ? index + 1 : options.length,
\r
2329 i = index < 0 ? max : one ? index : 0
\r
2330 for (; i < max; i++) {
\r
2331 option = options[i]
\r
2332 //旧式IE在reset后不会改变selected,需要改用i === index判定
\r
2333 //我们过滤所有disabled的option元素,但在safari5下,如果设置select为disable,那么其所有孩子都disable
\r
2334 //因此当一个元素为disable,需要检测其是否显式设置了disable及其父节点的disable情况
\r
2335 if ((option.selected || i === index) && !option.disabled) {
\r
2336 value = option.value
\r
2340 //收集所有selected值组成数组返回
\r
2341 values.push(value)
\r
2346 "select:set": function (node, values, optionSet) {
\r
2347 values = [].concat(values) //强制转换为数组
\r
2348 for (var i = 0, el; el = node.options[i++];) {
\r
2349 if ((el.selected = values.indexOf(el.value) > -1)) {
\r
2354 node.selectedIndex = -1
\r
2358 /*********************************************************************
\r
2360 **********************************************************************/
\r
2361 var quote = JSON.stringify
\r
2364 "break,case,catch,continue,debugger,default,delete,do,else,false",
\r
2365 "finally,for,function,if,in,instanceof,new,null,return,switch,this",
\r
2366 "throw,true,try,typeof,var,void,while,with", /* 关键字*/
\r
2367 "abstract,boolean,byte,char,class,const,double,enum,export,extends",
\r
2368 "final,float,goto,implements,import,int,interface,long,native",
\r
2369 "package,private,protected,public,short,static,super,synchronized",
\r
2370 "throws,transient,volatile", /*保留字*/
\r
2371 "arguments,let,yield,undefined" /* ECMA 5 - use strict*/].join(",")
\r
2372 var rrexpstr = /\/\*[\w\W]*?\*\/|\/\/[^\n]*\n|\/\/[^\n]*$|"(?:[^"\\]|\\[\w\W])*"|'(?:[^'\\]|\\[\w\W])*'|[\s\t\n]*\.[\s\t\n]*[$\w\.]+/g
\r
2373 var rsplit = /[^\w$]+/g
\r
2374 var rkeywords = new RegExp(["\\b" + keywords.replace(/,/g, '\\b|\\b') + "\\b"].join('|'), 'g')
\r
2375 var rnumber = /\b\d[^,]*/g
\r
2376 var rcomma = /^,+|,+$/g
\r
2377 var cacheVars = new Cache(512)
\r
2378 var getVariables = function (code) {
\r
2379 var key = "," + code.trim()
\r
2380 var ret = cacheVars.get(key)
\r
2385 .replace(rrexpstr, "")
\r
2386 .replace(rsplit, ",")
\r
2387 .replace(rkeywords, "")
\r
2388 .replace(rnumber, "")
\r
2389 .replace(rcomma, "")
\r
2391 return cacheVars.put(key, uniqSet(match))
\r
2395 function addAssign(vars, scope, name, data) {
\r
2397 prefix = " = " + name + "."
\r
2398 var isProxy = /\$proxy\$each/.test(scope.$id)
\r
2399 for (var i = vars.length, prop; prop = vars[--i];) {
\r
2400 var el = isProxy && scope.$map[prop] ? "el" : prop
\r
2401 if (scope.hasOwnProperty(el)) {
\r
2402 ret.push(prop + prefix + el)
\r
2403 data.vars.push(prop)
\r
2404 if (data.type === "duplex") {
\r
2405 vars.get = name + "." + el
\r
2413 function uniqSet(array) {
\r
2416 for (var i = 0; i < array.length; i++) {
\r
2418 var id = el && typeof el.$id === "string" ? el.$id : el
\r
2419 if (!unique[id]) {
\r
2420 unique[id] = ret.push(el)
\r
2427 var cacheExprs = new Cache(128)
\r
2429 var rduplex = /\w\[.*\]|\w\.\w/
\r
2430 var rproxy = /(\$proxy\$[a-z]+)\d+$/
\r
2431 var rthimRightParentheses = /\)\s*$/
\r
2432 var rthimOtherParentheses = /\)\s*\|/g
\r
2433 var rquoteFilterName = /\|\s*([$\w]+)/g
\r
2434 var rpatchBracket = /"\s*\["/g
\r
2435 var rthimLeftParentheses = /"\s*\(/g
\r
2437 function parseFilter(val, filters) {
\r
2439 .replace(rthimRightParentheses, "")//处理最后的小括号
\r
2440 .replace(rthimOtherParentheses, function () {//处理其他小括号
\r
2443 .replace(rquoteFilterName, function (a, b) { //处理|及它后面的过滤器的名字
\r
2444 return "[" + quote(b)
\r
2446 .replace(rpatchBracket, function () {
\r
2449 .replace(rthimLeftParentheses, function () {
\r
2452 return "return avalon.filters.$filter(" + val + ", " + filters + ")"
\r
2455 function parseExpr(code, scopes, data) {
\r
2456 var dataType = data.type
\r
2457 var filters = data.filters || ""
\r
2458 var exprId = scopes.map(function (el) {
\r
2459 return String(el.$id).replace(rproxy, "$1")
\r
2460 }) + code + dataType + filters
\r
2461 var vars = getVariables(code).concat(),
\r
2466 //args 是一个对象数组, names 是将要生成的求值函数的参数
\r
2467 scopes = uniqSet(scopes)
\r
2469 for (var i = 0, sn = scopes.length; i < sn; i++) {
\r
2470 if (vars.length) {
\r
2471 var name = "vm" + expose + "_" + i
\r
2473 args.push(scopes[i])
\r
2474 assigns.push.apply(assigns, addAssign(vars, scopes[i], name, data))
\r
2477 if (!assigns.length && dataType === "duplex") {
\r
2480 if (dataType !== "duplex" && (code.indexOf("||") > -1 || code.indexOf("&&") > -1)) {
\r
2481 //https://github.com/RubyLouvre/avalon/issues/583
\r
2482 data.vars.forEach(function (v) {
\r
2483 var reg = new RegExp("\\b" + v + "(?:\\.\\w+|\\[\\w+\\])+", "ig")
\r
2484 code = code.replace(reg, function (_) {
\r
2485 var c = _.charAt(v.length)
\r
2486 var r = IEVersion ? code.slice(arguments[1] + _.length) : RegExp.rightContext
\r
2487 var method = /^\s*\(/.test(r)
\r
2488 if (c === "." || c === "[" || method) {//比如v为aa,我们只匹配aa.bb,aa[cc],不匹配aaa.xxx
\r
2489 var name = "var" + String(Math.random()).replace(/^0\./, "")
\r
2490 if (method) {//array.size()
\r
2491 var array = _.split(".")
\r
2492 if (array.length > 2) {
\r
2493 var last = array.pop()
\r
2494 assigns.push(name + " = " + array.join("."))
\r
2495 return name + "." + last
\r
2500 assigns.push(name + " = " + _)
\r
2508 //---------------args----------------
\r
2510 //---------------cache----------------
\r
2511 var fn = cacheExprs.get(exprId) //直接从缓存,免得重复生成
\r
2513 data.evaluator = fn
\r
2516 prefix = assigns.join(", ")
\r
2518 prefix = "var " + prefix
\r
2520 if (/\S/.test(filters)) { //文本绑定,双工绑定才有过滤器
\r
2521 if (!/text|html/.test(data.type)) {
\r
2522 throw Error("ms-" + data.type + "不支持过滤器")
\r
2524 code = "\nvar ret" + expose + " = " + code + ";\r\n"
\r
2525 code += parseFilter("ret" + expose, filters)
\r
2526 } else if (dataType === "duplex") { //双工绑定
\r
2527 var _body = "'use strict';\nreturn function(vvv){\n\t" +
\r
2529 ";\n\tif(!arguments.length){\n\t\treturn " +
\r
2531 "\n\t}\n\t" + (!rduplex.test(code) ? vars.get : code) +
\r
2534 fn = Function.apply(noop, names.concat(_body))
\r
2535 data.evaluator = cacheExprs.put(exprId, fn)
\r
2537 log("debug: parse error," + e.message)
\r
2540 } else if (dataType === "on") { //事件绑定
\r
2541 if (code.indexOf("(") === -1) {
\r
2542 code += ".call(this, $event)"
\r
2544 code = code.replace("(", ".call(this,")
\r
2546 names.push("$event")
\r
2547 code = "\nreturn " + code + ";" //IE全家 Function("return ")出错,需要Function("return ;")
\r
2548 var lastIndex = code.lastIndexOf("\nreturn")
\r
2549 var header = code.slice(0, lastIndex)
\r
2550 var footer = code.slice(lastIndex)
\r
2551 code = header + "\n" + footer
\r
2553 code = "\nreturn " + code + ";" //IE全家 Function("return ")出错,需要Function("return ;")
\r
2556 fn = Function.apply(noop, names.concat("'use strict';\n" + prefix + code))
\r
2557 data.evaluator = cacheExprs.put(exprId, fn)
\r
2559 log("debug: parse error," + e.message)
\r
2561 vars = assigns = names = null //释放内存
\r
2566 //parseExpr的智能引用代理
\r
2568 function parseExprProxy(code, scopes, data, tokens, noRegister) {
\r
2569 if (Array.isArray(tokens)) {
\r
2570 code = tokens.map(function (el) {
\r
2571 return el.expr ? "(" + el.value + ")" : quote(el.value)
\r
2574 parseExpr(code, scopes, data)
\r
2575 if (data.evaluator && !noRegister) {
\r
2576 data.handler = bindingExecutors[data.handlerName || data.type]
\r
2578 //这里非常重要,我们通过判定视图刷新函数的element是否在DOM树决定
\r
2580 avalon.injectBinding(data)
\r
2584 avalon.parseExprProxy = parseExprProxy
\r
2585 var bools = ["autofocus,autoplay,async,allowTransparency,checked,controls",
\r
2586 "declare,disabled,defer,defaultChecked,defaultSelected",
\r
2587 "contentEditable,isMap,loop,multiple,noHref,noResize,noShade",
\r
2588 "open,readOnly,selected"
\r
2591 bools.replace(rword, function (name) {
\r
2592 boolMap[name.toLowerCase()] = name
\r
2595 var propMap = { //属性名映射
\r
2596 "accept-charset": "acceptCharset",
\r
2598 "charoff": "chOff",
\r
2599 "class": "className",
\r
2601 "http-equiv": "httpEquiv"
\r
2604 var anomaly = ["accessKey,bgColor,cellPadding,cellSpacing,codeBase,codeType,colSpan",
\r
2605 "dateTime,defaultValue,frameBorder,longDesc,maxLength,marginWidth,marginHeight",
\r
2606 "rowSpan,tabIndex,useMap,vSpace,valueType,vAlign"
\r
2608 anomaly.replace(rword, function (name) {
\r
2609 propMap[name.toLowerCase()] = name
\r
2612 var rnoscripts = /<noscript.*?>(?:[\s\S]+?)<\/noscript>/img
\r
2613 var rnoscriptText = /<noscript.*?>([\s\S]+?)<\/noscript>/im
\r
2615 var getXHR = function () {
\r
2616 return new (window.XMLHttpRequest || ActiveXObject)("Microsoft.XMLHTTP") // jshint ignore:line
\r
2619 var cacheTmpls = avalon.templateCache = {}
\r
2621 bindingHandlers.attr = function (data, vmodels) {
\r
2622 var text = data.value.trim(),
\r
2624 if (text.indexOf(openTag) > -1 && text.indexOf(closeTag) > 2) {
\r
2626 if (rexpr.test(text) && RegExp.rightContext === "" && RegExp.leftContext === "") {
\r
2631 if (data.type === "include") {
\r
2632 var elem = data.element
\r
2633 data.includeRendered = getBindingCallback(elem, "data-include-rendered", vmodels)
\r
2634 data.includeLoaded = getBindingCallback(elem, "data-include-loaded", vmodels)
\r
2635 var outer = data.includeReplace = !!avalon(elem).data("includeReplace")
\r
2636 if (avalon(elem).data("includeCache")) {
\r
2637 data.templateCache = {}
\r
2639 data.startInclude = DOC.createComment("ms-include")
\r
2640 data.endInclude = DOC.createComment("ms-include-end")
\r
2642 data.element = data.startInclude
\r
2643 elem.parentNode.insertBefore(data.startInclude, elem)
\r
2644 elem.parentNode.insertBefore(data.endInclude, elem.nextSibling)
\r
2646 elem.insertBefore(data.startInclude, elem.firstChild)
\r
2647 elem.appendChild(data.endInclude)
\r
2650 data.handlerName = "attr" //handleName用于处理多种绑定共用同一种bindingExecutor的情况
\r
2651 parseExprProxy(text, vmodels, data, (simple ? 0 : scanExpr(data.value)))
\r
2654 bindingExecutors.attr = function (val, elem, data) {
\r
2655 var method = data.type,
\r
2656 attrName = data.param
\r
2657 if (method === "css") {
\r
2658 avalon(elem).css(attrName, val)
\r
2659 } else if (method === "attr") {
\r
2660 // ms-attr-class="xxx" vm.xxx="aaa bbb ccc"将元素的className设置为aaa bbb ccc
\r
2661 // ms-attr-class="xxx" vm.xxx=false 清空元素的所有类名
\r
2662 // ms-attr-name="yyy" vm.yyy="ooo" 为元素设置name属性
\r
2663 var toRemove = (val === false) || (val === null) || (val === void 0)
\r
2665 if (!W3C && propMap[attrName]) { //旧式IE下需要进行名字映射
\r
2666 attrName = propMap[attrName]
\r
2668 var bool = boolMap[attrName]
\r
2669 if (typeof elem[bool] === "boolean") {
\r
2670 elem[bool] = !!val //布尔属性必须使用el.xxx = true|false方式设值
\r
2671 if (!val) { //如果为false, IE全系列下相当于setAttribute(xxx,''),会影响到样式,需要进一步处理
\r
2676 return elem.removeAttribute(attrName)
\r
2679 //SVG只能使用setAttribute(xxx, yyy), VML只能使用elem.xxx = yyy ,HTML的固有属性必须elem.xxx = yyy
\r
2680 var isInnate = rsvg.test(elem) ? false : (DOC.namespaces && isVML(elem)) ? true : attrName in elem.cloneNode(false)
\r
2682 elem[attrName] = val
\r
2684 elem.setAttribute(attrName, val)
\r
2686 } else if (method === "include" && val) {
\r
2687 var vmodels = data.vmodels
\r
2688 var rendered = data.includeRendered
\r
2689 var loaded = data.includeLoaded
\r
2690 var replace = data.includeReplace
\r
2691 var target = replace ? elem.parentNode : elem
\r
2692 var scanTemplate = function (text) {
\r
2694 var newText = loaded.apply(target, [text].concat(vmodels))
\r
2695 if (typeof newText === "string")
\r
2699 checkScan(target, function () {
\r
2700 rendered.call(target)
\r
2703 var lastID = data.includeLastID
\r
2704 if (data.templateCache && lastID && lastID !== val) {
\r
2705 var lastTemplate = data.templateCache[lastID]
\r
2706 if (!lastTemplate) {
\r
2707 lastTemplate = data.templateCache[lastID] = DOC.createElement("div")
\r
2708 ifGroup.appendChild(lastTemplate)
\r
2711 data.includeLastID = val
\r
2713 var node = data.startInclude.nextSibling
\r
2714 if (node && node !== data.endInclude) {
\r
2715 target.removeChild(node)
\r
2717 lastTemplate.appendChild(node)
\r
2722 var dom = getTemplateNodes(data, val, text)
\r
2723 var nodes = avalon.slice(dom.childNodes)
\r
2724 target.insertBefore(dom, data.endInclude)
\r
2725 scanNodeArray(nodes, vmodels)
\r
2728 if (data.param === "src") {
\r
2729 if (typeof cacheTmpls[val] === "string") {
\r
2730 avalon.nextTick(function () {
\r
2731 scanTemplate(cacheTmpls[val])
\r
2733 } else if (Array.isArray(cacheTmpls[val])) { //#805 防止在循环绑定中发出许多相同的请求
\r
2734 cacheTmpls[val].push(scanTemplate)
\r
2736 var xhr = getXHR()
\r
2737 xhr.onreadystatechange = function () {
\r
2738 if (xhr.readyState === 4) {
\r
2739 var s = xhr.status
\r
2740 if (s >= 200 && s < 300 || s === 304 || s === 1223) {
\r
2741 var text = xhr.responseText
\r
2742 for (var f = 0, fn; fn = cacheTmpls[val][f++];) {
\r
2745 cacheTmpls[val] = text
\r
2749 cacheTmpls[val] = [scanTemplate]
\r
2750 xhr.open("GET", val, true)
\r
2751 if ("withCredentials" in xhr) {
\r
2752 xhr.withCredentials = true
\r
2754 xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest")
\r
2758 //IE系列与够新的标准浏览器支持通过ID取得元素(firefox14+)
\r
2759 //http://tjvantoll.com/2012/07/19/dom-element-references-as-global-variables/
\r
2760 var el = val && val.nodeType === 1 ? val : DOC.getElementById(val)
\r
2762 if (el.tagName === "NOSCRIPT" && !(el.innerHTML || el.fixIE78)) { //IE7-8 innerText,innerHTML都无法取得其内容,IE6能取得其innerHTML
\r
2763 xhr = getXHR() //IE9-11与chrome的innerHTML会得到转义的内容,它们的innerText可以
\r
2764 xhr.open("GET", location, false) //谢谢Nodejs 乱炖群 深圳-纯属虚构
\r
2766 //http://bbs.csdn.net/topics/390349046?page=1#post-393492653
\r
2767 var noscripts = DOC.getElementsByTagName("noscript")
\r
2768 var array = (xhr.responseText || "").match(rnoscripts) || []
\r
2769 var n = array.length
\r
2770 for (var i = 0; i < n; i++) {
\r
2771 var tag = noscripts[i]
\r
2772 if (tag) { //IE6-8中noscript标签的innerHTML,innerText是只读的
\r
2773 tag.style.display = "none" //http://haslayout.net/css/noscript-Ghost-Bug
\r
2774 tag.fixIE78 = (array[i].match(rnoscriptText) || ["", " "])[1]
\r
2778 avalon.nextTick(function () {
\r
2779 scanTemplate(el.fixIE78 || el.value || el.innerText || el.innerHTML)
\r
2784 if (!root.hasAttribute && typeof val === "string" && (method === "src" || method === "href")) {
\r
2785 val = val.replace(/&/g, "&") //处理IE67自动转义的问题
\r
2787 elem[method] = val
\r
2788 if (window.chrome && elem.tagName === "EMBED") {
\r
2789 var parent = elem.parentNode //#525 chrome1-37下embed标签动态设置src不能发生请求
\r
2790 var comment = document.createComment("ms-src")
\r
2791 parent.replaceChild(comment, elem)
\r
2792 parent.replaceChild(elem, comment)
\r
2797 function getTemplateNodes(data, id, text) {
\r
2798 var div = data.templateCache && data.templateCache[id]
\r
2800 var dom = DOC.createDocumentFragment(),
\r
2802 while (firstChild = div.firstChild) {
\r
2803 dom.appendChild(firstChild)
\r
2807 return avalon.parseHTML(text)
\r
2810 //这几个指令都可以使用插值表达式,如ms-src="aaa/{{b}}/{{c}}.html"
\r
2811 "title,alt,src,value,css,include,href".replace(rword, function (name) {
\r
2812 bindingHandlers[name] = bindingHandlers.attr
\r
2814 //根据VM的属性值或表达式的值切换类名,ms-class="xxx yyy zzz:flag"
\r
2815 //http://www.cnblogs.com/rubylouvre/archive/2012/12/17/2818540.html
\r
2816 bindingHandlers["class"] = function (data, vmodels) {
\r
2817 var oldStyle = data.param,
\r
2818 text = data.value,
\r
2820 data.handlerName = "class"
\r
2821 if (!oldStyle || isFinite(oldStyle)) {
\r
2822 data.param = "" //去掉数字
\r
2823 var noExpr = text.replace(rexprg, function (a) {
\r
2824 return a.replace(/./g, "0")
\r
2825 //return Math.pow(10, a.length - 1) //将插值表达式插入10的N-1次方来占位
\r
2827 var colonIndex = noExpr.indexOf(":") //取得第一个冒号的位置
\r
2828 if (colonIndex === -1) { // 比如 ms-class="aaa bbb ccc" 的情况
\r
2829 var className = text
\r
2830 } else { // 比如 ms-class-1="ui-state-active:checked" 的情况
\r
2831 className = text.slice(0, colonIndex)
\r
2832 rightExpr = text.slice(colonIndex + 1)
\r
2833 parseExpr(rightExpr, vmodels, data) //决定是添加还是删除
\r
2834 if (!data.evaluator) {
\r
2835 log("debug: ms-class '" + (rightExpr || "").trim() + "' 不存在于VM中")
\r
2838 data._evaluator = data.evaluator
\r
2839 data._args = data.args
\r
2842 var hasExpr = rexpr.test(className) //比如ms-class="width{{w}}"的情况
\r
2844 data.immobileClass = className
\r
2846 parseExprProxy("", vmodels, data, (hasExpr ? scanExpr(className) : 0))
\r
2848 data.immobileClass = data.oldStyle = data.param
\r
2849 parseExprProxy(text, vmodels, data)
\r
2853 bindingExecutors["class"] = function (val, elem, data) {
\r
2854 var $elem = avalon(elem),
\r
2855 method = data.type
\r
2856 if (method === "class" && data.oldStyle) { //如果是旧风格
\r
2857 $elem.toggleClass(data.oldStyle, !!val)
\r
2860 data.toggleClass = data._evaluator ? !!data._evaluator.apply(elem, data._args) : true
\r
2861 data.newClass = data.immobileClass || val
\r
2862 if (data.oldClass && data.newClass !== data.oldClass) {
\r
2863 $elem.removeClass(data.oldClass)
\r
2865 data.oldClass = data.newClass
\r
2868 $elem.toggleClass(data.newClass, data.toggleClass)
\r
2872 if (!data.hasBindEvent) { //确保只绑定一次
\r
2873 var activate = "mouseenter" //在移出移入时切换类名
\r
2874 var abandon = "mouseleave"
\r
2875 if (method === "active") { //在聚焦失焦中切换类名
\r
2876 elem.tabIndex = elem.tabIndex || -1
\r
2877 activate = "mousedown"
\r
2878 abandon = "mouseup"
\r
2879 var fn0 = $elem.bind("mouseleave", function () {
\r
2880 data.toggleClass && $elem.removeClass(data.newClass)
\r
2883 var fn1 = $elem.bind(activate, function () {
\r
2884 data.toggleClass && $elem.addClass(data.newClass)
\r
2886 var fn2 = $elem.bind(abandon, function () {
\r
2887 data.toggleClass && $elem.removeClass(data.newClass)
\r
2889 data.rollback = function () {
\r
2890 $elem.unbind("mouseleave", fn0)
\r
2891 $elem.unbind(activate, fn1)
\r
2892 $elem.unbind(abandon, fn2)
\r
2894 data.hasBindEvent = true
\r
2901 "hover,active".replace(rword, function (method) {
\r
2902 bindingHandlers[method] = bindingHandlers["class"]
\r
2904 //ms-controller绑定已经在scanTag 方法中实现
\r
2905 //ms-css绑定已由ms-attr绑定实现
\r
2908 // bindingHandlers.data 定义在if.js
\r
2909 bindingExecutors.data = function (val, elem, data) {
\r
2910 var key = "data-" + data.param
\r
2911 if (val && typeof val === "object") {
\r
2914 elem.setAttribute(key, String(val))
\r
2918 var duplexBinding = bindingHandlers.duplex = function (data, vmodels) {
\r
2919 var elem = data.element,
\r
2921 parseExprProxy(data.value, vmodels, data, 0, 1)
\r
2923 data.changed = getBindingCallback(elem, "data-duplex-changed", vmodels) || noop
\r
2924 if (data.evaluator && data.args) {
\r
2926 var casting = oneObject("string,number,boolean,checked")
\r
2927 if (elem.type === "radio" && data.param === "") {
\r
2928 data.param = "checked"
\r
2930 if (elem.msData) {
\r
2931 elem.msData["ms-duplex"] = data.value
\r
2933 data.param.replace(/\w+/g, function (name) {
\r
2934 if (/^(checkbox|radio)$/.test(elem.type) && /^(radio|checked)$/.test(name)) {
\r
2935 if (name === "radio")
\r
2936 log("ms-duplex-radio已经更名为ms-duplex-checked")
\r
2938 data.isChecked = true
\r
2940 if (name === "bool") {
\r
2942 log("ms-duplex-bool已经更名为ms-duplex-boolean")
\r
2943 } else if (name === "text") {
\r
2945 log("ms-duplex-text已经更名为ms-duplex-string")
\r
2947 if (casting[name]) {
\r
2950 avalon.Array.ensure(params, name)
\r
2953 params.push("string")
\r
2955 data.param = params.join("-")
\r
2956 data.bound = function (type, callback) {
\r
2957 if (elem.addEventListener) {
\r
2958 elem.addEventListener(type, callback, false)
\r
2960 elem.attachEvent("on" + type, callback)
\r
2962 var old = data.rollback
\r
2963 data.rollback = function () {
\r
2964 elem.avalonSetter = null
\r
2965 avalon.unbind(elem, type, callback)
\r
2969 for (var i in avalon.vmodels) {
\r
2970 var v = avalon.vmodels[i]
\r
2971 v.$fire("avalon-ms-duplex-init", data)
\r
2973 var cpipe = data.pipe || (data.pipe = pipe)
\r
2974 cpipe(null, data, "init")
\r
2975 var tagName = elem.tagName
\r
2976 duplexBinding[tagName] && duplexBinding[tagName](elem, data.evaluator.apply(null, data.args), data)
\r
2979 //不存在 bindingExecutors.duplex
\r
2981 function fixNull(val) {
\r
2982 return val == null ? "" : val
\r
2985 avalon.duplexHooks = {
\r
2987 get: function (val, data) {
\r
2988 return !data.element.oldValue
\r
2992 get: function (val) { //同步到VM
\r
2998 get: function (val) {
\r
2999 return val === "true"
\r
3004 get: function (val, data) {
\r
3005 var number = parseFloat(val)
\r
3006 if (-val === -number) {
\r
3009 var arr = /strong|medium|weak/.exec(data.element.getAttribute("data-duplex-number")) || ["medium"]
\r
3014 return val === "" ? "" : 0
\r
3023 function pipe(val, data, action, e) {
\r
3024 data.param.replace(/\w+/g, function (name) {
\r
3025 var hook = avalon.duplexHooks[name]
\r
3026 if (hook && typeof hook[action] === "function") {
\r
3027 val = hook[action](val, data)
\r
3033 var TimerID, ribbon = []
\r
3035 avalon.tick = function (fn) {
\r
3036 if (ribbon.push(fn) === 1) {
\r
3037 TimerID = setInterval(ticker, 60)
\r
3041 function ticker() {
\r
3042 for (var n = ribbon.length - 1; n >= 0; n--) {
\r
3043 var el = ribbon[n]
\r
3044 if (el() === false) {
\r
3045 ribbon.splice(n, 1)
\r
3048 if (!ribbon.length) {
\r
3049 clearInterval(TimerID)
\r
3053 var watchValueInTimer = noop
\r
3054 var rmsinput = /text|password|hidden/
\r
3055 new function () { // jshint ignore:line
\r
3056 try { //#272 IE9-IE11, firefox
\r
3058 var aproto = HTMLInputElement.prototype
\r
3059 var bproto = HTMLTextAreaElement.prototype
\r
3061 function newSetter(value) { // jshint ignore:line
\r
3062 if (avalon.contains(root, this)) {
\r
3063 setters[this.tagName].call(this, value)
\r
3064 if (!rmsinput.test(this.type))
\r
3066 if (!this.msFocus && this.avalonSetter) {
\r
3067 this.avalonSetter()
\r
3072 var inputProto = HTMLInputElement.prototype
\r
3073 Object.getOwnPropertyNames(inputProto) //故意引发IE6-8等浏览器报错
\r
3074 setters["INPUT"] = Object.getOwnPropertyDescriptor(aproto, "value").set
\r
3075 Object.defineProperty(aproto, "value", {
\r
3078 setters["TEXTAREA"] = Object.getOwnPropertyDescriptor(bproto, "value").set
\r
3079 Object.defineProperty(bproto, "value", {
\r
3083 //在chrome 43中 ms-duplex终于不需要使用定时器实现双向绑定了
\r
3084 // http://updates.html5rocks.com/2015/04/DOM-attributes-now-on-the-prototype
\r
3085 // https://docs.google.com/document/d/1jwA8mtClwxI-QJuHT7872Z0pxpZz8PBkf2bGAbsUtqs/edit?pli=1
\r
3086 watchValueInTimer = avalon.tick
\r
3088 } // jshint ignore:line
\r
3089 //处理radio, checkbox, text, textarea, password
\r
3090 duplexBinding.INPUT = function (element, evaluator, data) {
\r
3091 var $type = element.type,
\r
3092 bound = data.bound,
\r
3093 $elem = avalon(element),
\r
3096 function callback(value) {
\r
3097 data.changed.call(this, value, data)
\r
3100 function compositionStart() {
\r
3104 function compositionEnd() {
\r
3108 //当value变化时改变model的值
\r
3110 var updateVModel = function () {
\r
3111 if (composing) //处理中文输入法在minlengh下引发的BUG
\r
3113 var val = element.oldValue = element.value //防止递归调用形成死循环
\r
3114 var lastValue = data.pipe(val, data, "get")
\r
3115 if ($elem.data("duplexObserve") !== false) {
\r
3116 evaluator(lastValue)
\r
3117 callback.call(element, lastValue)
\r
3118 if ($elem.data("duplex-focus")) {
\r
3119 avalon.nextTick(function () {
\r
3125 //当model变化时,它就会改变value的值
\r
3126 data.handler = function () {
\r
3127 var val = data.pipe(evaluator(), data, "set") + ""
\r
3128 if (val !== element.oldValue) {
\r
3129 element.value = val
\r
3132 if (data.isChecked || $type === "radio") {
\r
3133 updateVModel = function () {
\r
3134 if ($elem.data("duplexObserve") !== false) {
\r
3135 var lastValue = data.pipe(element.value, data, "get")
\r
3136 evaluator(lastValue)
\r
3137 callback.call(element, lastValue)
\r
3140 data.handler = function () {
\r
3141 var val = evaluator()
\r
3142 var checked = data.isChecked ? !!val : val + "" === element.value
\r
3143 element.checked = element.oldValue = checked
\r
3145 bound("click", updateVModel)
\r
3146 } else if ($type === "checkbox") {
\r
3147 updateVModel = function () {
\r
3148 if ($elem.data("duplexObserve") !== false) {
\r
3149 var method = element.checked ? "ensure" : "remove"
\r
3150 var array = evaluator()
\r
3151 if (!Array.isArray(array)) {
\r
3152 log("ms-duplex应用于checkbox上要对应一个数组")
\r
3155 avalon.Array[method](array, data.pipe(element.value, data, "get"))
\r
3156 callback.call(element, array)
\r
3159 data.handler = function () {
\r
3160 var array = [].concat(evaluator()) //强制转换为数组
\r
3161 element.checked = array.indexOf(data.pipe(element.value, data, "get")) > -1
\r
3163 bound("change", updateVModel)
\r
3165 var events = element.getAttribute("data-duplex-event") || "input"
\r
3166 if (element.attributes["data-event"]) {
\r
3167 log("data-event指令已经废弃,请改用data-duplex-event")
\r
3169 events.replace(rword, function (name) {
\r
3172 bound("input", updateVModel)
\r
3173 bound("DOMAutoComplete", updateVModel)
\r
3175 bound("compositionstart", compositionStart)
\r
3176 bound("compositionend", compositionEnd)
\r
3180 bound(name, updateVModel)
\r
3184 bound("focus", function () {
\r
3185 element.msFocus = true
\r
3187 bound("blur", function () {
\r
3188 element.msFocus = false
\r
3190 if (rmsinput.test($type)) {
\r
3191 watchValueInTimer(function () {
\r
3192 if (root.contains(element)) {
\r
3193 if (!element.msFocus && element.oldValue !== element.value) {
\r
3196 } else if (!element.msRetain) {
\r
3202 element.avalonSetter = updateVModel
\r
3205 element.oldValue = element.value
\r
3206 avalon.injectBinding(data)
\r
3207 callback.call(element, element.value)
\r
3209 duplexBinding.TEXTAREA = duplexBinding.INPUT
\r
3210 duplexBinding.SELECT = function (element, evaluator, data) {
\r
3211 var $elem = avalon(element)
\r
3213 function updateVModel() {
\r
3214 if ($elem.data("duplexObserve") !== false) {
\r
3215 var val = $elem.val() //字符串或字符串数组
\r
3216 if (Array.isArray(val)) {
\r
3217 val = val.map(function (v) {
\r
3218 return data.pipe(v, data, "get")
\r
3221 val = data.pipe(val, data, "get")
\r
3223 if (val + "" !== element.oldValue) {
\r
3226 data.changed.call(element, val, data)
\r
3230 data.handler = function () {
\r
3231 var val = evaluator()
\r
3232 val = val && val.$model || val
\r
3233 if (Array.isArray(val)) {
\r
3234 if (!element.multiple) {
\r
3235 log("ms-duplex在<select multiple=true>上要求对应一个数组")
\r
3238 if (element.multiple) {
\r
3239 log("ms-duplex在<select multiple=false>不能对应一个数组")
\r
3243 val = Array.isArray(val) ? val.map(String) : val + ""
\r
3244 if (val + "" !== element.oldValue) {
\r
3246 element.oldValue = val + ""
\r
3249 data.bound("change", updateVModel)
\r
3250 element.msCallback = function () {
\r
3251 avalon.injectBinding(data)
\r
3252 data.changed.call(element, evaluator(), data)
\r
3255 // bindingHandlers.html 定义在if.js
\r
3256 bindingExecutors.html = function (val, elem, data) {
\r
3257 val = val == null ? "" : val
\r
3258 var isHtmlFilter = "group" in data
\r
3259 var parent = isHtmlFilter ? elem.parentNode : elem
\r
3262 if (typeof val === "string") {
\r
3263 var fragment = avalon.parseHTML(val)
\r
3264 } else if (val.nodeType === 11) { //将val转换为文档碎片
\r
3266 } else if (val.nodeType === 1 || val.item) {
\r
3267 var nodes = val.nodeType === 1 ? val.childNodes : val.item
\r
3268 fragment = hyperspace.cloneNode(true)
\r
3269 while (nodes[0]) {
\r
3270 fragment.appendChild(nodes[0])
\r
3273 if (!fragment.firstChild) {
\r
3274 fragment.appendChild(DOC.createComment("ms-html"))
\r
3276 nodes = avalon.slice(fragment.childNodes)
\r
3277 //插入占位符, 如果是过滤器,需要有节制地移除指定的数量,如果是html指令,直接清空
\r
3278 if (isHtmlFilter) {
\r
3279 var n = data.group,
\r
3282 data.group = nodes.length
\r
3283 data.element = nodes[0]
\r
3286 var node = elem.nextSibling
\r
3288 parent.removeChild(node)
\r
3292 parent.replaceChild(fragment, elem)
\r
3294 avalon.clearHTML(parent).appendChild(fragment)
\r
3296 scanNodeArray(nodes, data.vmodels)
\r
3298 bindingHandlers["if"] =
\r
3299 bindingHandlers.data =
\r
3300 bindingHandlers.text =
\r
3301 bindingHandlers.html =
\r
3302 function (data, vmodels) {
\r
3303 parseExprProxy(data.value, vmodels, data)
\r
3306 bindingExecutors["if"] = function (val, elem, data) {
\r
3307 if (val) { //插回DOM树
\r
3308 if (elem.nodeType === 8) {
\r
3309 elem.parentNode.replaceChild(data.template, elem)
\r
3310 elem = data.element = data.template //这时可能为null
\r
3312 if (elem.getAttribute(data.name)) {
\r
3313 elem.removeAttribute(data.name)
\r
3314 scanAttr(elem, data.vmodels)
\r
3316 data.rollback = null
\r
3317 } else { //移出DOM树,并用注释节点占据原位置
\r
3318 if (elem.nodeType === 1) {
\r
3319 var node = data.element = DOC.createComment("ms-if")
\r
3320 elem.parentNode.replaceChild(node, elem)
\r
3321 data.template = elem //元素节点
\r
3322 ifGroup.appendChild(elem)
\r
3323 data.rollback = function () {
\r
3324 if (elem.parentNode === ifGroup) {
\r
3325 ifGroup.removeChild(elem)
\r
3331 //ms-important绑定已经在scanTag 方法中实现
\r
3332 //ms-include绑定已由ms-attr绑定实现
\r
3334 var rdash = /\(([^)]*)\)/
\r
3335 bindingHandlers.on = function (data, vmodels) {
\r
3336 var value = data.value
\r
3338 var eventType = data.param.replace(/-\d+$/, "") // ms-on-mousemove-10
\r
3339 if (typeof bindingHandlers.on[eventType + "Hook"] === "function") {
\r
3340 bindingHandlers.on[eventType + "Hook"](data)
\r
3342 if (value.indexOf("(") > 0 && value.indexOf(")") > -1) {
\r
3343 var matched = (value.match(rdash) || ["", ""])[1].trim()
\r
3344 if (matched === "" || matched === "$event") { // aaa() aaa($event)当成aaa处理
\r
3345 value = value.replace(rdash, "")
\r
3348 parseExprProxy(value, vmodels, data)
\r
3351 bindingExecutors.on = function (callback, elem, data) {
\r
3352 callback = function (e) {
\r
3353 var fn = data.evaluator || noop
\r
3354 return fn.apply(this, data.args.concat(e))
\r
3356 var eventType = data.param.replace(/-\d+$/, "") // ms-on-mousemove-10
\r
3357 if (eventType === "scan") {
\r
3358 callback.call(elem, {
\r
3361 } else if (typeof data.specialBind === "function") {
\r
3362 data.specialBind(elem, callback)
\r
3364 var removeFn = avalon.bind(elem, eventType, callback)
\r
3366 data.rollback = function () {
\r
3367 if (typeof data.specialUnbind === "function") {
\r
3368 data.specialUnbind()
\r
3370 avalon.unbind(elem, eventType, removeFn)
\r
3374 bindingHandlers.repeat = function (data, vmodels) {
\r
3375 var type = data.type
\r
3376 parseExprProxy(data.value, vmodels, data, 0, 1)
\r
3377 var freturn = false
\r
3379 var $repeat = data.$repeat = data.evaluator.apply(0, data.args || [])
\r
3380 var xtype = avalon.type($repeat)
\r
3381 if (xtype !== "object" && xtype !== "array") {
\r
3383 avalon.log("warning:" + data.value + "只能是对象或数组")
\r
3389 var arr = data.value.split(".") || []
\r
3390 if (arr.length > 1) {
\r
3393 for (var i = 0, v; v = vmodels[i++];) {
\r
3394 if (v && v.hasOwnProperty(n)) {
\r
3395 var events = v[n].$events || {}
\r
3396 events[subscribers] = events[subscribers] || []
\r
3397 events[subscribers].push(data)
\r
3402 var elem = data.element
\r
3403 elem.removeAttribute(data.name)
\r
3405 data.sortedCallback = getBindingCallback(elem, "data-with-sorted", vmodels)
\r
3406 data.renderedCallback = getBindingCallback(elem, "data-" + type + "-rendered", vmodels)
\r
3407 var signature = generateID(type)
\r
3408 var comment = data.element = DOC.createComment(signature + ":end")
\r
3409 data.clone = DOC.createComment(signature)
\r
3410 hyperspace.appendChild(comment)
\r
3412 if (type === "each" || type === "with") {
\r
3413 data.template = elem.innerHTML.trim()
\r
3414 avalon.clearHTML(elem).appendChild(comment)
\r
3416 data.template = elem.outerHTML.trim()
\r
3417 elem.parentNode.replaceChild(comment, elem)
\r
3419 data.template = avalon.parseHTML(data.template)
\r
3420 data.rollback = function () {
\r
3421 var elem = data.element
\r
3424 bindingExecutors.repeat.call(data, "clear")
\r
3425 var parentNode = elem.parentNode
\r
3426 var content = data.template
\r
3427 var target = content.firstChild
\r
3428 parentNode.replaceChild(content, elem)
\r
3429 var start = data.$with
\r
3430 start && start.parentNode && start.parentNode.removeChild(start)
\r
3431 target = data.element = data.type === "repeat" ? target : parentNode
\r
3436 data.handler = bindingExecutors.repeat
\r
3438 var check0 = "$key"
\r
3439 var check1 = "$val"
\r
3440 if (Array.isArray($repeat)) {
\r
3441 $repeat.$map[data.param || "el"] = 1
\r
3445 for (i = 0; v = vmodels[i++];) {
\r
3446 if (v.hasOwnProperty(check0) && v.hasOwnProperty(check1)) {
\r
3451 var $events = $repeat.$events
\r
3452 var $list = ($events || {})[subscribers]
\r
3453 if ($list && avalon.Array.ensure($list, data)) {
\r
3454 addSubscribers(data, $list)
\r
3456 if (xtype === "object") {
\r
3458 var pool = !$events ? {} : $events.$withProxyPool || ($events.$withProxyPool = {})
\r
3459 data.handler("append", $repeat, pool)
\r
3460 } else if ($repeat.length) {
\r
3461 data.handler("add", 0, $repeat.length)
\r
3465 bindingExecutors.repeat = function (method, pos, el) {
\r
3467 var data = this, start, fragment
\r
3468 var end = data.element
\r
3469 var comments = getComments(data)
\r
3470 var parent = end.parentNode
\r
3471 var transation = hyperspace.cloneNode(false)
\r
3473 case "add": //在pos位置后添加el数组(pos为插入位置,el为要插入的个数)
\r
3475 var fragments = []
\r
3476 var array = data.$repeat
\r
3477 for (var i = pos; i < n; i++) {
\r
3478 var proxy = array.$proxy[i]
\r
3479 proxy.$outer = data.$outer
\r
3480 shimController(data, transation, proxy, fragments)
\r
3482 parent.insertBefore(transation, comments[pos] || end)
\r
3483 for (i = 0; fragment = fragments[i++];) {
\r
3484 scanNodeArray(fragment.nodes, fragment.vmodels)
\r
3485 fragment.nodes = fragment.vmodels = null
\r
3488 case "del": //将pos后的el个元素删掉(pos, el都是数字)
\r
3489 sweepNodes(comments[pos], comments[pos + el] || end)
\r
3492 start = comments[0]
\r
3494 sweepNodes(start, end)
\r
3498 start = comments[0]
\r
3500 var signature = start.nodeValue
\r
3504 sweepNodes(start, end, function () {
\r
3505 room.unshift(this)
\r
3506 if (this.nodeValue === signature) {
\r
3507 rooms.unshift(room)
\r
3511 sortByIndex(rooms, pos)
\r
3512 while (room = rooms.shift()) {
\r
3513 while (node = room.shift()) {
\r
3514 transation.appendChild(node)
\r
3517 parent.insertBefore(transation, end)
\r
3520 case "append": //将pos的键值对从el中取出(pos为一个普通对象,el为预先生成好的代理VM对象池)
\r
3524 for (var key in pos) { //得到所有键名
\r
3525 if (pos.hasOwnProperty(key) && key !== "hasOwnProperty") {
\r
3529 if (data.sortedCallback) { //如果有回调,则让它们排序
\r
3530 var keys2 = data.sortedCallback.call(parent, keys)
\r
3531 if (keys2 && Array.isArray(keys2) && keys2.length) {
\r
3535 for (i = 0; key = keys[i++];) {
\r
3536 if (key !== "hasOwnProperty") {
\r
3538 pool[key] = withProxyAgent(key, data)
\r
3540 shimController(data, transation, pool[key], fragments)
\r
3543 var comment = data.$with = data.clone
\r
3544 parent.insertBefore(comment, end)
\r
3545 parent.insertBefore(transation, end)
\r
3546 for (i = 0; fragment = fragments[i++];) {
\r
3547 scanNodeArray(fragment.nodes, fragment.vmodels)
\r
3548 fragment.nodes = fragment.vmodels = null
\r
3552 if (method === "clear")
\r
3554 var callback = data.renderedCallback || noop,
\r
3556 checkScan(parent, function () {
\r
3557 callback.apply(parent, args)
\r
3558 if (parent.oldValue && parent.tagName === "SELECT") { //fix #503
\r
3559 avalon(parent).val(parent.oldValue.split(","))
\r
3565 "with,each".replace(rword, function (name) {
\r
3566 bindingHandlers[name] = bindingHandlers.repeat
\r
3569 function shimController(data, transation, proxy, fragments) {
\r
3570 var content = data.template.cloneNode(true)
\r
3571 var nodes = avalon.slice(content.childNodes)
\r
3572 if (!data.$with) {
\r
3573 content.insertBefore(data.clone.cloneNode(false), content.firstChild)
\r
3575 transation.appendChild(content)
\r
3576 var nv = [proxy].concat(data.vmodels)
\r
3581 fragments.push(fragment)
\r
3584 function getComments(data) {
\r
3585 var end = data.element
\r
3586 var signature = end.nodeValue.replace(":end", "")
\r
3587 var node = end.previousSibling
\r
3590 if (node.nodeValue === signature) {
\r
3591 array.unshift(node)
\r
3593 node = node.previousSibling
\r
3599 //移除掉start与end之间的节点(保留end)
\r
3600 function sweepNodes(start, end, callback) {
\r
3602 var node = end.previousSibling
\r
3605 node.parentNode.removeChild(node)
\r
3606 callback && callback.call(node)
\r
3607 if (node === start) {
\r
3613 // 为ms-each,ms-with, ms-repeat会创建一个代理VM,
\r
3614 // 通过它们保持一个下上文,让用户能调用$index,$first,$last,$remove,$key,$val,$outer等属性与方法
\r
3615 // 所有代理VM的产生,消费,收集,存放通过xxxProxyFactory,xxxProxyAgent, recycleProxies,xxxProxyPool实现
\r
3616 var eachProxyPool = []
\r
3617 var withProxyPool = []
\r
3619 function eachProxyFactory() {
\r
3627 $remove: avalon.noop,
\r
3629 get: function () {
\r
3630 var e = this.$events
\r
3631 var array = e.$index
\r
3632 e.$index = e.el //#817 通过$index为el收集依赖
\r
3634 return this.$host[this.$index]
\r
3639 set: function (val) {
\r
3640 this.$host.set(this.$index, val)
\r
3650 var proxy = modelFactory(source, second)
\r
3651 proxy.$id = generateID("$proxy$each")
\r
3655 function eachProxyAgent(index, host) {
\r
3656 var proxy = eachProxyPool.shift()
\r
3658 proxy = eachProxyFactory()
\r
3660 var last = host.length - 1
\r
3661 proxy.$index = index
\r
3662 proxy.$first = index === 0
\r
3663 proxy.$last = index === last
\r
3664 proxy.$map = host.$map
\r
3665 proxy.$host = host
\r
3666 proxy.$remove = function () {
\r
3667 return host.removeAt(proxy.$index)
\r
3672 function withProxyFactory() {
\r
3673 var proxy = modelFactory({
\r
3678 get: function () {
\r
3679 return this.$host[this.$key]
\r
3681 set: function (val) {
\r
3682 this.$host[this.$key] = val
\r
3688 proxy.$id = generateID("$proxy$with")
\r
3692 function withProxyAgent(key, data) {
\r
3693 var proxy = withProxyPool.pop()
\r
3695 proxy = withProxyFactory()
\r
3697 var host = data.$repeat
\r
3699 proxy.$host = host
\r
3700 proxy.$outer = data.$outer
\r
3701 if (host.$events) {
\r
3702 proxy.$events.$val = host.$events[key]
\r
3704 proxy.$events = {}
\r
3709 function recycleProxies(proxies, type) {
\r
3710 var proxyPool = type === "each" ? eachProxyPool : withProxyPool
\r
3711 avalon.each(proxies, function (key, proxy) {
\r
3712 if (proxy.$events) {
\r
3713 for (var i in proxy.$events) {
\r
3714 if (Array.isArray(proxy.$events[i])) {
\r
3715 proxy.$events[i].forEach(function (data) {
\r
3716 if (typeof data === "object")
\r
3718 }) // jshint ignore:line
\r
3719 proxy.$events[i].length = 0
\r
3722 proxy.$host = proxy.$outer = {}
\r
3723 if (proxyPool.unshift(proxy) > kernel.maxRepeatSize) {
\r
3728 if (type === "each")
\r
3729 proxies.length = 0
\r
3732 /*********************************************************************
\r
3734 **********************************************************************/
\r
3735 //ms-skip绑定已经在scanTag 方法中实现
\r
3736 // bindingHandlers.text 定义在if.js
\r
3737 bindingExecutors.text = function (val, elem) {
\r
3738 val = val == null ? "" : val //不在页面上显示undefined null
\r
3739 if (elem.nodeType === 3) { //绑定在文本节点上
\r
3740 try { //IE对游离于DOM树外的节点赋值会报错
\r
3744 } else { //绑定在特性节点上
\r
3745 elem.textContent = val
\r
3748 function parseDisplay(nodeName, val) {
\r
3749 //用于取得此类标签的默认display值
\r
3750 var key = "_" + nodeName
\r
3751 if (!parseDisplay[key]) {
\r
3752 var node = DOC.createElement(nodeName)
\r
3753 root.appendChild(node)
\r
3755 val = getComputedStyle(node, null).display
\r
3757 val = node.currentStyle.display
\r
3759 root.removeChild(node)
\r
3760 parseDisplay[key] = val
\r
3762 return parseDisplay[key]
\r
3765 avalon.parseDisplay = parseDisplay
\r
3767 bindingHandlers.visible = function (data, vmodels) {
\r
3768 var elem = avalon(data.element)
\r
3769 var display = elem.css("display")
\r
3770 if (display === "none") {
\r
3771 var style = elem[0].style
\r
3772 var has = /visibility/i.test(style.cssText)
\r
3773 var visible = elem.css("visibility")
\r
3774 style.display = ""
\r
3775 style.visibility = "hidden"
\r
3776 display = elem.css("display")
\r
3777 if (display === "none") {
\r
3778 display = parseDisplay(elem[0].nodeName)
\r
3780 style.visibility = has ? visible : ""
\r
3782 data.display = display
\r
3783 parseExprProxy(data.value, vmodels, data)
\r
3786 bindingExecutors.visible = function (val, elem, data) {
\r
3787 elem.style.display = val ? data.display : "none"
\r
3789 bindingHandlers.widget = function (data, vmodels) {
\r
3790 var args = data.value.match(rword)
\r
3791 var elem = data.element
\r
3792 var widget = args[0]
\r
3794 if (!id || id === "$") { //没有定义或为$时,取组件名+随机数
\r
3795 id = generateID(widget)
\r
3797 var optName = args[2] || widget //没有定义,取组件名
\r
3798 var constructor = avalon.ui[widget]
\r
3799 if (typeof constructor === "function") { //ms-widget="tabs,tabsAAA,optname"
\r
3800 vmodels = elem.vmodels || vmodels
\r
3801 for (var i = 0, v; v = vmodels[i++];) {
\r
3802 if (v.hasOwnProperty(optName) && typeof v[optName] === "object") {
\r
3803 var vmOptions = v[optName]
\r
3804 vmOptions = vmOptions.$model || vmOptions
\r
3809 var wid = vmOptions[widget + "Id"]
\r
3810 if (typeof wid === "string") {
\r
3811 log("warning!不再支持" + widget + "Id")
\r
3815 //抽取data-tooltip-text、data-tooltip-attr属性,组成一个配置对象
\r
3816 var widgetData = avalon.getWidgetData(elem, widget)
\r
3817 data.value = [widget, id, optName].join(",")
\r
3818 data[widget + "Id"] = id
\r
3819 data.evaluator = noop
\r
3820 elem.msData["ms-widget-id"] = id
\r
3821 var options = data[widget + "Options"] = avalon.mix({}, constructor.defaults, vmOptions || {}, widgetData)
\r
3822 elem.removeAttribute("ms-widget")
\r
3823 var vmodel = constructor(elem, data, vmodels) || {} //防止组件不返回VM
\r
3825 avalon.vmodels[id] = vmodel
\r
3826 createSignalTower(elem, vmodel)
\r
3828 vmodel.$init(function () {
\r
3829 avalon.scan(elem, [vmodel].concat(vmodels))
\r
3830 if (typeof options.onInit === "function") {
\r
3831 options.onInit.call(elem, vmodel, options, vmodels)
\r
3836 data.rollback = function () {
\r
3838 vmodel.widgetElement = null
\r
3843 delete avalon.vmodels[vmodel.$id]
\r
3845 addSubscribers(data, widgetList)
\r
3846 if (window.chrome) {
\r
3847 elem.addEventListener("DOMNodeRemovedFromDocument", function () {
\r
3848 setTimeout(removeSubscribers)
\r
3852 avalon.scan(elem, vmodels)
\r
3854 } else if (vmodels.length) { //如果该组件还没有加载,那么保存当前的vmodels
\r
3855 elem.vmodels = vmodels
\r
3858 var widgetList = []
\r
3859 //不存在 bindingExecutors.widget
\r
3860 /*********************************************************************
\r
3862 **********************************************************************/
\r
3863 var rscripts = /<script[^>]*>([\S\s]*?)<\/script\s*>/gim
\r
3864 var ron = /\s+(on[^=\s]+)(?:=("[^"]*"|'[^']*'|[^\s>]+))?/g
\r
3865 var ropen = /<\w+\b(?:(["'])[^"]*?(\1)|[^>])*>/ig
\r
3867 a: /\b(href)\=("javascript[^"]*"|'javascript[^']*')/ig,
\r
3868 img: /\b(src)\=("javascript[^"]*"|'javascript[^']*')/ig,
\r
3869 form: /\b(action)\=("javascript[^"]*"|'javascript[^']*')/ig
\r
3871 var rsurrogate = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g
\r
3872 var rnoalphanumeric = /([^\#-~| |!])/g;
\r
3874 function numberFormat(number, decimals, point, thousands) {
\r
3875 //form http://phpjs.org/functions/number_format/
\r
3876 //number 必需,要格式化的数字
\r
3877 //decimals 可选,规定多少个小数位。
\r
3878 //point 可选,规定用作小数点的字符串(默认为 . )。
\r
3879 //thousands 可选,规定用作千位分隔符的字符串(默认为 , ),如果设置了该参数,那么所有其他参数都是必需的。
\r
3880 number = (number + '')
\r
3881 .replace(/[^0-9+\-Ee.]/g, '')
\r
3882 var n = !isFinite(+number) ? 0 : +number,
\r
3883 prec = !isFinite(+decimals) ? 3 : Math.abs(decimals),
\r
3884 sep = thousands || ",",
\r
3885 dec = point || ".",
\r
3887 toFixedFix = function (n, prec) {
\r
3888 var k = Math.pow(10, prec)
\r
3889 return '' + (Math.round(n * k) / k)
\r
3892 // Fix for IE parseFloat(0.55).toFixed(0) = 0;
\r
3893 s = (prec ? toFixedFix(n, prec) : '' + Math.round(n))
\r
3895 if (s[0].length > 3) {
\r
3896 s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, sep)
\r
3901 s[1] += new Array(prec - s[1].length + 1)
\r
3904 return s.join(dec)
\r
3908 var filters = avalon.filters = {
\r
3909 uppercase: function (str) {
\r
3910 return str.toUpperCase()
\r
3912 lowercase: function (str) {
\r
3913 return str.toLowerCase()
\r
3915 truncate: function (str, length, truncation) {
\r
3916 //length,新字符串长度,truncation,新字符串的结尾的字段,返回新字符串
\r
3917 length = length || 30
\r
3918 truncation = truncation === void(0) ? "..." : truncation
\r
3919 return str.length > length ? str.slice(0, length - truncation.length) + truncation : String(str)
\r
3921 $filter: function (val) {
\r
3922 for (var i = 1, n = arguments.length; i < n; i++) {
\r
3923 var array = arguments[i]
\r
3924 var fn = avalon.filters[array.shift()]
\r
3925 if (typeof fn === "function") {
\r
3926 var arr = [val].concat(array)
\r
3927 val = fn.apply(null, arr)
\r
3932 camelize: camelize,
\r
3933 //https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
\r
3934 // <a href="javasc
ript:alert('XSS')">chrome</a>
\r
3935 // <a href="data:text/html;base64, PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KDEpPg==">chrome</a>
\r
3936 // <a href="jav ascript:alert('XSS');">IE67chrome</a>
\r
3937 // <a href="jav	ascript:alert('XSS');">IE67chrome</a>
\r
3938 // <a href="jav
ascript:alert('XSS');">IE67chrome</a>
\r
3939 sanitize: function (str) {
\r
3940 return str.replace(rscripts, "").replace(ropen, function (a, b) {
\r
3941 var match = a.toLowerCase().match(/<(\w+)\s/)
\r
3942 if (match) { //处理a标签的href属性,img标签的src属性,form标签的action属性
\r
3943 var reg = rsanitize[match[1]]
\r
3945 a = a.replace(reg, function (s, name, value) {
\r
3946 var quote = value.charAt(0)
\r
3947 return name + "=" + quote + "javascript:void(0)" + quote// jshint ignore:line
\r
3951 return a.replace(ron, " ").replace(/\s+/g, " ") //移除onXXX事件
\r
3954 escape: function (str) {
\r
3955 //将字符串经过 str 转义得到适合在页面中显示的内容, 例如替换 < 为 <
\r
3956 return String(str).
\r
3957 replace(/&/g, '&').
\r
3958 replace(rsurrogate, function (value) {
\r
3959 var hi = value.charCodeAt(0)
\r
3960 var low = value.charCodeAt(1)
\r
3961 return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';'
\r
3963 replace(rnoalphanumeric, function (value) {
\r
3964 return '&#' + value.charCodeAt(0) + ';'
\r
3966 replace(/</g, '<').
\r
3967 replace(/>/g, '>')
\r
3969 currency: function (amount, symbol, fractionSize) {
\r
3970 return (symbol || "\uFFE5") + numberFormat(amount, isFinite(fractionSize) ? fractionSize : 2)
\r
3972 number: numberFormat
\r
3975 'yyyy': 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010)
\r
3976 'yy': 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10)
\r
3977 'y': 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199)
\r
3978 'MMMM': Month in year (January-December)
\r
3979 'MMM': Month in year (Jan-Dec)
\r
3980 'MM': Month in year, padded (01-12)
\r
3981 'M': Month in year (1-12)
\r
3982 'dd': Day in month, padded (01-31)
\r
3983 'd': Day in month (1-31)
\r
3984 'EEEE': Day in Week,(Sunday-Saturday)
\r
3985 'EEE': Day in Week, (Sun-Sat)
\r
3986 'HH': Hour in day, padded (00-23)
\r
3987 'H': Hour in day (0-23)
\r
3988 'hh': Hour in am/pm, padded (01-12)
\r
3989 'h': Hour in am/pm, (1-12)
\r
3990 'mm': Minute in hour, padded (00-59)
\r
3991 'm': Minute in hour (0-59)
\r
3992 'ss': Second in minute, padded (00-59)
\r
3993 's': Second in minute (0-59)
\r
3995 'Z': 4 digit (+sign) representation of the timezone offset (-1200-+1200)
\r
3996 format string can also be one of the following predefined localizable formats:
\r
3998 'medium': equivalent to 'MMM d, y h:mm:ss a' for en_US locale (e.g. Sep 3, 2010 12:05:08 pm)
\r
3999 'short': equivalent to 'M/d/yy h:mm a' for en_US locale (e.g. 9/3/10 12:05 pm)
\r
4000 'fullDate': equivalent to 'EEEE, MMMM d,y' for en_US locale (e.g. Friday, September 3, 2010)
\r
4001 'longDate': equivalent to 'MMMM d, y' for en_US locale (e.g. September 3, 2010
\r
4002 'mediumDate': equivalent to 'MMM d, y' for en_US locale (e.g. Sep 3, 2010)
\r
4003 'shortDate': equivalent to 'M/d/yy' for en_US locale (e.g. 9/3/10)
\r
4004 'mediumTime': equivalent to 'h:mm:ss a' for en_US locale (e.g. 12:05:08 pm)
\r
4005 'shortTime': equivalent to 'h:mm a' for en_US locale (e.g. 12:05 pm)
\r
4007 new function () {// jshint ignore:line
\r
4008 function toInt(str) {
\r
4009 return parseInt(str, 10) || 0
\r
4012 function padNumber(num, digits, trim) {
\r
4019 while (num.length < digits)
\r
4022 num = num.substr(num.length - digits)
\r
4026 function dateGetter(name, size, offset, trim) {
\r
4027 return function (date) {
\r
4028 var value = date["get" + name]()
\r
4029 if (offset > 0 || value > -offset)
\r
4031 if (value === 0 && offset === -12) {
\r
4034 return padNumber(value, size, trim)
\r
4038 function dateStrGetter(name, shortForm) {
\r
4039 return function (date, formats) {
\r
4040 var value = date["get" + name]()
\r
4041 var get = (shortForm ? ("SHORT" + name) : name).toUpperCase()
\r
4042 return formats[get][value]
\r
4046 function timeZoneGetter(date) {
\r
4047 var zone = -1 * date.getTimezoneOffset()
\r
4048 var paddedZone = (zone >= 0) ? "+" : ""
\r
4049 paddedZone += padNumber(Math[zone > 0 ? "floor" : "ceil"](zone / 60), 2) + padNumber(Math.abs(zone % 60), 2)
\r
4055 function ampmGetter(date, formats) {
\r
4056 return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]
\r
4059 var DATE_FORMATS = {
\r
4060 yyyy: dateGetter("FullYear", 4),
\r
4061 yy: dateGetter("FullYear", 2, 0, true),
\r
4062 y: dateGetter("FullYear", 1),
\r
4063 MMMM: dateStrGetter("Month"),
\r
4064 MMM: dateStrGetter("Month", true),
\r
4065 MM: dateGetter("Month", 2, 1),
\r
4066 M: dateGetter("Month", 1, 1),
\r
4067 dd: dateGetter("Date", 2),
\r
4068 d: dateGetter("Date", 1),
\r
4069 HH: dateGetter("Hours", 2),
\r
4070 H: dateGetter("Hours", 1),
\r
4071 hh: dateGetter("Hours", 2, -12),
\r
4072 h: dateGetter("Hours", 1, -12),
\r
4073 mm: dateGetter("Minutes", 2),
\r
4074 m: dateGetter("Minutes", 1),
\r
4075 ss: dateGetter("Seconds", 2),
\r
4076 s: dateGetter("Seconds", 1),
\r
4077 sss: dateGetter("Milliseconds", 3),
\r
4078 EEEE: dateStrGetter("Day"),
\r
4079 EEE: dateStrGetter("Day", true),
\r
4083 var rdateFormat = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/
\r
4084 var raspnetjson = /^\/Date\((\d+)\)\/$/
\r
4085 filters.date = function (date, format) {
\r
4086 var locate = filters.date.locate,
\r
4090 format = format || "mediumDate"
\r
4091 format = locate[format] || format
\r
4092 if (typeof date === "string") {
\r
4093 if (/^\d+$/.test(date)) {
\r
4094 date = toInt(date)
\r
4095 } else if (raspnetjson.test(date)) {
\r
4098 var trimDate = date.trim()
\r
4099 var dateArray = [0, 0, 0, 0, 0, 0, 0]
\r
4100 var oDate = new Date(0)
\r
4102 trimDate = trimDate.replace(/^(\d+)\D(\d+)\D(\d+)/, function (_, a, b, c) {
\r
4103 var array = c.length === 4 ? [c, a, b] : [a, b, c]
\r
4104 dateArray[0] = toInt(array[0]) //年
\r
4105 dateArray[1] = toInt(array[1]) - 1 //月
\r
4106 dateArray[2] = toInt(array[2]) //日
\r
4109 var dateSetter = oDate.setFullYear
\r
4110 var timeSetter = oDate.setHours
\r
4111 trimDate = trimDate.replace(/[T\s](\d+):(\d+):?(\d+)?\.?(\d)?/, function (_, a, b, c, d) {
\r
4112 dateArray[3] = toInt(a) //小时
\r
4113 dateArray[4] = toInt(b) //分钟
\r
4114 dateArray[5] = toInt(c) //秒
\r
4116 dateArray[6] = Math.round(parseFloat("0." + d) * 1000)
\r
4122 trimDate = trimDate.replace(/Z|([+-])(\d\d):?(\d\d)/, function (z, symbol, c, d) {
\r
4123 dateSetter = oDate.setUTCFullYear
\r
4124 timeSetter = oDate.setUTCHours
\r
4126 tzHour = toInt(symbol + c)
\r
4127 tzMin = toInt(symbol + d)
\r
4132 dateArray[3] -= tzHour
\r
4133 dateArray[4] -= tzMin
\r
4134 dateSetter.apply(oDate, dateArray.slice(0, 3))
\r
4135 timeSetter.apply(oDate, dateArray.slice(3))
\r
4139 if (typeof date === "number") {
\r
4140 date = new Date(date)
\r
4142 if (avalon.type(date) !== "date") {
\r
4146 match = rdateFormat.exec(format)
\r
4148 parts = parts.concat(match.slice(1))
\r
4149 format = parts.pop()
\r
4151 parts.push(format)
\r
4155 parts.forEach(function (value) {
\r
4156 fn = DATE_FORMATS[value]
\r
4157 text += fn ? fn(date, locate) : value.replace(/(^'|'$)/g, "").replace(/''/g, "'")
\r
4198 fullDate: "y年M月d日EEEE",
\r
4199 longDate: "y年M月d日",
\r
4200 medium: "yyyy-M-d H:mm:ss",
\r
4201 mediumDate: "yyyy-M-d",
\r
4202 mediumTime: "H:mm:ss",
\r
4203 "short": "yy-M-d ah:mm",
\r
4204 shortDate: "yy-M-d",
\r
4205 shortTime: "ah:mm"
\r
4207 locate.SHORTMONTH = locate.MONTH
\r
4208 filters.date.locate = locate
\r
4209 }// jshint ignore:line
\r
4210 /*********************************************************************
\r
4212 **********************************************************************/
\r
4213 //https://www.devbridge.com/articles/understanding-amd-requirejs/
\r
4214 //http://maxogden.com/nested-dependencies.html
\r
4215 var modules = avalon.modules = {
\r
4225 //Object(modules[id]).state拥有如下值
\r
4228 // 2(loading) 已经被执行但还没有执行完成,在这个阶段define方法会被执行
\r
4229 // 3(loaded) 执行完毕,通过onload/onreadystatechange回调判定,在这个阶段checkDeps方法会执行
\r
4230 // 4(execute) 其依赖也执行完毕, 值放到exports对象上,在这个阶段fireFactory方法会执行
\r
4231 modules.exports = modules.avalon
\r
4233 new function () {// jshint ignore:line
\r
4234 var loadings = [] //正在加载中的模块列表
\r
4235 var factorys = [] //放置define方法的factory函数
\r
4236 var rjsext = /\.js$/i
\r
4238 function makeRequest(name, config) {
\r
4241 name = name.replace(/^(\w+)\!/, function (a, b) {
\r
4245 if (res === "ready") {
\r
4246 log("debug: ready!已经被废弃,请使用domReady!")
\r
4249 //2. 去掉querystring, hash
\r
4251 name = name.replace(rquery, function (a) {
\r
4256 var suffix = "." + res
\r
4257 var ext = /js|css/.test(suffix) ? suffix : ""
\r
4258 name = name.replace(/\.[a-z0-9]+$/g, function (a) {
\r
4259 if (a === suffix) {
\r
4266 var req = avalon.mix({
\r
4277 function fireRequest(req) {
\r
4278 var name = req.name
\r
4280 //1. 如果该模块已经发出请求,直接返回
\r
4281 var module = modules[name]
\r
4282 var urlNoQuery = name && req.urlNoQuery
\r
4283 if (module && module.state >= 1) {
\r
4286 module = modules[urlNoQuery]
\r
4287 if (module && module.state >= 3) {
\r
4288 innerRequire(module.deps || [], module.factory, urlNoQuery)
\r
4291 if (name && !module) {
\r
4292 module = modules[urlNoQuery] = {
\r
4296 var wrap = function (obj) {
\r
4297 resources[res] = obj
\r
4298 obj.load(name, req, function (a) {
\r
4299 if (arguments.length && a !== void 0) {
\r
4300 module.exports = a
\r
4307 if (!resources[res]) {
\r
4308 innerRequire([res], wrap)
\r
4310 wrap(resources[res])
\r
4313 return name ? urlNoQuery : res + "!"
\r
4317 var requireQueue = []
\r
4318 var isUserFirstRequire = false
\r
4319 innerRequire = avalon.require = function (array, factory, parentUrl, defineConfig) {
\r
4320 if (!isUserFirstRequire) {
\r
4321 requireQueue.push(avalon.slice(arguments))
\r
4322 if (arguments.length <= 2) {
\r
4323 isUserFirstRequire = true
\r
4324 var queue = requireQueue.splice(0, requireQueue.length), args
\r
4325 while (args = queue.shift()) {
\r
4326 innerRequire.apply(null, args)
\r
4332 if (!Array.isArray(array)) {
\r
4333 avalon.error("require方法的第一个参数应为数组 " + array)
\r
4335 var deps = [] // 放置所有依赖项的完整路径
\r
4336 var uniq = createMap()
\r
4337 var id = parentUrl || "callback" + setTimeout("1")// jshint ignore:line
\r
4338 defineConfig = defineConfig || createMap()
\r
4339 defineConfig.baseUrl = kernel.baseUrl
\r
4340 var isBuilt = !!defineConfig.built
\r
4342 defineConfig.parentUrl = parentUrl.substr(0, parentUrl.lastIndexOf("/"))
\r
4343 defineConfig.mapUrl = parentUrl.replace(rjsext, "")
\r
4346 var req = makeRequest(defineConfig.defineName, defineConfig)
\r
4347 id = req.urlNoQuery
\r
4349 array.forEach(function (name) {
\r
4350 var req = makeRequest(name, defineConfig)
\r
4351 var url = fireRequest(req) //加载资源,并返回该资源的完整地址
\r
4355 uniq[url] = "司徒正美" //去重
\r
4361 var module = modules[id]
\r
4362 if (!module || module.state !== 4) {
\r
4365 deps: isBuilt ? array.concat() : deps,
\r
4366 factory: factory || noop,
\r
4371 //如果此模块是定义在另一个JS文件中, 那必须等该文件加载完毕, 才能放到检测列队中
\r
4378 innerRequire.define = function (name, deps, factory) { //模块名,依赖列表,模块本身
\r
4379 if (typeof name !== "string") {
\r
4382 name = "anonymous"
\r
4384 if (!Array.isArray(deps)) {
\r
4389 built: !isUserFirstRequire, //用r.js打包后,所有define会放到requirejs之前
\r
4392 var args = [deps, factory, config]
\r
4393 factory.require = function (url) {
\r
4394 args.splice(2, 0, url)
\r
4395 if (modules[url]) {
\r
4396 modules[url].state = 3 //loaded
\r
4397 var isCycle = false
\r
4399 isCycle = checkCycle(modules[url].deps, url)
\r
4403 avalon.error(url + "模块与之前的模块存在循环依赖,请不要直接用script标签引入" + url + "模块")
\r
4406 delete factory.require //释放内存
\r
4407 innerRequire.apply(null, args) //0,1,2 --> 1,2,0
\r
4409 //根据标准,所有遵循W3C标准的浏览器,script标签会按标签的出现顺序执行。
\r
4410 //老的浏览器中,加载也是按顺序的:一个文件下载完成后,才开始下载下一个文件。
\r
4411 //较新的浏览器中(IE8+ 、FireFox3.5+ 、Chrome4+ 、Safari4+),为了减小请求时间以优化体验,
\r
4412 //下载可以是并行的,但是执行顺序还是按照标签出现的顺序。
\r
4413 //但如果script标签是动态插入的, 就未必按照先请求先执行的原则了,目测只有firefox遵守
\r
4414 //唯一比较一致的是,IE10+及其他标准浏览器,一旦开始解析脚本, 就会一直堵在那里,直接脚本解析完毕
\r
4415 //亦即,先进入loading阶段的script标签(模块)必然会先进入loaded阶段
\r
4416 var url = config.built ? "unknown" : getCurrentScript()
\r
4418 var module = modules[url]
\r
4422 factory.require(url)
\r
4423 } else {//合并前后的safari,合并后的IE6-9走此分支
\r
4424 factorys.push(factory)
\r
4427 //核心API之三 require.config(settings)
\r
4428 innerRequire.config = kernel
\r
4429 //核心API之四 define.amd 标识其符合AMD规范
\r
4430 innerRequire.define.amd = modules
\r
4432 //==========================对用户配置项进行再加工==========================
\r
4433 var allpaths = kernel["orig.paths"] = createMap()
\r
4434 var allmaps = kernel["orig.map"] = createMap()
\r
4435 var allpackages = kernel["packages"] = []
\r
4436 var allargs = kernel["orig.args"] = createMap()
\r
4437 avalon.mix(plugins, {
\r
4438 paths: function (hash) {
\r
4439 avalon.mix(allpaths, hash)
\r
4440 kernel.paths = makeIndexArray(allpaths)
\r
4442 map: function (hash) {
\r
4443 avalon.mix(allmaps, hash)
\r
4444 var list = makeIndexArray(allmaps, 1, 1)
\r
4445 avalon.each(list, function (_, item) {
\r
4446 item.val = makeIndexArray(item.val)
\r
4450 packages: function (array) {
\r
4451 array = array.concat(allpackages)
\r
4452 var uniq = createMap()
\r
4454 for (var i = 0, pkg; pkg = array[i++];) {
\r
4455 pkg = typeof pkg === "string" ? {name: pkg} : pkg
\r
4456 var name = pkg.name
\r
4457 if (!uniq[name]) {
\r
4458 var url = joinPath(pkg.location || name, pkg.main || "main")
\r
4459 url = url.replace(rjsext, "")
\r
4461 uniq[name] = pkg.location = url
\r
4462 pkg.reg = makeMatcher(name)
\r
4465 kernel.packages = ret.sort()
\r
4467 urlArgs: function (hash) {
\r
4468 if (typeof hash === "string") {
\r
4469 hash = {"*": hash}
\r
4471 avalon.mix(allargs, hash)
\r
4472 kernel.urlArgs = makeIndexArray(allargs, 1)
\r
4474 baseUrl: function (url) {
\r
4475 if (!isAbsUrl(url)) {
\r
4476 var baseElement = head.getElementsByTagName("base")[0]
\r
4477 if (baseElement) {
\r
4478 head.removeChild(baseElement)
\r
4480 var node = DOC.createElement("a")
\r
4483 if (baseElement) {
\r
4484 head.insertBefore(baseElement, head.firstChild)
\r
4487 if (url.length > 3)
\r
4488 kernel.baseUrl = url
\r
4490 shim: function (obj) {
\r
4491 for (var i in obj) {
\r
4492 var value = obj[i]
\r
4493 if (Array.isArray(value)) {
\r
4494 value = obj[i] = {
\r
4498 if (!value.exportsFn && (value.exports || value.init)) {
\r
4499 value.exportsFn = makeExports(value)
\r
4508 //==============================内部方法=================================
\r
4509 function checkCycle(deps, nick) {
\r
4511 for (var i = 0, id; id = deps[i++];) {
\r
4512 if (modules[id].state !== 4 &&
\r
4513 (id === nick || checkCycle(modules[id].deps, nick))) {
\r
4519 function checkFail(node, onError) {
\r
4520 var id = trimQuery(node.src) //检测是否死链
\r
4521 node.onload = node.onerror = null
\r
4523 setTimeout(function () {
\r
4524 head.removeChild(node)
\r
4525 node = null // 处理旧式IE下的循环引用问题
\r
4527 log("debug: 加载 " + id + " 失败" + onError + " " + (!modules[id].state))
\r
4533 function checkDeps() {
\r
4534 //检测此JS模块的依赖是否都已安装完毕,是则安装自身
\r
4535 loop: for (var i = loadings.length, id; id = loadings[--i];) {
\r
4536 var obj = modules[id],
\r
4540 for (var j = 0, key; key = deps[j]; j++) {
\r
4541 if (Object(modules[key]).state !== 4) {
\r
4545 //如果deps是空对象或者其依赖的模块的状态都是2
\r
4546 if (obj.state !== 4) {
\r
4547 loadings.splice(i, 1) //必须先移除再安装,防止在IE下DOM树建完后手动刷新页面,会多次执行它
\r
4548 fireFactory(obj.id, obj.deps, obj.factory)
\r
4549 checkDeps() //如果成功,则再执行一次,以防有些模块就差本模块没有安装好
\r
4554 function loadJS(url, id, callback) {
\r
4555 //通过script节点加载目标模块
\r
4556 var node = DOC.createElement("script")
\r
4557 node.className = subscribers //让getCurrentScript只处理类名为subscribers的script节点
\r
4558 node.onload = function () {
\r
4559 var factory = factorys.pop()
\r
4560 factory && factory.require(id)
\r
4564 log("debug: 已成功加载 " + url)
\r
4565 id && loadings.push(id)
\r
4568 node.onerror = function () {
\r
4569 checkFail(node, true)
\r
4572 head.insertBefore(node, head.firstChild) //chrome下第二个参数不能为null
\r
4573 node.src = url //插入到head的第一个节点前,防止IE6下head标签没闭合前使用appendChild抛错
\r
4574 log("debug: 正准备加载 " + url) //更重要的是IE6下可以收窄getCurrentScript的寻找范围
\r
4577 var resources = innerRequire.plugins = {
\r
4578 //三大常用资源插件 js!, css!, text!, ready!
\r
4583 load: function (name, req, onLoad) {
\r
4585 var id = req.urlNoQuery
\r
4586 var shim = kernel.shim[name.replace(rjsext, "")]
\r
4587 if (shim) { //shim机制
\r
4588 innerRequire(shim.deps || [], function () {
\r
4589 var args = avalon.slice(arguments)
\r
4590 loadJS(url, id, function () {
\r
4591 onLoad(shim.exportsFn ? shim.exportsFn.apply(0, args) : void 0)
\r
4600 load: function (name, req, onLoad) {
\r
4602 head.insertAdjacentHTML("afterBegin", '<link rel="stylesheet" href="' + url + '">')
\r
4603 log("debug: 已成功加载 " + url)
\r
4608 load: function (name, req, onLoad) {
\r
4610 var xhr = getXHR()
\r
4611 xhr.onload = function () {
\r
4612 var status = xhr.status;
\r
4613 if (status > 399 && status < 600) {
\r
4614 avalon.error(url + " 对应资源不存在或没有开启 CORS")
\r
4616 log("debug: 已成功加载 " + url)
\r
4617 onLoad(xhr.responseText)
\r
4620 xhr.open("GET", url, true)
\r
4621 if ("withCredentials" in xhr) {//这是处理跨域
\r
4622 xhr.withCredentials = true
\r
4624 xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest")//告诉后端这是AJAX请求
\r
4626 log("debug: 正准备加载 " + url)
\r
4630 innerRequire.checkDeps = checkDeps
\r
4632 var rquery = /(\?[^#]*)$/
\r
4634 function trimQuery(url) {
\r
4635 return (url || "").replace(rquery, "")
\r
4638 function isAbsUrl(path) {
\r
4639 //http://stackoverflow.com/questions/10687099/how-to-test-if-a-url-string-is-absolute-or-relative
\r
4640 return /^(?:[a-z]+:)?\/\//i.test(String(path))
\r
4644 function getCurrentScript() {
\r
4645 // inspireb by https://github.com/samyk/jiagra/blob/master/jiagra.js
\r
4648 a.b.c() //强制报错,以便捕获e.stack
\r
4649 } catch (e) { //safari5的sourceURL,firefox的fileName,它们的效果与e.stack不一样
\r
4653 /**e.stack最后一行在所有支持的浏览器大致如下:
\r
4655 * at http://113.93.50.63/data.js:4:1
\r
4657 *@http://113.93.50.63/query.js:4
\r
4658 *opera12:http://www.oldapps.com/opera.php?system=Windows_XP
\r
4659 *@http://113.93.50.63/data.js:4
\r
4661 * at Global code (http://113.93.50.63/data.js:4:1)
\r
4662 * //firefox4+ 可以用document.currentScript
\r
4664 stack = stack.split(/[@ ]/g).pop() //取得最后一行,最后一个空格或@之后的部分
\r
4665 stack = stack[0] === "(" ? stack.slice(1, -1) : stack.replace(/\s/, "") //去掉换行符
\r
4666 return trimQuery(stack.replace(/(:\d+)?:\d+$/i, "")) //去掉行号与或许存在的出错字符起始位置
\r
4668 var nodes = head.getElementsByTagName("script") //只在head标签中寻找
\r
4669 for (var i = nodes.length, node; node = nodes[--i];) {
\r
4670 if (node.className === subscribers && node.readyState === "interactive") {
\r
4671 var url = node.src
\r
4672 return node.className = trimQuery(url)
\r
4677 var rcallback = /^callback\d+$/
\r
4679 function fireFactory(id, deps, factory) {
\r
4680 var module = Object(modules[id])
\r
4682 for (var i = 0, array = [], d; d = deps[i++];) {
\r
4683 if (d === "exports") {
\r
4684 var obj = module.exports || (module.exports = createMap())
\r
4687 array.push(modules[d].exports)
\r
4691 var ret = factory.apply(window, array)
\r
4693 log("执行[" + id + "]模块的factory抛错: " + e)
\r
4695 if (ret !== void 0) {
\r
4696 module.exports = ret
\r
4698 if (rcallback.test(id)) {
\r
4699 delete modules[id]
\r
4701 delete module.factory
\r
4705 function toUrl(id) {
\r
4706 if (id.indexOf(this.res + "!") === 0) {
\r
4707 id = id.slice(this.res.length + 1) //处理define("css!style",[], function(){})的情况
\r
4712 var baseUrl = this.baseUrl
\r
4713 var rootUrl = this.parentUrl || baseUrl
\r
4714 eachIndexArray(id, kernel.paths, function (value, key) {
\r
4715 url = url.replace(key, value)
\r
4718 //2. 是否命中packages配置项
\r
4720 eachIndexArray(id, kernel.packages, function (value, key, item) {
\r
4721 url = url.replace(item.name, item.location)
\r
4725 if (this.mapUrl) {
\r
4726 eachIndexArray(this.mapUrl, kernel.map, function (array) {
\r
4727 eachIndexArray(url, array, function (mdValue, mdKey) {
\r
4728 url = url.replace(mdKey, mdValue)
\r
4733 var ext = this.ext
\r
4734 if (ext && usePath && url.slice(-ext.length) === ext) {
\r
4735 url = url.slice(0, -ext.length)
\r
4738 if (!isAbsUrl(url)) {
\r
4739 rootUrl = this.built || /^\w/.test(url) ? baseUrl : rootUrl
\r
4740 url = joinPath(rootUrl, url)
\r
4743 var urlNoQuery = url + ext
\r
4744 url = urlNoQuery + this.query
\r
4746 eachIndexArray(id, kernel.urlArgs, function (value) {
\r
4747 url += (url.indexOf("?") === -1 ? "?" : "&") + value;
\r
4750 return this.urlNoQuery = urlNoQuery
\r
4753 function makeIndexArray(hash, useStar, part) {
\r
4754 //创建一个经过特殊算法排好序的数组
\r
4755 var index = hash2array(hash, useStar, part)
\r
4756 index.sort(descSorterByName)
\r
4760 function makeMatcher(prefix) {
\r
4761 return new RegExp('^' + prefix + '(/|$)')
\r
4764 function makeExports(value) {
\r
4765 return function () {
\r
4768 ret = value.init.apply(window, arguments)
\r
4770 return ret || (value.exports && getGlobal(value.exports))
\r
4775 function hash2array(hash, useStar, part) {
\r
4777 for (var key in hash) {
\r
4778 // if (hash.hasOwnProperty(key)) {//hash是由createMap创建没有hasOwnProperty
\r
4784 item.reg = key === "*" && useStar ? /^/ : makeMatcher(key)
\r
4785 if (part && key !== "*") {
\r
4786 item.reg = new RegExp('\/' + key.replace(/^\//, "") + '(/|$)')
\r
4793 function eachIndexArray(moduleID, array, matcher) {
\r
4794 array = array || []
\r
4795 for (var i = 0, el; el = array[i++];) {
\r
4796 if (el.reg.test(moduleID)) {
\r
4797 matcher(el.val, el.name, el)
\r
4803 // 根据元素的name项进行数组字符数逆序的排序函数
\r
4804 function descSorterByName(a, b) {
\r
4807 if (bbb === "*") {
\r
4810 if (aaa === "*") {
\r
4813 return bbb.length - aaa.length
\r
4816 var rdeuce = /\/\w+\/\.\./
\r
4818 function joinPath(a, b) {
\r
4819 if (a.charAt(a.length - 1) !== "/") {
\r
4822 if (b.slice(0, 2) === "./") { //相对于兄弟路径
\r
4823 return a + b.slice(2)
\r
4825 if (b.slice(0, 2) === "..") { //相对于父路径
\r
4827 while (rdeuce.test(a)) {
\r
4828 a = a.replace(rdeuce, "")
\r
4832 if (b.slice(0, 1) === "/") {
\r
4833 return a + b.slice(1)
\r
4838 function getGlobal(value) {
\r
4843 value.split(".").forEach(function (part) {
\r
4849 var mainNode = DOC.scripts[DOC.scripts.length - 1]
\r
4850 var dataMain = mainNode.getAttribute("data-main")
\r
4852 plugins.baseUrl(dataMain)
\r
4853 var href = kernel.baseUrl
\r
4854 kernel.baseUrl = href.slice(0, href.lastIndexOf("/") + 1)
\r
4855 loadJS(href.replace(rjsext, "") + ".js")
\r
4857 var loaderUrl = trimQuery(mainNode.src)
\r
4858 kernel.baseUrl = loaderUrl.slice(0, loaderUrl.lastIndexOf("/") + 1)
\r
4860 }// jshint ignore:line
\r
4862 /*********************************************************************
\r
4864 **********************************************************************/
\r
4865 var readyList = [], isReady
\r
4866 var fireReady = function (fn) {
\r
4868 if (innerRequire) {
\r
4869 modules["domReady!"].state = 4
\r
4870 innerRequire.checkDeps()
\r
4872 while (fn = readyList.shift()) {
\r
4878 if (DOC.readyState === "complete") {
\r
4879 setTimeout(fireReady) //如果在domReady之外加载
\r
4881 DOC.addEventListener("DOMContentLoaded", fireReady)
\r
4883 window.addEventListener("load", fireReady)
\r
4884 avalon.ready = function (fn) {
\r
4886 readyList.push(fn)
\r
4894 avalon.ready(function () {
\r
4895 avalon.scan(DOC.body)
\r
4898 // Register as a named AMD module, since avalon can be concatenated with other
\r
4899 // files that may use define, but not via a proper concatenation script that
\r
4900 // understands anonymous AMD modules. A named AMD is safest and most robust
\r
4901 // way to register. Lowercase avalon is used because AMD module names are
\r
4902 // derived from file names, and Avalon is normally delivered in a lowercase
\r
4903 // file name. Do this after creating the global so that if an AMD module wants
\r
4904 // to call noConflict to hide this version of avalon, it will work.
\r
4906 // Note that for maximum portability, libraries that are not avalon should
\r
4907 // declare themselves as anonymous modules, and avoid setting a global if an
\r
4908 // AMD loader is present. avalon is a special case. For more information, see
\r
4909 // https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon
\r
4910 if (typeof define === "function" && define.amd) {
\r
4911 define("avalon", [], function () {
\r
4915 // Map over avalon in case of overwrite
\r
4916 var _avalon = window.avalon
\r
4917 avalon.noConflict = function (deep) {
\r
4918 if (deep && window.avalon === avalon) {
\r
4919 window.avalon = _avalon
\r
4923 // Expose avalon identifiers, even in AMD
\r
4924 // and CommonJS for browser emulators
\r
4925 if (noGlobal === void 0) {
\r
4926 window.avalon = avalon
\r
4929 window._injectTer = function (code) {
\r