From cfde5a361c9af52a2c7d3ef1430a194160078d05 Mon Sep 17 00:00:00 2001 From: star Date: Thu, 21 Aug 2014 00:00:19 +0800 Subject: [PATCH 1/6] Update avalon.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复DOMNodeRemoved事件发生时,对于元素的判断 之前的问题是在chrome下,无法判断其它节点的删除 --- avalon.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/avalon.js b/avalon.js index 699e5d8a3..10d328763 100644 --- a/avalon.js +++ b/avalon.js @@ -3075,7 +3075,7 @@ elem.addEventListener("DOMNodeRemoved", function(e) { if (e.target === this && !this.msRetain && //#441 chrome浏览器对文本域进行Ctrl+V操作,会触发DOMNodeRemoved事件 - (window.chrome ? this.tagName === "INPUT" && e.relatedNode.nodeType === 1 : 1)) { + (window.chrome ? (this.tagName === "INPUT" ? e.relatedNode.nodeType === 1 : 1) : 1)) { offTree() } }) From 1b70e27e3f7f8c03dabdb84dda55a3f0cec5da0f Mon Sep 17 00:00:00 2001 From: ilife5 Date: Mon, 20 Oct 2014 16:51:12 +0800 Subject: [PATCH 2/6] update avalon method --- avalon.js | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/avalon.js b/avalon.js index c07ffaae8..d9654cf01 100644 --- a/avalon.js +++ b/avalon.js @@ -1778,9 +1778,10 @@ var callbacks = events[type] || [] var all = events.$all || [] var args = aslice.call(arguments, 1) + var eventValue = true //事件传播的返回值,默认为true for (var i = 0, callback; callback = callbacks[i++]; ) { if (isFunction(callback)) - callback.apply(this, args) + eventValue = callback.apply(this, args) && eventValue } for (var i = 0, callback; callback = all[i++]; ) { if (isFunction(callback)) @@ -1789,6 +1790,7 @@ var element = events.expr && findNode(events.expr) if (element) { var detail = [type].concat(args) + var alls = [] if (special === "up" || special === "down" || special === "all") { for (var i in avalon.vmodels) { var v = avalon.vmodels[i] @@ -1801,29 +1803,26 @@ var ok = special === "all" ? 1 : //全局广播 special === "down" ? element.contains(node) : //向下捕获 node.contains(element)//向上冒泡 - if (ok) { - node._avalon = v//符合条件的加一个标识 + if (ok && document.body.contains(node)) { + alls.push([node, v]) } } } } var nodes = DOC.getElementsByTagName("*")//实现节点排序 - var alls = [] - Array.prototype.forEach.call(nodes, function(el) { - if (el._avalon) { - alls.push(el._avalon) - el._avalon = "" - el.removeAttribute("_avalon") - } + alls.sort(function(a, b) { + return Array.prototype.indexOf.call(nodes, a[0]) - Array.prototype.indexOf.call(nodes, b[0]) }) if (special === "up") { alls.reverse() } - alls.forEach(function(v) { - v.$fire.apply(v, detail) + alls.every(function(v) { + return v[1].$fire.apply(v[1], detail) !== false }) } } + return eventValue + } } var ravalon = /(\w+)\[(avalonctrl)="(\S+)"\]/ From 63d4f4edf06fe3aacb5e94f06d0b9009f93c8e8f Mon Sep 17 00:00:00 2001 From: ilife5 Date: Mon, 20 Oct 2014 18:35:53 +0800 Subject: [PATCH 3/6] update avalon --- avalon.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/avalon.js b/avalon.js index d9654cf01..fad070006 100644 --- a/avalon.js +++ b/avalon.js @@ -1778,10 +1778,9 @@ var callbacks = events[type] || [] var all = events.$all || [] var args = aslice.call(arguments, 1) - var eventValue = true //事件传播的返回值,默认为true for (var i = 0, callback; callback = callbacks[i++]; ) { if (isFunction(callback)) - eventValue = callback.apply(this, args) && eventValue + callback.apply(this, args) } for (var i = 0, callback; callback = all[i++]; ) { if (isFunction(callback)) @@ -1803,7 +1802,7 @@ var ok = special === "all" ? 1 : //全局广播 special === "down" ? element.contains(node) : //向下捕获 node.contains(element)//向上冒泡 - if (ok && document.body.contains(node)) { + if (ok) { alls.push([node, v]) } } @@ -1816,13 +1815,11 @@ if (special === "up") { alls.reverse() } - alls.every(function(v) { - return v[1].$fire.apply(v[1], detail) !== false + alls.forEach(function(v) { + v[1].$fire.apply(v[1], detail) }) } } - return eventValue - } } var ravalon = /(\w+)\[(avalonctrl)="(\S+)"\]/ From 42baefcf4942f6d135eaa5b5b6c84d813b637582 Mon Sep 17 00:00:00 2001 From: ilife5 Date: Tue, 18 Nov 2014 14:03:26 +0800 Subject: [PATCH 4/6] merge --- README.md | 40 +- avalon.iscroll.js | 1178 ++++++ avalon.js | 2088 ++++----- avalon.min.js | 241 +- avalon.mobile.min.js | 101 - avalon.mobile.js => avalon.modern.js | 1991 ++++----- avalon.modern.min.js | 104 + avalon.observe.js | 1232 +++--- examples/$fire.html | 60 + examples/$watch.html | 44 + examples/aa1.html | 50 - examples/aa2.html | 134 - examples/aa3.html | 26 - examples/aa4.html | 130 - examples/architecture.html | 203 + examples/attr.html | 131 + examples/avalon.$events.js | 4922 ++++++++++++++++++++++ examples/avalon.shim.js | 4259 +++++++++++++++++++ examples/class.html | 92 +- examples/class1.html | 65 + examples/class3.html | 39 + examples/css1.html | 13 +- examples/css2.html | 4 +- examples/css3.html | 1 + examples/data.html | 50 + examples/dialog.html | 72 + examples/dialog1.html | 1 + examples/dialog2.html | 14 + examples/duplex4.html | 2 +- examples/grid.html | 12 +- examples/grid2.html | 134 - examples/href.html | 38 + examples/if1.html | 2 +- examples/if4.html | 40 + examples/include.html | 17 +- examples/includeTemplate1.html | 11 + examples/index.html | 4 +- examples/on0.html | 39 +- examples/on1.html | 25 +- examples/on2.html | 19 +- examples/on3.html | 6 +- examples/on4.html | 14 +- examples/on5.html | 17 +- examples/repeat1.html | 8 +- examples/repeat13.html | 2 +- examples/repeat15.html | 72 + test4.html => examples/repeat16.html | 4 +- examples/repeat2.html | 2 +- examples/repeat3.html | 4 +- examples/repeat4.html | 49 +- examples/repeat5.html | 4 +- examples/repeat6.html | 9 +- examples/repeat7.html | 2 +- examples/repeat8.html | 4 +- examples/repeat9.html | 5 +- examples/{antixss.html => sanisize.html} | 8 +- examples/svg-innerHTML-outerHTML.html | 67 - examples/svg.html | 50 +- examples/tabs.html | 36 + examples/test1.html | 106 + examples/test2.html | 31 + examples/test3.html | 50 + examples/test4.html | 75 + examples/test5.html | 40 + examples/text6.html | 27 + examples/todos.html | 29 +- examples/todos2.html | 130 + examples/tree.html | 2 +- mobile.js | 350 +- test1.html | 49 - test2.html | 93 - test3.html | 132 - test5.html | 41 - test6.html | 28 - 74 files changed, 15111 insertions(+), 4063 deletions(-) create mode 100644 avalon.iscroll.js delete mode 100644 avalon.mobile.min.js rename avalon.mobile.js => avalon.modern.js (72%) create mode 100644 avalon.modern.min.js create mode 100644 examples/$fire.html create mode 100644 examples/$watch.html delete mode 100644 examples/aa1.html delete mode 100644 examples/aa2.html delete mode 100644 examples/aa3.html delete mode 100644 examples/aa4.html create mode 100644 examples/architecture.html create mode 100644 examples/attr.html create mode 100644 examples/avalon.$events.js create mode 100644 examples/avalon.shim.js create mode 100644 examples/class1.html create mode 100644 examples/class3.html create mode 100644 examples/data.html create mode 100644 examples/dialog.html create mode 100644 examples/dialog1.html create mode 100644 examples/dialog2.html delete mode 100644 examples/grid2.html create mode 100644 examples/if4.html create mode 100644 examples/repeat15.html rename test4.html => examples/repeat16.html (92%) rename examples/{antixss.html => sanisize.html} (53%) delete mode 100644 examples/svg-innerHTML-outerHTML.html create mode 100644 examples/tabs.html create mode 100644 examples/test1.html create mode 100644 examples/test2.html create mode 100644 examples/test3.html create mode 100644 examples/test4.html create mode 100644 examples/test5.html create mode 100644 examples/text6.html create mode 100644 examples/todos2.html delete mode 100644 test1.html delete mode 100644 test2.html delete mode 100644 test3.html delete mode 100644 test5.html delete mode 100644 test6.html diff --git a/README.md b/README.md index 5bfd62bf1..4ca52bc88 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,8 @@
  • avalon.js 兼容IE6,及标准浏览器
  • -
  • avalon.mobile.js 则只支持IE10及其以上版本,及标准浏览器
  • +
  • avalon.modern.js 则只支持IE10及其以上版本,及标准浏览器,主流山寨浏览器(QQ, 猎豹, 搜狗, 360, 傲游)
  • +
  • 如果想支持触摸屏,请把mobile.js的源码拷贝到avalon.modern.js最后一个})之前,要不会报"Uncaught ReferenceError: W3CFire is not defined mobile.js:313"错误
  • 想使用路由器,可以用mmRouter, 想使用动画,可以用mmAnimate, 想使用AJAX,可以用mmRequest, @@ -20,8 +21,8 @@

    优势

    • 使用简单,在HTML中添加绑定,在JS中用avalon.define定义ViewModel,再调用avalon.scan方法,它就能动了!
    • -
    • 兼容到IE6(其他mvvm框架, knockoutjs IE6, angularjs IE8, emberjs IE8, winJS IE9 ),另有avalon.mobile,它可以更高效地运行于IE10等新版本浏览器中
    • -
    • 没有任何依赖,不到4000行,压缩后不到50KB
    • +
    • 兼容到IE6(其他MVVM框架,KnockoutJS(IE6), AngularJS(IE9), EmberJS(IE8), WinJS(IE9) ),另有avalon.mobile,它可以更高效地运行于IE10等新版本浏览器中
    • +
    • 没有任何依赖,不到4000行,压缩后不到50KiB
    • 支持管道符风格的过滤函数,方便格式化输出
    • 局部刷新的颗粒度已细化到一个文本节点,特性节点
    • 要操作的节点,在第一次扫描就与视图刷新函数相绑定,并缓存起来,因此没有选择器出场的余地。
    • @@ -32,17 +33,19 @@
    • 自带AMD模块加载器,省得与其他加载器进行整合。
    -

    相关学习教程: 《avalon-learning 教程》《入门教程》官网→ +

    学习教程

    +

    avalon学习教程

    +

    其他教程: 《avalon-learning 教程》《入门教程》HTML5交流会有关avalon的PPT《avalon最佳实践》

    运行github中的示例

    -

    将项目下载到本地,里面有一个叫server.exe的.NET小型服务器(可以需要安装.Net4.0才能运行), +

    将项目下载到本地,里面有一个叫server.exe的.Net小型服务器(可以需要安装.Net4.0才能运行), 点击它然后打开里面与index开头的HTML文件,一边看运行效果,一边看源码进行学习。

    JS文件的压缩

    ``` java -jar compiler.jar --js avalon.js --js_output_file avalon.min.js -java -jar compiler.jar --js avalon.mobile.js --js_output_file avalon.mobile.min.js +java -jar compiler.jar --js avalon.modern.js --js_output_file avalon.modern.min.js ```

    大家也可以在新浪微博第一时间了解它的变更或各种秘笈分享!

    @@ -56,6 +59,7 @@ java -jar compiler.jar --js avalon.mobile.js --js_output_file avalon.mobile.min. @@ -92,8 +106,8 @@ java -jar compiler.jar --js avalon.mobile.js --js_output_file avalon.mobile.min.

    Hello,

    {{firstName +" | "+ lastName }}
      -
    • 全选
    • -
    • {{el}}
    • +
    • 全选
    • +
    • {{el}}
    @@ -105,7 +119,7 @@ java -jar compiler.jar --js avalon.mobile.js --js_output_file avalon.mobile.min. - 全局变量及方法 - avalon的静态方法定义区 - modelFactory -- javascript 底层补丁 +- JavaScript 底层补丁 - DOM 底层补丁 - 配置系统 - avalon的原型方法定义区 diff --git a/avalon.iscroll.js b/avalon.iscroll.js new file mode 100644 index 000000000..c5b2fc2c0 --- /dev/null +++ b/avalon.iscroll.js @@ -0,0 +1,1178 @@ +define(["avalon"], function() { + var rAF = window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function(callback) { + window.setTimeout(callback, 1000 / 60); + }; + var noScroll = avalon.oneObject("TEXTAREA,INPUT,SELECT") + var utils = { + ease: { + quadratic: { + style: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)', + fn: function(k) { + return k * (2 - k); + } + }, + circular: { + style: 'cubic-bezier(0.1, 0.57, 0.1, 1)', // Not properly "circular" but this looks better, it should be (0.075, 0.82, 0.165, 1) + fn: function(k) { + return Math.sqrt(1 - (--k * k)); + } + }, + back: { + style: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)', + fn: function(k) { + var b = 4; + return (k = k - 1) * k * ((b + 1) * k + b) + 1; + } + }, + bounce: { + style: '', + fn: function(k) { + if ((k /= 1) < (1 / 2.75)) { + return 7.5625 * k * k; + } else if (k < (2 / 2.75)) { + return 7.5625 * (k -= (1.5 / 2.75)) * k + 0.75; + } else if (k < (2.5 / 2.75)) { + return 7.5625 * (k -= (2.25 / 2.75)) * k + 0.9375; + } else { + return 7.5625 * (k -= (2.625 / 2.75)) * k + 0.984375; + } + } + }, + elastic: { + style: '', + fn: function(k) { + var f = 0.22, + e = 0.4; + + if (k === 0) { + return 0; + } + if (k === 1) { + return 1; + } + + return (e * Math.pow(2, -10 * k) * Math.sin((k - f / 4) * (2 * Math.PI) / f) + 1); + } + } + }, + offset: function(el) { + var left = -el.offsetLeft, + top = -el.offsetTop; + + // jshint -W084 + while (el = el.offsetParent) { + left -= el.offsetLeft; + top -= el.offsetTop; + } + // jshint +W084 + + return { + left: left, + top: top + }; + }, + momentum: function(current, start, time, lowerMargin, wrapperSize, deceleration) { + var distance = current - start, + speed = Math.abs(distance) / time, + destination, + duration; + + deceleration = deceleration === void 0 ? 0.0006 : deceleration; + + destination = current + (speed * speed) / (2 * deceleration) * (distance < 0 ? -1 : 1) + duration = speed / deceleration + + if (destination < lowerMargin) { + destination = wrapperSize ? lowerMargin - (wrapperSize / 2.5 * (speed / 8)) : lowerMargin + distance = Math.abs(destination - current) + duration = distance / speed + } else if (destination > 0) { + destination = wrapperSize ? wrapperSize / 2.5 * (speed / 8) : 0 + distance = Math.abs(current) + destination + duration = distance / speed + } + + return { + destination: Math.round(destination), + duration: duration + }; + }, + isBadAndroid: /Android /.test(window.navigator.appVersion) && !(/Chrome\/\d/.test(window.navigator.appVersion)), + style: { + transform: avalon.cssName("transform"), + transitionTimingFunction: avalon.cssName("transitionTimingFunction"), + transitionDuration: avalon.cssName("transitionDuration"), + transitionDelay: avalon.cssName("transitionDelay"), + transformOrigin: avalon.cssName("transformOrigin") + }, + addEvent: function(el, type, fn, capture) { + el.addEventListener(type, fn, !!capture); + }, + removeEvent: function(el, type, fn, capture) { + el.removeEventListener(type, fn, !!capture); + } + } + + if (!window.addEventListener) { + utils.addEvent = function(el, type, fn, capture) { + el.attachEvent("on" + type, fn) + } + utils.removeEvent = function(el, type, fn, capture) { + el.detachEvent("on" + type, fn) + } + } + + + + avalon.mix(utils, { + hasTransform: !!avalon.cssName("transform"), + hasPerspective: !!avalon.cssName("perspective"), + hasTransition: !!avalon.cssName("transition") + }) + var touchNames = ["mousedown", "mousemove", "mouseup", ""] + var IE11touch = navigator.pointerEnabled + var IE9_10touch = navigator.msPointerEnabled + if (IE11touch) { //IE11 与 W3C + touchNames = ["pointerdown", "pointermove", "pointerup", "pointercancel"] + } else if (IE9_10touch) { //IE9-10 + touchNames = ["MSPointerDown", "MSPointerMove", "MSPointerUp", "MSPointerCancel"] + } else if ("ontouchstart" in window) { + touchNames = ["touchstart", "touchmove", "touchend", "touchcancel"] + } + + var Passthrough = { + vertical: "vertical", + horizontal: "horizontal", + true: "vertical" + } + +//https://www.gitbook.io/book/iiunknown/iscroll-5-api-cn/reviews + function IScroll(el, options) { + this.wrapper = typeof el === 'string' ? document.querySelector(el) : el; + this.scroller = this.wrapper.children[0]; + this.scrollerStyle = this.scroller.style; + //默认参数 + + this.options = { + mouseWheelSpeed: 20, + snapThreshold: 0.334, + startX: 0, + startY: 0, + scrollY: true, + directionLockThreshold: 5, + momentum: true, //动量效果,拖动惯性;关闭此功能将大幅度提升性能。 + bounce: true, //当滚动器到达容器边界时他将执行一个小反弹动画。在老的或者性能低的设备上禁用反弹对实现平滑的滚动有帮助。 + bounceTime: 600, + bounceEasing: '', + preventDefault: true, + HWCompositing: true, //开启CSS3硬件加速(通过translateZ(0)实现) + useTransition: true, + useTransform: true, + scrollbars: false, //是否出现滚动条 + fadeScrollbars: false, //不想使用滚动条淡入淡出方式时,需要设置此属性为false以便节省资源。 + interactiveScrollbars: false, //此属性可以让滚动条能拖动,用户可以与之交互。 + //滚动条尺寸改变基于容器和滚动区域的宽/高之间的比例。此属性设置为false让滚动条固定大小。 + //这可能有助于自定义滚动条样式(参考下面)。 + resizeScrollbars: true + + + + }; + avalon.mix(this.options, options) + + // 调整参数 + this.translateZ = this.options.HWCompositing && utils.hasPerspective ? ' translateZ(0)' : ''; + + //使用CSS transition来实现动画效果(动量和弹力)。如果设置为false,那么将使用requestAnimationFrame代替。 + //在现在浏览器中这两者之间的差异并不明显。在老的设备上transitions执行得更好。 + this.options.useTransition = utils.hasTransition && this.options.useTransition + + //默认情况下引擎会使用CSStransform属性。如果现在还是2007年,那么可以设置这个属性为false,这就是说:引擎将使 + //用top/left属性来进行滚动。 + //这个属性在滚动器感知到Flash,iframe或者视频插件内容时会有用,但是需要注意:性能会有极大的损耗。 + this.options.useTransform = utils.hasTransform && this.options.useTransform; + + // 决定哪一个滚动条使用原生滚动条 + // 当其值为horizontal时,横向为人工的,纵向为原生的; + // 当其值为vertical时,横向为原生的,纵向为人工的; + // 它只有horizontal,vertical,undefined三种值 + var ep = this.options.eventPassthrough + ep = this.options.eventPassthrough = Passthrough[ep] + + // 默认情况下scrollY为true, scrollX为false,因此只出现纵向滚动条,想出现横向滚动条,需要设置scrollX = true + this.options.scrollY = ep === "vertical" ? false : !!this.options.scrollY + this.options.scrollX = ep === "horizontal" ? false : !!this.options.scrollX + + this.options.preventDefault = !this.options.eventPassthrough && !!this.options.preventDefault; + + // 此属性针对于两个两个纬度的滚动条(当你需要横向和纵向滚动条)。通常情况下你开始滚动一个方向上的滚动条,另外一 + // 个方向上会被锁定不动。有些时候,你需要无约束的移动(横向和纵向可以同时响应),在这样的情况下此属性需要设置 + // 为true。默认值:false + + this.options.freeScroll = !!(this.options.freeScroll && !this.options.eventPassthrough); + this.options.directionLockThreshold = this.options.eventPassthrough ? 0 : this.options.directionLockThreshold; + + this.options.bounceEasing = typeof this.options.bounceEasing === 'string' ? utils.ease[this.options.bounceEasing] || utils.ease.circular : this.options.bounceEasing; + + this.options.resizePolling = this.options.resizePolling === void 0 ? 60 : this.options.resizePolling; + + if (this.options.tap === true) { + this.options.tap = 'tap'; + } + + if (this.options.shrinkScrollbars == 'scale') { + this.options.useTransition = false; + } + + this.options.invertWheelDirection = this.options.invertWheelDirection ? -1 : 1; + +// INSERT POINT: NORMALIZATION + + // Some defaults + this.x = 0; + this.y = 0; + this.directionX = 0; + this.directionY = 0; + this._events = {}; + +// INSERT POINT: DEFAULTS + + this._init(); + this.refresh(); + + this.scrollTo(this.options.startX, this.options.startY); + this.enable(); + } + avalon.IScroll = IScroll + + function getTransitionEndEventName() { + var obj = { + TransitionEvent: "transitionend", + WebKitTransitionEvent: "webkittransitionEnd", + OTransitionEvent: "OTransitionEnd", + otransitionEvent: "otransitionEnd", + MSTransitionEvent: "MSTransitionEnd" + } + // var ev = document.createEvent("TransitionEvent"); // FIXME: un-specified + // ev.initTransitionEvent("transitionend", true, true, "some-unknown-prop", -4.75); + // document.body.dispatchEvent(ev); + var ret = false, ev + for (var name in obj) { + try { + ev = document.createEvent(name)//只有firefox不支持 + ret = obj[name] + break + } catch (e) { + } + } + if (ret === false) { + //https://bugzilla.mozilla.org/show_bug.cgi?id=868751 + //https://hg.mozilla.org/integration/mozilla-inbound/rev/a20ea0d494a0 + try { + ev = new TransitionEvent("transitionend", + { + bubbles: true, + cancelable: true, + propertyName: "some-unknown-prop", + elapsedTime: 0.5, + pseudoElement: "pseudo" + }); + ret = "transitionend" + } catch (e) { + } + } + getTransitionEndEventName = function() { + return ret + } + return ret + } + + function fixEvent(event) { + var ret = {} + for (var i in event) { + ret[i] = event[i] + } + var target = ret.target = event.srcElement + if (event.type.indexOf("key") === 0) { + ret.which = event.charCode != null ? event.charCode : event.keyCode + } else if (/mouse|click/.test(event.type)) { + var doc = target.ownerDocument || DOC + var box = doc.compatMode === "BackCompat" ? doc.body : doc.documentElement + ret.pageX = event.clientX + (box.scrollLeft >> 0) - (box.clientLeft >> 0) + ret.pageY = event.clientY + (box.scrollTop >> 0) - (box.clientTop >> 0) + } + ret.timeStamp = new Date - 0 + ret.originalEvent = event + ret.preventDefault = function() { //阻止默认行为 + event.returnValue = false + } + ret.stopPropagation = function() { //阻止事件在DOM树中的传播 + event.cancelBubble = true + } + return ret + } + + IScroll.prototype = { + version: '5.1.2', + _init: function() { + this._initEvents() + console.log(this.options.scrollbars || this.options.indicators) + if (this.options.scrollbars || this.options.indicators) { + // this._initIndicators(); + } + + if (this.options.mouseWheel) { + this._initWheel(); + } + + if (this.options.snap) { + // this._initSnap(); + } + + if (this.options.keyBindings) { + // this._initKeys(); + } + + }, + destroy: function() { + this._initEvents(true); + + this._execEvent('destroy'); + }, + _transitionEnd: function(e) { + if (e.target !== this.scroller || !this.isInTransition) { + return; + } + + this._transitionTime(); + if (!this.resetPosition(this.options.bounceTime)) { + this.isInTransition = false; + this._execEvent('scrollEnd'); + } + }, + refresh: function() { + var rf = this.wrapper.offsetHeight; // Force reflow + + this.wrapperWidth = this.wrapper.clientWidth; + this.wrapperHeight = this.wrapper.clientHeight; + + /* REPLACE START: refresh */ + + this.scrollerWidth = this.scroller.offsetWidth; + this.scrollerHeight = this.scroller.offsetHeight; + + this.maxScrollX = this.wrapperWidth - this.scrollerWidth; + this.maxScrollY = this.wrapperHeight - this.scrollerHeight; + + /* REPLACE END: refresh */ + + this.hasHorizontalScroll = this.options.scrollX && this.maxScrollX < 0; + this.hasVerticalScroll = this.options.scrollY && this.maxScrollY < 0; + + if (!this.hasHorizontalScroll) { + this.maxScrollX = 0; + this.scrollerWidth = this.wrapperWidth; + } + + if (!this.hasVerticalScroll) { + this.maxScrollY = 0; + this.scrollerHeight = this.wrapperHeight; + } + + this.endTime = 0; + this.directionX = 0; + this.directionY = 0; + + this.wrapperOffset = utils.offset(this.wrapper); + + this._execEvent('refresh'); + + this.resetPosition(); + +// INSERT POINT: _refresh + + }, + _initEvents: function(remove) { + //绑定或卸载事件 + var eventType = remove ? utils.removeEvent : utils.addEvent + + var target = this.options.bindToWrapper ? this.wrapper : window + var that = this + + function hander(e) { + if (!("target" in e)) { + e = fixEvent(e) + } + that.handleEvent(e) + } + + + eventType(window, 'onorientationchange' in window ? "orientationchange" : "resize", hander) + + if (this.options.click) { + eventType(this.wrapper, "click", hander, true) + } + for (var i = 0, type; type = touchNames[i]; i++) { + var el = i === 0 ? this.wrapper : target + eventType(el, type, hander) + } + eventType(this.scroller, getTransitionEndEventName(), hander) + }, + handleEvent: function(e) { + switch (e.type) { + case touchNames[0]: + this._start(e) + break + case touchNames[1]: + this._move(e) + break + case touchNames[2]: + case touchNames[3]: + this._end(e) + break + case "orientationchange": + case "resize": + this._resize() + break + case getTransitionEndEventName(): + this._transitionEnd(e) + break + case "keydown": + this._key(e) + break + case "click": + if (!e._constructed) { + e.preventDefault() + e.stopPropagation() + } + break + } + }, + on: function(type, fn) {//添加用户回调 + if (!this._events[type]) { + this._events[type] = [] + } + this._events[type].push(fn) + }, + off: function(type, fn) {//卸载用户回调 + var fns = this._events[type] || [] + var index = fns.indexOf(fn) + if (index > -1) { + this._events[type].splice(index, 1) + } + }, + _execEvent: function(type) {//触发用户回调 + var fns = this._events[type] || [] + var args = [].slice.call(arguments, 1) + for (var i = 0, fn; fn = fns[i++]; ) { + fn.apply(this, args) + } + }, + _start: function(e) { + if (!this.enabled || this.initiated || e.button !== 0) { + return//只处理左键 + } + + if (this.options.preventDefault && !utils.isBadAndroid && !noScroll[e.target.tagName]) { + if (!this.options.mouseWheel) { + e.preventDefault(); + } + } + + var point = e.touches ? e.touches[0] : e + + this.initiated = true + this.moved = false; + this.distX = 0; + this.distY = 0; + this.directionX = 0; + this.directionY = 0; + this.directionLocked = 0; + + this._transitionTime() + + this.startTime = new Date - 0 + + if (this.options.useTransition && this.isInTransition) { + this.isInTransition = false; + var pos = this.getComputedPosition(); + this._translate(Math.round(pos.x), Math.round(pos.y)); + this._execEvent('scrollEnd'); + } else if (!this.options.useTransition && this.isAnimating) { + this.isAnimating = false; + this._execEvent('scrollEnd'); + } + + this.startX = this.x; + this.startY = this.y; + this.absStartX = this.x; + this.absStartY = this.y; + this.pointStartX = point.pageX; + this.pointStartY = point.pageY; + this.pointX = point.pageX; + this.pointY = point.pageY; + + this._execEvent('beforeScrollStart') // + }, + _move: function(e) { + if (!this.initiated) { + return; + } + + if (this.options.preventDefault) { // increases performance on Android? TODO: check! + e.preventDefault(); + } + + var point = e.touches ? e.touches[0] : e, + deltaX = point.pageX - this.pointStartX, + deltaY = point.pageY - this.pointStartY, + timestamp = new Date - 0, + newX, newY, + absDistX, absDistY; + + this.pointX = point.pageX;//当前相对于页面的坐标 + this.pointY = point.pageY; + + this.distX = deltaX; + this.distY = deltaY; + absDistX = Math.abs(this.distX); + absDistY = Math.abs(this.distY); + + // We need to move at least 10 pixels for the scrolling to initiate + if (timestamp - this.endTime > 300 && (absDistX < 10 && absDistY < 10)) { + return; + } + + // If you are scrolling in one direction lock the other + if (!this.directionLocked && !this.options.freeScroll) { + if (absDistX > absDistY + this.options.directionLockThreshold) { + this.directionLocked = 'h'; // lock horizontally + } else if (absDistY >= absDistX + this.options.directionLockThreshold) { + this.directionLocked = 'v'; // lock vertically + } else { + this.directionLocked = 'n'; // no lock + } + } + + if (this.directionLocked === 'h') { + if (this.options.eventPassthrough === 'vertical') { + e.preventDefault(); + } else if (this.options.eventPassthrough === 'horizontal') { + this.initiated = false; + return; + } + + deltaY = 0; + } else if (this.directionLocked === 'v') { + if (this.options.eventPassthrough === 'horizontal') { + e.preventDefault(); + } else if (this.options.eventPassthrough === 'vertical') { + this.initiated = false; + return; + } + + deltaX = 0; + } + + deltaX = this.hasHorizontalScroll ? deltaX : 0; + deltaY = this.hasVerticalScroll ? deltaY : 0; + + newX = this.x + deltaX; + newY = this.y + deltaY; + + // Slow down if outside of the boundaries + if (newX > 0 || newX < this.maxScrollX) { + newX = this.options.bounce ? this.x + deltaX / 3 : newX > 0 ? 0 : this.maxScrollX; + } + if (newY > 0 || newY < this.maxScrollY) { + newY = this.options.bounce ? this.y + deltaY / 3 : newY > 0 ? 0 : this.maxScrollY; + } + + this.directionX = deltaX > 0 ? -1 : deltaX < 0 ? 1 : 0; + this.directionY = deltaY > 0 ? -1 : deltaY < 0 ? 1 : 0; + + if (!this.moved) { + this._execEvent('scrollStart'); + } + + this.moved = true; + + this._translate(newX, newY); + + /* REPLACE START: _move */ + + if (timestamp - this.startTime > 300) { + this.startTime = timestamp; + this.startX = this.x; + this.startY = this.y; + } + + /* REPLACE END: _move */ + + }, + _end: function(e) { + if (!this.enabled) { + return; + } + + if (this.options.preventDefault && !noScroll[e.target.tagName]) { + e.preventDefault(); + } + + var point = e.changedTouches ? e.changedTouches[0] : e, + momentumX, + momentumY, + duration = new Date - this.startTime, + newX = Math.round(this.x), + newY = Math.round(this.y), + distanceX = Math.abs(newX - this.startX), + distanceY = Math.abs(newY - this.startY), + time = 0, + easing = ''; + + this.isInTransition = 0; + this.initiated = false; + this.endTime = new Date - 0; + + // reset if we are outside of the boundaries + if (this.resetPosition(this.options.bounceTime)) { + return; + } + + this.scrollTo(newX, newY); // ensures that the last position is rounded + + // we scrolled less than 10 pixels + if (!this.moved) { + this._execEvent('scrollCancel'); + return; + } + + // start momentum animation if needed + if (this.options.momentum && duration < 300) { + momentumX = this.hasHorizontalScroll ? utils.momentum(this.x, this.startX, duration, this.maxScrollX, this.options.bounce ? this.wrapperWidth : 0, this.options.deceleration) : {destination: newX, duration: 0}; + momentumY = this.hasVerticalScroll ? utils.momentum(this.y, this.startY, duration, this.maxScrollY, this.options.bounce ? this.wrapperHeight : 0, this.options.deceleration) : {destination: newY, duration: 0}; + newX = momentumX.destination; + newY = momentumY.destination; + time = Math.max(momentumX.duration, momentumY.duration); + this.isInTransition = 1; + } + +// INSERT POINT: _end + + if (newX !== this.x || newY !== this.y) { + // change easing function when scroller goes out of the boundaries + if (newX > 0 || newX < this.maxScrollX || newY > 0 || newY < this.maxScrollY) { + easing = utils.ease.quadratic; + } + + this.scrollTo(newX, newY, time, easing); + return; + } + + this._execEvent('scrollEnd'); + }, + _translate: function(x, y) { + if (this.options.useTransform) {//使用transform + this.scrollerStyle[utils.style.transform] = 'translate(' + x + 'px,' + y + 'px)' + this.translateZ; + } else { + x = Math.round(x);//使用translate + y = Math.round(y); + this.scrollerStyle.left = x + 'px'; + this.scrollerStyle.top = y + 'px'; + } + this.x = x; + this.y = y; + }, + _animate: function(destX, destY, duration, easingFn) { + var that = this, + startX = this.x, + startY = this.y, + startTime = utils.getTime(), + destTime = startTime + duration; + + function step() { + var now = utils.getTime(), + newX, newY, + easing; + + if (now >= destTime) { + that.isAnimating = false; + that._translate(destX, destY); + + if (!that.resetPosition(that.options.bounceTime)) { + that._execEvent('scrollEnd'); + } + + return; + } + + now = (now - startTime) / duration; + easing = easingFn(now); + newX = (destX - startX) * easing + startX; + newY = (destY - startY) * easing + startY; + that._translate(newX, newY); + + if (that.isAnimating) { + rAF(step); + } + } + + this.isAnimating = true; + step(); + }, + _transitionTime: function(time) { + //调整时长 + time = time || 0 + this.scrollerStyle[utils.style.transitionDuration] = time + 'ms'; + if (!time && utils.isBadAndroid) { + this.scrollerStyle[utils.style.transitionDuration] = '0.001s'; + } + }, + _transitionTimingFunction: function(easing) { + //设置缓动公式 + this.scrollerStyle[utils.style.transitionTimingFunction] = easing; + }, + getComputedPosition: function() { + var matrix = window.getComputedStyle(this.scroller, null), + x, y; + + if (this.options.useTransform) { + matrix = matrix[utils.style.transform].split(')')[0].split(', '); + x = +(matrix[12] || matrix[4]); + y = +(matrix[13] || matrix[5]); + } else { + x = +matrix.left.replace(/[^-\d.]/g, ''); + y = +matrix.top.replace(/[^-\d.]/g, ''); + } + + return {x: x, y: y}; + }, + _resize: function() { + var that = this; + + clearTimeout(this.resizeTimeout); + + this.resizeTimeout = setTimeout(function() { + that.refresh(); + }, this.options.resizePolling); + }, + resetPosition: function(time) { + var x = this.x, + y = this.y; + + time = time || 0; + + if (!this.hasHorizontalScroll || this.x > 0) { + x = 0; + } else if (this.x < this.maxScrollX) { + x = this.maxScrollX; + } + + if (!this.hasVerticalScroll || this.y > 0) { + y = 0; + } else if (this.y < this.maxScrollY) { + y = this.maxScrollY; + } + + if (x === this.x && y === this.y) { + return false; + } + + this.scrollTo(x, y, time, this.options.bounceEasing); + + return true; + }, + //调用这个方法会立即停止动画滚动,并且把滚动位置还原成0,取消绑定touchmove, touchend、touchcancel事件。 + disable: function() { + this.enabled = false; + }, + //调用这个方法,使得iscroll恢复默认正常状态 + enable: function() { + this.enabled = true; + }, + /********************************************************************* + * 滚动到目标位置 * + **********************************************************************/ + scrollBy: function(x, y, time, easing) { + x = this.x + x; + y = this.y + y; + time = time || 0; + + this.scrollTo(x, y, time, easing); + }, + //这个方法接受4个参数 x, y, time, easing x 为移动的x轴坐标,y为移动的y轴坐标, time为移动时间,easing表示使用何种缓动公式。 + scrollTo: function(x, y, time, easing) { + easing = easing || utils.ease.circular; + this.isInTransition = this.options.useTransition && time > 0;// 正在使用CSS3transition + if (!time || (this.options.useTransition && easing.style)) { + this._transitionTimingFunction(easing.style); + this._transitionTime(time); + this._translate(x, y); + } else {//如果不支持,使用JS动画 + this._animate(x, y, time, easing.fn); + } + }, + // scrollToElement --> scrollTo --> _translate or _animate + // scrollBy --> scrollTo --> _translate or _animate + scrollToElement: function(el, time, offsetX, offsetY, easing) { + el = el.nodeType ? el : this.scroller.querySelector(el); + + if (!el) { + return; + } + + var pos = utils.offset(el); + + pos.left -= this.wrapperOffset.left; + pos.top -= this.wrapperOffset.top; + + // if offsetX/Y are true we center the element to the screen + if (offsetX === true) { + offsetX = Math.round(el.offsetWidth / 2 - this.wrapper.offsetWidth / 2); + } + if (offsetY === true) { + offsetY = Math.round(el.offsetHeight / 2 - this.wrapper.offsetHeight / 2); + } + + pos.left -= offsetX || 0; + pos.top -= offsetY || 0; + + pos.left = pos.left > 0 ? 0 : pos.left < this.maxScrollX ? this.maxScrollX : pos.left; + pos.top = pos.top > 0 ? 0 : pos.top < this.maxScrollY ? this.maxScrollY : pos.top; + + time = time == null || time === 'auto' ? Math.max(Math.abs(this.x - pos.left), Math.abs(this.y - pos.top)) : time; + + this.scrollTo(pos.left, pos.top, time, easing); + }, + /********************************************************************* + * 对齐处理 * + **********************************************************************/ + _initWheel: function() { + var that = this + var removeFn = avalon.bind(this.wrapper, "mousewheel", function(e) { + if (!that.enabled) { + return; + } + + e.preventDefault(); + e.stopPropagation() + + if (that.wheelTimeout === undefined) { + that._execEvent('scrollStart'); + } + + // Execute the scrollEnd event after 400ms the wheel stopped scrolling + clearTimeout(that.wheelTimeout); + that.wheelTimeout = setTimeout(function() { + that._execEvent('scrollEnd'); + that.wheelTimeout = undefined; + }, 400); + if (e.wheelDeltaX === void 0) { + e.wheelDeltaY = e.wheelDelta + e.wheelDeltaX = 0 + } + var wheelDeltaX = e.wheelDeltaX / 120 * that.options.mouseWheelSpeed + var wheelDeltaY = e.wheelDeltaY / 120 * that.options.mouseWheelSpeed + wheelDeltaX *= that.options.invertWheelDirection + wheelDeltaY *= that.options.invertWheelDirection + + if (!that.hasVerticalScroll) { + wheelDeltaX = wheelDeltaY; + wheelDeltaY = 0; + } + + if (that.options.snap) { + var newX = that.currentPage.pageX; + var newY = that.currentPage.pageY; + + if (wheelDeltaX > 0) { + newX--; + } else if (wheelDeltaX < 0) { + newX++; + } + + if (wheelDeltaY > 0) { + newY--; + } else if (wheelDeltaY < 0) { + newY++; + } + + that.goToPage(newX, newY); + + return; + } + newX = that.x + Math.round(that.hasHorizontalScroll ? wheelDeltaX : 0); + newY = that.y + Math.round(that.hasVerticalScroll ? wheelDeltaY : 0); + + if (newX > 0) { + newX = 0; + } else if (newX < that.maxScrollX) { + newX = that.maxScrollX; + } + + if (newY > 0) { + newY = 0; + } else if (newY < that.maxScrollY) { + newY = that.maxScrollY; + } + + that.scrollTo(newX, newY, 0); + + }) + + this.on('destroy', function() { + avalon.bind(this.wrapper, "mousewheel", removeFn) + }); + }, + /********************************************************************* + * Snap * + **********************************************************************/ + _initSnap: function() { + this.currentPage = {}; + + if (typeof this.options.snap === 'string') { + this.options.snap = this.scroller.querySelectorAll(this.options.snap); + } + + this.on('refresh', function() { + var i = 0, l, + m = 0, n, + cx, cy, + x = 0, y, + stepX = this.options.snapStepX || this.wrapperWidth, + stepY = this.options.snapStepY || this.wrapperHeight, + el; + + this.pages = []; + + if (!this.wrapperWidth || !this.wrapperHeight || !this.scrollerWidth || !this.scrollerHeight) { + return; + } + + if (this.options.snap === true) { + cx = Math.round(stepX / 2); + cy = Math.round(stepY / 2); + + while (x > -this.scrollerWidth) { + this.pages[i] = []; + l = 0; + y = 0; + + while (y > -this.scrollerHeight) { + this.pages[i][l] = { + x: Math.max(x, this.maxScrollX), + y: Math.max(y, this.maxScrollY), + width: stepX, + height: stepY, + cx: x - cx, + cy: y - cy + }; + + y -= stepY; + l++; + } + + x -= stepX; + i++; + } + } else { + el = this.options.snap; + l = el.length; + n = -1; + + for (; i < l; i++) { + if (i === 0 || el[i].offsetLeft <= el[i - 1].offsetLeft) { + m = 0; + n++; + } + + if (!this.pages[m]) { + this.pages[m] = []; + } + + x = Math.max(-el[i].offsetLeft, this.maxScrollX); + y = Math.max(-el[i].offsetTop, this.maxScrollY); + cx = x - Math.round(el[i].offsetWidth / 2); + cy = y - Math.round(el[i].offsetHeight / 2); + + this.pages[m][n] = { + x: x, + y: y, + width: el[i].offsetWidth, + height: el[i].offsetHeight, + cx: cx, + cy: cy + }; + + if (x > this.maxScrollX) { + m++; + } + } + } + + this.goToPage(this.currentPage.pageX || 0, this.currentPage.pageY || 0, 0); + + // Update snap threshold if needed + if (this.options.snapThreshold % 1 === 0) { + this.snapThresholdX = this.options.snapThreshold; + this.snapThresholdY = this.options.snapThreshold; + } else { + this.snapThresholdX = Math.round(this.pages[this.currentPage.pageX][this.currentPage.pageY].width * this.options.snapThreshold); + this.snapThresholdY = Math.round(this.pages[this.currentPage.pageX][this.currentPage.pageY].height * this.options.snapThreshold); + } + }); + + this.on('flick', function() { + var time = this.options.snapSpeed || Math.max( + Math.max( + Math.min(Math.abs(this.x - this.startX), 1000), + Math.min(Math.abs(this.y - this.startY), 1000) + ), 300); + + this.goToPage( + this.currentPage.pageX + this.directionX, + this.currentPage.pageY + this.directionY, + time + ); + }); + }, + _nearestSnap: function(x, y) { + if (!this.pages.length) { + return {x: 0, y: 0, pageX: 0, pageY: 0}; + } + + var i = 0, + l = this.pages.length, + m = 0; + + // Check if we exceeded the snap threshold + if (Math.abs(x - this.absStartX) < this.snapThresholdX && + Math.abs(y - this.absStartY) < this.snapThresholdY) { + return this.currentPage; + } + + if (x > 0) { + x = 0; + } else if (x < this.maxScrollX) { + x = this.maxScrollX; + } + + if (y > 0) { + y = 0; + } else if (y < this.maxScrollY) { + y = this.maxScrollY; + } + + for (; i < l; i++) { + if (x >= this.pages[i][0].cx) { + x = this.pages[i][0].x; + break; + } + } + + l = this.pages[i].length; + + for (; m < l; m++) { + if (y >= this.pages[0][m].cy) { + y = this.pages[0][m].y; + break; + } + } + + if (i === this.currentPage.pageX) { + i += this.directionX; + + if (i < 0) { + i = 0; + } else if (i >= this.pages.length) { + i = this.pages.length - 1; + } + + x = this.pages[i][0].x; + } + + if (m === this.currentPage.pageY) { + m += this.directionY; + + if (m < 0) { + m = 0; + } else if (m >= this.pages[0].length) { + m = this.pages[0].length - 1; + } + + y = this.pages[0][m].y; + } + + return { + x: x, + y: y, + pageX: i, + pageY: m + }; + }, + goToPage: function(x, y, time, easing) { + easing = easing || this.options.bounceEasing; + + if (x >= this.pages.length) { + x = this.pages.length - 1; + } else if (x < 0) { + x = 0; + } + + if (y >= this.pages[x].length) { + y = this.pages[x].length - 1; + } else if (y < 0) { + y = 0; + } + + var posX = this.pages[x][y].x, + posY = this.pages[x][y].y; + + time = time === undefined ? this.options.snapSpeed || Math.max( + Math.max( + Math.min(Math.abs(posX - this.x), 1000), + Math.min(Math.abs(posY - this.y), 1000) + ), 300) : time; + + this.currentPage = { + x: posX, + y: posY, + pageX: x, + pageY: y + }; + + this.scrollTo(posX, posY, time, easing); + }, + next: function(time, easing) { + var x = this.currentPage.pageX, + y = this.currentPage.pageY; + + x++; + + if (x >= this.pages.length && this.hasVerticalScroll) { + x = 0; + y++; + } + + this.goToPage(x, y, time, easing); + }, + prev: function(time, easing) { + var x = this.currentPage.pageX, + y = this.currentPage.pageY; + + x--; + + if (x < 0 && this.hasVerticalScroll) { + x = 0; + y--; + } + + this.goToPage(x, y, time, easing); + } + } +}) diff --git a/avalon.js b/avalon.js index 10d328763..fad070006 100644 --- a/avalon.js +++ b/avalon.js @@ -1,17 +1,16 @@ /*================================================== - Copyright 20013-2014 司徒正美 and other contributors + Copyright 2013-2014 司徒正美 and other contributors http://www.cnblogs.com/rubylouvre/ https://github.com/RubyLouvre http://weibo.com/jslouvre/ Released under the MIT license - avalon 1.3.2 2014.8.11 + avalon 1.3.6 2014.10.10 ==================================================*/ (function(DOC) { /********************************************************************* * 全局变量及方法 * **********************************************************************/ - var prefix = "ms-" var expose = new Date - 0 var subscribers = "$" + expose //http://addyosmani.com/blog/understanding-mvvm-a-guide-for-javascript-developers/ @@ -22,7 +21,8 @@ var rword = /[^, ]+/g //切割字符串为一个个小块,以空格或豆号分开它们,结合replace实现字符串的forEach var rnative = /\[native code\]/ //判定是否原生函数 var rcomplexType = /^(?:object|array)$/ - var rwindow = /^\[object (Window|DOMWindow|global)\]$/ + var rsvg = /^\[object SVG\w*Element\]$/ + var rwindow = /^\[object (?:Window|DOMWindow|global)\]$/ var oproto = Object.prototype var ohasOwn = oproto.hasOwnProperty var serialize = oproto.toString @@ -43,9 +43,10 @@ function noop() { } - function log(a) { + function log() { if (window.console && avalon.config.debug) { - console.log(W3C ? a : a + "") + // http://stackoverflow.com/questions/8785624/how-to-safely-wrap-console-log + Function.apply.call(console.log, console, arguments) } } @@ -61,7 +62,7 @@ return result } - //生成UUID http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript +//生成UUID http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript function generateID() { return "avalon" + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15) } @@ -87,6 +88,17 @@ typeof obj } + var isFunction = typeof alert === "object" ? function(fn) { + try { + return /^\s*\bfunction\b/.test(fn + "") + } catch (e) { + return false + } + } : function(fn) { + return serialize.call(fn) == "[object Function]" + } + avalon.isFunction = isFunction + avalon.isWindow = function(obj) { if (!obj) return false @@ -101,6 +113,11 @@ if (isWindow(window)) { avalon.isWindow = isWindow } + var enu + for (enu in avalon({})) { + break + } + var enumerateBUG = enu !== "0"//IE6下为true, 其他为false /*判定是否是一个朴素的javascript对象(Object),不是DOM对象,不是BOM对象,不是自定义类的实例*/ avalon.isPlainObject = function(obj, key) { if (!obj || avalon.type(obj) !== "object" || obj.nodeType || avalon.isWindow(obj)) { @@ -115,16 +132,21 @@ } catch (e) {//IE8 9会在这里抛错 return false; } + if (enumerateBUG) { + for (key in obj) { + return ohasOwn.call(obj, key) + } + } for (key in obj) { } - return key === undefined || ohasOwn.call(obj, key); + return key === void 0 || ohasOwn.call(obj, key); } if (rnative.test(Object.getPrototypeOf)) { avalon.isPlainObject = function(obj) { return !!obj && typeof obj === "object" && Object.getPrototypeOf(obj) === oproto } } - //与jQuery.extend方法,可用于浅拷贝,深拷贝 +//与jQuery.extend方法,可用于浅拷贝,深拷贝 avalon.mix = avalon.fn.mix = function() { var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {}, @@ -139,23 +161,27 @@ i++ } - //确保接受方为一个复杂的数据类型 +//确保接受方为一个复杂的数据类型 if (typeof target !== "object" && avalon.type(target) !== "function") { target = {} } - //如果只有一个参数,那么新成员添加于mix所在的对象上 +//如果只有一个参数,那么新成员添加于mix所在的对象上 if (i === length) { target = this i-- } for (; i < length; i++) { - //只处理非空参数 +//只处理非空参数 if ((options = arguments[i]) != null) { for (name in options) { src = target[name] - copy = options[name] + try { + copy = options[name]//当options为VBS对象时报错 + } catch (e) { + continue + } // 防止环引用 if (target === copy) { @@ -188,7 +214,7 @@ avalon.mix({ rword: rword, subscribers: subscribers, - version: 1.33, + version: 1.36, ui: {}, log: log, slice: W3C ? function(nodes, start, end) { @@ -332,9 +358,8 @@ /*只有当前数组不存在此元素时只添加它*/ ensure: function(target, item) { if (target.indexOf(item) === -1) { - target.push(item) + return target.push(item) } - return target }, /*移除数组中指定位置的元素,返回布尔表示成功与否*/ removeAt: function(target, index) { @@ -380,12 +405,12 @@ avalon.define = function(id, factory) { var $id = id.$id || id if (!$id) { - log("warning: 必须指定$id") + log("warning: vm必须指定$id") } if (VMODELS[id]) { log("warning: " + $id + " 已经存在于avalon.vmodels中") } - if (typeof id == "object") { + if (typeof id === "object") { var model = modelFactory(id) } else { var scope = { @@ -401,69 +426,177 @@ return VMODELS[$id] = model } - function modelFactory(scope, model) { - if (Array.isArray(scope)) { - var arr = scope.concat() - scope.length = 0 - var collection = Collection(scope) - collection.push.apply(collection, arr) + //一些不需要被监听的属性 + var $$skipArray = String("$id,$watch,$unwatch,$fire,$events,$model,$skipArray").match(rword) + function isObservable(name, value, $skipArray) { + if (isFunction(value) || value && value.nodeType) { + return false + } + if ($skipArray.indexOf(name) !== -1) { + return false + } + if ($$skipArray.indexOf(name) !== -1) { + return false + } + var $special = $skipArray.$special + if (name && name.charAt(0) === "$" && !$special[name]) { + return false + } + return true + } + + function modelFactory($scope, $special, $model) { + if (Array.isArray($scope)) { + var arr = $scope.concat() + $scope.length = 0 + var collection = Collection($scope) + collection.pushArray(arr) return collection } - if (typeof scope.nodeType === "number") { - return scope - } - var vmodel = {} //要返回的对象 - model = model || {} //放置$model上的属性 - var accessingProperties = {} //监控属性 - var normalProperties = {} //普通属性 - var computedProperties = [] //计算属性 - var watchProperties = arguments[2] || {} //强制要监听的属性 - var skipArray = scope.$skipArray //要忽略监控的属性 - for (var i = 0, name; name = skipProperties[i++]; ) { - if (typeof name !== "string") { - log("warning:$skipArray[" + name + "] must be a string") - } - delete scope[name] - normalProperties[name] = true - } - if (Array.isArray(skipArray)) { - for (var i = 0, name; name = skipArray[i++]; ) { - normalProperties[name] = true - } - } - for (var i in scope) { - accessorFactory(i, scope[i], model, normalProperties, accessingProperties, computedProperties, watchProperties) - } - vmodel = defineProperties(vmodel, descriptorFactory(accessingProperties), normalProperties) //生成一个空的ViewModel - for (var name in normalProperties) { - vmodel[name] = normalProperties[name] - } - watchProperties.vmodel = vmodel - vmodel.$model = model - vmodel.$events = {} - vmodel.$id = generateID() - vmodel.$accessors = accessingProperties - vmodel[subscribers] = [] - for (var i in Events) { - var fn = Events [i] - if (!W3C) { //在IE6-8下,VB对象的方法里的this并不指向自身,需要用bind处理一下 - fn = fn.bind(vmodel) + if (typeof $scope.nodeType === "number") { + return $scope + } + if ($scope.$id && $scope.$model && $scope.$events) {//fix IE6-8 createWithProxy $val: val引发的BUG + return $scope + } + if (!Array.isArray($scope.$skipArray)) { + $scope.$skipArray = [] + } + $scope.$skipArray.$special = $special || {}//强制要监听的属性 + var $vmodel = {} //要返回的对象, 它在IE6-8下可能被偷龙转凤 + $model = $model || {} //vmodels.$model属性 + var $events = {} //vmodel.$events属性 + var watchedProperties = {} //监控属性 + var computedProperties = [] //计算属性 + for (var i in $scope) { + (function(name, val) { + $model[name] = val + if (!isObservable(name, val, $scope.$skipArray)) { + return //过滤所有非监控属性 + } + //总共产生三种accessor + var accessor + var valueType = avalon.type(val) + $events[name] = [] + //总共产生三种accessor + if (valueType === "object" && isFunction(val.get) && Object.keys(val).length <= 2) { + var setter = val.set + var getter = val.get + //第1种对应计算属性, 因变量,通过其他监控属性触发其改变 + accessor = function(newValue) { + var $events = $vmodel.$events + var oldValue = $model[name] + if (arguments.length) { + if (stopRepeatAssign) { + return + } + if (isFunction(setter)) { + var backup = $events[name] + $events[name] = [] //清空回调,防止内部冒泡而触发多次$fire + setter.call($vmodel, newValue) + $events[name] = backup + } + } else { + if (avalon.openComputedCollect) { // 收集视图刷新函数 + collectSubscribers($events[name]) + } + } + newValue = $model[name] = getter.call($vmodel) //同步$model + if (!isEqual(oldValue, newValue)) { + withProxyCount && updateWithProxy($vmodel.$id, name, newValue) //同步循环绑定中的代理VM + notifySubscribers($events[name]) //同步视图 + safeFire($vmodel, name, newValue, oldValue) //触发$watch回调 + } + return newValue + } + computedProperties.push(function() { + Registry[expose] = { + evaluator: accessor, + element: head, + type: "computed::" + name, + handler: noop, + args: [] + } + accessor() + collectSubscribers($events[name]) + delete Registry[expose] + }) + } else if (rcomplexType.test(valueType)) { + //第2种对应子ViewModel或监控数组 + accessor = function(newValue) { + var childVmodel = accessor.child + var oldValue = $model[name] + if (arguments.length) { + if (stopRepeatAssign) { + return + } + if (!isEqual(oldValue, newValue)) { + childVmodel = accessor.child = updateChild($vmodel, name, newValue, valueType) + newValue = $model[name] = childVmodel.$model //同步$model + var fn = rebindings[childVmodel.$id] + fn && fn() //同步视图 + safeFire($vmodel, name, newValue, oldValue) //触发$watch回调 + } + } else { + collectSubscribers($events[name]) //收集视图函数 + return childVmodel + } + } + var childVmodel = accessor.child = modelFactory(val, 0, $model[name]) + childVmodel.$events[subscribers] = $events[name] + } else { + //第3种对应简单的数据类型,自变量,监控属性 + accessor = function(newValue) { + var oldValue = $model[name] + if (arguments.length) { + if (!isEqual(oldValue, newValue)) { + $model[name] = newValue //同步$model + withProxyCount && updateWithProxy($vmodel.$id, name, newValue) //同步代理VM + notifySubscribers($events[name]) //同步视图 + safeFire($vmodel, name, newValue, oldValue) //触发$watch回调 + } + } else { + collectSubscribers($events[name]) + return oldValue + } + } + } + watchedProperties[name] = accessor + })(i, $scope[i]) + } + + $$skipArray.forEach(function(name) { + delete $scope[name] + delete $model[name] //这些特殊属性不应该在$model中出现 + }) + + $vmodel = defineProperties($vmodel, descriptorFactory(watchedProperties), $scope) //生成一个空的ViewModel + for (var name in $scope) { + if (!watchedProperties[name]) { + $vmodel[name] = $scope[name] } - vmodel[i] = fn } - vmodel.hasOwnProperty = function(name) { - return name in vmodel.$model + //添加$id, $model, $events, $watch, $unwatch, $fire + $vmodel.$id = generateID() + $vmodel.$model = $model + $vmodel.$events = $events + for (var i in EventManager) { + var fn = EventManager [i] + if (!W3C) { //在IE6-8下,VB对象的方法里的this并不指向自身,需要用bind处理一下 + fn = fn.bind($vmodel) + } + $vmodel[i] = fn } - for (var i = 0, fn; fn = computedProperties[i++]; ) { //最后强逼计算属性 计算自己的值 - Registry[expose] = fn - fn() - collectSubscribers(fn) - delete Registry[expose] + + $vmodel.hasOwnProperty = function(name) { + return name in $vmodel.$model } - return vmodel + computedProperties.forEach(function(collect) {//收集依赖 + collect() + }) + return $vmodel } - //一些不需要被监听的属性 - var skipProperties = String("$id,$watch,$unwatch,$fire,$events,$model,$skipArray,$accessors," + subscribers).match(rword) + //比较两个值是否相等 var isEqual = Object.is || function(v1, v2) { if (v1 === 0 && v2 === 0) { @@ -477,7 +610,7 @@ function safeFire(a, b, c, d) { if (a.$events) { - Events.$fire.call(a, b, c, d) + EventManager.$fire.call(a, b, c, d) } } var descriptorFactory = W3C ? function(obj) { @@ -494,106 +627,7 @@ } : function(a) { return a } - //循环生成访问器属性需要的setter, getter函数(这里统称为accessor) - function accessorFactory(name, val, model, normalProperties, accessingProperties, computedProperties, watchProperties) { - model[name] = val - // 如果是元素节点 或者 在全局的skipProperties里 或者在当前的$skipArray里 - // 或者是以$开头并又不在watchPropertie里,这些属性是不会产生accessor - if (normalProperties[name] || (val && val.nodeType) || (name.charAt(0) === "$" && !watchProperties[name])) { - return normalProperties[name] = val - } - // 此外, 函数也不会产生accessor - var valueType = avalon.type(val) - if (valueType === "function") { - return normalProperties[name] = val - } - //总共产生三种accessor - var accessor, oldArgs - if (valueType === "object" && typeof val.get === "function" && Object.keys(val).length <= 2) { - var setter = val.set, - getter = val.get - //第1种对应计算属性, 因变量,通过其他监控属性触发其改变 - accessor = function(newValue) { - var vmodel = watchProperties.vmodel - var value = model[name], - preValue = value - if (arguments.length) { - if (stopRepeatAssign) { - return - } - if (typeof setter === "function") { - var backup = vmodel.$events[name] - vmodel.$events[name] = [] //清空回调,防止内部冒泡而触发多次$fire - setter.call(vmodel, newValue) - vmodel.$events[name] = backup - } - if (!isEqual(oldArgs, newValue)) { - oldArgs = newValue - newValue = model[name] = getter.call(vmodel) //同步$model - withProxyCount && updateWithProxy(vmodel.$id, name, newValue) //同步循环绑定中的代理VM - notifySubscribers(accessor) //通知顶层改变 - safeFire(vmodel, name, newValue, preValue) //触发$watch回调 - } - } else { - if (avalon.openComputedCollect) { // 收集视图刷新函数 - collectSubscribers(accessor) - } - newValue = model[name] = getter.call(vmodel) - if (!isEqual(value, newValue)) { - oldArgs = void 0 - safeFire(vmodel, name, newValue, preValue) - } - return newValue - } - } - computedProperties.push(accessor) - } else if (rcomplexType.test(valueType)) { - //第2种对应子ViewModel或监控数组 - accessor = function(newValue) { - var realAccessor = accessor.$vmodel, - preValue = realAccessor.$model - if (arguments.length) { - if (stopRepeatAssign) { - return - } - if (!isEqual(preValue, newValue)) { - newValue = accessor.$vmodel = updateVModel(realAccessor, newValue, valueType) - var fn = rebindings[newValue.$id] - fn && fn() //更新视图 - var parent = watchProperties.vmodel - model[name] = newValue.$model //同步$model - notifySubscribers(realAccessor) //通知顶层改变 - safeFire(parent, name, model[name], preValue) //触发$watch回调 - } - } else { - collectSubscribers(realAccessor) //收集视图函数 - return realAccessor - } - } - accessor.$vmodel = val.$model ? val : modelFactory(val, val) - model[name] = accessor.$vmodel.$model - } else { - //第3种对应简单的数据类型,自变量,监控属性 - accessor = function(newValue) { - var preValue = model[name] - if (arguments.length) { - if (!isEqual(preValue, newValue)) { - model[name] = newValue //同步$model - var vmodel = watchProperties.vmodel - withProxyCount && updateWithProxy(vmodel.$id, name, newValue) //同步循环绑定中的代理VM - notifySubscribers(accessor) //通知顶层改变 - safeFire(vmodel, name, newValue, preValue) //触发$watch回调 - } - } else { - collectSubscribers(accessor) //收集视图函数 - return preValue - } - } - model[name] = val - } - accessor[subscribers] = [] //订阅者数组 - accessingProperties[name] = accessor - } + //ms-with, ms-repeat绑定生成的代理对象储存池 var withProxyPool = {} var withProxyCount = 0 @@ -606,23 +640,23 @@ } } //应用于第2种accessor - function updateVModel(a, b, valueType) { + function updateChild(parent, name, value, valueType) { //a为原来的VM, b为新数组或新对象 + var son = parent[name] if (valueType === "array") { - if (!Array.isArray(b)) { - return a //fix https://github.com/RubyLouvre/avalon/issues/261 + if (!Array.isArray(value) || son === value) { + return son //fix https://github.com/RubyLouvre/avalon/issues/261 } - var bb = b.concat() - a.clear() - a.push.apply(a, bb) - return a + son.clear() + son.pushArray(value.concat()) + return son } else { - var iterators = a[subscribers] || [] - if (withProxyPool[a.$id]) { + var iterators = parent.$events[name] + if (withProxyPool[son.$id]) { withProxyCount-- - delete withProxyPool[a.$id] + delete withProxyPool[son.$id] } - var ret = modelFactory(b) + var ret = modelFactory(value) rebindings[ret.$id] = function(data) { while (data = iterators.shift()) { (function(el) { @@ -701,7 +735,7 @@ function VBMediator(accessingProperties, name, value) { var accessor = accessingProperties[name] - if (typeof accessor == "function") { + if (typeof accessor === "function") { if (arguments.length === 3) { accessor(value) } else { @@ -709,10 +743,9 @@ } } } - defineProperties = function(name, accessingProperties, normalProperties) { + defineProperties = function(name, accessors, properties) { var className = "VBClass" + setTimeout("1"), buffer = [] - buffer.push( "\r\n\tPrivate [__data__], [__proxy__]", "\tPublic Default Function [__const__](d, p)", @@ -720,35 +753,42 @@ "\t\tSet [__const__] = Me", //链式调用 "\tEnd Function") //添加普通属性,因为VBScript对象不能像JS那样随意增删属性,必须在这里预先定义好 - for (name in normalProperties) { - buffer.push("\tPublic [" + name + "]") + for (name in properties) { + if (!accessors.hasOwnProperty(name)) { + buffer.push("\tPublic [" + name + "]") + } } + $$skipArray.forEach(function(name) { + if (!accessors.hasOwnProperty(name)) { + buffer.push("\tPublic [" + name + "]") + } + }) buffer.push("\tPublic [" + 'hasOwnProperty' + "]") //添加访问器属性 - for (name in accessingProperties) { - if (!(name in normalProperties)) { //防止重复定义 - buffer.push( - //由于不知对方会传入什么,因此set, let都用上 - "\tPublic Property Let [" + name + "](val" + expose + ")", //setter - "\t\tCall [__proxy__]([__data__], \"" + name + "\", val" + expose + ")", - "\tEnd Property", - "\tPublic Property Set [" + name + "](val" + expose + ")", //setter - "\t\tCall [__proxy__]([__data__], \"" + name + "\", val" + expose + ")", - "\tEnd Property", - "\tPublic Property Get [" + name + "]", //getter - "\tOn Error Resume Next", //必须优先使用set语句,否则它会误将数组当字符串返回 - "\t\tSet[" + name + "] = [__proxy__]([__data__],\"" + name + "\")", - "\tIf Err.Number <> 0 Then", - "\t\t[" + name + "] = [__proxy__]([__data__],\"" + name + "\")", - "\tEnd If", - "\tOn Error Goto 0", - "\tEnd Property") - } + for (name in accessors) { + buffer.push( + //由于不知对方会传入什么,因此set, let都用上 + "\tPublic Property Let [" + name + "](val" + expose + ")", //setter + "\t\tCall [__proxy__]([__data__], \"" + name + "\", val" + expose + ")", + "\tEnd Property", + "\tPublic Property Set [" + name + "](val" + expose + ")", //setter + "\t\tCall [__proxy__]([__data__], \"" + name + "\", val" + expose + ")", + "\tEnd Property", + "\tPublic Property Get [" + name + "]", //getter + "\tOn Error Resume Next", //必须优先使用set语句,否则它会误将数组当字符串返回 + "\t\tSet[" + name + "] = [__proxy__]([__data__],\"" + name + "\")", + "\tIf Err.Number <> 0 Then", + "\t\t[" + name + "] = [__proxy__]([__data__],\"" + name + "\")", + "\tEnd If", + "\tOn Error Goto 0", + "\tEnd Property") + } + buffer.push("End Class") var code = buffer.join("\r\n"), realClassName = window['findOrDefineVBClass'](className, code) //如果该VB类已定义,返回类名。否则用className创建一个新类。 - if (realClassName == className) { + if (realClassName === className) { window.parseVB([ "Function " + className + "Factory(a, b)", //创建实例并传入两个关键的参数 "\tDim o", @@ -757,7 +797,7 @@ "End Function" ].join("\r\n")) } - var ret = window[realClassName + "Factory"](accessingProperties, VBMediator) //得到其产品 + var ret = window[realClassName + "Factory"](accessors, VBMediator) //得到其产品 return ret //得到其产品 } } @@ -814,7 +854,7 @@ } if (!Array.isArray) { Array.isArray = function(a) { - return a && avalon.type(a) === "array" + return serialize.call(a) === "[object Array]" } } @@ -879,16 +919,18 @@ /********************************************************************* * DOM 底层补丁 * **********************************************************************/ - function fixContains(a, b) { - if (b) { - while ((b = b.parentNode)) { - if (b === a) { + + function fixContains(root, el) { + try {//IE6-8,游离于DOM树外的文本节点,访问parentNode有时会抛错 + while ((el = el.parentNode)) + if (el === root) return true; - } - } + return false + } catch (e) { + return false } - return false; } + //safari5+是把contains方法放在Element.prototype上而不是Node.prototype if (!root.contains) { Node.prototype.contains = function(arg) { @@ -898,37 +940,38 @@ //IE6-11的文档对象没有contains if (!DOC.contains) { DOC.contains = function(b) { - return fixContains(this, b) + return fixContains(DOC, b) } } - //IE9-11,firefox不支持SVG元素的innerHTML,outerHTML属性 function outerHTML() { return new XMLSerializer().serializeToString(this) } - function enumerateNode(node, targetNode) { - if (node && node.childNodes) { - var nodes = node.childNodes - for (var i = 0, el; el = nodes[i++]; ) { - if (el.tagName) { - var svg = document.createElementNS(svgns, - el.tagName.toLowerCase()) - ap.forEach.call(el.attributes, function(attr) { - svg.setAttribute(attr.name, attr.value)//复制属性 - }) - // 递归处理子节点 - enumerateNode(el, svg) - targetNode.appendChild(svg) + + + if (window.SVGElement) { + var svgns = "http://www.w3.org/2000/svg" + var svg = DOC.createElementNS(svgns, "svg") + svg.innerHTML = '' + if (!rsvg.test(svg.firstChild)) {// #409 + function enumerateNode(node, targetNode) { + if (node && node.childNodes) { + var nodes = node.childNodes + for (var i = 0, el; el = nodes[i++]; ) { + if (el.tagName) { + var svg = DOC.createElementNS(svgns, + el.tagName.toLowerCase()) + ap.forEach.call(el.attributes, function(attr) { + svg.setAttribute(attr.name, attr.value)//复制属性 + }) + // 递归处理子节点 + enumerateNode(el, svg) + targetNode.appendChild(svg) + } + } } } - } - } - var svgns = "http://www.w3.org/2000/svg" - if (window.SVGElement) { - var svg = document.createElementNS(svgns, "svg") - svg.innerHTML = '' - if (!(svg.firstChild && svg.firstChild.tagName === "rect")) { Object.defineProperties(SVGElement.prototype, { - "outerHTML": { + "outerHTML": {//IE9-11,firefox不支持SVG元素的innerHTML,outerHTML属性 enumerable: true, configurable: true, get: outerHTML, @@ -941,7 +984,7 @@ par.insertBefore(frag, this) // svg节点的子节点类似 } else { - var newFrag = document.createDocumentFragment() + var newFrag = DOC.createDocumentFragment() enumerateNode(frag, newFrag) par.insertBefore(newFrag, this) } @@ -958,9 +1001,11 @@ return s.replace(ropen, "").replace(rclose, "") }, set: function(html) { - avalon.clearHTML(this) - var frag = avalon.parseHTML(html) - enumerateNode(frag, this) + if (avalon.clearHTM) { + avalon.clearHTML(this) + var frag = avalon.parseHTML(html) + enumerateNode(frag, this) + } } } }) @@ -1022,6 +1067,7 @@ rbind = new RegExp(o + ".*?" + c + "|\\sms-") } } + kernel.debug = true kernel.plugins = plugins kernel.plugins['interpolate'](["{{", "}}"]) @@ -1050,26 +1096,26 @@ } var ClassListMethods = { - toString: function() { + _toString: function() { var node = this.node//IE6,7元素节点不存在hasAttribute方法 var cls = node.className var str = typeof cls === "string" ? cls : cls.baseVal return str.split(/\s+/).join(" ") }, - contains: function(cls) { + _contains: function(cls) { return (" " + this + " ").indexOf(" " + cls + " ") > -1 }, - add: function(cls) { + _add: function(cls) { if (!this.contains(cls)) { this._set(this + " " + cls) } }, - remove: function(cls) { + _remove: function(cls) { this._set((" " + this + " ").replace(" " + cls + " ", " ").trim()) }, - _set: function(cls) { + __set: function(cls) { var node = this.node - if (typeof node.className == "string") { + if (typeof node.className === "string") { node.className = cls } else {//SVG元素的className是一个对象 SVGAnimatedString { baseVal="", animVal=""},只能通过set/getAttribute操作 node.setAttribute("class", cls) @@ -1078,19 +1124,22 @@ } function ClassList(node) { if (!("classList" in node)) { - avalon.mix(node.classList = { + node.classList = { node: node - }, ClassListMethods) - node.classList.toString = ClassListMethods.toString //fix IE + } + for (var k in ClassListMethods) { + node.classList[k.slice(1)] = ClassListMethods[k] + } } return node.classList } + "add,remove".replace(rword, function(method) { avalon.fn[method + "Class"] = function(cls) { var el = this[0] //https://developer.mozilla.org/zh-CN/docs/Mozilla/Firefox/Releases/26 - if (cls && typeof cls === "string" && el && el.nodeType == 1) { + if (cls && typeof cls === "string" && el && el.nodeType === 1) { cls.replace(/\S+/g, function(c) { ClassList(el)[method](c) }) @@ -1321,6 +1370,7 @@ } else { var rnumnonpx = /^-?(?:\d*\.)?\d+(?!px)[^\d\s]+$/i var rposition = /^(top|right|bottom|left)$/ + var ralpha = /alpha\([^)]*\)/i var ie8 = !!window.XDomainRequest var salpha = "DXImageTransform.Microsoft.Alpha" var border = { @@ -1359,13 +1409,23 @@ return ret === "" ? "auto" : border[ret] || ret } cssHooks["opacity:set"] = function(node, name, value) { - node.style.filter = 'alpha(opacity=' + value * 100 + ')' - node.style.zoom = 1 + var style = node.style + var opacity = isFinite(value) && value <= 1 ? "alpha(opacity=" + value * 100 + ")" : "" + var filter = style.filter || ""; + style.zoom = 1 + //不能使用以下方式设置透明度 + //node.filters.alpha.opacity = value * 100 + style.filter = (ralpha.test(filter) ? + filter.replace(ralpha, opacity) : + filter + " " + opacity).trim() + if (!style.filter) { + style.removeAttribute("filter") + } } cssHooks["opacity:get"] = function(node) { //这是最快的获取IE透明值的方式,不需要动用正则了! var alpha = node.filters.alpha || node.filters[salpha], - op = alpha ? alpha.opacity : 100 + op = alpha && alpha.enabled ? alpha.opacity : 100 return (op / 100) + "" //确保返回的是字符串 } } @@ -1400,34 +1460,39 @@ array.push(obj) } var parent = node.parentNode - if (parent && parent.nodeType == 1) { + if (parent && parent.nodeType === 1) { showHidden(parent, array) } } } - "Width,Height".replace(rword, function(name) { + "Width,Height".replace(rword, function(name) {//fix 481 var method = name.toLowerCase(), clientProp = "client" + name, scrollProp = "scroll" + name, offsetProp = "offset" + name cssHooks[method + ":get"] = function(node, which, override) { - var boxSizing = "content-box" - if (typeof override === "string") { + var boxSizing = -4 + if (typeof override === "number") { boxSizing = override } which = name === "Width" ? ["Left", "Right"] : ["Top", "Bottom"] - switch (boxSizing) { - case "content-box": - return node["client" + name] - avalon.css(node, "padding" + which[0], true) - - avalon.css(node, "padding" + which[1], true) - case "padding-box": - return node["client" + name] - case "border-box": - return node["offset" + name] - case "margin-box": - return node["offset" + name] + avalon.css(node, "margin" + which[0], true) + - avalon.css(node, "margin" + which[1], true) + var ret = node[offsetProp] // border-box 0 + if (boxSizing === 2) { // margin-box 2 + return ret + + avalon.css(node, "margin" + which[0], true) + + avalon.css(node, "margin" + which[1], true) + } + if (boxSizing < 0) { // padding-box -2 + ret = ret + - avalon.css(node, "border" + which[0] + "Width", true) + - avalon.css(node, "border" + which[1] + "Width", true) + } + if (boxSizing === -4) { // content-box -4 + ret = ret + - avalon.css(node, "padding" + which[0], true) + - avalon.css(node, "padding" + which[1], true) } + return ret } cssHooks[method + "&get"] = function(node) { var hidden = []; @@ -1462,10 +1527,10 @@ } } avalon.fn["inner" + name] = function() { - return cssHooks[method + ":get"](this[0], void 0, "padding-box") + return cssHooks[method + ":get"](this[0], void 0, -2) } avalon.fn["outer" + name] = function(includeMargin) { - return cssHooks[method + ":get"](this[0], void 0, includeMargin === true ? "margin-box" : "border-box") + return cssHooks[method + ":get"](this[0], void 0, includeMargin === true ? 2 : 0) } }) avalon.fn.offset = function() { //取得距离页面左右角的坐标 @@ -1486,7 +1551,7 @@ //http://hkom.blog1.fc2.com/?mode=m&no=750 body的偏移量是不包含margin的 //我们可以通过getBoundingClientRect来获得元素相对于client的rect. //http://msdn.microsoft.com/en-us/library/ms536433.aspx - if (typeof node.getBoundingClientRect !== "undefined") { + if (node.getBoundingClientRect) { box = node.getBoundingClientRect() // BlackBerry 5, iOS 3 (original iPhone) } //chrome/IE6: body.scrollTop, firefox/other: root.scrollTop @@ -1577,13 +1642,17 @@ thead: [1, "", "
    "], tr: [2, ""], td: [3, "
    "], + text: [1, '', ''], //IE6-8在用innerHTML生成节点时,不能直接创建no-scope元素与HTML5的新标签 _default: W3C ? [0, ""] : [1, "X
    "] //div可以不用闭合 } + tagHooks.optgroup = tagHooks.option tagHooks.tbody = tagHooks.tfoot = tagHooks.colgroup = tagHooks.caption = tagHooks.thead tagHooks.th = tagHooks.td - +//处理SVG + tagHooks.circle = tagHooks.ellipse = tagHooks.line = tagHooks.path = + tagHooks.polygon = tagHooks.polyline = tagHooks.rect = tagHooks.text var script = DOC.createElement("script") avalon.parseHTML = function(html) { if (typeof html !== "string") { @@ -1624,12 +1693,28 @@ el.parentNode.removeChild(el) } } + for (els = wrapper.all, i = 0; el = els[i++]; ) {//fix VML + if (isVML(el)) { + fixVML(el) + } + } } while (firstChild = wrapper.firstChild) { // 将wrapper上的节点转移到文档碎片上! fragment.appendChild(firstChild) } return fragment } + function isVML(src) { + var nodeName = src.nodeName + return nodeName.toLowerCase() === nodeName && src.scopeName && src.outerText === "" + } + function fixVML(node) { + if (node.currentStyle.behavior !== "url(#default#VML)") { + node.style.behavior = "url(#default#VML)" + node.style.display = "inline-block" + node.style.zoom = 1 //hasLayout + } + } avalon.innerHTML = function(node, html) { if (!W3C && (!rcreate.test(html) && !rnest.test(html))) { try { @@ -1642,13 +1727,16 @@ this.clearHTML(node).appendChild(a) } avalon.clearHTML = function(node) { - expelFromSanctuary(node) + node.textContent = "" + while (node.firstChild) { + node.removeChild(node.firstChild) + } return node } /********************************************************************* - * 自定义事件系统 * + * 事件管理器 * **********************************************************************/ - var Events = { + var EventManager = { $watch: function(type, callback) { if (typeof callback === "function") { var callbacks = this.$events[type] @@ -1682,7 +1770,7 @@ }, $fire: function(type) { var special - if (/^(\w+)!(\w+)$/.test(type)) { + if (/^(\w+)!(\S+)$/.test(type)) { special = RegExp.$1 type = RegExp.$2 } @@ -1691,111 +1779,151 @@ var all = events.$all || [] var args = aslice.call(arguments, 1) for (var i = 0, callback; callback = callbacks[i++]; ) { - callback.apply(this, args) + if (isFunction(callback)) + callback.apply(this, args) } for (var i = 0, callback; callback = all[i++]; ) { - callback.apply(this, arguments) + if (isFunction(callback)) + callback.apply(this, arguments) } - var element = events.element + var element = events.expr && findNode(events.expr) if (element) { var detail = [type].concat(args) - if (special === "up") { - if (W3C) { - W3CFire(element, "dataavailable", detail) - } else { - var event = document.createEventObject() - event.detail = detail - element.fireEvent("ondataavailable", event) - } - } else if (special === "down") { - var alls = [] + var alls = [] + if (special === "up" || special === "down" || special === "all") { for (var i in avalon.vmodels) { var v = avalon.vmodels[i] - if (v && v.$events && v.$events.element) { - var node = v.$events.element; - if (avalon.contains(element, node) && element != node) { - alls.push(v) + if (v && v.$events && v.$events.expr) { + if (v !== this) { + var node = findNode(v.$events.expr) + if (!node) { + continue + } + var ok = special === "all" ? 1 : //全局广播 + special === "down" ? element.contains(node) : //向下捕获 + node.contains(element)//向上冒泡 + if (ok) { + alls.push([node, v]) + } } } } - alls.forEach(function(v) { - v.$fire.apply(v, detail) + var nodes = DOC.getElementsByTagName("*")//实现节点排序 + alls.sort(function(a, b) { + return Array.prototype.indexOf.call(nodes, a[0]) - Array.prototype.indexOf.call(nodes, b[0]) }) - } else if (special === "all") { - for (var i in avalon.vmodels) { - var v = avalon.vmodels[i] - if (v !== this) { - v.$fire.apply(v, detail) - } + if (special === "up") { + alls.reverse() } + alls.forEach(function(v) { + v[1].$fire.apply(v[1], detail) + }) } } } } + var ravalon = /(\w+)\[(avalonctrl)="(\S+)"\]/ + var findNode = DOC.querySelector ? function(str) { + return DOC.querySelector(str) + } : function(str) { + var match = str.match(ravalon) + var all = DOC.getElementsByTagName(match[1]) + for (var i = 0, el; el = all[i++]; ) { + if (el.getAttribute(match[2]) === match[3]) { + return el + } + } + } /********************************************************************* * 依赖调度系统 * **********************************************************************/ - - function registerSubscriber(data, val) { + var ronduplex = /^(duplex|on)$/ + function registerSubscriber(data) { Registry[expose] = data //暴光此函数,方便collectSubscribers收集 avalon.openComputedCollect = true var fn = data.evaluator if (fn) { //如果是求值函数 - if (data.type === "duplex") { - data.handler() - } else { - try { - var c = data.type === "on" ? data : fn.apply(0, data.args) - data.handler(c, data.element, data) - } catch (e) { - delete data.evaluator - if (data.nodeType === 3) { - if (kernel.commentInterpolate) { - data.element.replaceChild(DOC.createComment(data.value), data.node) - } else { - data.node.data = openTag + data.value + closeTag - } + try { + var c = ronduplex.test(data.type) ? data : fn.apply(0, data.args) + data.handler(c, data.element, data) + } catch (e) { + log("warning:exception throwed in [registerSubscriber] " + e) + delete data.evaluator + var node = data.element + if (node.nodeType === 3) { + var parent = node.parentNode + if (kernel.commentInterpolate) { + parent.replaceChild(DOC.createComment(data.value), node) + } else { + node.data = openTag + data.value + closeTag } - log("warning:evaluator of [" + data.value + "] throws error!") } } - } else { //如果是计算属性的accessor - data() } avalon.openComputedCollect = false delete Registry[expose] } - function collectSubscribers(accessor) { //收集依赖于这个访问器的订阅者 - if (Registry[expose]) { - var list = accessor[subscribers] - list && avalon.Array.ensure(list, Registry[expose]) //只有数组不存在此元素才push进去 + function collectSubscribers(list) { //收集依赖于这个访问器的订阅者 + var data = Registry[expose] + if (list && data && avalon.Array.ensure(list, data) && data.element) { //只有数组不存在此元素才push进去 + $$subscribers.push({ + data: data, list: list + }) } } - - function notifySubscribers(accessor) { //通知依赖于这个访问器的订阅者更新自身 - var list = accessor[subscribers] + var $$subscribers = [], $startIndex = 0, $maxIndex = 200 + function removeSubscribers() { + for (var i = $startIndex, n = $startIndex + $maxIndex; i < n; i++) { + var obj = $$subscribers[i] + if (!obj) { + break + } + var data = obj.data + var el = data.element + var remove = el === null ? 1 : (el.nodeType === 1 ? typeof el.sourceIndex === "number" ? + el.sourceIndex === 0 : !root.contains(el) : !avalon.contains(root, el)) + if (remove) { //如果它没有在DOM树 + $$subscribers.splice(i, 1) + avalon.Array.remove(obj.list, data) + //log("debug: remove " + data.type) + if (data.type === "if" && data.template && data.template.parentNode === head) { + head.removeChild(data.template) + } + for (var key in data) { + data[key] = null + } + obj.data = obj.list = null + i-- + n-- + } + } + obj = $$subscribers[i] + if (obj) { + $startIndex = n + } else { + $startIndex = 0 + } + } + var beginTime = new Date(), removeID + function notifySubscribers(list) { //通知依赖于这个访问器的订阅者更新自身 + var currentTime = new Date() + clearTimeout(removeID) + if (currentTime - beginTime > 333) { + removeSubscribers() + beginTime = new Date() + } else { + removeID = setTimeout(removeSubscribers, 333) + } if (list && list.length) { var args = aslice.call(arguments, 1) for (var i = list.length, fn; fn = list[--i]; ) { - var el = fn.element, - remove - if (el && !avalon.contains(ifSanctuary, el)) { - if (typeof el.sourceIndex == "number") { //IE6-IE11 - remove = el.sourceIndex === 0 - } else { - remove = !avalon.contains(root, el) - } - } - if (remove) { //如果它没有在DOM树 - list.splice(i, 1) - log("debug: remove " + fn.name) - } else if (typeof fn === "function") { - fn.apply(0, args) //强制重新计算自身 - } else if (fn.getter) { + var el = fn.element + if (fn.$repeat) { fn.handler.apply(fn, args) //处理监控数组的方法 - } else { - fn.handler(fn.evaluator.apply(0, fn.args || []), el, fn) + } else if (fn.element && fn.type !== "on") {//事件绑定只能由用户触发,不能由程序触发 + var fun = fn.evaluator || noop + fn.handler(fun.apply(0, fn.args || []), el, fn) } } } @@ -1833,13 +1961,13 @@ function scanTag(elem, vmodels, node) { //扫描顺序 ms-skip(0) --> ms-important(1) --> ms-controller(2) --> ms-if(10) --> ms-repeat(100) //--> ms-if-loop(110) --> ms-attr(970) ...--> ms-each(1400)-->ms-with(1500)--〉ms-duplex(2000)垫后 - var a = elem.getAttribute(prefix + "skip") + var a = elem.getAttribute("ms-skip") //#360 在旧式IE中 Object标签在引入Flash等资源时,可能出现没有getAttributeNode,innerHTML的情形 if (!elem.getAttributeNode) { return log("warning " + elem.tagName + " no getAttributeNode method") } - var b = elem.getAttributeNode(prefix + "important") - var c = elem.getAttributeNode(prefix + "controller") + var b = elem.getAttributeNode("ms-important") + var c = elem.getAttributeNode("ms-controller") if (typeof a === "string") { return } else if (node = b || c) { @@ -1849,36 +1977,40 @@ } //ms-important不包含父VM,ms-controller相反 vmodels = node === b ? [newVmodel] : [newVmodel].concat(vmodels) + var name = node.name + elem.removeAttribute(name) //removeAttributeNode不会刷新[ms-controller]样式规则 + elem.setAttribute("avalonctrl", node.value) + newVmodel.$events.expr = elem.tagName + '[avalonctrl="' + node.value + '"]' + avalon(elem).removeClass(name) - elem.removeAttribute(node.name) //removeAttributeNode不会刷新[ms-controller]样式规则 - newVmodel.$events.element = elem - avalon.bind(elem, "dataavailable", function(e) { - if (typeof e.detail === "object" && elem !== e.target) { - newVmodel.$fire.apply(newVmodel, e.detail) - } - }) - avalon(elem).removeClass(node.name) } - scanAttr(elem, vmodels) //扫描特性节点 } - function scanNodes(parent, vmodels) { + function scanNodeList(parent, vmodels) { var node = parent.firstChild while (node) { var nextNode = node.nextSibling - var nodeType = node.nodeType - if (nodeType === 1) { - scanTag(node, vmodels) //扫描元素节点 - } else if (nodeType === 3 && rexpr.test(node.data)) { - scanText(node, vmodels) //扫描文本节点 - } else if (kernel.commentInterpolate && nodeType === 8 && !rexpr.test(node.nodeValue)) { - scanText(node, vmodels) //扫描注释节点 - } + scanNode(node, node.nodeType, vmodels) node = nextNode } } + function scanNodeArray(nodes, vmodels) { + for (var i = 0, node; node = nodes[i++]; ) { + scanNode(node, node.nodeType, vmodels) + } + } + function scanNode(node, nodeType, vmodels) { + if (nodeType === 1) { + scanTag(node, vmodels) //扫描元素节点 + } else if (nodeType === 3 && rexpr.test(node.data)) { + scanText(node, vmodels) //扫描文本节点 + } else if (kernel.commentInterpolate && nodeType === 8 && !rexpr.test(node.nodeValue)) { + scanText(node, vmodels) //扫描注释节点 + } + } + function scanText(textNode, vmodels) { var bindings = [] if (textNode.nodeType === 8) { @@ -1902,15 +2034,14 @@ var filters = token.filters var binding = { type: "text", - node: node, - nodeType: 3, + element: node, value: token.value, filters: filters } if (filters && filters.indexOf("html") !== -1) { avalon.Array.remove(filters, "html") binding.type = "html" - binding.replaceNodes = [node] + binding.group = 1 if (!filters.length) { delete bindings.filters } @@ -1929,14 +2060,17 @@ var priorityMap = { "if": 10, "repeat": 90, + "data": 100, "widget": 110, "each": 1400, "with": 1500, "duplex": 2000, "on": 3000 } - var ons = oneObject("animationend,blur,change,input,click,dblclick,focus,keydown,keypress,keyup,mousedown,mouseenter,mouseleave,mousemove,mouseout,mouseover,mouseup,scroll,submit") - + var events = oneObject("animationend,blur,change,input,click,dblclick,focus,keydown,keypress,keyup,mousedown,mouseenter,mouseleave,mousemove,mouseout,mouseover,mouseup,scan,scroll,submit") + function bindingSorter(a, b) { + return a.priority - b.priority + } function scanAttr(elem, vmodels) { //防止setAttribute, removeAttribute时 attributes自动被同步,导致for循环出错 var attributes = getAttributes ? getAttributes(elem) : avalon.slice(elem.attributes) @@ -1952,7 +2086,7 @@ var value = attr.value var name = attr.name msData[name] = value - if (ons[type]) { + if (events[type]) { param = type type = "on" } else if (type === "enabled") {//吃掉ms-enabled绑定,用ms-disabled代替 @@ -1991,9 +2125,7 @@ } } } - bindings.sort(function(a, b) { - return a.priority - b.priority - }) + bindings.sort(bindingSorter) if (msData["ms-checked"] && msData["ms-duplex"]) { log("warning!一个元素上不能同时定义ms-checked与ms-duplex") } @@ -2007,19 +2139,11 @@ default: executeBindings(bindings, vmodels) if (!stopScan[elem.tagName] && rbind.test(elem.innerHTML.replace(rlt, "<").replace(rgt, ">"))) { - scanNodes(elem, vmodels) //扫描子孙元素 + scanNodeList(elem, vmodels) //扫描子孙元素 } break; } - if (elem.patchRepeat) { - elem.patchRepeat() - try { - elem.patchRepeat = "" - elem.removeAttribute("patchRepeat") - } catch (e) { - } - } } //IE67下,在循环绑定中,一个节点如果是通过cloneNode得到,自定义属性的specified为false,无法进入里面的分支, @@ -2035,7 +2159,7 @@ // window.onload = function() { // var body = document.body // for (var i = 0, el; el = body.children[i++]; ) { - // console.log(el.outerHTML) + // avalon.log(el.outerHTML) // } // } //依次输出
    ,
    @@ -2074,7 +2198,7 @@ for (var i = 0, data; data = bindings[i++]; ) { data.vmodels = vmodels bindingHandlers[data.type](data, vmodels) - if (data.evaluator && data.name) { //移除数据绑定,防止被二次解析 + if (data.evaluator && data.element && data.element.nodeType === 1) { //移除数据绑定,防止被二次解析 //chrome使用removeAttributeNode移除不存在的特性节点时会报错 https://github.com/RubyLouvre/avalon/issues/99 data.element.removeAttribute(data.name) } @@ -2146,16 +2270,18 @@ /********************************************************************* * 编译系统 * **********************************************************************/ - var keywords = // 关键字 - "break,case,catch,continue,debugger,default,delete,do,else,false" + ",finally,for,function,if,in,instanceof,new,null,return,switch,this" + ",throw,true,try,typeof,var,void,while,with" + "break,case,catch,continue,debugger,default,delete,do,else,false" + + ",finally,for,function,if,in,instanceof,new,null,return,switch,this" + + ",throw,true,try,typeof,var,void,while,with" // 保留字 - + ",abstract,boolean,byte,char,class,const,double,enum,export,extends" + ",final,float,goto,implements,import,int,interface,long,native" + ",package,private,protected,public,short,static,super,synchronized" + ",throws,transient,volatile" - + + ",abstract,boolean,byte,char,class,const,double,enum,export,extends" + + ",final,float,goto,implements,import,int,interface,long,native" + + ",package,private,protected,public,short,static,super,synchronized" + + ",throws,transient,volatile" // ECMA 5 - use strict + ",arguments,let,yield" - + ",undefined" var rrexpstr = /\/\*[\w\W]*?\*\/|\/\/[^\n]*\n|\/\/[^\n]*$|"(?:[^"\\]|\\[\w\W])*"|'(?:[^'\\]|\\[\w\W])*'|[\s\t\n]*\.[\s\t\n]*[$\w\.]+/g var rsplit = /[^\w$]+/g @@ -2175,48 +2301,40 @@ .replace(rnumber, "") .replace(rcomma, "") .split(/^$|,+/) - var vars = [], - unique = {} - for (var i = 0; i < match.length; ++i) { - var variable = match[i] - if (!unique[variable]) { - unique[variable] = vars.push(variable) - } - } - return cacheVars(key, vars) + return cacheVars(key, uniqSet(match)) } - - //添加赋值语句 - - function addAssign(vars, scope, name, duplex) { + /*添加赋值语句*/ + function addAssign(vars, scope, name, data) { var ret = [], prefix = " = " + name + "." for (var i = vars.length, prop; prop = vars[--i]; ) { - if (scope.hasOwnProperty && scope.hasOwnProperty(prop)) { //IE6下节点没有hasOwnProperty + if (scope.hasOwnProperty(prop)) { ret.push(prop + prefix + prop) - if (duplex === "duplex") { + if (data.type === "duplex") { vars.get = name + "." + prop } vars.splice(i, 1) } } return ret + } - function uniqVmodels(arr) { - var uniq = {} - return arr.filter(function(el) { - if (!uniq[el.$id]) { - uniq[el.$id] = 1 - return true + function uniqSet(array) { + var ret = [], unique = {} + for (var i = 0; i < array.length; i++) { + var el = array[i] + var id = el && typeof el.$id === "string" ? el.$id : el + if (!unique[id]) { + unique[id] = ret.push(el) } - }) + } + return ret } - //缓存求值函数,以便多次利用 + function createCache(maxLength) { var keys = [] - function cache(key, value) { if (keys.push(key) > maxLength) { delete cache[keys.shift()] @@ -2225,14 +2343,15 @@ } return cache; } - var cacheExprs = createCache(256) + //缓存求值函数,以便多次利用 + var cacheExprs = createCache(128) //取得求值函数及其传参 var rduplex = /\w\[.*\]|\w\.\w/ var rproxy = /(\$proxy\$[a-z]+)\d+$/ function parseExpr(code, scopes, data) { var dataType = data.type - var filters = dataType == "html" || dataType === "text" ? data.filters : "" + var filters = data.filters ? data.filters.join("") : "" var exprId = scopes.map(function(el) { return el.$id.replace(rproxy, "$1") }) + code + dataType + filters @@ -2242,13 +2361,13 @@ args = [], prefix = "" //args 是一个对象数组, names 是将要生成的求值函数的参数 - scopes = uniqVmodels(scopes) + scopes = uniqSet(scopes) for (var i = 0, sn = scopes.length; i < sn; i++) { if (vars.length) { var name = "vm" + expose + "_" + i names.push(name) args.push(scopes[i]) - assigns.push.apply(assigns, addAssign(vars, scopes[i], name, dataType)) + assigns.push.apply(assigns, addAssign(vars, scopes[i], name, data)) } } if (!assigns.length && dataType === "duplex") { @@ -2304,10 +2423,12 @@ } return } else if (dataType === "on") { //事件绑定 - code = code.replace("(", ".call(this,") - if (data.hasArgs === "$event") { - names.push("$event") + if (code.indexOf("(") === -1) { + code += ".call(this, $event)" + } else { + code = code.replace("(", ".call(this,") } + names.push("$event") code = "\nreturn " + code + ";" //IE全家 Function("return ")出错,需要Function("return ;") var lastIndex = code.lastIndexOf("\nreturn") var header = code.slice(0, lastIndex) @@ -2334,7 +2455,7 @@ '\r': '\\r', '"': '\\"', '\\': '\\\\' - }; + } var quote = window.JSON && JSON.stringify || function(str) { return '"' + str.replace(/[\\\"\x00-\x1f]/g, function(a) { var c = meta[a]; @@ -2365,30 +2486,6 @@ /********************************************************************* * 绑定处理系统 * **********************************************************************/ - var cacheDisplay = oneObject("a,abbr,b,span,strong,em,font,i,kbd", "inline") - avalon.mix(cacheDisplay, oneObject("div,h1,h2,h3,h4,h5,h6,section,p", "block")) - - function parseDisplay(nodeName, val) { - //用于取得此类标签的默认display值 - nodeName = nodeName.toLowerCase() - if (!cacheDisplay[nodeName]) { - var node = DOC.createElement(nodeName) - root.appendChild(node) - if (W3C) { - val = getComputedStyle(node, null).display - } else { - val = node.currentStyle.display - } - root.removeChild(node) - cacheDisplay[nodeName] = val - } - return cacheDisplay[nodeName] - } - avalon.parseDisplay = parseDisplay - var supportDisplay = (function(td) { - return W3C ? getComputedStyle(td, null).display === "table-cell" : true - })(DOC.createElement("td")) - var propMap = {//属性名映射 "accept-charset": "acceptCharset", "char": "ch", @@ -2397,13 +2494,14 @@ "for": "htmlFor", "http-equiv": "httpEquiv" } - var anomaly = "accessKey,allowTransparency,bgColor,cellPadding,cellSpacing,codeBase,codeType,colSpan,contentEditable," - + "dateTime,defaultChecked,defaultSelected,defaultValue,frameBorder,isMap,longDesc,maxLength,marginWidth,marginHeight," - + "noHref,noResize,noShade,readOnly,rowSpan,tabIndex,useMap,vSpace,valueType,vAlign" + + var anomaly = "accessKey,bgColor,cellPadding,cellSpacing,codeBase,codeType,colSpan," + + "dateTime,defaultValue,frameBorder,longDesc,maxLength,marginWidth,marginHeight," + + "rowSpan,tabIndex,useMap,vSpace,valueType,vAlign" anomaly.replace(rword, function(name) { propMap[name.toLowerCase()] = name }) - var rdash = /\(([^)]*)\)/ + var cssText = "" head.insertBefore(avalon.parseHTML(cssText), head.firstChild) //避免IE6 base标签BUG var rnoscripts = /(?:[\s\S]+?)<\/noscript>/img @@ -2423,16 +2521,16 @@ } } var cacheTmpls = avalon.templateCache = {} - var ifSanctuary = DOC.createElement("div") - ifSanctuary.innerHTML = "a" - try { - ifSanctuary.contains(ifSanctuary.firstChild) - avalon.contains = function(a, b) { - return a.contains(b) - } - } catch (e) { - avalon.contains = fixContains - } + + avalon.contains = fixContains + + var bools = "autofocus,autoplay,async,allowTransparency,checked,controls,declare,disabled,defer,defaultChecked,defaultSelected" + + "contentEditable,isMap,loop,multiple,noHref,noResize,noShade,open,readOnly,selected" + var boolMap = {} + bools.replace(rword, function(name) { + boolMap[name.toLowerCase()] = name + }) + //这里的函数每当VM发生改变后,都会被执行(操作方为notifySubscribers) var bindingExecutors = avalon.bindingExecutors = { "attr": function(val, elem, data) { @@ -2444,17 +2542,32 @@ // ms-attr-class="xxx" vm.xxx="aaa bbb ccc"将元素的className设置为aaa bbb ccc // ms-attr-class="xxx" vm.xxx=false 清空元素的所有类名 // ms-attr-name="yyy" vm.yyy="ooo" 为元素设置name属性 + if (boolMap[attrName]) { + var bool = boolMap[attrName] + if (typeof elem[bool] === "boolean") { + return elem[bool] = !!val + } + } var toRemove = (val === false) || (val === null) || (val === void 0) + if (!W3C && propMap[attrName]) {//旧式IE下需要进行名字映射 + attrName = propMap[attrName] + var isInnate = true + } if (toRemove) { - elem.removeAttribute(attrName) - } else if (!W3C) { - attrName = propMap[attrName] || attrName - if (toRemove) { - elem.removeAttribute(attrName) - } else { - elem[attrName] = val + return elem.removeAttribute(attrName) + } + if (window.VBArray && !isInnate) {//IE下需要区分固有属性与自定义属性 + if (isVML(elem)) { + isInnate = true + } else if (!rsvg.test(elem)) { + var attrs = elem.attributes || {} + var attr = attrs[attrName] + isInnate = attr ? attr.expando === false : attr === null } - } else if (!toRemove) { + } + if (isInnate) { + elem[attrName] = val + } else { elem.setAttribute(attrName, val) } } else if (method === "include" && val) { @@ -2467,14 +2580,16 @@ text = loaded.apply(elem, [text].concat(vmodels)) } avalon.innerHTML(elem, text) - scanNodes(elem, vmodels) + scanNodeList(elem, vmodels) rendered && checkScan(elem, function() { rendered.call(elem) }) } if (data.param === "src") { if (cacheTmpls[val]) { - scanTemplate(cacheTmpls[val]) + avalon.nextTick(function() { + scanTemplate(cacheTmpls[val]) + }) } else { var xhr = getXHR() xhr.onreadystatechange = function() { @@ -2495,7 +2610,7 @@ } else { //IE系列与够新的标准浏览器支持通过ID取得元素(firefox14+) //http://tjvantoll.com/2012/07/19/dom-element-references-as-global-variables/ - var el = val && val.nodeType == 1 ? val : DOC.getElementById(val) + var el = val && val.nodeType === 1 ? val : DOC.getElementById(val) if (el) { if (el.tagName === "NOSCRIPT" && !(el.innerHTML || el.fixIE78)) { //IE7-8 innerText,innerHTML都无法取得其内容,IE6能取得其innerHTML var xhr = getXHR() //IE9-11与chrome的innerHTML会得到转义的内容,它们的innerText可以 @@ -2523,44 +2638,51 @@ val = val.replace(/&/g, "&") //处理IE67自动转义的问题 } elem[method] = val + if (window.chrome && elem.tagName === "EMBED") { + var parent = elem.parentNode//#525 chrome1-37下embed标签动态设置src不能发生请求 + var comment = document.createComment("ms-src") + parent.replaceChild(comment, elem) + parent.replaceChild(elem, comment) + } } }, "class": function(val, elem, data) { var $elem = avalon(elem), method = data.type - if (method === "class" && data.param) { //如果是旧风格 - $elem.toggleClass(data.param, !!val) + if (method === "class" && data.oldStyle) { //如果是旧风格 + $elem.toggleClass(data.oldStyle, !!val) } else { - var toggle = data._evaluator ? !!data._evaluator.apply(elem, data._args) : true - var className = data._class || val + //如果存在冒号就有求值函数 + data.toggleClass = data._evaluator ? !!data._evaluator.apply(elem, data._args) : true + data.newClass = data.immobileClass || val + if (data.oldClass && data.newClass !== data.oldClass) { + $elem.removeClass(data.oldClass) + } + data.oldClass = data.newClass switch (method) { case "class": - if (toggle && data.oldClass) { - $elem.removeClass(data.oldClass) - } - $elem.toggleClass(className, toggle) - data.oldClass = className - break; + $elem.toggleClass(data.newClass, data.toggleClass) + break case "hover": case "active": - if (!data.init) { //确保只绑定一次 - if (method === "hover") { //在移出移入时切换类名 - var event1 = "mouseenter", - event2 = "mouseleave" - } else { //在聚焦失焦中切换类名 + if (!data.hasBindEvent) { //确保只绑定一次 + var activate = "mouseenter" //在移出移入时切换类名 + var abandon = "mouseleave" + if (method === "active") {//在聚焦失焦中切换类名 elem.tabIndex = elem.tabIndex || -1 - event1 = "mousedown", event2 = "mouseup" + activate = "mousedown" + abandon = "mouseup" $elem.bind("mouseleave", function() { - toggle && $elem.removeClass(className) + data.toggleClass && $elem.removeClass(data.newClass) }) } - $elem.bind(event1, function() { - toggle && $elem.addClass(className) + $elem.bind(activate, function() { + data.toggleClass && $elem.addClass(data.newClass) }) - $elem.bind(event2, function() { - toggle && $elem.removeClass(className) + $elem.bind(abandon, function() { + data.toggleClass && $elem.removeClass(data.newClass) }) - data.init = 1 + data.hasBindEvent = true } break; } @@ -2577,44 +2699,38 @@ "repeat": function(method, pos, el) { if (method) { var data = this - var group = data.group - var pp = data.startRepeat && data.startRepeat.parentNode - if (pp) { //fix #300 #307 - data.parent = pp - } - var parent = data.parent + var parent = data.element.parentNode var proxies = data.proxies var transation = hyperspace.cloneNode(false) + if (method === "del" || method === "move") { - var locatedNode = getLocatedNode(parent, data, pos) + var locatedNode = locateFragment(data, pos) } + var group = data.group switch (method) { case "add": //在pos位置后添加el数组(pos为数字,el为数组) var arr = el - var last = data.getter().length - 1 - var spans = [] - var lastFn = {} + var last = data.$repeat.length - 1 + var fragments = [] for (var i = 0, n = arr.length; i < n; i++) { var ii = i + pos var proxy = getEachProxy(ii, arr[i], data, last) proxies.splice(ii, 0, proxy) - lastFn = shimController(data, transation, spans, proxy) + shimController(data, transation, proxy, fragments) } - locatedNode = getLocatedNode(parent, data, pos) - lastFn.node = locatedNode - lastFn.parent = parent + locatedNode = locateFragment(data, pos) parent.insertBefore(transation, locatedNode) - for (var i = 0, node; node = spans[i++]; ) { - scanTag(node, data.vmodels) + for (var i = 0, fragment; fragment = fragments[i++]; ) { + scanNodeArray(fragment.nodes, fragment.vmodels) + fragment.nodes = fragment.vmodels = null } - spans = null + calculateFragmentGroup(data) break case "del": //将pos后的el个元素删掉(pos, el都是数字) var removed = proxies.splice(pos, el) - for (var i = 0, proxy; proxy = removed[i++]; ) { - recycleEachProxy(proxy) - } - expelFromSanctuary(removeView(locatedNode, group, el)) + var transation = removeFragment(locatedNode, group, el) + avalon.clearHTML(transation) + recycleEachProxies(removed) break case "index": //将proxies中的第pos个起的所有元素重新索引(pos为数字,el用作循环变量) var last = proxies.length - 1 @@ -2625,27 +2741,27 @@ } break case "clear": - if (data.startRepeat) { + var size = "proxySize" in data ? data.proxySize : proxies.length + if (size) { + var n = size * group, k = 0 while (true) { - var node = data.startRepeat.nextSibling - if (node && node !== data.endRepeat) { - transation.appendChild(node) + var nextNode = data.element.nextSibling + if (nextNode && k < n) { + parent.removeChild(nextNode) + k++ } else { break } } - } else { - transation = parent + recycleEachProxies(proxies) } - expelFromSanctuary(transation) - proxies.length = 0 break case "move": //将proxies中的第pos个元素移动el位置上(pos, el都是数字) var t = proxies.splice(pos, 1)[0] if (t) { proxies.splice(el, 0, t) - transation = removeView(locatedNode, group) - locatedNode = getLocatedNode(parent, data, el) + transation = removeFragment(locatedNode, group) + locatedNode = locateFragment(data, el) parent.insertBefore(transation, locatedNode) } break @@ -2657,45 +2773,48 @@ break case "append": //将pos的键值对从el中取出(pos为一个普通对象,el为预先生成好的代理VM对象池) var pool = el - var callback = getBindingCallback(data.callbackElement, "data-with-sorted", data.vmodels) var keys = [] - var spans = [] - var lastFn = {} + var fragments = [] for (var key in pos) { //得到所有键名 if (pos.hasOwnProperty(key) && key !== "hasOwnProperty") { keys.push(key) } } - if (callback) { //如果有回调,则让它们排序 - var keys2 = callback.call(parent, keys) + if (data.sortedCallback) { //如果有回调,则让它们排序 + var keys2 = data.sortedCallback.call(parent, keys) if (keys2 && Array.isArray(keys2) && keys2.length) { keys = keys2 } } for (var i = 0, key; key = keys[i++]; ) { if (key !== "hasOwnProperty") { - lastFn = shimController(data, transation, spans, pool[key]) + shimController(data, transation, pool[key], fragments) } } - lastFn.parent = parent - lastFn.node = data.endRepeat || null - parent.insertBefore(transation, lastFn.node) - for (var i = 0, el; el = spans[i++]; ) { - scanTag(el, data.vmodels) + data.proxySize = keys.length + parent.insertBefore(transation, data.element.nextSibling) + for (var i = 0, fragment; fragment = fragments[i++]; ) { + scanNodeArray(fragment.nodes, fragment.vmodels) + fragment.nodes = fragment.vmodels = null } - spans = null + calculateFragmentGroup(data) break } - iteratorCallback.call(data, arguments) + var callback = data.renderedCallback || noop, args = arguments + checkScan(parent, function() { + callback.apply(parent, args) + if (parent.oldValue && parent.tagName === "SELECT" && method === "index") {//fix #503 + avalon(parent).val(parent.oldValue.split(",")) + } + }) } }, "html": function(val, elem, data) { val = val == null ? "" : val - if (!elem) { - elem = data.element = data.node.parentNode - } - if (data.replaceNodes) { + var parent = "group" in data ? elem.parentNode : elem + if ("group" in data) { var fragment, nodes + //将值转换为文档碎片,原值可以为元素节点,文档碎片,NodeList,字符串 if (val.nodeType === 11) { fragment = val } else if (val.nodeType === 1 || val.item) { @@ -2707,62 +2826,57 @@ } else { fragment = avalon.parseHTML(val) } - var replaceNodes = avalon.slice(fragment.childNodes) - elem.insertBefore(fragment, data.replaceNodes[0] || null) //fix IE6-8 insertBefore的第2个参数只能为节点或null - for (var i = 0, node; node = data.replaceNodes[i++]; ) { - elem.removeChild(node) + nodes = avalon.slice(fragment.childNodes) + if (nodes.length === 0) { + var comment = DOC.createComment("ms-html") + fragment.appendChild(comment) + nodes = [comment] + } + parent.insertBefore(fragment, elem) //fix IE6-8 insertBefore的第2个参数只能为节点或null + var length = data.group + while (elem) { + var nextNode = elem.nextSibling + parent.removeChild(elem) + length-- + if (length === 0 || nextNode === null) + break + elem = nextNode } - data.replaceNodes = replaceNodes + data.element = nodes[0] + data.group = nodes.length } else { - avalon.innerHTML(elem, val) + avalon.innerHTML(parent, val) } avalon.nextTick(function() { - scanNodes(elem, data.vmodels) + scanNodeList(parent, data.vmodels) }) }, "if": function(val, elem, data) { - var placehoder = data.placehoder if (val) { //插回DOM树 - if (!data.msInDocument) { - data.msInDocument = true - try { - placehoder.parentNode.replaceChild(elem, placehoder) - } catch (e) { - log("debug: ms-if " + e.message) - } + if (elem.nodeType === 8) { + elem.parentNode.replaceChild(data.template, elem) + elem = data.element = data.template } - if (rbind.test(elem.outerHTML.replace(rlt, "<").replace(rgt, ">"))) { + if (elem.getAttribute(data.name)) { + elem.removeAttribute(data.name) scanAttr(elem, data.vmodels) } - } else { //移出DOM树,放进ifSanctuary DIV中,并用注释节点占据原位置 - if (data.msInDocument) { - data.msInDocument = false - try { - elem.parentNode.replaceChild(placehoder, elem) - } catch (e) { - log("debug: ms-if: elem.parentNode= " + elem.parentNode) - } - placehoder.elem = elem - ifSanctuary.appendChild(elem) + } else { //移出DOM树,并用注释节点占据原位置 + if (elem.nodeType === 1) { + var node = data.element = DOC.createComment("ms-if") + elem.parentNode.replaceChild(node, elem) + data.template = elem //元素节点 + head.appendChild(elem) } } }, "on": function(callback, elem, data) { - var fn = data.evaluator - var args = data.args - var vmodels = data.vmodels - if (!data.hasArgs) { - callback = function(e) { - return fn.apply(0, args).call(this, e) - } - } else { - callback = function(e) { - return fn.apply(this, args.concat(e)) - } + data.type = "on" + callback = function(e) { + var fn = data.evaluator || noop + return fn.apply(this, data.args.concat(e)) } - elem.$vmodel = vmodels[0] - elem.$vmodels = vmodels - var eventType = data.param = data.param.replace(/-\d+$/, "") // ms-on-mousemove-10 + var eventType = data.param.replace(/-\d+$/, "") // ms-on-mousemove-10 if (eventType === "scan") { callback.call(elem, {type: eventType}) } else if (typeof data.specialBind === "function") { @@ -2777,20 +2891,15 @@ avalon.unbind(elem, eventType, removeFn) } } - data.evaluator = data.handler = noop }, - "text": function(val, elem, data) { + "text": function(val, elem) { val = val == null ? "" : val //不在页面上显示undefined null - var node = data.node - if (data.nodeType === 3) { //绑定在文本节点上 + if (elem.nodeType === 3) { //绑定在文本节点上 try {//IE对游离于DOM树外的节点赋值会报错 - node.data = val + elem.data = val } catch (e) { } } else { //绑定在特性节点上 - if (!elem) { - elem = data.element = node.parentNode - } if ("textContent" in elem) { elem.textContent = val } else { @@ -2803,10 +2912,30 @@ }, "widget": noop } - var rwhitespace = /^\s+$/ + + var rdash = /\(([^)]*)\)/ + + function parseDisplay(nodeName, val) { + //用于取得此类标签的默认display值 + var key = "_" + nodeName + if (!parseDisplay[key]) { + var node = DOC.createElement(nodeName) + root.appendChild(node) + if (W3C) { + val = getComputedStyle(node, null).display + } else { + val = node.currentStyle.display + } + root.removeChild(node) + parseDisplay[key] = val + } + return parseDisplay[key] + } + + avalon.parseDisplay = parseDisplay //这里的函数只会在第一次被扫描后被执行一次,并放进行对应VM属性的subscribers数组内(操作方为registerSubscriber) var bindingHandlers = avalon.bindingHandlers = { - //这是一个字符串属性绑定的范本, 方便你在title, alt, src, href, include, css添加插值表达式 + //这是一个字符串属性绑定的范本, 方便你在title, alt, src, href, include, css添加插值表达式 // "attr": function(data, vmodels) { var text = data.value.trim(), @@ -2850,10 +2979,11 @@ } var hasExpr = rexpr.test(className) //比如ms-class="width{{w}}"的情况 if (!hasExpr) { - data._class = className + data.immobileClass = className } parseExprProxy("", vmodels, data, (hasExpr ? scanExpr(className) : null)) - } else if (data.type === "class") { + } else { + data.immobileClass = data.oldStyle = data.param parseExprProxy(text, vmodels, data) } }, @@ -2869,6 +2999,20 @@ if (form && form.msValidate) { form.msValidate(elem) } + data.msType = data.param || "" + if (data.msType === "bool") { + data.msType = "boolean" + log("ms-duplex-bool已经更名为ms-duplex-boolean") + } else if (data.msType === "text") { + data.msType = "string" + log("ms-duplex-text已经更名为ms-duplex-string") + } + if (data.msType === "radio") { + log("ms-duplex-radio将在2.0废掉,请尽量不要用") + } + if (!/boolean|string|number/.test(data.msType)) { + data.msType = "" + } data.bound = function(type, callback) { if (elem.addEventListener) { elem.addEventListener(type, callback, false) @@ -2886,66 +3030,56 @@ } }, "repeat": function(data, vmodels) { - var type = data.type, - list + var type = data.type parseExpr(data.value, vmodels, data) - if (type !== "repeat") { - log("warning:建议使用ms-repeat代替ms-each, ms-with, ms-repeat只占用一个标签并且性能更好") - } - var elem = data.callbackElement = data.parent = data.element //用于判定当前元素是否位于DOM树 - data.getter = function() { - return this.evaluator.apply(0, this.args || []) - } data.proxies = [] - var freturn = true + var freturn = false try { - list = data.getter() - var xtype = avalon.type(list) - if (xtype == "object" || xtype == "array") { - freturn = false + var $repeat = data.$repeat = data.evaluator.apply(0, data.args || []) + var xtype = avalon.type($repeat) + if (xtype !== "object" && xtype !== "array") { + freturn = true + avalon.log("warning:" + data.value + "对应类型不正确") } } catch (e) { + freturn = true + avalon.log("warning:" + data.value + "编译出错") } - var template = hyperspace.cloneNode(false) - if (type === "repeat") { - var startRepeat = DOC.createComment("ms-repeat-start") - var endRepeat = DOC.createComment("ms-repeat-end") - data.element = data.parent = elem.parentNode - data.startRepeat = startRepeat - data.endRepeat = endRepeat - elem.removeAttribute(data.name) - data.parent.replaceChild(endRepeat, elem) - data.parent.insertBefore(startRepeat, endRepeat) - template.appendChild(elem) + var elem = data.element + elem.removeAttribute(data.name) + data.sortedCallback = getBindingCallback(elem, "data-with-sorted", vmodels) + data.renderedCallback = getBindingCallback(elem, "data-" + type + "-rendered", vmodels) + + var comment = data.element = DOC.createComment("ms-repeat") + if (type === "each" || type === "with") { + data.template = elem.innerHTML.trim() + avalon.clearHTML(elem).appendChild(comment) } else { - var node - while (node = elem.firstChild) { - if (node.nodeType === 3 && rwhitespace.test(node.data)) { - elem.removeChild(node) - } else { - template.appendChild(node) - } - } + data.template = elem.outerHTML.trim() + data.group = 1 + elem.parentNode.replaceChild(comment, elem) } - data.template = template - data.rollback = function() { + + data.rollback = function() {//只用于list为对象的情况 bindingExecutors.repeat.call(data, "clear") - var endRepeat = data.endRepeat - var parent = data.parent - parent.insertBefore(data.template, endRepeat || null) - if (endRepeat) { - parent.removeChild(endRepeat) - parent.removeChild(data.startRepeat) - data.element = data.callbackElement - } + var elem = data.element + var parentNode = elem.parentNode + var content = avalon.parseHTML(data.template) + var target = content.firstChild + parentNode.replaceChild(content, elem) + target = data.element = data.type === "repeat" ? target : parentNode + data.group = null + target.setAttribute(data.name, data.value) } var arr = data.value.split(".") || [] if (arr.length > 1) { arr.pop() var n = arr[0] for (var i = 0, v; v = vmodels[i++]; ) { - if (v && v.hasOwnProperty(n) && v[n][subscribers]) { - v[n][subscribers].push(data) + if (v && v.hasOwnProperty(n)) { + var events = v[n].$events + events[subscribers] = events[subscribers] || [] + events[subscribers].push(data) break } } @@ -2953,12 +3087,12 @@ if (freturn) { return } - data.callbackName = "data-" + type + "-rendered" + data.handler = bindingExecutors.repeat data.$outer = {} var check0 = "$key", check1 = "$val" - if (Array.isArray(list)) { + if (Array.isArray($repeat)) { check0 = "$first" check1 = "$last" } @@ -2968,65 +3102,66 @@ break } } - node = template.firstChild - data.fastRepeat = !!node && node.nodeType === 1 && template.lastChild === node && !node.attributes["ms-controller"] && !node.attributes["ms-important"] - list[subscribers] && list[subscribers].push(data) - if (!Array.isArray(list) && type !== "each") { - var pool = withProxyPool[list.$id] + var $list = ($repeat.$events || {})[subscribers] + if ($list && avalon.Array.ensure($list, data)) { + $$subscribers.push({ + data: data, list: $list + }) + } + if (!Array.isArray($repeat) && type !== "each") { + var pool = withProxyPool[$repeat.$id] if (!pool) { withProxyCount++ - pool = withProxyPool[list.$id] = {} - for (var key in list) { - if (list.hasOwnProperty(key) && key !== "hasOwnProperty") { + pool = withProxyPool[$repeat.$id] = {} + for (var key in $repeat) { + if ($repeat.hasOwnProperty(key) && key !== "hasOwnProperty") { (function(k, v) { pool[k] = createWithProxy(k, v, {}) pool[k].$watch("$val", function(val) { - list[k] = val //#303 + $repeat[k] = val //#303 }) - })(key, list[key]) + })(key, $repeat[key]) } } } - data.handler("append", list, pool) + data.handler("append", $repeat, pool) } else { - data.handler("add", 0, list) + data.handler("add", 0, $repeat) } }, "html": function(data, vmodels) { parseExprProxy(data.value, vmodels, data) }, - "if": function(data, vmodels) { - var elem = data.element - elem.removeAttribute(data.name) - if (!data.placehoder) { - data.msInDocument = data.placehoder = DOC.createComment("ms-if") - } - data.vmodels = vmodels - parseExprProxy(data.value, vmodels, data) - - }, "on": function(data, vmodels) { - var value = data.value, - four = "$event" + var value = data.value + var eventType = data.param.replace(/-\d+$/, "") // ms-on-mousemove-10 + if (typeof bindingHandlers.on[eventType + "Hook"] === "function") { + bindingHandlers.on[eventType + "Hook"](data) + } if (value.indexOf("(") > 0 && value.indexOf(")") > -1) { var matched = (value.match(rdash) || ["", ""])[1].trim() if (matched === "" || matched === "$event") { // aaa() aaa($event)当成aaa处理 - four = void 0 value = value.replace(rdash, "") } - } else { - four = void 0 } - data.hasArgs = four - parseExprProxy(value, vmodels, data, four) + parseExprProxy(value, vmodels, data) }, "visible": function(data, vmodels) { - var elem = data.element - if (!supportDisplay && !root.contains(elem)) { //fuck firfox 全家! - var display = parseDisplay(elem.tagName) - } - display = display || avalon(elem).css("display") - data.display = display === "none" ? parseDisplay(elem.tagName) : display + var elem = avalon(data.element) + var display = elem.css("display") + if (display === "none") { + var style = elem[0].style + var has = /visibility/i.test(style.cssText) + var visible = elem.css("visibility") + style.display = "" + style.visibility = "hidden" + display = elem.css("display") + if (display === "none") { + display = parseDisplay(elem[0].nodeName) + } + style.visibility = has ? visible : "" + } + data.display = display parseExprProxy(data.value, vmodels, data) }, "widget": function(data, vmodels) { @@ -3066,22 +3201,20 @@ vmodel.$init() } if (vmodel.hasOwnProperty("$remove")) { - var offTree = function() { - vmodel.$remove() - elem.msData = {} - delete VMODELS[vmodel.$id] + function offTree() { + if (!elem.msRetain && !root.contains(elem)) { + vmodel.$remove() + elem.msData = {} + delete VMODELS[vmodel.$id] + return false + } } - if (supportMutationEvents) { - elem.addEventListener("DOMNodeRemoved", function(e) { - if (e.target === this && !this.msRetain && - //#441 chrome浏览器对文本域进行Ctrl+V操作,会触发DOMNodeRemoved事件 - (window.chrome ? (this.tagName === "INPUT" ? e.relatedNode.nodeType === 1 : 1) : 1)) { - offTree() - } + if (window.chrome) { + elem.addEventListener("DOMNodeRemovedFromDocument", function() { + setTimeout(offTree) }) } else { - elem.offTree = offTree - launchImpl(elem) + avalon.tick(offTree) } } } else if (vmodels.length) { //如果该组件还没有加载,那么保存当前的vmodels @@ -3089,9 +3222,6 @@ } } } - - var supportMutationEvents = W3C && DOC.implementation.hasFeature("MutationEvents", "2.0") - //============================ class preperty binding ======================= "hover,active".replace(rword, function(method) { bindingHandlers[method] = bindingHandlers["class"] @@ -3099,7 +3229,7 @@ "with,each".replace(rword, function(name) { bindingHandlers[name] = bindingHandlers.repeat }) - bindingHandlers.data = bindingHandlers.text = bindingHandlers.html + bindingHandlers["if"] = bindingHandlers.data = bindingHandlers.text = bindingHandlers.html //============================= string preperty binding ======================= //与href绑定器 用法差不多的其他字符串属性的绑定器 //建议不要直接在src属性上修改,这样会发出无效的请求,请使用ms-src @@ -3113,96 +3243,123 @@ //如果一个input标签添加了model绑定。那么它对应的字段将与元素的value连结在一起 //字段变,value就变;value变,字段也跟着变。默认是绑定input事件, duplexBinding.INPUT = function(element, evaluator, data) { - var fixType = data.param, - type = element.type, + var type = element.type, bound = data.bound, $elem = avalon(element), firstTigger = false, - composing = false, - callback = function(value) { - firstTigger = true - data.changed.call(this, value) - }, - compositionStart = function() { - composing = true - }, - compositionEnd = function() { - composing = false - }, - //当value变化时改变model的值 - updateVModel = function() { - if (composing) - return - var val = element.oldValue = element.value - if ($elem.data("duplex-observe") !== false) { - evaluator(val) - callback.call(element, val) - } + composing = false + function callback(value) { + firstTigger = true + data.changed.call(this, value) + } + function compositionStart() { + composing = true + } + function compositionEnd() { + composing = false + } + //当value变化时改变model的值 + function updateVModel() { + if (composing)//处理中文输入法在minlengh下引发的BUG + return + var val = element.oldValue = element.value //防止递归调用形成死循环 + var typedVal = getTypedValue(data, val) //尝式转换为正确的格式 + if ($elem.data("duplex-observe") !== false) { + evaluator(typedVal) + callback.call(element, typedVal) + if ($elem.data("duplex-focus")) { + avalon.nextTick(function() { + element.focus() + }) } + } + } //当model变化时,它就会改变value的值 data.handler = function() { var val = evaluator() + val = val == null ? "" : val + "" if (val !== element.value) { - element.value = val + "" + element.value = val } } - if (type === "checkbox" && fixType === "radio") { + if (type === "checkbox" && data.param === "radio") { type = "radio" } if (type === "radio") { - data.handler = function() { - //IE6是通过defaultChecked来实现打勾效果 - element.defaultChecked = (element.checked = /bool|text/.test(fixType) ? evaluator() + "" === element.value : !!evaluator()) - } + var IE6 = !window.XMLHttpRequest updateVModel = function() { if ($elem.data("duplex-observe") !== false) { var val = element.value - if (fixType === "text") { - evaluator(val) - } else if (fixType === "bool") { - val = val === "true" - evaluator(val) - } else { - val = !element.defaultChecked - evaluator(val) - element.checked = val - } - callback.call(element, val) + var typedValue = data.msType ? getTypedValue(data, val) : !element.oldValue + evaluator(typedValue) + callback.call(element, typedValue) } } - bound(fixType ? "click" : "mousedown", updateVModel) + data.handler = function() { + var val = evaluator() + var checked = data.msType ? val + "" === element.value : !!val + element.oldValue = checked + if (IE6) { + setTimeout(function() { + //IE8 checkbox, radio是使用defaultChecked控制选中状态, + //并且要先设置defaultChecked后设置checked + //并且必须设置延迟 + element.defaultChecked = checked + element.checked = checked + }, 100) + } else { + element.checked = checked + } + } + bound(IE6 ? "mouseup" : "click", updateVModel) } else if (type === "checkbox") { updateVModel = function() { if ($elem.data("duplex-observe") !== false) { var method = element.checked ? "ensure" : "remove" var array = evaluator() - if (Array.isArray(array)) { - avalon.Array[method](array, element.value) - } else { - avalon.error("ms-duplex位于checkbox时要求对应一个数组") + if (!Array.isArray(array)) { + log("ms-duplex应用于checkbox上要对应一个数组") + array = [array] } + var typedValue = getTypedValue(data, element.value) + avalon.Array[method](array, typedValue) callback.call(element, array) } } + data.handler = function() { var array = [].concat(evaluator()) //强制转换为数组 - element.checked = array.indexOf(element.value) >= 0 + element.checked = array.indexOf(getTypedValue(data, element.value)) >= 0 } bound(W3C ? "change" : "click", updateVModel) } else { var event = element.attributes["data-duplex-event"] || element.attributes["data-event"] || {} + if (element.attributes["data-event"]) { + log("data-event指令已经废弃,请改用data-duplex-event") + } event = event.value if (event === "change") { bound("change", updateVModel) } else { - if (W3C && DOC.documentMode !== 9) { //IE10+, W3C + if (W3C) { //IE9+, W3C bound("input", updateVModel) bound("compositionstart", compositionStart) bound("compositionend", compositionEnd) + if ("onselectionchange"in DOC) {//fix IE9 http://www.matts411.com/post/internet-explorer-9-oninput/ + function selectionchange(e) { + if (e.type === "focus") { + DOC.addEventListener("selectionchange", updateVModel, false); + } else { + DOC.removeEventListener("selectionchange", updateVModel, false); + } + } + bound("focus", selectionchange) + bound("blur", selectionchange) + } } else { var events = ["keyup", "paste", "cut", "change"] @@ -3228,12 +3385,16 @@ }) } } - } } - element.onTree = onTree - launch(element) element.oldValue = element.value + launch(function() { + if (avalon.contains(root, element)) { + onTree.call(element) + } else if (!element.msRetain) { + return false + } + }) registerSubscriber(data) var timer = setTimeout(function() { if (!firstTigger) { @@ -3242,6 +3403,18 @@ clearTimeout(timer) }, 31) } + + function getTypedValue(data, val) { + switch (data.msType) { + case "boolean": + return val === "true" + case "number": + return isFinite(val) || val === "" ? parseFloat(val) || 0 : val + default: + return val + } + } + var TimerID, ribbon = [], launch = noop function W3CFire(el, name, detail) { @@ -3252,6 +3425,7 @@ } el.dispatchEvent(event) } + function onTree() { //disabled状态下改动不触发input事件 if (!this.disabled && this.oldValue !== this.value) { if (W3C) { @@ -3262,13 +3436,16 @@ } } + avalon.tick = function(fn) { + if (ribbon.push(fn) === 1) { + TimerID = setInterval(ticker, 60) + } + } + function ticker() { for (var n = ribbon.length - 1; n >= 0; n--) { var el = ribbon[n] - if (avalon.contains(root, el)) { - el.onTree && el.onTree() - } else if (!el.msRetain) { - el.offTree && el.offTree() + if (el() === false) { ribbon.splice(n, 1) } } @@ -3276,11 +3453,6 @@ clearInterval(TimerID) } } - function launchImpl(el) { - if (ribbon.push(el) === 1) { - TimerID = setInterval(ticker, 30) - } - } function newSetter(newValue) { oldSetter.call(this, newValue) @@ -3296,29 +3468,44 @@ set: newSetter }) } catch (e) { - launch = launchImpl + launch = avalon.tick } duplexBinding.SELECT = function(element, evaluator, data) { var $elem = avalon(element) - function updateVModel() { if ($elem.data("duplex-observe") !== false) { var val = $elem.val() //字符串或字符串数组 + if (Array.isArray(val)) { + val = val.map(function(v) { + return getTypedValue(data, v) + }) + } else { + val = getTypedValue(data, val) + } if (val + "" !== element.oldValue) { evaluator(val) - element.oldValue = val + "" } data.changed.call(element, val) } } data.handler = function() { - var curValue = evaluator() - curValue = curValue && curValue.$model || curValue - curValue = Array.isArray(curValue) ? curValue.map(String) : curValue + "" - if (curValue + "" !== element.oldValue) { - $elem.val(curValue) - element.oldValue = curValue + "" + var val = evaluator() + val = val && val.$model || val + //必须变成字符串后才能比较 + if (Array.isArray(val)) { + if (!element.multiple) { + log("ms-duplex在不能对应一个数组") + } + } + val = Array.isArray(val) ? val.map(String) : val + "" + if (val + "" !== element.oldValue) { + $elem.val(val) + element.oldValue = val + "" } } data.bound("change", updateVModel) @@ -3351,6 +3538,8 @@ var box = doc.compatMode === "BackCompat" ? doc.body : doc.documentElement ret.pageX = event.clientX + (box.scrollLeft >> 0) - (box.clientLeft >> 0) ret.pageY = event.clientY + (box.scrollTop >> 0) - (box.clientTop >> 0) + ret.wheelDeltaY = ret.wheelDelta + ret.wheelDeltaX = 0 } ret.timeStamp = new Date - 0 ret.originalEvent = event @@ -3397,7 +3586,7 @@ } }) //针对IE6-8修正input - if (!("oninput" in document.createElement("input"))) { + if (!("oninput" in DOC.createElement("input"))) { eventHooks.input = { type: "propertychange", deel: function(elem, fn) { @@ -3410,17 +3599,20 @@ } } } - if (document.onmousewheel === void 0) { + if (DOC.onmousewheel === void 0) { /* IE6-11 chrome mousewheel wheelDetla 下 -120 上 120 firefox DOMMouseScroll detail 下3 上-3 firefox wheel detlaY 下3 上-3 IE9-11 wheel deltaY 下40 上-40 chrome wheel deltaY 下100 上-100 */ + var fixWheelType = DOC.onwheel !== void 0 ? "wheel" : "DOMMouseScroll" + var fixWheelDelta = fixWheelType === "wheel" ? "deltaY" : "detail" eventHooks.mousewheel = { - type: "DOMMouseScroll", + type: fixWheelType, deel: function(elem, fn) { return function(e) { - e.wheelDelta = e.detail > 0 ? -120 : 120 + e.wheelDeltaY = e.wheelDelta = e[fixWheelDelta] > 0 ? -120 : 120 + e.wheelDeltaX = 0 if (Object.defineProperty) { Object.defineProperty(e, "type", { value: "mousewheel" @@ -3439,17 +3631,17 @@ function Collection(model) { var array = [] array.$id = generateID() - array[subscribers] = [] - array.$model = model // model.concat() - array.$events = {} //VB对象的方法里的this并不指向自身,需要使用bind处理一下 + array.$model = model //数据模型 + array.$events = {} + array.$events[subscribers] = [] array._ = modelFactory({ length: model.length }) array._.$watch("length", function(a, b) { array.$fire("length", a, b) }) - for (var i in Events) { - array[i] = Events[i] + for (var i in EventManager) { + array[i] = EventManager[i] } avalon.mix(array, CollectionPrototype) return array @@ -3458,15 +3650,18 @@ var _splice = ap.splice var CollectionPrototype = { _splice: _splice, + _fire: function(method, a, b) { + notifySubscribers(this.$events[subscribers], method, a, b) + }, _add: function(arr, pos) { //在第pos个位置上,添加一组元素 var oldLength = this.length pos = typeof pos === "number" ? pos : oldLength var added = [] for (var i = 0, n = arr.length; i < n; i++) { - added[i] = convert(arr[i]) + added[i] = convert(arr[i], this.$model[i]) } _splice.apply(this, [pos, 0].concat(added)) - notifySubscribers(this, "add", pos, added) + this._fire("add", pos, added) if (!this._stopFireLength) { return this._.length = this.length } @@ -3474,7 +3669,7 @@ _del: function(pos, n) { //在第pos个位置上,删除N个元素 var ret = this._splice(pos, n) if (ret.length) { - notifySubscribers(this, "del", pos, n) + this._fire("del", pos, n) if (!this._stopFireLength) { this._.length = this.length } @@ -3484,7 +3679,7 @@ push: function() { ap.push.apply(this.$model, arguments) var n = this._add(arguments) - notifySubscribers(this, "index", n > 2 ? n - 2 : 0) + this._fire("index", n > 2 ? n - 2 : 0) return n }, pushArray: function(array) { @@ -3493,13 +3688,13 @@ unshift: function() { ap.unshift.apply(this.$model, arguments) this._add(arguments, 0) - notifySubscribers(this, "index", arguments.length) + this._fire("index", arguments.length) return this.$model.length //IE67的unshift不会返回长度 }, shift: function() { var el = this.$model.shift() this._del(0, 1) - notifySubscribers(this, "index", 0) + this._fire("index", 0) return el //返回被移除的元素 }, pop: function() { @@ -3524,7 +3719,7 @@ this._stopFireLength = false this._.length = this.length if (change) { - notifySubscribers(this, "index", 0) + this._fire("index", 0) } return ret //返回被移除的元素 }, @@ -3542,7 +3737,7 @@ }, clear: function() { this.$model.length = this.length = this._.length = 0 //清空数组 - notifySubscribers(this, "clear", 0) + this._fire("clear", 0) return this }, removeAll: function(all) { //移除N个元素 @@ -3585,7 +3780,7 @@ } else if (target !== val) { this[index] = val this.$model[index] = val - notifySubscribers(this, "set", index, val) + this._fire("set", index, val) } } return this @@ -3607,197 +3802,84 @@ var remove2 = bbb.splice(index, 1)[0] this._splice(i, 0, remove) bbb.splice(i, 0, remove2) - notifySubscribers(this, "move", index, i) + this._fire("move", index, i) } } bbb = void 0 if (sorted) { - notifySubscribers(this, "index", 0) + this._fire("index", 0) } return this } }) - function convert(val) { + function convert(val, $model) { if (rcomplexType.test(avalon.type(val))) { - val = val.$id ? val : modelFactory(val, val) + val = val.$id ? val : modelFactory(val, 0, $model) } return val } //============ each/repeat/with binding 用到的辅助函数与对象 ====================== - //得到某一元素节点或文档碎片对象下的所有注释节点 - var queryComments = DOC.createTreeWalker ? function(parent) { - var tw = DOC.createTreeWalker(parent, NodeFilter.SHOW_COMMENT, null, null), - comment, ret = [] - while (comment = tw.nextNode()) { - ret.push(comment) - } - return ret - } : function(parent) { - return parent.getElementsByTagName("!") - } - //将通过ms-if移出DOM树放进ifSanctuary的元素节点移出来,以便垃圾回收 - - function expelFromSanctuary(parent) { - var comments = queryComments(parent) - for (var i = 0, comment; comment = comments[i++]; ) { - if (comment.nodeValue == "ms-if") { - cinerator.appendChild(comment.elem) - } - } - while (comment = parent.firstChild) { - cinerator.appendChild(comment) - } - cinerator.innerHTML = "" - } - function iteratorCallback(args) { - var callback = getBindingCallback(this.callbackElement, this.callbackName, this.vmodels) - if (callback) { - var parent = this.parent - checkScan(parent, function() { - callback.apply(parent, args) - }) + //为ms-each, ms-with, ms-repeat要循环的元素外包一个msloop临时节点,ms-controller的值为代理VM的$id + function shimController(data, transation, proxy, fragments) { + var dom = avalon.parseHTML(data.template) + var nodes = avalon.slice(dom.childNodes) + transation.appendChild(dom) + proxy.$outer = data.$outer + var fragment = { + nodes: nodes, + vmodels: [proxy].concat(data.vmodels) } + fragments.push(fragment) } - function getAll(elem) {//VML的getElementsByTagName("*")不能取得所有元素节点 - var ret = [] - function get(parent, array) { - var nodes = parent.childNodes - for (var i = 0, el; el = nodes[i++]; ) { - if (el.nodeType === 1) { - array.push(el) - get(el, array) - } - } - return array - } - return get(elem, ret) - } - function fixCloneNode(src) { - var target = src.cloneNode(true) - if (window.VBArray) {//只处理IE - var srcAll = getAll(src) - var destAll = getAll(target) - for (var k = 0, src; src = srcAll[k]; k++) { - if (src.nodeType === 1) { - var nodeName = src.nodeName - var dest = destAll[k] - if (nodeName === "INPUT" && /radio|checkbox/.test(src.type)) { - dest.defaultChecked = dest.checked = src.checked - if (dest.value !== src.value) { - dest.value = src.value//IE67复制后,value从on变成"" - } - } else if (nodeName === "OBJECT") { - if (dest.parentNode) {//IE6-10拷贝子孙元素失败了 - dest.outerHTML = src.outerHTML - } - } else if (nodeName === "OPTION") { - dest.defaultSelected = dest.selected = src.defaultSelected - } else if (nodeName === "INPUT" || nodeName === "TEXTAREA") { - dest.defaultValue = src.defaultValue - } else if (nodeName.toLowerCase() === nodeName && src.scopeName && src.outerText === "") { - //src.tagUrn === "urn:schemas-microsoft-com:vml"//判定是否为VML元素 - var props = {}//处理VML元素 - src.outerHTML.replace(/\s*=\s*/g, "=").replace(/(\w+)="([^"]+)"/g, function(a, prop, val) { - props[prop] = val - }).replace(/(\w+)='([^']+)'/g, function(a, prop, val) { - props[prop] = val - }) - dest.outerHTML.replace(/\s*=\s*/g, "=").replace(/(\w+)="/g, function(a, prop) { - delete props[prop] - }).replace(/(\w+)='/g, function(a, prop) { - delete props[prop] - }) - delete props.urn - delete props.implementation - for (var i in props) { - dest.setAttribute(i, props[i]) - } - fixVML(dest) - } + // 取得用于定位的节点。比如data.group = 3, 结构为 + //






    + // 当pos为0时,返回 br#first + // 当pos为1时,返回 br#second + // 当pos为2时,返回 null + function locateFragment(data, pos) { + var comment = data.element + if (data.type == "repeat") {//ms-repeat,data.group为1 + var node = comment.nextSibling + for (var i = 0, n = pos; i < n; i++) { + if (node) { + node = node.nextSibling + } else { + break } } + } else { + var nodes = avalon.slice(comment.parentNode.childNodes, 1) + var group = data.group || nodes.length / data.proxies.length + node = nodes[group * pos] } - return target - } - - function fixVML(node) { - if (node.currentStyle.behavior !== "url(#default#VML)") { - node.style.behavior = "url(#default#VML)" - node.style.display = "inline-block" - node.style.zoom = 1 //hasLayout - } + return node || null } - //为ms-each, ms-with, ms-repeat要循环的元素外包一个msloop临时节点,ms-controller的值为代理VM的$id - function shimController(data, transation, spans, proxy) { - var tview = fixCloneNode(data.template) - var id = proxy.$id - var span = tview.firstChild - if (!data.fastRepeat) { - span = DOC.createElement("msloop") - span.style.display = "none" - span.appendChild(tview) - } - span.setAttribute("ms-controller", id) - span.removeAttribute(data.callbackName) - span.removeAttribute("data-with-sorted") - spans.push(span) - transation.appendChild(span) - proxy.$outer = data.$outer - VMODELS[id] = proxy - - function fn() { - delete VMODELS[id] - data.group = 1 - if (!data.fastRepeat) { - data.group = span.childNodes.length - span.parentNode.removeChild(span) - while (span.firstChild) { - transation.appendChild(span.firstChild) - } - if (fn.node !== void 0) { - fn.parent.insertBefore(transation, fn.node) - } + function removeFragment(node, group, pos) { + var n = group * (pos || 1) + var nodes = [node], i = 1 + var view = hyperspace + while (i < n) { + node = node.nextSibling + if (node) { + nodes[i++] = node } } - return span.patchRepeat = fn - } - // 取得用于定位的节点。在绑定了ms-each, ms-with属性的元素里,它的整个innerHTML都会视为一个子模板先行移出DOM树, - // 然后如果它的元素有多少个(ms-each)或键值对有多少双(ms-with),就将它复制多少份(多少为N),再经过扫描后,重新插入该元素中。 - // 这时该元素的孩子将分为N等分,每等份的第一个节点就是这个用于定位的节点, - // 方便我们根据它算出整个等分的节点们,然后整体移除或移动它们。 - - function getLocatedNode(parent, data, pos) { - if (data.startRepeat) { - var ret = data.startRepeat, - end = data.endRepeat - pos += 1 - for (var i = 0; i < pos; i++) { - ret = ret.nextSibling - if (ret == end) - return end - } - return ret - } else { - return parent.childNodes[data.group * pos] || null + for (var i = 0; node = nodes[i++]; ) { + view.appendChild(node) } + return view } - function removeView(node, group, n) { - var length = group * (n || 1) - var view = hyperspace//.cloneNode(false)//??? - while (--length >= 0) { - var nextSibling = node.nextSibling - view.appendChild(node) - node = nextSibling - if (!node) { - break - } + function calculateFragmentGroup(data) { + if (!isFinite(data.group)) { + var nodes = avalon.slice(data.element.parentNode.childNodes, 1) + var n = "proxySize" in data ? data.proxySize : data.proxies.length + data.group = nodes.length / n } - return view } // 为ms-each, ms-repeat创建一个代理对象,通过它们能使用一些额外的属性与功能($index,$first,$last,$remove,$key,$val,$outer) var watchEachOne = oneObject("$index,$first,$last") @@ -3807,11 +3889,11 @@ $key: key, $outer: $outer, $val: val - }, 0, { + }, { $val: 1, $key: 1 }) - proxy.$id = "$proxy$with" + Math.random() + proxy.$id = ("$proxy$with" + Math.random()).replace(/0\./, "") return proxy } var eachProxyPool = [] @@ -3819,7 +3901,7 @@ var param = data.param || "el", proxy var source = { $remove: function() { - return data.getter().removeAt(proxy.$index) + return data.$repeat.removeAt(proxy.$index) }, $itemName: param, $index: index, @@ -3831,8 +3913,8 @@ for (var i = 0, n = eachProxyPool.length; i < n; i++) { var proxy = eachProxyPool[i] if (proxy.hasOwnProperty(param)) { - for (var i in source) { - proxy[i] = source[i] + for (var k in source) { + proxy[k] = source[k] } eachProxyPool.splice(i, 1) return proxy @@ -3841,17 +3923,26 @@ if (rcomplexType.test(avalon.type(item))) { source.$skipArray = [param] } - proxy = modelFactory(source, 0, watchEachOne) - proxy.$id = "$proxy$" + data.type + Math.random() + proxy = modelFactory(source, watchEachOne) + proxy.$watch(param, function(val) { + data.$repeat.set(proxy.$index, val) + }) + proxy.$id = ("$proxy$" + data.type + Math.random()).replace(/0\./, "") return proxy } + + function recycleEachProxies(array) { + for (var i = 0, el; el = array[i++]; ) { + recycleEachProxy(el) + } + array.length = 0 + } + function recycleEachProxy(proxy) { - var obj = proxy.$accessors, name = proxy.$itemName; - ["$index", "$last", "$first"].forEach(function(prop) { - obj[prop][subscribers].length = 0 - }) - if (proxy[name][subscribers]) { - proxy[name][subscribers].length = 0; + for (var i in proxy.$events) { + if (Array.isArray(proxy.$events[i])) { + proxy.$events[i].length = 0 + } } if (eachProxyPool.unshift(proxy) > kernel.maxRepeatSize) { eachProxyPool.pop() @@ -3861,10 +3952,13 @@ * 自带过滤器 * **********************************************************************/ var rscripts = /]*>([\S\s]*?)<\/script\s*>/gim - var raimg = /^<(a|img)\s/i var ron = /\s+(on[^=\s]+)(?:=("[^"]*"|'[^']*'|[^\s>]+))?/g var ropen = /<\w+\b(?:(["'])[^"]*?(\1)|[^>])*>/ig - var rjavascripturl = /\s+(src|href)(?:=("javascript[^"]*"|'javascript[^']*'))?/ig + var rsanitize = { + a: /\b(href)\=("javascript[^"]*"|'javascript[^']*')/ig, + img: /\b(src)\=("javascript[^"]*"|'javascript[^']*')/ig, + form: /\b(action)\=("javascript[^"]*"|'javascript[^']*')/ig + } var rsurrogate = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g var rnoalphanumeric = /([^\#-~| |!])/g; var filters = avalon.filters = { @@ -3889,8 +3983,15 @@ //
    IE67chrome sanitize: function(str) { return str.replace(rscripts, "").replace(ropen, function(a, b) { - if (raimg.test(a)) { - a = a.replace(rjavascripturl, " $1=''")//移除javascript伪协议 + var match = a.toLowerCase().match(/<(\w+)\s/) + if (match) {//处理a标签的href属性,img标签的src属性,form标签的action属性 + var reg = rsanitize[match[1]] + if (reg) { + a = a.replace(reg, function(s, name, value) { + var quote = value.charAt(0) + return name + "=" + quote + "javascript:void(0)" + quote + }) + } } return a.replace(ron, " ").replace(/\s+/g, " ")//移除onXXX事件 }) @@ -4169,6 +4270,8 @@ locate.SHORTMONTH = locate.MONTH filters.date.locate = locate } + + /********************************************************************* * AMD加载器 * **********************************************************************/ @@ -4533,12 +4636,18 @@ /********************************************************************* * DOMReady * **********************************************************************/ - var ready = W3C ? "DOMContentLoaded" : "readystatechange" + var readyList = [] function fireReady() { if (DOC.body) { // 在IE8 iframe中doScrollCheck可能不正确 - modules["ready!"].state = 2 - innerRequire.checkDeps() + if (innerRequire) { + modules["ready!"].state = 2 + innerRequire.checkDeps() + } else { + readyList.forEach(function(a) { + a(avalon) + }) + } fireReady = noop //隋性函数,防止IE9二次调用_checkDeps } } @@ -4555,58 +4664,39 @@ if (DOC.readyState === "complete") { setTimeout(fireReady) //如果在domReady之外加载 } else if (W3C) { - DOC.addEventListener(ready, fireReady) - window.addEventListener("load", fireReady) + DOC.addEventListener("DOMContentLoaded", fireReady) } else { DOC.attachEvent("onreadystatechange", function() { if (DOC.readyState === "complete") { fireReady() } }) - window.attachEvent("onload", fireReady) if (root.doScroll) { doScrollCheck() } } + avalon.bind(window, "load", fireReady) + + avalon.ready = function(fn) { + if (innerRequire) { + innerRequire("ready!", fn) + } else if (fireReady === noop) { + fn(avalon) + } else { + readyList.push(fn) + } + } + avalon.config({ loader: true }) - avalon.ready = function(fn) { - innerRequire("ready!", fn) - } avalon.ready(function() { - //IE6-9下这个通常只要1ms,而且没有副作用,不会发出请求,setImmediate如果只执行一次,与setTimeout一样要140ms上下 - if (window.VBArray && !window.setImmediate) { - var handlerQueue = [] - - function drainQueue() { - var fn = handlerQueue.shift() - if (fn) { - fn() - if (handlerQueue.length) { - avalon.nextTick() - } - } - } - avalon.nextTick = function(callback) { - if (typeof callback === "function") { - handlerQueue.push(callback) - } - var node = DOC.createElement("script") - node.onreadystatechange = function() { - drainQueue() //在interactive阶段就触发 - node.onreadystatechange = null - head.removeChild(node) - node = null - } - head.appendChild(node) - } - } avalon.scan(DOC.body) }) })(document) /** http://www.cnblogs.com/henryzhu/p/mvvm-1-why-mvvm.ht http://dev.oupeng.com/wp-content/uploads/20131109-kennyluck-optimizing-js-games.html#controls-slide + http://ps.p12345.com/ */ diff --git a/avalon.min.js b/avalon.min.js index e84ea936e..cbfbfdd8d 100644 --- a/avalon.min.js +++ b/avalon.min.js @@ -1,120 +1,121 @@ -(function(q){function C(){}function y(b){r.console&&avalon.config.debug&&console.log(t?b:b+"")}function J(b,c){"string"===typeof b&&(b=b.match(B)||[]);for(var d={},e=void 0!==c?c:1,f=0,g=b.length;fb?Math.max(c+b,0):Math.min(b,c)}function K(b,c,d){if(Array.isArray(b)){var e= -b.concat();b.length=0;b=Hb(b);b.push.apply(b,e);return b}if("number"===typeof b.nodeType)return b;var f={};c=c||{};var g={},h={},e=[],k=d||{},l=b.$skipArray;d=0;for(var m;m=Ib[d++];)"string"!==typeof m&&y("warning:$skipArray["+m+"] must be a string"),delete b[m],h[m]=!0;if(Array.isArray(l))for(d=0;m=l[d++];)h[m]=!0;for(d in b)Jb(d,b[d],c,h,g,e,k);f=ga(f,Kb(g),h);for(m in h)f[m]=h[m];k.vmodel=f;f.$model=c;f.$events={};f.$id=Va();f.$accessors=g;f[z]=[];for(d in L)b=L[d],t||(b=b.bind(f)),f[d]=b;f.hasOwnProperty= -function(b){return b in f.$model};for(d=0;b=e[d++];)U[x]=b,b(),ha(b),delete U[x];return f}function Jb(b,c,d,e,f,g,h){d[b]=c;if(e[b]||c&&c.nodeType||"$"===b.charAt(0)&&!h[b])return e[b]=c;var k=avalon.type(c);if("function"===k)return e[b]=c;var l,m;if("object"===k&&"function"===typeof c.get&&2>=Object.keys(c).length){var n=c.set,p=c.get;l=function(c){var e=h.vmodel,f=d[b];if(arguments.length){if(!ka){if("function"===typeof n){var g=e.$events[b];e.$events[b]=[];n.call(e,c);e.$events[b]=g}if(!Y(m,c)){m= -c;c=d[b]=p.call(e);if(la&&(g=V[e.$id])&&g[b])g[b].$val=c;D(l);e.$events&&L.$fire.call(e,b,c,f)}}}else return avalon.openComputedCollect&&ha(l),c=d[b]=p.call(e),Y(f,c)||(m=void 0,e.$events&&L.$fire.call(e,b,c,f)),c};g.push(l)}else ra.test(k)?(l=function(c){var e=l.$vmodel,f=e.$model;if(arguments.length){if(!ka&&!Y(f,c)){c=l.$vmodel=Mb(e,c,k);var g=sa[c.$id];g&&g();g=h.vmodel;d[b]=c.$model;D(e);g.$events&&L.$fire.call(g,b,d[b],f)}}else return ha(e),e},l.$vmodel=c.$model?c:K(c,c),d[b]=l.$vmodel.$model): -(l=function(c){var e=d[b];if(arguments.length){if(!Y(e,c)){d[b]=c;var f=h.vmodel;if(la){var g=V[f.$id];g&&g[b]&&(g[b].$val=c)}D(l);f.$events&&L.$fire.call(f,b,c,e)}}else return ha(l),e},d[b]=c);l[z]=[];f[b]=l}function Mb(b,c,d){if("array"===d){if(!Array.isArray(c))return b;c=c.concat();b.clear();b.push.apply(b,c);return b}var e=b[z]||[];V[b.$id]&&(la--,delete V[b.$id]);var f=K(c);sa[f.$id]=function(b){for(;b=e.shift();)(function(b){b.type&&avalon.nextTick(function(){b.rollback&&b.rollback();E[b.type](b, -b.vmodels)})})(b);delete sa[f.$id]};return f}function Z(b,c,d){b="for(var "+b+"i=0,n = this.length; i < n; i++){"+c.replace("_","((i in this) && fn.call(scope,this[i],i,this))")+"}"+d;return Function("fn,scope",b)}function Ya(b,c){if(c)for(;c=c.parentNode;)if(c===b)return!0;return!1}function Za(){return(new XMLSerializer).serializeToString(this)}function ta(b,c){if(b&&b.childNodes)for(var d=b.childNodes,e=0,f;f=d[e++];)if(f.tagName){var g=document.createElementNS($a,f.tagName.toLowerCase());I.forEach.call(f.attributes, -function(b){g.setAttribute(b.name,b.value)});ta(f,g);c.appendChild(g)}}function v(b){for(var c in b)if(Q.call(b,c)){var d=b[c];if("function"===typeof v.plugins[c])v.plugins[c](d);else"object"===typeof v[c]?avalon.mix(v[c],d):v[c]=d}return this}function ab(b){return b.replace(/([a-z\d])([A-Z]+)/g,"$1-$2").toLowerCase()}function ma(b){return 0>b.indexOf("-")&&0>b.indexOf("_")?b:b.replace(/[-_][^-_]/g,function(b){return b.charAt(1).toUpperCase()})}function bb(b){"classList"in b||(avalon.mix(b.classList= -{node:b},cb),b.classList.toString=cb.toString);return b.classList}function db(b){try{b="true"===b?!0:"false"===b?!1:"null"===b?null:+b+""===b?+b:Nb.test(b)?avalon.parseJSON(b):b}catch(c){}return b}function eb(b,c){if(0>=b.offsetWidth){if(Ob.test(w["@:get"](b,"display"))){var d={node:b},e;for(e in fb)d[e]=b.style[e],b.style[e]=fb[e];c.push(d)}(d=b.parentNode)&&1==d.nodeType&&eb(d,c)}}function ua(b,c){U[x]=b;avalon.openComputedCollect=!0;var d=b.evaluator;if(d)if("duplex"===b.type)b.handler();else try{var e= -"on"===b.type?b:d.apply(0,b.args);b.handler(e,b.element,b)}catch(f){delete b.evaluator,3===b.nodeType&&(v.commentInterpolate?b.element.replaceChild(q.createComment(b.value),b.node):b.node.data=M+b.value+N),y("warning:evaluator of ["+b.value+"] throws error!")}else b();avalon.openComputedCollect=!1;delete U[x]}function ha(b){U[x]&&(b=b[z])&&avalon.Array.ensure(b,U[x])}function D(b){var c=b[z];if(c&&c.length)for(var d=$.call(arguments,1),e=c.length,f;f=c[--e];){var g=f.element,h;g&&!avalon.contains(aa, -g)&&(h="number"==typeof g.sourceIndex?0===g.sourceIndex:!avalon.contains(A,g));h?(c.splice(e,1),y("debug: remove "+f.name)):"function"===typeof f?f.apply(0,d):f.getter?f.handler.apply(f,d):f.handler(f.evaluator.apply(0,f.args||[]),g,f)}}function gb(b,c){var d=NaN,e=setInterval(function(){var f=b.innerHTML;f===d?(clearInterval(e),c()):d=f},Pb)}function na(b,c,d){d=b.getAttribute(va+"skip");if(!b.getAttributeNode)return y("warning "+b.tagName+" no getAttributeNode method");var e=b.getAttributeNode(va+ -"important"),f=b.getAttributeNode(va+"controller");if("string"!==typeof d){if(d=e||f){var g=W[d.value];if(!g)return;c=d===e?[g]:[g].concat(c);b.removeAttribute(d.name);g.$events.element=b;avalon.bind(b,"dataavailable",function(c){"object"===typeof c.detail&&b!==c.target&&g.$fire.apply(g,c.detail)});avalon(b).removeClass(d.name)}hb(b,c)}}function wa(b,c){for(var d=b.firstChild;d;){var e=d.nextSibling,f=d.nodeType;1===f?na(d,c):3===f&&ba.test(d.data)?ib(d,c):v.commentInterpolate&&(8===f&&!ba.test(d.nodeValue))&& -ib(d,c);d=e}}function ib(b,c){var d=[];if(8===b.nodeType){var e=[],f={expr:!0,value:jb(b.nodeValue,e)};e.length&&(f.filters=e);e=[f]}else e=xa(b.data);if(e.length){for(var g=0;f=e[g++];){var h=q.createTextNode(f.value);if(f.expr){var k=f.filters,f={type:"text",node:h,nodeType:3,value:f.value,filters:k};k&&-1!==k.indexOf("html")&&(avalon.Array.remove(k,"html"),f.type="html",f.replaceNodes=[h],k.length||delete d.filters);d.push(f)}R.appendChild(h)}b.parentNode.replaceChild(R,b);d.length&&ya(d,c)}}function hb(b, -c){for(var d=kb?kb(b):avalon.slice(b.attributes),e=[],f={},g,h=0,k;k=d[h++];)if(k.specified&&(g=k.name.match(lb))){var l=g[1],m=g[2]||"",n=k.value;k=k.name;f[k]=n;Qb[l]?(m=l,l="on"):"enabled"===l&&(l="disabled",n="!("+n+")");if("checked"===l||"selected"===l||"disabled"===l||"readonly"===l)m=l,l="attr",b.removeAttribute(k),k="ms-attr-"+m,b.setAttribute(k,n),g=[k],f[k]=n;"function"===typeof E[l]&&(n={type:l,param:m,element:b,name:g[0],value:n,priority:l in mb?mb[l]:10*l.charCodeAt(0)+(Number(m)||0)}, -"if"===l&&-1"))&&wa(b,c)}if(b.patchRepeat){b.patchRepeat(); -try{b.patchRepeat="",b.removeAttribute("patchRepeat")}catch(p){}}}function ya(b,c){for(var d=0,e;e=b[d++];)e.vmodels=c,E[e.type](e,c),e.evaluator&&e.name&&e.element.removeAttribute(e.name);b.length=0}function jb(b,c){0b&&delete c[d.shift()]; -return c[e]=f}var d=[];return c}function oa(b,c,d){var e=d.type,f="html"==e||"text"===e?d.filters:"",g=c.map(function(b){return b.$id.replace(Xb,"$1")})+b+e+f,h=Yb(b).concat(),k=[],l=[],m=[],n="";c=Wb(c);for(var p=0,n=c.length;p>0)-(d.clientLeft>>0),c.pageY=b.clientY+(d.scrollTop>>0)-(d.clientTop>>0));c.timeStamp=new Date-0;c.originalEvent=b;c.preventDefault=function(){b.returnValue=!1};c.stopPropagation=function(){b.cancelBubble=!0};return c}function Hb(b){var c=[];c.$id=Va();c[z]=[];c.$model=b;c.$events={};c._=K({length:b.length});c._.$watch("length",function(b,d){c.$fire("length",b,d)});for(var d in L)c[d]=L[d];avalon.mix(c,rb);return c}function Fa(b){for(var c= -fc(b),d=0,e;e=c[d++];)"ms-if"==e.nodeValue&&P.appendChild(e.elem);for(;e=b.firstChild;)P.appendChild(e);P.innerHTML=""}function gc(b){var c=da(this.callbackElement,this.callbackName,this.vmodels);if(c){var d=this.parent;gb(d,function(){c.apply(d,b)})}}function sb(b){function c(b,e){for(var f=b.childNodes,g=0,h;h=f[g++];)1===h.nodeType&&(e.push(h),c(h,e));return e}return c(b,[])}function hc(b){var c=b.cloneNode(!0);if(r.VBArray)for(var d=sb(b),e=sb(c),f=0;b=d[f];f++)if(1===b.nodeType){var g=b.nodeName, -h=e[f];if("INPUT"===g&&/radio|checkbox/.test(b.type))h.defaultChecked=h.checked=b.checked,h.value!==b.value&&(h.value=b.value);else if("OBJECT"===g)h.parentNode&&(h.outerHTML=b.outerHTML);else if("OPTION"===g)h.defaultSelected=h.selected=b.defaultSelected;else if("INPUT"===g||"TEXTAREA"===g)h.defaultValue=b.defaultValue;else if(g.toLowerCase()===g&&b.scopeName&&""===b.outerText){var k={};b.outerHTML.replace(/\s*=\s*/g,"=").replace(/(\w+)="([^"]+)"/g,function(b,c,d){k[c]=d}).replace(/(\w+)='([^']+)'/g, -function(b,c,d){k[c]=d});h.outerHTML.replace(/\s*=\s*/g,"=").replace(/(\w+)="/g,function(b,c){delete k[c]}).replace(/(\w+)='/g,function(b,c){delete k[c]});delete k.urn;delete k.implementation;for(var l in k)h.setAttribute(l,k[l]);ic(h)}}return c}function ic(b){"url(#default#VML)"!==b.currentStyle.behavior&&(b.style.behavior="url(#default#VML)",b.style.display="inline-block",b.style.zoom=1)}function tb(b,c,d,e){function f(){delete W[h];b.group=1;if(!b.fastRepeat){b.group=k.childNodes.length;for(k.parentNode.removeChild(k);k.firstChild;)c.appendChild(k.firstChild); -void 0!==f.node&&f.parent.insertBefore(c,f.node)}}var g=hc(b.template),h=e.$id,k=g.firstChild;b.fastRepeat||(k=q.createElement("msloop"),k.style.display="none",k.appendChild(g));k.setAttribute("ms-controller",h);k.removeAttribute(b.callbackName);k.removeAttribute("data-with-sorted");d.push(k);c.appendChild(k);e.$outer=b.$outer;W[h]=e;return k.patchRepeat=f}function Ga(b,c,d){if(c.startRepeat){b=c.startRepeat;c=c.endRepeat;d+=1;for(var e=0;ev.maxRepeatSize&&ea.pop()}function S(){q.body&&(s["ready!"].state=2,F.checkDeps(),S=C)}function vb(){try{A.doScroll("left"),S()}catch(b){setTimeout(vb)}}var va="ms-",x=new Date-0,z="$"+x,r=this||(0,eval)("this"),nc=r.require,oc=r.define,ka=!1,B=/[^, ]+/g,wb=/\[native code\]/, -ra=/^(?:object|array)$/,Gb=/^\[object (Window|DOMWindow|global)\]$/,Ha=Object.prototype,Q=Ha.hasOwnProperty,Xa=Ha.toString,I=Array.prototype,$=I.slice,U={},t=r.dispatchEvent,A=q.documentElement,H=q.getElementsByTagName("head")[0],R=q.createDocumentFragment(),P=q.createElement("div"),xb={};"Boolean Number String Function Array Date RegExp Object Error".replace(B,function(b){xb["[object "+b+"]"]=b.toLowerCase()});avalon=function(b){return new avalon.init(b)};avalon.init=function(b){this[0]=this.element= -b};avalon.fn=avalon.prototype=avalon.init.prototype;avalon.type=function(b){return null==b?String(b):"object"===typeof b||"function"===typeof b?xb[Xa.call(b)]||"object":typeof b};avalon.isWindow=function(b){return!b?!1:b==b.document&&b.document!=b};Wa(r)&&(avalon.isWindow=Wa);avalon.isPlainObject=function(b,c){if(!b||"object"!==avalon.type(b)||b.nodeType||avalon.isWindow(b))return!1;try{if(b.constructor&&!Q.call(b,"constructor")&&!Q.call(b.constructor.prototype,"isPrototypeOf"))return!1}catch(d){return!1}for(c in b); -return void 0===c||Q.call(b,c)};wb.test(Object.getPrototypeOf)&&(avalon.isPlainObject=function(b){return!!b&&"object"===typeof b&&Object.getPrototypeOf(b)===Ha});avalon.mix=avalon.fn.mix=function(){var b,c,d,e,f,g=arguments[0]||{},h=1,k=arguments.length,l=!1;"boolean"===typeof g&&(l=g,g=arguments[1]||{},h++);"object"!==typeof g&&"function"!==avalon.type(g)&&(g={});h===k&&(g=this,h--);for(;h 0 Then","\t\t["+b+'] = [__proxy__]([__data__],"'+b+'")',"\tEnd If","\tOn Error Goto 0","\tEnd Property");f.push("End Class");b=f.join("\r\n");b=r.findOrDefineVBClass(e,b);b==e&&r.parseVB(["Function "+e+"Factory(a, b)","\tDim o", -"\tSet o = (New "+e+")(a, b)","\tSet "+e+"Factory = o","End Function"].join("\r\n"));return r[b+"Factory"](c,pc)}}if(!"\u53f8\u5f92\u6b63\u7f8e".trim){var qc=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;String.prototype.trim=function(){return this.replace(qc,"")}}var rc=!{toString:null}.propertyIsEnumerable("toString"),sc=function(){}.propertyIsEnumerable("prototype"),yb="toString toLocaleString valueOf hasOwnProperty isPrototypeOf propertyIsEnumerable constructor".split(" "),tc=yb.length;Object.keys||(Object.keys= -function(b){var c=[],d=sc&&"function"===typeof b;if("string"===typeof b||b&&b.callee)for(d=0;darguments.length&&void 0===b)return this; -var c=this,d=arguments;return function(){var e=[],f;for(f=1;fe&&(e+=d);ee&&(e=Math.max(0,d+e));0<=e;e--)if(this[e]===b)return e;return-1},forEach:Z("","_",""),filter:Z("r=[],j=0,","if(_)r[j++]=this[i]","return r"), -map:Z("r=[],","r[i]=_","return r"),some:Z("","if(_)return true","return false"),every:Z("","if(!_)return false","return true")});A.contains||(Node.prototype.contains=function(b){return!!(this.compareDocumentPosition(b)&16)});q.contains||(q.contains=function(b){return Ya(this,b)});var $a="http://www.w3.org/2000/svg";if(r.SVGElement){var Ja=document.createElementNS($a,"svg");Ja.innerHTML='';Ja.firstChild&&"rect"===Ja.firstChild.tagName||Object.defineProperties(SVGElement.prototype, -{outerHTML:{enumerable:!0,configurable:!0,get:Za,set:function(b){var c=this.tagName.toLowerCase(),d=this.parentNode;b=avalon.parseHTML(b);"svg"===c?d.insertBefore(b,this):(c=document.createDocumentFragment(),ta(b,c),d.insertBefore(c,this));d.removeChild(this)}},innerHTML:{enumerable:!0,configurable:!0,get:function(){var b=RegExp("$","i");return this.outerHTML.replace(RegExp("<"+this.nodeName+'\\b(?:(["\'])[^"]*?(\\1)|[^>])*>',"i"),"").replace(b,"")},set:function(b){avalon.clearHTML(this); -b=avalon.parseHTML(b);ta(b,this)}}})}!A.outerHTML&&r.HTMLElement&&HTMLElement.prototype.__defineGetter__("outerHTML",Za);var M,N,ba,zb,za,Ab=/[-.*+?^${}()|[\]\/\\]/g,T={loader:function(b){r.define=b?F.define:oc;r.require=b?F:nc},interpolate:function(b){M=b[0];N=b[1];if(M===N)throw new SyntaxError("openTag!==closeTag");if("\x3c!--,--\x3e"===b+"")v.commentInterpolate=!0;else{b=M+"test"+N;P.innerHTML=b;if(P.innerHTML!==b&&0<=P.innerHTML.indexOf("<"))throw new SyntaxError("\u6b64\u5b9a\u754c\u7b26\u4e0d\u5408\u6cd5"); -P.innerHTML=""}b=(M+"").replace(Ab,"\\$&");var c=(N+"").replace(Ab,"\\$&");ba=RegExp(b+"(.*?)"+c);zb=RegExp(b+"(.*?)"+c,"g");za=RegExp(b+".*?"+c+"|\\sms-")}};v.debug=!0;v.plugins=T;v.plugins.interpolate(["{{","}}"]);v.paths={};v.shim={};v.maxRepeatSize=100;avalon.config=v;var cb={toString:function(){var b=this.node.className;return("string"===typeof b?b:b.baseVal).split(/\s+/).join(" ")},contains:function(b){return-1<(" "+this+" ").indexOf(" "+b+" ")},add:function(b){this.contains(b)||this._set(this+ -" "+b)},remove:function(b){this._set((" "+this+" ").replace(" "+b+" "," ").trim())},_set:function(b){var c=this.node;"string"==typeof c.className?c.className=b:c.setAttribute("class",b)}};"add,remove".replace(B,function(b){avalon.fn[b+"Class"]=function(c){var d=this[0];c&&("string"===typeof c&&d&&1==d.nodeType)&&c.replace(/\S+/g,function(c){bb(d)[b](c)});return this}});avalon.fn.mix({hasClass:function(b){var c=this[0]||{};return 1===c.nodeType&&bb(c).contains(b)},toggleClass:function(b,c){for(var d, -e=0,f=b.split(/\s+/),g="boolean"===typeof c;d=f[e++];)this[(g?c:!this.hasClass(d))?"addClass":"removeClass"](d);return this},attr:function(b,c){return 2===arguments.length?(this[0].setAttribute(b,c),this):this[0].getAttribute(b)},data:function(b,c){b="data-"+ab(b||"");switch(arguments.length){case 2:return this.attr(b,c),this;case 1:var d=this.attr(b);return db(d);case 0:var e={};I.forEach.call(this[0].attributes,function(c){c&&(b=c.name,b.indexOf("data-")||(b=ma(b.slice(5)),e[b]=db(c.value)))}); -return e}},removeData:function(b){b="data-"+ab(b);this[0].removeAttribute(b);return this},css:function(b,c){if(avalon.isPlainObject(b))for(var d in b)avalon.css(this,d,b[d]);else var e=avalon.css(this,b,c);return void 0!==e?e:this},position:function(){var b,c,d=this[0],e={top:0,left:0};if(d)return"fixed"===this.css("position")?c=d.getBoundingClientRect():(b=this.offsetParent(),c=this.offset(),"HTML"!==b[0].tagName&&(e=b.offset()),e.top+=avalon.css(b[0],"borderTopWidth",!0),e.left+=avalon.css(b[0], -"borderLeftWidth",!0)),{top:c.top-e.top-avalon.css(d,"marginTop",!0),left:c.left-e.left-avalon.css(d,"marginLeft",!0)}},offsetParent:function(){for(var b=this[0].offsetParent||A;b&&"HTML"!==b.tagName&&"static"===avalon.css(b,"position");)b=b.offsetParent;return avalon(b||A)},bind:function(b,c,d){if(this[0])return avalon.bind(this[0],b,c,d)},unbind:function(b,c,d){this[0]&&avalon.unbind(this[0],b,c,d);return this},val:function(b){var c=this[0];if(c&&1===c.nodeType){var d=0===arguments.length,e=d?":get": -":set",f=Ka,g;g=c.tagName.toLowerCase();g="input"===g&&/checkbox|radio/.test(c.type)?"checked":g;if(e=f[g+e])var h=e(c,b);else{if(d)return(c.value||"").replace(/\r/g,"");c.value=b}}return d?h:this}});var Nb=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,uc=/^[\],:{}\s]*$/,vc=/(?:^|:|,)(?:\s*\[)+/g,wc=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,xc=/"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g;avalon.parseJSON=r.JSON?JSON.parse:function(b){if("string"===typeof b){if((b=b.trim())&&uc.test(b.replace(wc, -"@").replace(xc,"]").replace(vc,"")))return(new Function("return "+b))();avalon.error("Invalid JSON: "+b)}};avalon.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(b,c){avalon.fn[b]=function(d){var e=this[0]||{},f=e.window&&e.document?e:9===e.nodeType?e.defaultView||e.parentWindow:!1,g="scrollTop"===b;if(arguments.length)f?f.scrollTo(!g?d:avalon(f).scrollLeft(),g?d:avalon(f).scrollTop()):e[b]=d;else return f?c in f?f[c]:A[b]:e[b]}});var w=avalon.cssHooks={},Bb=["","-webkit-","-o-", -"-moz-","-ms-"],La={"float":"cssFloat",background:"backgroundColor"};avalon.cssNumber=J("columnCount,order,fillOpacity,fontWeight,lineHeight,opacity,orphans,widows,zIndex,zoom");avalon.cssName=function(b,c,d){if(La[b])return La[b];c=c||A.style;for(var e=0,f=Bb.length;e]+))?)*\s+value[\s=]/i,Ka={"option:get":function(b){return b.hasAttribute?b.hasAttribute("value")?b.value:b.text.trim():Bc.test(b.outerHTML)?b.value:b.text},"select:get":function(b,c){for(var d,e=b.options, -f=b.selectedIndex,g=Ka["option:get"],h="select-one"===b.type||0>f,k=h?null:[],l=h?f+1:e.length,m=0>f?l:h?f:0;m]*)\/>/ig,Cb=t?/[^\d\D]/:/(<(?:script|link|style|meta|noscript))/ig, -Ec=J("text/javascript","text/ecmascript","application/ecmascript","application/javascript","text/vbscript"),Fc=/<(?:tb|td|tf|th|tr|col|opt|leg|cap|area)/,G={area:[1,""],param:[1,""],col:[2,"
    ","
    "],legend:[1,"
    "],option:[1,""],thead:[1,"","
    "],tr:[2,""],td:[3,"
    "],text:[1,'',""],_default:v?[0,""]:[1,"X
    "]};y.optgroup=y.option;y.tbody=y.tfoot=y.colgroup=y.caption=y.thead;y.th=y.td;y.circle=y.ellipse=y.line=y.path=y.polygon=y.polyline=y.rect=y.text;var Pc=p.createElement("script");avalon.parseHTML= +function(b){"string"!==typeof b&&(b+="");b=b.replace(Mc,"<$1>").trim();var c=(Lc.exec(b)||["",""])[1].toLowerCase(),d=y[c]||y._default,c=W.cloneNode(!1),e=ga,f;v||(b=b.replace(Mb,"
    $1"));e.innerHTML=d[1]+b+(d[2]||"");b=e.getElementsByTagName("script");if(b.length)for(var g=0,h;h=b[g++];)if(!h.type||Nc[h.type])f=Pc.cloneNode(!1),I.forEach.call(h.attributes,function(b){b&&b.specified&&(f[b.name]=b.value)}),f.text=h.text,h.parentNode.replaceChild(f,h);for(g=d[0];g--;e=e.lastChild); +if(!v){b=e.getElementsByTagName("br");for(g=0;h=b[g++];)h.className&&"msNoScope"===h.className&&h.parentNode.removeChild(h);b=e.all;for(g=0;h=b[g++];)eb(h)&&Yb(h)}for(;d=e.firstChild;)c.appendChild(d);return c};avalon.innerHTML=function(b,c){if(!v&&!Mb.test(c)&&!Oc.test(c))try{b.innerHTML=c;return}catch(d){}var e=this.parseHTML(c);this.clearHTML(b).appendChild(e)};avalon.clearHTML=function(b){for(b.textContent="";b.firstChild;)b.removeChild(b.firstChild);return b};var O={$watch:function(b,c){if("function"=== +typeof c){var d=this.$events[b];d?d.push(c):this.$events[b]=[c]}else this.$events=this.$watch.backup;return this},$unwatch:function(b,c){var d=arguments.length;if(0===d)this.$watch.backup=this.$events,this.$events={};else if(1===d)this.$events[b]=[];else for(var d=this.$events[b]||[],e=d.length;0>~--e;)if(d[e]===c)return d.splice(e,1);return this},$fire:function(b){var c;/^(\w+)!(\S+)$/.test(b)&&(c=RegExp.$1,b=RegExp.$2);for(var d=this.$events,e=d[b]||[],f=d.$all||[],g=ba.call(arguments,1),h=0,k;k= +e[h++];)U(k)&&k.apply(this,g);for(h=0;k=f[h++];)U(k)&&k.apply(this,arguments);if(d=d.expr&&Nb(d.expr)){var l=[b].concat(g);if("up"===c||"down"===c||"all"===c){for(h in avalon.vmodels)if((g=avalon.vmodels[h])&&(g.$events&&g.$events.expr)&&g!==this)if((e=Nb(g.$events.expr))&&("all"===c||("down"===c?d.contains(e):e.contains(d))))e._avalon=g;var h=p.getElementsByTagName("*"),n=[];Array.prototype.forEach.call(h,function(b){b._avalon&&(n.push(b._avalon),b._avalon="",b.removeAttribute("_avalon"))});"up"=== +c&&n.reverse();n.forEach(function(b){b.$fire.apply(b,l)})}}}},Qc=/(\w+)\[(avalonctrl)="(\S+)"\]/,Nb=p.querySelector?function(b){return p.querySelector(b)}:function(b){b=b.match(Qc);for(var c=p.getElementsByTagName(b[1]),d=0,e;e=c[d++];)if(e.getAttribute(b[2])===b[3])return e},Zb=/^(duplex|on)$/,aa=[],ya=0,$b=200,hb=new Date,gb;avalon.scan=function(b,c){b=b||B;var d=c?[].concat(c):[];jb(b,d)};var dc=T("AREA,BASE,BASEFONT,BR,COL,COMMAND,EMBED,HR,IMG,INPUT,LINK,META,PARAM,SOURCE,TRACK,WBR,NOSCRIPT,SCRIPT,STYLE,TEXTAREA"), +ac=v?15:50,qb=/ms-(\w+)-?(.*)/,rb={"if":10,repeat:90,data:100,widget:110,each:1400,"with":1500,duplex:2E3,on:3E3},cc=T("animationend,blur,change,input,click,dblclick,focus,keydown,keypress,keyup,mousedown,mouseenter,mouseleave,mousemove,mouseout,mouseover,mouseup,scan,scroll,submit");if(!"1"[0])var Ma=Ca(512),Rc=/\s+(ms-[^=\s]+)(?:=("[^"]*"|'[^']*'|[^\s>]+))?/g,Sc=/^['"]/,Tc=/<\w+\b(?:(["'])[^"]*?(\1)|[^>])*>/i,Uc=/&/g,pb=function(b){b=b.outerHTML;if(".avalonHide{ display: none!important }"), +F.firstChild);var ad=/(?:[\s\S]+?)<\/noscript>/img,bd=/([\s\S]+?)<\/noscript>/im,Pa=function(){return new (r.XMLHttpRequest||ActiveXObject)("Microsoft.XMLHTTP")},ha=function(b,c,d){if(b=b.getAttribute(c)){c=0;for(var e;e=d[c++];)if(e.hasOwnProperty(b)&&"function"===typeof e[b])return e[b]}},Qa=avalon.templateCache={};avalon.contains=Xa;var Ra={};"autofocus,autoplay,async,allowTransparency,checked,controls,declare,disabled,defer,defaultChecked,defaultSelectedcontentEditable,isMap,loop,multiple,noHref,noResize,noShade,open,readOnly,selected".replace(D, +function(b){Ra[b.toLowerCase()]=b});var Ea=avalon.bindingExecutors={attr:function(b,c,d){var e=d.type,f=d.param;if("css"===e)avalon(c).css(f,b);else if("attr"===e){if(Ra[f]&&(d=Ra[f],"boolean"===typeof c[d]))return c[d]=!!b;if(!v&&Oa[f])var f=Oa[f],g=!0;if(!1===b||null===b||void 0===b)return c.removeAttribute(f);r.VBArray&&!g&&(eb(c)?g=!0:Eb.test(c)||(g=(g=(c.attributes||{})[f])?!1===g.expando:null===g));g?c[f]=b:c.setAttribute(f,b)}else if("include"===e&&b){var h=d.vmodels,k=ha(c,"data-include-rendered", +h),l=ha(c,"data-include-loaded",h),n=function(b){l&&(b=l.apply(c,[b].concat(h)));avalon.innerHTML(c,b);za(c,h);k&&ib(c,function(){k.call(c)})};if("src"===d.param)if(Qa[b])avalon.nextTick(function(){n(Qa[b])});else{var m=Pa();m.onreadystatechange=function(){if(4===m.readyState){var c=m.status;if(200<=c&&300>c||304===c||1223===c)n(Qa[b]=m.responseText)}};m.open("GET",b,!0);"withCredentials"in m&&(m.withCredentials=!0);m.setRequestHeader("X-Requested-With","XMLHttpRequest");m.send(null)}else{var q=b&& +1===b.nodeType?b:p.getElementById(b);if(q){if("NOSCRIPT"===q.tagName&&!q.innerHTML&&!q.fixIE78){m=Pa();m.open("GET",location,!1);m.send(null);f=p.getElementsByTagName("noscript");g=(m.responseText||"").match(ad)||[];d=g.length;for(e=0;ec||37<=c&&40>=c)||("cut"===b.type?avalon.nextTick(h):h())};H.forEach(function(c){b.attachEvent("on"+c,K)});d.rollback=function(){H.forEach(function(c){b.detachEvent("on"+c,K)})}}b.oldValue=b.value;Pb(function(){if(avalon.contains(B,b))oc.call(b);else if(!b.msRetain)return!1});xa(d);var N=setTimeout(function(){m||e.call(b,b.value);clearTimeout(N)},31)};var vb,ea=[],Pb=A;avalon.tick=function(b){1===ea.push(b)&&(vb=setInterval(pc, +60))};try{var Sa=HTMLInputElement.prototype;Object.getOwnPropertyNames(Sa);var rc=Object.getOwnPropertyDescriptor(Sa,"value").set;Object.defineProperty(Sa,"value",{set:qc})}catch(kd){Pb=avalon.tick}Y.SELECT=function(b,c,d){var e=avalon(b);d.handler=function(){var d=c(),d=d&&d.$model||d;Array.isArray(d)?b.multiple||x("ms-duplex\u5728\u4e0d\u80fd\u5bf9\u5e94\u4e00\u4e2a\u6570\u7ec4"); +d=Array.isArray(d)?d.map(String):d+"";d+""!==b.oldValue&&(e.val(d),b.oldValue=d+"")};d.bound("change",function(){if(!1!==e.data("duplex-observe")){var f=e.val(),f=Array.isArray(f)?f.map(function(b){return X(d,b)}):X(d,f);f+""!==b.oldValue&&c(f);d.changed.call(b,f)}});var f=NaN,g=setInterval(function(){var e=b.innerHTML;e===f?(clearInterval(g),xa(d),d.changed.call(b,c())):f=e},20)};Y.TEXTAREA=Y.INPUT;var ia=avalon.eventHooks;"onmouseenter"in B||avalon.each({mouseenter:"mouseover",mouseleave:"mouseout"}, +function(b,c){ia[b]={type:c,deel:function(c,e){return function(f){var g=f.relatedTarget;if(!g||g!==c&&!(c.compareDocumentPosition(g)&16))return delete f.type,f.type=b,e.call(c,f)}}}});avalon.each({AnimationEvent:"animationend",WebKitAnimationEvent:"webkitAnimationEnd"},function(b,c){r[b]&&!ia.animationend&&(ia.animationend={type:c})});"oninput"in p.createElement("input")||(ia.input={type:"propertychange",deel:function(b,c){return function(d){if("value"===d.propertyName)return d.type="input",c.call(b, +d)}}});if(void 0===p.onmousewheel){var Qb=void 0!==p.onwheel?"wheel":"DOMMouseScroll",cd="wheel"===Qb?"deltaY":"detail";ia.mousewheel={type:Qb,deel:function(b,c){return function(d){d.wheelDeltaY=d.wheelDelta=0]*>([\S\s]*?)<\/script\s*>/gim,ed=/\s+(on[^=\s]+)(?:=("[^"]*"|'[^']*'|[^\s>]+))?/g,fd=/<\w+\b(?:(["'])[^"]*?(\1)|[^>])*>/ig,gd={a:/\b(href)\=("javascript[^"]*"|'javascript[^']*')/ig,img:/\b(src)\=("javascript[^"]*"|'javascript[^']*')/ig, +form:/\b(action)\=("javascript[^"]*"|'javascript[^']*')/ig},hd=/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,id=/([^\#-~| |!])/g,Ua=avalon.filters={uppercase:function(b){return b.toUpperCase()},lowercase:function(b){return b.toLowerCase()},truncate:function(b,c,d){c=c||30;d=void 0===d?"...":d;return b.length>c?b.slice(0,c-d.length)+d:String(b)},camelize:pa,sanitize:function(b){return b.replace(dd,"").replace(fd,function(b,d){var e=b.toLowerCase().match(/<(\w+)\s/);e&&(e=gd[e[1]])&&(b=b.replace(e,function(b,c, +d){b=d.charAt(0);return c+"="+b+"javascript:void(0)"+b}));return b.replace(ed," ").replace(/\s+/g," ")})},escape:function(b){return String(b).replace(/&/g,"&").replace(hd,function(b){var d=b.charCodeAt(0);b=b.charCodeAt(1);return"&#"+(1024*(d-55296)+(b-56320)+65536)+";"}).replace(id,function(b){return"&#"+b.charCodeAt(0)+";"}).replace(//g,">")},currency:function(b,c){return(c||"\uffe5")+avalon.filters.number(b)},number:function(b,c,d,e){b=(b+"").replace(/[^0-9+\-Ee.]/g, +"");b=!isFinite(+b)?0:+b;c=!isFinite(+c)?0:Math.abs(c);e=e||",";d=d||".";var f="",f=function(b,c){var d=Math.pow(10,c);return""+Math.round(b*d)/d},f=(c?f(b,c):""+Math.round(b)).split(".");3b&&(e="-",b=-b);for(b=""+b;b.length-e)g+=e;0===g&&-12===e&&(g=12);return b(g,d,f)}}function d(b,c){return function(d,e){var f=d["get"+b](),g=(c?"SHORT"+b:b).toUpperCase();return e[g][f]}}function e(b){var c;if(c=b.match(k)){b=new Date(0);var d=0,e=0,f=c[8]?b.setUTCFullYear:b.setFullYear,g=c[8]?b.setUTCHours:b.setHours;c[9]&&(d=parseInt(c[9]+c[10],10),e=parseInt(c[9]+c[11],10));f.call(b,parseInt(c[1],10),parseInt(c[2],10)-1,parseInt(c[3],10));d=parseInt(c[4]||0,10)-d;e=parseInt(c[5]||0,10)-e;f=parseInt(c[6]|| +0,10);c=Math.round(1E3*parseFloat("0."+(c[7]||0)));g.call(b,d,e,f,c)}return b}var f={yyyy:c("FullYear",4),yy:c("FullYear",2,0,!0),y:c("FullYear",1),MMMM:d("Month"),MMM:d("Month",!0),MM:c("Month",2,1),M:c("Month",1,1),dd:c("Date",2),d:c("Date",1),HH:c("Hours",2),H:c("Hours",1),hh:c("Hours",2,-12),h:c("Hours",1,-12),mm:c("Minutes",2),m:c("Minutes",1),ss:c("Seconds",2),s:c("Seconds",1),sss:c("Milliseconds",3),EEEE:d("Day"),EEE:d("Day",!0),a:function(b,c){return 12>b.getHours()?c.AMPMS[0]:c.AMPMS[1]}, +Z:function(c){c=-1*c.getTimezoneOffset();return c=(0<=c?"+":"")+(b(Math[0e?avalon.error(b+" \u5bf9\u5e94\u8d44\u6e90\u4e0d\u5b58\u5728\u6216\u6ca1\u6709\u5f00\u542f CORS"):(s[d].state=2,s[d].exports=c.responseText,E.checkDeps())}};c.open("GET", +b,!0);"withCredentials"in c&&(c.withCredentials=!0);c.setRequestHeader("X-Requested-With","XMLHttpRequest");c.send();return d};var q=c(!0);q||(q=avalon.slice(p.scripts).pop().src);q=b(q);m=t.base=q.slice(0,q.lastIndexOf("/")+1);var u=/\/\w+\/\.\./;E=avalon.require=function(b,c,d){var f={},h=[],n=0,q=0,p=d||"callback"+setTimeout("1");d=d||m;String(b).replace(D,function(b){if(b=g(b,d))n++,s[b]&&2===s[b].state&&q++,f[b]||(h.push(b),f[b]="\u53f8\u5f92\u6b63\u7f8e")});s[p]={id:p,factory:c,deps:f,args:h, +state:1};n===q?k(p,h,c):l.unshift(p);e()};E.define=function(e,f,g){var h=ba.call(arguments);if("string"===typeof e)var k=h.shift();"function"===typeof h[0]&&h.unshift([]);var l=s[k]&&1<=s[k].state?k:b(c());!s[l]&&k&&(s[l]={id:l,factory:g,state:1});g=h[1];g.id=k;g.delay=function(b){h.push(b);var c=!0;try{c=d(s[b].deps,b)}catch(e){}c&&avalon.error(b+"\u6a21\u5757\u4e0e\u4e4b\u524d\u7684\u6a21\u5757\u5b58\u5728\u5faa\u73af\u4f9d\u8d56\uff0c\u8bf7\u4e0d\u8981\u76f4\u63a5\u7528script\u6807\u7b7e\u5f15\u5165"+ +b+"\u6a21\u5757");delete g.delay;E.apply(null,h)};l?g.delay(l,h):n.push(g)};E.define.amd=s;E.config=t;E.checkDeps=e};var Bb=[];"complete"===p.readyState?setTimeout(R):v?p.addEventListener("DOMContentLoaded",R):(p.attachEvent("onreadystatechange",function(){"complete"===p.readyState&&R()}),B.doScroll&&Cb());avalon.bind(r,"load",R);avalon.ready=function(b){E?E("ready!",b):R===A?b(avalon):Bb.push(b)};avalon.config({loader:!0});avalon.ready(function(){avalon.scan(p.body)})})(document); diff --git a/avalon.mobile.min.js b/avalon.mobile.min.js deleted file mode 100644 index b99b5b65a..000000000 --- a/avalon.mobile.min.js +++ /dev/null @@ -1,101 +0,0 @@ -(function(p){function E(){}function w(b){avalon.config.debug&&console.log(b)}function F(b,c){"string"===typeof b&&(b=b.match(z)||[]);for(var d={},e=void 0!==c?c:1,f=0,g=b.length;f=Object.keys(c).length){var n=c.set,q=c.get;k=function(c){var e=h.vmodel,f=d[b];if(arguments.length){if(!aa){if("function"===typeof n){var g=e.$events[b];e.$events[b]=[];n.call(e,c);e.$events[b]=g}if(!V(m,c)){m=c;c=d[b]=q.call(e);if(ba&&(g=R[e.$id])&& -g[b])g[b].$val=c;A(k);e.$events&&I.$fire.call(e,b,c,f)}}}else return avalon.openComputedCollect&&$(k),c=d[b]=q.call(e),V(f,c)||(m=void 0,e.$events&&I.$fire.call(e,b,c,f)),c};g.push(k)}else oa.test(l)?(k=function(c){var e=k.$vmodel,f=e.$model;if(arguments.length){if(!aa&&!V(f,c)){c=k.$vmodel=rb(e,c,l);var g=pa[c.$id];g&&g();g=h.vmodel;d[b]=c.$model;A(e);g.$events&&I.$fire.call(g,b,d[b],f)}}else return $(e),e},k.$vmodel=c.$model?c:H(c,c),d[b]=k.$vmodel.$model):(k=function(c){var e=d[b];if(arguments.length){if(!V(e, -c)){d[b]=c;var f=h.vmodel;if(ba){var g=R[f.$id];g&&g[b]&&(g[b].$val=c)}A(k);f.$events&&I.$fire.call(f,b,c,e)}}else return $(k),e},d[b]=c);k[t]=[];f[b]=k}function rb(b,c,d){if("array"===d){if(!Array.isArray(c))return b;c=c.concat();b.clear();b.push.apply(b,c);return b}var e=b[t]||[];R[b.$id]&&(ba--,delete R[b.$id]);var f=H(c);pa[f.$id]=function(b){for(;b=e.shift();)(function(b){b.type&&avalon.nextTick(function(){b.rollback&&b.rollback();B[b.type](b,b.vmodels)})})(b);delete pa[f.$id]};return f}function s(b){for(var c in b)if(Ka.call(b, -c)){var d=b[c];if("function"===typeof s.plugins[c])s.plugins[c](d);else"object"===typeof s[c]?avalon.mix(s[c],d):s[c]=d}return this}function La(b){return b.replace(/([a-z\d])([A-Z]+)/g,"$1-$2").toLowerCase()}function ca(b){return 0>b.indexOf("-")&&0>b.indexOf("_")?b:b.replace(/[-_][^-_]/g,function(b){return b.charAt(1).toUpperCase()})}function da(b){try{b="true"===b?!0:"false"===b?!1:"null"===b?null:+b+""===b?+b:sb.test(b)?JSON.parse(b):b}catch(c){}return b}function Ma(b,c){if(0>=b.offsetWidth){var d= -getComputedStyle(b,null);if(tb.test(d.display)){var e={node:b},f;for(f in Na)e[f]=d[f],b.style[f]=Na[f];c.push(e)}(d=b.parentNode)&&1==d.nodeType&&Ma(d,c)}}function qa(b,c){Q[x]=b;avalon.openComputedCollect=!0;var d=b.evaluator;if(d)if("duplex"===b.type)b.handler();else try{var e="on"===b.type?b:d.apply(0,b.args);b.handler(e,b.element,b)}catch(f){delete b.evaluator,3===b.nodeType&&(s.commentInterpolate?b.element.replaceChild(p.createComment(b.value),b.node):b.node.data=J+b.value+K),w("warning:evaluator of ["+ -b.value+"] throws error!")}else b();avalon.openComputedCollect=!1;delete Q[x]}function $(b){Q[x]&&(b=b[t])&&avalon.Array.ensure(b,Q[x])}function A(b){var c=b[t];if(c&&c.length)for(var d=ea.call(arguments,1),e=c.length,f;f=c[--e];){var g=f.element;g&&!Oa.contains(g)&&!C.contains(g)?(c.splice(e,1),w("debug: remove "+f.name)):"function"===typeof f?f.apply(0,d):f.getter?f.handler.apply(f,d):f.handler(f.evaluator.apply(0,f.args||[]),g,f)}}function Pa(b,c){var d=NaN,e=setInterval(function(){var f=b.innerHTML; -f===d?(clearInterval(e),c()):d=f},15)}function fa(b,c,d){d=b.getAttribute(ra+"skip");var e=b.getAttributeNode(ra+"important"),f=b.getAttributeNode(ra+"controller");if("string"!==typeof d){if(d=e||f){var g=S[d.value];if(!g)return;c=d===e?[g]:[g].concat(c);b.removeAttribute(d.name);b.classList.remove(d.name);g.$events.element=b;b.addEventListener("dataavailable",function(c){"object"===typeof c.detail&&b!==c.target&&g.$fire.apply(g,c.detail)})}Qa(b,c)}}function sa(b,c){for(var d=b.firstChild;d;){var e= -d.nextSibling,f=d.nodeType;1===f?fa(d,c):3===f&&W.test(d.data)?Ra(d,c):s.commentInterpolate&&(8===f&&!W.test(d.nodeValue))&&Ra(d,c);d=e}}function Ra(b,c){var d=[];if(8===b.nodeType){var e=[],f={expr:!0,value:Sa(b.nodeValue,e)};e.length&&(f.filters=e);e=[f]}else e=ta(b.data);if(e.length){for(var g=0;f=e[g++];){var h=p.createTextNode(f.value);if(f.expr){var l=f.filters,f={type:"text",node:h,nodeType:3,value:f.value,filters:l};l&&-1!==l.indexOf("html")&&(avalon.Array.remove(l,"html"),f.type="html",f.replaceNodes= -[h],l.length||delete d.filters);d.push(f)}O.appendChild(h)}b.parentNode.replaceChild(O,b);d.length&&ua(d,c)}}function Qa(b,c){for(var d=b.hasAttributes()?avalon.slice(b.attributes):[],e=[],f={},g,h=0,l;l=d[h++];)if(l.specified&&(g=l.name.match(ub))){var k=g[1],m=g[2]||"",n=l.value;l=l.name;f[l]=n;vb[k]?(m=k,k="on"):"enabled"===k&&(k="disabled",n="!("+n+")");if("checked"===k||"selected"===k||"disabled"===k||"readonly"===k)m=k,k="attr",b.removeAttribute(l),l="ms-attr-"+m,b.setAttribute(l,n),g=[l],f[l]= -n;"function"===typeof B[k]&&(n={type:k,param:m,element:b,name:g[0],value:n,priority:k in Ta?Ta[k]:10*k.charCodeAt(0)+(Number(m)||0)},"if"===k&&"loop"===m&&(n.priority+=100),c.length&&(e.push(n),"widget"===k&&(b.msData=b.msData||f)))}f["ms-checked"]&&f["ms-duplex"]&&w("warning!\u4e00\u4e2a\u5143\u7d20\u4e0a\u4e0d\u80fd\u540c\u65f6\u5b9a\u4e49ms-checked\u4e0ems-duplex");e.sort(function(b,c){return b.priority-c.priority});d=e[0]||{};switch(d.type){case "if":case "repeat":case "widget":ua([d],c);break; -default:ua(e,c),!wb[b.tagName]&&va.test(b.innerHTML+b.textContent)&&sa(b,c)}b.patchRepeat&&(b.patchRepeat(),b.patchRepeat="",b.removeAttribute("patchRepeat"))}function ua(b,c){for(var d=0,e;e=b[d++];)e.vmodels=c,B[e.type](e,c),e.evaluator&&e.name&&e.element.removeAttribute(e.name);b.length=0}function Sa(b,c){0b&&delete c[d.shift()];return c[e]=f}var d=[];return c}function ga(b,c,d){var e=d.type,f="html"===e||"text"===e?d.filters:"",g=c.map(function(b){return b.$id.replace(Cb,"$1")})+b+e+f,h=Db(b).concat(),l=[],k=[],m=[],n="";c=Bb(c);for(var q=0,n=c.length;qs.maxRepeatSize&&Z.pop()}function ja(){r["ready!"].state=2;D.checkDeps();ja=E}var ra="ms-",x=Date.now(),t="$"+x,u=this||(0,eval)("this"),Ob=u.require,Pb=u.define,aa=!1,z=/[^, ]+/g,oa=/^(?:object|array)$/,Qb=/^\[object (Window|DOMWindow|global)\]$/, -Aa=Object.prototype,Ka=Aa.hasOwnProperty,la=Aa.toString,G=Array.prototype,ea=G.slice,Q={},M=p.head,C=p.documentElement,O=p.createDocumentFragment(),P=p.createElement("div"),ab={};"Boolean Number String Function Array Date RegExp Object Error".replace(z,function(b){ab["[object "+b+"]"]=b.toLowerCase()});u.avalon=function(b){return new avalon.init(b)};avalon.init=function(b){this[0]=this.element=b};avalon.fn=avalon.prototype=avalon.init.prototype;avalon.type=function(b){return null==b?String(b):"object"=== -typeof b||"function"===typeof b?ab[la.call(b)]||"object":typeof b};avalon.isWindow=function(b){return Qb.test(la.call(b))};avalon.isPlainObject=function(b){return!!b&&"object"===typeof b&&Object.getPrototypeOf(b)===Aa};avalon.mix=avalon.fn.mix=function(){var b,c,d,e,f,g=arguments[0]||{},h=1,l=arguments.length,k=!1;"boolean"===typeof g&&(k=g,g=arguments[1]||{},h++);"object"!==typeof g&&"function"!==avalon.type(g)&&(g={});h===l&&(g=this,h--);for(;h';var Rb=Ba.firstChild&& -"rect"===Ba.firstChild.tagName;u.SVGElement&&!Rb&&Object.defineProperties(SVGElement.prototype,{outerHTML:{enumerable:!0,configurable:!0,get:lb,set:function(b){var c=this.tagName.toLowerCase(),d=this.parentNode;b=avalon.parseHTML(b);"svg"===c?d.insertBefore(b,this):(c=document.createDocumentFragment(),ma(b,c),d.insertBefore(c,this));d.removeChild(this)}},innerHTML:{enumerable:!0,configurable:!0,get:function(){var b=RegExp("$","i");return this.outerHTML.replace(RegExp("<"+this.nodeName+ -'\\b(?:(["\'])[^"]*?(\\1)|[^>])*>',"i"),"").replace(b,"")},set:function(b){avalon.clearHTML(this);b=avalon.parseHTML(b);ma(b,this)}}});var S=avalon.vmodels={};avalon.define=function(b,c){var d=b.$id||b;d||w("warning: \u5fc5\u987b\u6307\u5b9a$id");S[b]&&w("warning: "+d+" \u5df2\u7ecf\u5b58\u5728\u4e8eavalon.vmodels\u4e2d");if("object"==typeof b)var e=H(b);else e={$watch:E},c(e),e=H(e),aa=!0,c(e),aa=!1;e.$id=d;return S[d]=e};var nb=String("$id,$watch,$unwatch,$fire,$events,$model,$skipArray,$accessors,"+ -t).match(z),V=Object.is||function(b,c){return 0===b&&0===c?1/b===1/c:b!==b?c!==c:b===c},R={},ba=0,pa={},J,K,W,bb,va,cb=/[-.*+?^${}()|[\]\/\\]/g,N={loader:function(b){u.define=b?D.define:Pb;u.require=b?D:Ob},interpolate:function(b){J=b[0];K=b[1];J===K?avalon.error("openTag!==closeTag",SyntaxError):"\x3c!--,--\x3e"===b+""?s.commentInterpolate=!0:(b=J+"test"+K,P.innerHTML=b,P.innerHTML!==b&&0<=P.innerHTML.indexOf("<")&&avalon.error("\u6b64\u5b9a\u754c\u7b26\u4e0d\u5408\u6cd5",SyntaxError),P.innerHTML= -"");b=(J+"").replace(cb,"\\$&");var c=(K+"").replace(cb,"\\$&");W=RegExp(b+"(.*?)"+c);bb=RegExp(b+"(.*?)"+c,"g");va=RegExp(b+".*?"+c+"|\\sms-")}};s.debug=!0;s.plugins=N;s.plugins.interpolate(["{{","}}"]);s.paths={};s.shim={};s.maxRepeatSize=100;avalon.config=s;"add,remove".replace(z,function(b){avalon.fn[b+"Class"]=function(c){var d=this[0];c&&("string"===typeof c&&d&&1==d.nodeType)&&c.replace(/\S+/g,function(c){d.classList[b](c)});return this}});avalon.fn.mix({hasClass:function(b){var c=this[0]|| -{};return 1===c.nodeType&&c.classList.contains(b)},toggleClass:function(b,c){for(var d,e=0,f=b.split(/\s+/),g="boolean"===typeof c;d=f[e++];)this[(g?c:!this.hasClass(d))?"addClass":"removeClass"](d);return this},attr:function(b,c){return 2===arguments.length?(this[0].setAttribute(b,c),this):this[0].getAttribute(b)},data:function(b,c){b="data-"+La(b||"");switch(arguments.length){case 2:return this.attr(b,c),this;case 1:var d=this.attr(b);return da(d);case 0:var e={};G.forEach.call(this[0].attributes, -function(c){c&&(b=c.name,b.indexOf("data-")||(b=ca(b.slice(5)),e[b]=da(c.value)))});return e}},removeData:function(b){b="data-"+La(b);this[0].removeAttribute(b);return this},css:function(b,c){if(avalon.isPlainObject(b))for(var d in b)avalon.css(this,d,b[d]);else var e=avalon.css(this,b,c);return void 0!==e?e:this},position:function(){var b,c,d=this[0],e={top:0,left:0};if(d)return"fixed"===this.css("position")?c=d.getBoundingClientRect():(b=this.offsetParent(),c=this.offset(),"HTML"!==b[0].tagName&& -(e=b.offset()),e.top+=avalon.css(b[0],"borderTopWidth",!0),e.left+=avalon.css(b[0],"borderLeftWidth",!0)),{top:c.top-e.top-avalon.css(d,"marginTop",!0),left:c.left-e.left-avalon.css(d,"marginLeft",!0)}},offsetParent:function(){for(var b=this[0].offsetParent||C;b&&"HTML"!==b.tagName&&"static"===avalon.css(b,"position");)b=b.offsetParent;return avalon(b||C)},bind:function(b,c,d){if(this[0])return avalon.bind(this[0],b,c,d)},unbind:function(b,c,d){this[0]&&avalon.unbind(this[0],b,c,d);return this},val:function(b){var c= -this[0];if(c&&1===c.nodeType){var d=0===arguments.length,e=d?":get":":set",f=Sb,g;g=c.tagName.toLowerCase();g="input"===g&&/checkbox|radio/.test(c.type)?"checked":g;if(e=f[g+e])var h=e(c,b);else{if(d)return(c.value||"").replace(/\r/g,"");c.value=b}}return d?h:this}});C.dataset&&(avalon.data=function(b,c){var d=this[0].dataset;switch(arguments.length){case 2:return d[b]=c,this;case 1:return c=d[b],da(c);case 0:var e={};for(b in d)e[b]=da(d[b]);return e}});var sb=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/;avalon.each({scrollLeft:"pageXOffset", -scrollTop:"pageYOffset"},function(b,c){avalon.fn[b]=function(d){var e=this[0]||{},f=e.window&&e.document?e:9===e.nodeType?e.defaultView:!1,g="scrollTop"===b;if(arguments.length)f?f.scrollTo(!g?d:avalon(f).scrollLeft(),g?d:avalon(f).scrollTop()):e[b]=d;else return f?f[c]:e[b]}});var v=avalon.cssHooks={},db=["","-webkit-","-o-","-moz-","-ms-"],Ca={"float":"cssFloat",background:"backgroundColor"};avalon.cssNumber=F("columnCount,order,fillOpacity,fontWeight,lineHeight,opacity,orphans,widows,zIndex,zoom"); -avalon.cssName=function(b,c,d){if(Ca[b])return Ca[b];c=c||C.style;for(var e=0,f=db.length;ef,h=g?null:[],l=g?f+1:e.length,k=0>f?l:g?f:0;k]*)\/>/ig,Vb=F("text/javascript","text/ecmascript","application/ecmascript","application/javascript","text/vbscript"),eb=/<(?:tb|td|tf|th|tr|col|opt|leg|cap|area)/,fb=new function(){avalon.mix(this,{option:p.createElement("select"),thead:p.createElement("table"),td:p.createElement("tr"),area:p.createElement("map"),tr:p.createElement("tbody"),col:p.createElement("colgroup"),legend:p.createElement("fieldset"),"*":p.createElement("div")}); -this.optgroup=this.option;this.tbody=this.tfoot=this.colgroup=this.caption=this.thead;this.th=this.td};avalon.clearHTML=function(b){b.textContent="";return b};var Wb=p.createElement("script");avalon.parseHTML=function(b){"string"!==typeof b&&(b+="");b=b.replace(Ub,"<$1>").trim();if(Xb.createContextualFragment&&!eb.test(b)&&!/ + + + + +

    avalon vm.$fire的升级版

    + +
    + +
    + +
    +
    + + diff --git a/examples/$watch.html b/examples/$watch.html new file mode 100644 index 000000000..9d199eb40 --- /dev/null +++ b/examples/$watch.html @@ -0,0 +1,44 @@ + + + + + + + + + + +
    {{aaa}}
    +
    {{bbb}}
    +
    {{$ccc}}
    +
    {{ddd}}
    + + diff --git a/examples/aa1.html b/examples/aa1.html deleted file mode 100644 index 380ce1ee8..000000000 --- a/examples/aa1.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - -
    -
    - {{$maps[name] | html}} -
    - - {{oo.xx}} {{oo}} -
    -
    -
    - -
    - - -
    - - - \ No newline at end of file diff --git a/examples/aa2.html b/examples/aa2.html deleted file mode 100644 index f462b15f4..000000000 --- a/examples/aa2.html +++ /dev/null @@ -1,134 +0,0 @@ - - - - by 司徒正美 - - - - - - - - -
    -
    -
    -
    - - - -
    表格1第{{el}}行表格3
    - - - - - \ No newline at end of file diff --git a/examples/aa3.html b/examples/aa3.html deleted file mode 100644 index dc4fc700e..000000000 --- a/examples/aa3.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - by 司徒正美 - - - - -
    - -
    - - - - \ No newline at end of file diff --git a/examples/aa4.html b/examples/aa4.html deleted file mode 100644 index c53a2f757..000000000 --- a/examples/aa4.html +++ /dev/null @@ -1,130 +0,0 @@ - - - - by 司徒正美 - - - - - - - - -
    -
    -
    - - - - -
    表格1第{{el}}行表格3
    -
    -
    -
    - - \ No newline at end of file diff --git a/examples/architecture.html b/examples/architecture.html new file mode 100644 index 000000000..64d5126e7 --- /dev/null +++ b/examples/architecture.html @@ -0,0 +1,203 @@ + + + + TODO supply a title + + + + + +
    +

    披荆斩棘

    +
    + 语言底层的修复
    + String.prototype.trim
    + Object.keys
    + Object.defineProperties
    + Function.prototype.bind
    + Array.prototype.indexOf
    + Array.prototype.lastIndexOf
    + Array.prototype.forEach
    + Array.prototype.filter
    + Array.prototype.map
    + Array.prototype.every
    + Array.prototype.some
    + JSON.parse的代替品parseData(未公开)
    + JSON.stringify的代替品quote(未公开) +
    +
    + DOM底层的修复
    + Node.prototype.contains
    + Element.prototype.classList
    + HTMLElement.prototype.outerHTML
    + SVGElement.prototype.innerHTML
    + SVGElement.prototype.outerHTML +
    + +
    +
    +

    青铜时代

    +
    + 常用工具函数
    + avalon.type
    + avalon.isWindow
    + avalon.isPlainObject
    + avalon.mix
    + avalon.slice
    + avalon.oneObject
    + avalon.error
    + avalon.log
    + avalon.noop
    + avalon.range
    + avalon.each
    + avalon.Array.ensure
    + avalon.Array.removeAt
    + avalon.Array.remove
    + avalon.nextTick +
    +
    + DOM的高级封装
    + avalon.css
    + avalon.bind
    + avalon.unbind
    +
    +
    更多事件的支持
    +
    input
    +
    mouseener
    +
    mouseleave
    +
    mousewheel
    +
    animationend
    +
    + avalon.parseHTML
    + avalon.innerHTML
    + avalon.clearHTML
    + fixCloneNode +
    +
    +
    +

    光彩年华

    +
    + 设计模式的应用
    + 事件管理系统: EventManager
    + 心跳检测:avalon.tick
    + 缓存系统:createCache
    +
    +
    过滤器集
    +
    uppercase
    +
    lowercase
    +
    truncate
    +
    camelize
    +
    sanitize
    +
    escape
    +
    currency
    +
    number
    +
    date
    +
    +
    +
    + DOM的优雅封装
    + avalon.fn.bind
    + avalon.fn.unbind
    + avalon.fn.data
    + avalon.fn.attr
    + avalon.fn.val
    + avalon.fn.css
    + avalon.fn.offset
    + avalon.fn.offsetParent
    + avalon.fn.scrollLeft
    + avalon.fn.scrollTop
    + avalon.fn.addClass
    + avalon.fn.hasClass
    + avalon.fn.removeClass
    + avalon.fn.toggleClass
    + avalon.fn.width
    + avalon.fn.height
    + avalon.fn.innerWidth
    + avalon.fn.innerHeight
    + avalon.fn.innerWidth
    + avalon.fn.innerHeight
    +
    + +
    +
    +

    鼎盛王朝

    +
    + 数据之域
    +
    +
    两个VM工厂
    +
    modelFactory
    +
    Collection
    +
    +
    +
    三个创建代理VM的作坊
    +
    getEachPrxoy
    +
    getWithProxy
    +
    recycleEachProxy
    +
    +
    +
    两个池子
    +
    eachProxyPool
    +
    withProxyPool
    +
    +
    +
    依赖调度系统
    +
    registerSubscriber
    +
    notifySubscribers
    +
    collectSubscribers
    +
    +
    +
    + 视图之领
    +
    +
    扫描系统
    +
    avalon.scan
    +
    scanTag
    +
    scanAttr
    +
    scanText
    +
    scanExpr
    +
    scanNodes
    +
    executeBindings
    +
    +
    +
    双向绑定系统
    +
    avalon.bindingExecutors
    +
    avalon.bindingHandlers
    +
    avalon.parseExprProxy
    +
    +
    +
    +
    +

    天下大同

    +
    +
    +
    编译系统
    +
    getVariables(抽取用户变量)
    +
    addAssign(拼接赋值语句)
    +
    parseExpr(转换为求值函数)
    +
    +
    +
    外挂
    +
    AMD加载器
    +
    DOMReady模块
    +
    +
    +
    + + diff --git a/examples/attr.html b/examples/attr.html new file mode 100644 index 000000000..c7ef0eaa5 --- /dev/null +++ b/examples/attr.html @@ -0,0 +1,131 @@ + + + + ms-attr-* + + + + + + + + + +
    + + + + + + +

    + + + +

    + + + + + +
    + + diff --git a/examples/avalon.$events.js b/examples/avalon.$events.js new file mode 100644 index 000000000..f36aeceb1 --- /dev/null +++ b/examples/avalon.$events.js @@ -0,0 +1,4922 @@ +/*================================================== + Copyright 2013-2014 司徒正美 and other contributors + http://www.cnblogs.com/rubylouvre/ + https://github.com/RubyLouvre + http://weibo.com/jslouvre/ + + Released under the MIT license + avalon 1.4 2014.10.5 + ==================================================*/ +(function(DOC) { + /********************************************************************* + * 全局变量及方法 * + **********************************************************************/ + var expose = new Date - 0 + var subscribers = "$" + expose + //http://addyosmani.com/blog/understanding-mvvm-a-guide-for-javascript-developers/ + var window = this || (0, eval)("this") + var otherRequire = window.require + var otherDefine = window.define + var stopRepeatAssign = false + var rword = /[^, ]+/g //切割字符串为一个个小块,以空格或豆号分开它们,结合replace实现字符串的forEach + var rnative = /\[native code\]/ //判定是否原生函数 + var rcomplexType = /^(?:object|array)$/ + var rsvg = /^\[object SVG\w*Element\]$/ + var rwindow = /^\[object (?:Window|DOMWindow|global)\]$/ + var oproto = Object.prototype + var ohasOwn = oproto.hasOwnProperty + var serialize = oproto.toString + var ap = Array.prototype + var aslice = ap.slice + var W3C = window.dispatchEvent + var root = DOC.documentElement + var head = DOC.getElementsByTagName("head")[0] //HEAD元素 + var hyperspace = DOC.createDocumentFragment() + var cinerator = DOC.createElement("div") + var class2type = {} + "Boolean Number String Function Array Date RegExp Object Error".replace(rword, function(name) { + class2type["[object " + name + "]"] = name.toLowerCase() + }) + + var isFunction = typeof alert === "object" ? function(fn) { + try { + return /^\s*\bfunction\b/.test(fn + "") + } catch (e) { + return false + } + } : function(fn) { + return serialize.call(fn) == "[object Function]" + } + + function noop() { + } + + function log() { + if (window.console && avalon.config.debug) { + // http://stackoverflow.com/questions/8785624/how-to-safely-wrap-console-log + Function.apply.call(console.log, console, arguments) + } + } + + + function oneObject(array, val) { + if (typeof array === "string") { + array = array.match(rword) || [] + } + var result = {}, + value = val !== void 0 ? val : 1 + for (var i = 0, n = array.length; i < n; i++) { + result[array[i]] = value + } + return result + } + +//生成UUID http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript + function generateID() { + return "avalon" + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15) + } + /********************************************************************* + * avalon的静态方法定义区 * + **********************************************************************/ + avalon = function(el) { //创建jQuery式的无new 实例化结构 + return new avalon.init(el) + } + + avalon.init = function(el) { + this[0] = this.element = el + } + avalon.isFunction = isFunction + avalon.fn = avalon.prototype = avalon.init.prototype + + avalon.type = function(obj) {//取得目标的类型 + if (obj == null) { + return String(obj) + } + // 早期的webkit内核浏览器实现了已废弃的ecma262v4标准,可以将正则字面量当作函数使用,因此typeof在判定正则时会返回function + return typeof obj === "object" || typeof obj === "function" ? + class2type[serialize.call(obj)] || "object" : + typeof obj + } + + avalon.isWindow = function(obj) { + if (!obj) + return false + // 利用IE678 window == document为true,document == window竟然为false的神奇特性 + // 标准浏览器及IE9,IE10等使用 正则检测 + return obj == obj.document && obj.document != obj + } + + function isWindow(obj) { + return rwindow.test(serialize.call(obj)) + } + if (isWindow(window)) { + avalon.isWindow = isWindow + } + var enu + for (enu in avalon({})) { + break + } + var enumerateBUG = enu !== "0"//IE6下为true, 其他为false + /*判定是否是一个朴素的javascript对象(Object),不是DOM对象,不是BOM对象,不是自定义类的实例*/ + avalon.isPlainObject = function(obj, key) { + if (!obj || avalon.type(obj) !== "object" || obj.nodeType || avalon.isWindow(obj)) { + return false; + } + try {//IE内置对象没有constructor + if (obj.constructor && + !ohasOwn.call(obj, "constructor") && + !ohasOwn.call(obj.constructor.prototype, "isPrototypeOf")) { + return false; + } + } catch (e) {//IE8 9会在这里抛错 + return false; + } + if (enumerateBUG) { + for (key in obj) { + return ohasOwn.call(obj, key) + } + } + for (key in obj) { + } + return key === void 0 || ohasOwn.call(obj, key); + } + if (rnative.test(Object.getPrototypeOf)) { + avalon.isPlainObject = function(obj) { + return !!obj && typeof obj === "object" && Object.getPrototypeOf(obj) === oproto + } + } +//与jQuery.extend方法,可用于浅拷贝,深拷贝 + avalon.mix = avalon.fn.mix = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false + + // 如果第一个参数为布尔,判定是否深拷贝 + if (typeof target === "boolean") { + deep = target + target = arguments[1] || {} + i++ + } + +//确保接受方为一个复杂的数据类型 + if (typeof target !== "object" && !isFunction(target)) { + target = {} + } + +//如果只有一个参数,那么新成员添加于mix所在的对象上 + if (i === length) { + target = this + i-- + } + + for (; i < length; i++) { +//只处理非空参数 + if ((options = arguments[i]) != null) { + for (name in options) { + src = target[name] + try { + copy = options[name]//当options为VBS对象时报错 + } catch (e) { + continue + } + + // 防止环引用 + if (target === copy) { + continue + } + if (deep && copy && (avalon.isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))) { + + if (copyIsArray) { + copyIsArray = false + clone = src && Array.isArray(src) ? src : [] + + } else { + clone = src && avalon.isPlainObject(src) ? src : {} + } + + target[name] = avalon.mix(deep, clone, copy) + } else if (copy !== void 0) { + target[name] = copy + } + } + } + } + return target + } + + function _number(a, len) { //用于模拟slice, splice的效果 + a = Math.floor(a) || 0 + return a < 0 ? Math.max(len + a, 0) : Math.min(a, len); + } + avalon.mix({ + rword: rword, + subscribers: subscribers, + version: 1.4, + ui: {}, + log: log, + slice: W3C ? function(nodes, start, end) { + return aslice.call(nodes, start, end) + } : function(nodes, start, end) { + var ret = [] + var len = nodes.length + if (end === void 0) + end = len + if (typeof end === "number" && isFinite(end)) { + start = _number(start, len) + end = _number(end, len) + for (var i = start; i < end; ++i) { + ret[i - start] = nodes[i] + } + } + return ret + }, + noop: noop, + /*如果不用Error对象封装一下,str在控制台下可能会乱码*/ + error: function(str, e) { + throw new (e || Error)(str) + }, + /*将一个以空格或逗号隔开的字符串或数组,转换成一个键值都为1的对象*/ + oneObject: oneObject, + /* avalon.range(10) + => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + avalon.range(1, 11) + => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + avalon.range(0, 30, 5) + => [0, 5, 10, 15, 20, 25] + avalon.range(0, -10, -1) + => [0, -1, -2, -3, -4, -5, -6, -7, -8, -9] + avalon.range(0) + => []*/ + range: function(start, end, step) { // 用于生成整数数组 + step || (step = 1) + if (end == null) { + end = start || 0 + start = 0 + } + var index = -1, + length = Math.max(0, Math.ceil((end - start) / step)), + result = Array(length) + while (++index < length) { + result[index] = start + start += step + } + return result + }, + eventHooks: {}, + /*绑定事件*/ + bind: function(el, type, fn, phase) { + var hooks = avalon.eventHooks + var hook = hooks[type] + if (typeof hook === "object") { + type = hook.type + if (hook.deel) { + fn = hook.deel(el, fn) + } + } + var callback = W3C ? fn : function(e) { + fn.call(el, fixEvent(e)); + } + if (W3C) { + el.addEventListener(type, callback, !!phase) + } else { + el.attachEvent("on" + type, callback) + } + return callback + }, + /*卸载事件*/ + unbind: function(el, type, fn, phase) { + var hooks = avalon.eventHooks + var hook = hooks[type] + var callback = fn || noop + if (typeof hook === "object") { + type = hook.type + } + if (W3C) { + el.removeEventListener(type, callback, !!phase) + } else { + el.detachEvent("on" + type, callback) + } + }, + /*读写删除元素节点的样式*/ + css: function(node, name, value) { + if (node instanceof avalon) { + node = node[0] + } + var prop = /[_-]/.test(name) ? camelize(name) : name + name = avalon.cssName(prop) || prop + if (value === void 0 || typeof value === "boolean") { //获取样式 + var fn = cssHooks[prop + ":get"] || cssHooks["@:get"] + var val = fn(node, name) + return value === true ? parseFloat(val) || 0 : val + } else if (value === "") { //请除样式 + node.style[name] = "" + } else { //设置样式 + if (value == null || value !== value) { + return + } + if (isFinite(value) && !avalon.cssNumber[prop]) { + value += "px" + } + fn = cssHooks[prop + ":set"] || cssHooks["@:set"] + fn(node, name, value) + } + }, + /*遍历数组与对象,回调的第一个参数为索引或键名,第二个或元素或键值*/ + each: function(obj, fn) { + if (obj) { //排除null, undefined + var i = 0 + if (isArrayLike(obj)) { + for (var n = obj.length; i < n; i++) { + fn(i, obj[i]) + } + } else { + for (i in obj) { + if (obj.hasOwnProperty(i)) { + fn(i, obj[i]) + } + } + } + } + }, + //收集元素的data-{{prefix}}-*属性,并转换为对象 + getWidgetData: function(elem, prefix) { + var raw = avalon(elem).data() + var result = {} + for (var i in raw) { + if (i.indexOf(prefix) === 0) { + result[i.replace(prefix, "").replace(/\w/, function(a) { + return a.toLowerCase() + })] = raw[i] + } + } + return result + }, + Array: { + /*只有当前数组不存在此元素时只添加它*/ + ensure: function(target, item) { + if (target.indexOf(item) === -1) { + return target.push(item) + } + }, + /*移除数组中指定位置的元素,返回布尔表示成功与否*/ + removeAt: function(target, index) { + return !!target.splice(index, 1).length + }, + /*移除数组中第一个匹配传参的那个元素,返回布尔表示成功与否*/ + remove: function(target, item) { + var index = target.indexOf(item) + if (~index) + return avalon.Array.removeAt(target, index) + return false + } + } + }) + + /*判定类数组,如节点集合,纯数组,arguments与拥有非负整数的length属性的纯JS对象*/ + function isArrayLike(obj) { + if (obj && typeof obj === "object" && !avalon.isWindow(obj)) { + var n = obj.length + if (+n === n && !(n % 1) && n >= 0) { //检测length属性是否为非负整数 + try { + if ({}.propertyIsEnumerable.call(obj, "length") === false) { //如果是原生对象 + return Array.isArray(obj) || /^\s?function/.test(obj.item || obj.callee) + } + return true + } catch (e) { //IE的NodeList直接抛错 + return true + } + } + } + return false + } + //from Q.js + avalon.nextTick = (function() { + // linked list of tasks (single, with head node) + var head = {task: void 0, next: null} + var tail = head + var flushing = false + var requestTick = void 0 + + function flush() { + while (head.next) { + head = head.next + var task = head.task + head.task = void 0 + try { + task() + } catch (e) { + // In browsers, uncaught exceptions are not fatal. + // Re-throw them asynchronously to avoid slow-downs. + setTimeout(function() { + throw e + }, 0) + } + } + flushing = false + } + + nextTick = function(task) { + tail = tail.next = { + task: task, + next: null + } + if (!flushing) { + flushing = true + requestTick() + } + } + if (typeof setImmediate === "function") {//IE10-11 + requestTick = setImmediate.bind(window, flush) + } else if (typeof MessageChannel !== "undefined") { // W3C + // http://www.nonblocking.io/2011/06/windownexttick.html + var channel = new MessageChannel(); + channel.port1.onmessage = function() { + requestTick = requestPortTick + channel.port1.onmessage = flush + flush() + } + var requestPortTick = function() { + channel.port2.postMessage(0); + } + requestTick = function() { + setTimeout(flush, 0) + requestPortTick() + } + } else if (window.VBArray) {//IE6-9 + requestTick = function() { + var node = DOC.createElement("script") + node.onreadystatechange = function() { + flush() //在interactive阶段就触发 + node.onreadystatechange = null + head.removeChild(node) + node = null + } + head.appendChild(node) + } + } else { // old W3C + requestTick = function() { + setTimeout(flush, 0) + } + } + return nextTick + })() + /********************************************************************* + * modelFactory * + **********************************************************************/ + //avalon最核心的方法的两个方法之一(另一个是avalon.scan),返回一个ViewModel(VM) + var VMODELS = avalon.vmodels = {}//所有vmodel都储存在这里 + avalon.define = function(id, factory) { + var $id = id.$id || id + if (!$id) { + log("warning: vm必须指定$id") + } + if (VMODELS[id]) { + log("warning: " + $id + " 已经存在于avalon.vmodels中") + } + if (typeof id === "object") { + var model = modelFactory(id) + } else { + var scope = { + $watch: noop + } + factory(scope) //得到所有定义 + model = modelFactory(scope) //偷天换日,将scope换为model + stopRepeatAssign = true + factory(model) + stopRepeatAssign = false + } + model.$id = $id + return VMODELS[$id] = model + } + //一些不需要被监听的属性 + var $$skipArray = String("$id,$watch,$unwatch,$fire,$events,$model,$skipArray").match(rword) + function isObservable(name, value, $skipArray) { + if (isFunction(value) || value && value.nodeType) { + return false + } + if ($skipArray.indexOf(name) !== -1) { + return false + } + if ($$skipArray.indexOf(name) !== -1) { + return false + } + var $special = $skipArray.$special + if (name && name.charAt(0) === "$" && !$special[name]) { + return false + } + return true + } + var rthis = /\bthis\./g + + function modelFactory($scope, $special, $model) { + if (Array.isArray($scope)) { + var arr = $scope.concat() + $scope.length = 0 + var collection = Collection($scope) + collection.pushArray(arr) + return collection + } + if (typeof $scope.nodeType === "number") { + return $scope + } + if ($scope.$id && $scope.$model && $scope.$events) {//fix IE6-8 createWithProxy $val: val引发的BUG + return $scope + } + if (!Array.isArray($scope.$skipArray)) { + $scope.$skipArray = [] + } + $scope.$skipArray.$special = $special || {}//强制要监听的属性 + var $vmodel = {} //要返回的对象, 它在IE6-8下可能被偷龙转凤 + $model = $model || {} //vmodels.$model属性 + var watchedProperties = {} //监控属性 + var computedProperties = [] //计算属性 + var $events = {} + for (var i in $scope) { + (function(name, val) { + $model[name] = val + if (!isObservable(name, val, $scope.$skipArray)) { + return //过滤所有非监控属性 + } + //总共产生三种accessor + var accessor + var valueType = avalon.type(val) + $events[name] = [] + if (valueType === "object" && isFunction(val.get) && Object.keys(val).length <= 2) { + var setter = val.set + var getter = val.get + //第1种对应计算属性, 因变量,通过其他监控属性触发其改变 + accessor = function(newValue) { + var $events = $vmodel.$events + var oldValue = $model[name] + if (arguments.length) { + if (stopRepeatAssign) { + return + } + if (isFunction(setter)) { + var backup = $events[name] + $events[name] = [] //清空回调,防止内部冒泡而触发多次$fire + setter.call($vmodel, newValue) + $events[name] = backup + } + } + newValue = $model[name] = getter.call($vmodel) //同步$model + if (!isEqual(oldValue, newValue)) { + withProxyCount && updateWithProxy($vmodel.$id, name, newValue) //同步循环绑定中的代理VM + notifySubscribers($events[name]) //同步视图 + safeFire($vmodel, name, newValue, oldValue) //触发$watch回调 + } + return newValue + } + computedProperties.push(function() { + var data = { + evaluator: accessor, + element: head, + type: "computed::" + name, + handler: noop, + args: [] + } + var vars = getVariables(getter.toString().replace(rthis, "")).concat() + if (vars.length) {//计算依赖 + addAssign(vars, $vmodel, name, data) + } + accessor()//强逼计算自身 + }) + } else if (rcomplexType.test(valueType)) { + //第2种对应子ViewModel或监控数组 + accessor = function(newValue) { + var childVmodel = accessor.child + var oldValue = $model[name] + if (arguments.length) { + if (stopRepeatAssign) { + return + } + if (!isEqual(oldValue, newValue)) { + childVmodel = accessor.child = updateChild($vmodel, name, newValue, valueType) + newValue = $model[name] = childVmodel.$model //同步$model + var fn = rebindings[childVmodel.$id] + fn && fn() //同步视图 + safeFire($vmodel, name, newValue, oldValue) //触发$watch回调 + } + } else { + return childVmodel + } + } + var childVmodel = accessor.child = modelFactory(val, 0, $model[name]) + childVmodel.$events[subscribers] = $events[name] + } else { + //第3种对应简单的数据类型,自变量,监控属性 + accessor = function(newValue) { + var oldValue = $model[name] + if (arguments.length) { + if (!isEqual(oldValue, newValue)) { + $model[name] = newValue //同步$model + withProxyCount && updateWithProxy($vmodel.$id, name, newValue) //同步代理VM + notifySubscribers($vmodel.$events[name]) //同步视图 + safeFire($vmodel, name, newValue, oldValue) //触发$watch回调 + } + } else { + return oldValue + } + } + } + watchedProperties[name] = accessor + })(i, $scope[i]) + } + + $$skipArray.forEach(function(name) { + delete $scope[name] + delete $model[name] //这些特殊属性不应该在$model中出现 + }) + + $vmodel = defineProperties($vmodel, descriptorFactory(watchedProperties), $scope) //生成一个空的ViewModel + for (var name in $scope) { + if (!watchedProperties[name]) { + $vmodel[name] = $scope[name] + } + } + //添加$id, $model, $events, $watch, $unwatch, $fire + $vmodel.$id = generateID() + $vmodel.$model = $model + $vmodel.$events = $events + for (var i in EventManager) { + var fn = EventManager [i] + if (!W3C) { //在IE6-8下,VB对象的方法里的this并不指向自身,需要用bind处理一下 + fn = fn.bind($vmodel) + } + $vmodel[i] = fn + } + + $vmodel.hasOwnProperty = function(name) { + return name in $vmodel.$model + } + computedProperties.forEach(function(collect) { + collect()//收集依赖 + }) + return $vmodel + } + + //比较两个值是否相等 + var isEqual = Object.is || function(v1, v2) { + if (v1 === 0 && v2 === 0) { + return 1 / v1 === 1 / v2 + } else if (v1 !== v1) { + return v2 !== v2 + } else { + return v1 === v2 + } + } + + function safeFire(a, b, c, d) { + if (a.$events) { + EventManager.$fire.call(a, b, c, d) + } + } + var descriptorFactory = W3C ? function(obj) { + var descriptors = {} + for (var i in obj) { + descriptors[i] = { + get: obj[i], + set: obj[i], + enumerable: true, + configurable: true + } + } + return descriptors + } : function(a) { + return a + } + + //ms-with, ms-repeat绑定生成的代理对象储存池 + var withProxyPool = {} + var withProxyCount = 0 + var rebindings = {} + + function updateWithProxy($id, name, val) { + var pool = withProxyPool[$id] + if (pool && pool[name]) { + pool[name].$val = val + } + } + //应用于第2种accessor + function updateChild(parent, name, value, valueType) { + //a为原来的VM, b为新数组或新对象 + var son = parent[name] + if (valueType === "array") { + if (!Array.isArray(value) || son === value) { + return son //fix https://github.com/RubyLouvre/avalon/issues/261 + } + son.clear() + son.pushArray(value.concat()) + return son + } else { + var iterators = parent.$events[name] + if (withProxyPool[son.$id]) { + withProxyCount-- + delete withProxyPool[son.$id] + } + var ret = modelFactory(value) + rebindings[ret.$id] = function(data) { + while (data = iterators.shift()) { + (function(el) { + if (el.type) { //重新绑定 + avalon.nextTick(function() { + el.rollback && el.rollback() //还原 ms-with ms-on + bindingHandlers[el.type](el, el.vmodels) + }) + } + })(data) + } + delete rebindings[ret.$id] + } + return ret + } + } + + //===================修复浏览器对Object.defineProperties的支持================= + var defineProperty = Object.defineProperty + //如果浏览器不支持ecma262v5的Object.defineProperties或者存在BUG,比如IE8 + //标准浏览器使用__defineGetter__, __defineSetter__实现 + try { + defineProperty({}, "_", { + value: "x" + }) + var defineProperties = Object.defineProperties + } catch (e) { + if ("__defineGetter__" in avalon) { + defineProperty = function(obj, prop, desc) { + if ('value' in desc) { + obj[prop] = desc.value + } + if ("get" in desc) { + obj.__defineGetter__(prop, desc.get) + } + if ('set' in desc) { + obj.__defineSetter__(prop, desc.set) + } + return obj + } + defineProperties = function(obj, descs) { + for (var prop in descs) { + if (descs.hasOwnProperty(prop)) { + defineProperty(obj, prop, descs[prop]) + } + } + return obj + } + } + } + //IE6-8使用VBScript类的set get语句实现 + if (!defineProperties && window.VBArray) { + window.execScript([ + "Function parseVB(code)", + "\tExecuteGlobal(code)", + "End Function", + "Dim VBClassBodies", + "Set VBClassBodies=CreateObject(\"Scripting.Dictionary\")", + "Function findOrDefineVBClass(name,body)", + "\tDim found", + "\tfound=\"\"", + "\tFor Each key in VBClassBodies", + "\t\tIf body=VBClassBodies.Item(key) Then", + "\t\t\tfound=key", + "\t\t\tExit For", + "\t\tEnd If", + "\tnext", + "\tIf found=\"\" Then", + "\t\tparseVB(\"Class \" + name + body)", + "\t\tVBClassBodies.Add name, body", + "\t\tfound=name", + "\tEnd If", + "\tfindOrDefineVBClass=found", + "End Function" + ].join("\n"), "VBScript") + + function VBMediator(accessingProperties, name, value) { + var accessor = accessingProperties[name] + if (typeof accessor === "function") { + if (arguments.length === 3) { + accessor(value) + } else { + return accessor() + } + } + } + defineProperties = function(name, accessors, properties) { + var className = "VBClass" + setTimeout("1"), + buffer = [] + buffer.push( + "\r\n\tPrivate [__data__], [__proxy__]", + "\tPublic Default Function [__const__](d, p)", + "\t\tSet [__data__] = d: set [__proxy__] = p", + "\t\tSet [__const__] = Me", //链式调用 + "\tEnd Function") + //添加普通属性,因为VBScript对象不能像JS那样随意增删属性,必须在这里预先定义好 + for (name in properties) { + if (!accessors.hasOwnProperty(name)) { + buffer.push("\tPublic [" + name + "]") + } + } + $$skipArray.forEach(function(name) { + if (!accessors.hasOwnProperty(name)) { + buffer.push("\tPublic [" + name + "]") + } + }) + buffer.push("\tPublic [" + 'hasOwnProperty' + "]") + //添加访问器属性 + for (name in accessors) { + buffer.push( + //由于不知对方会传入什么,因此set, let都用上 + "\tPublic Property Let [" + name + "](val" + expose + ")", //setter + "\t\tCall [__proxy__]([__data__], \"" + name + "\", val" + expose + ")", + "\tEnd Property", + "\tPublic Property Set [" + name + "](val" + expose + ")", //setter + "\t\tCall [__proxy__]([__data__], \"" + name + "\", val" + expose + ")", + "\tEnd Property", + "\tPublic Property Get [" + name + "]", //getter + "\tOn Error Resume Next", //必须优先使用set语句,否则它会误将数组当字符串返回 + "\t\tSet[" + name + "] = [__proxy__]([__data__],\"" + name + "\")", + "\tIf Err.Number <> 0 Then", + "\t\t[" + name + "] = [__proxy__]([__data__],\"" + name + "\")", + "\tEnd If", + "\tOn Error Goto 0", + "\tEnd Property") + + } + + buffer.push("End Class") + var code = buffer.join("\r\n"), + realClassName = window['findOrDefineVBClass'](className, code) //如果该VB类已定义,返回类名。否则用className创建一个新类。 + if (realClassName === className) { + window.parseVB([ + "Function " + className + "Factory(a, b)", //创建实例并传入两个关键的参数 + "\tDim o", + "\tSet o = (New " + className + ")(a, b)", + "\tSet " + className + "Factory = o", + "End Function" + ].join("\r\n")) + } + var ret = window[realClassName + "Factory"](accessors, VBMediator) //得到其产品 + return ret //得到其产品 + } + } + /********************************************************************* + * javascript 底层补丁 * + **********************************************************************/ + if (!"司徒正美".trim) { + var rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g + String.prototype.trim = function() { + return this.replace(rtrim, "") + } + } + var hasDontEnumBug = !({'toString': null}).propertyIsEnumerable('toString'), + hasProtoEnumBug = (function() { + }).propertyIsEnumerable('prototype'), + dontEnums = [ + "toString", + "toLocaleString", + "valueOf", + "hasOwnProperty", + "isPrototypeOf", + "propertyIsEnumerable", + "constructor" + ], + dontEnumsLength = dontEnums.length; + if (!Object.keys) { + Object.keys = function(object) { //ecma262v5 15.2.3.14 + var theKeys = []; + var skipProto = hasProtoEnumBug && typeof object === "function" + if (typeof object === "string" || (object && object.callee)) { + for (var i = 0; i < object.length; ++i) { + theKeys.push(String(i)) + } + } else { + for (var name in object) { + if (!(skipProto && name === "prototype") && ohasOwn.call(object, name)) { + theKeys.push(String(name)) + } + } + } + + if (hasDontEnumBug) { + var ctor = object.constructor, + skipConstructor = ctor && ctor.prototype === object; + for (var j = 0; j < dontEnumsLength; j++) { + var dontEnum = dontEnums[j] + if (!(skipConstructor && dontEnum === "constructor") && ohasOwn.call(object, dontEnum)) { + theKeys.push(dontEnum) + } + } + } + return theKeys + } + } + if (!Array.isArray) { + Array.isArray = function(a) { + return serialize.call(a) === "[object Array]" + } + } + + if (!noop.bind) { + Function.prototype.bind = function(scope) { + if (arguments.length < 2 && scope === void 0) + return this + var fn = this, + argv = arguments + return function() { + var args = [], + i + for (i = 1; i < argv.length; i++) + args.push(argv[i]) + for (i = 0; i < arguments.length; i++) + args.push(arguments[i]) + return fn.apply(scope, args) + } + } + } + + function iterator(vars, body, ret) { + var fun = 'for(var ' + vars + 'i=0,n = this.length; i < n; i++){' + body.replace('_', '((i in this) && fn.call(scope,this[i],i,this))') + '}' + ret + return Function("fn,scope", fun) + } + if (!rnative.test([].map)) { + avalon.mix(ap, { + //定位操作,返回数组中第一个等于给定参数的元素的索引值。 + indexOf: function(item, index) { + var n = this.length, + i = ~~index + if (i < 0) + i += n + for (; i < n; i++) + if (this[i] === item) + return i + return -1 + }, + //定位操作,同上,不过是从后遍历。 + lastIndexOf: function(item, index) { + var n = this.length, + i = index == null ? n - 1 : index + if (i < 0) + i = Math.max(0, n + i) + for (; i >= 0; i--) + if (this[i] === item) + return i + return -1 + }, + //迭代操作,将数组的元素挨个儿传入一个函数中执行。Prototype.js的对应名字为each。 + forEach: iterator("", '_', ""), + //迭代类 在数组中的每个项上运行一个函数,如果此函数的值为真,则此元素作为新数组的元素收集起来,并返回新数组 + filter: iterator('r=[],j=0,', 'if(_)r[j++]=this[i]', 'return r'), + //收集操作,将数组的元素挨个儿传入一个函数中执行,然后把它们的返回值组成一个新数组返回。Prototype.js的对应名字为collect。 + map: iterator('r=[],', 'r[i]=_', 'return r'), + //只要数组中有一个元素满足条件(放进给定函数返回true),那么它就返回true。Prototype.js的对应名字为any。 + some: iterator("", 'if(_)return true', 'return false'), + //只有数组中的元素都满足条件(放进给定函数返回true),它才返回true。Prototype.js的对应名字为all。 + every: iterator("", 'if(!_)return false', 'return true') + }) + } + /********************************************************************* + * DOM 底层补丁 * + **********************************************************************/ + function fixContains(a, b) { + if (b) { + while ((b = b.parentNode)) { + if (b === a) { + return true; + } + } + } + return false; + } + //safari5+是把contains方法放在Element.prototype上而不是Node.prototype + if (!root.contains) { + Node.prototype.contains = function(arg) { + return !!(this.compareDocumentPosition(arg) & 16) + } + } + //IE6-11的文档对象没有contains + if (!DOC.contains) { + DOC.contains = function(b) { + return fixContains(DOC, b) + } + } + function outerHTML() { + return new XMLSerializer().serializeToString(this) + } + + + if (window.SVGElement) { + var svgns = "http://www.w3.org/2000/svg" + var svg = document.createElementNS(svgns, "svg") + svg.innerHTML = '' + if (!rsvg.test(svg.firstChild)) {// #409 + function enumerateNode(node, targetNode) { + if (node && node.childNodes) { + var nodes = node.childNodes + for (var i = 0, el; el = nodes[i++]; ) { + if (el.tagName) { + var svg = document.createElementNS(svgns, + el.tagName.toLowerCase()) + ap.forEach.call(el.attributes, function(attr) { + svg.setAttribute(attr.name, attr.value)//复制属性 + }) + // 递归处理子节点 + enumerateNode(el, svg) + targetNode.appendChild(svg) + } + } + } + } + Object.defineProperties(SVGElement.prototype, { + "outerHTML": {//IE9-11,firefox不支持SVG元素的innerHTML,outerHTML属性 + enumerable: true, + configurable: true, + get: outerHTML, + set: function(html) { + var tagName = this.tagName.toLowerCase(), + par = this.parentNode, + frag = avalon.parseHTML(html) + // 操作的svg,直接插入 + if (tagName === "svg") { + par.insertBefore(frag, this) + // svg节点的子节点类似 + } else { + var newFrag = document.createDocumentFragment() + enumerateNode(frag, newFrag) + par.insertBefore(newFrag, this) + } + par.removeChild(this) + } + }, + "innerHTML": { + enumerable: true, + configurable: true, + get: function() { + var s = this.outerHTML + var ropen = new RegExp("<" + this.nodeName + '\\b(?:(["\'])[^"]*?(\\1)|[^>])*>', "i") + var rclose = new RegExp("<\/" + this.nodeName + ">$", "i") + return s.replace(ropen, "").replace(rclose, "") + }, + set: function(html) { + if (avalon.clearHTM) { + avalon.clearHTML(this) + var frag = avalon.parseHTML(html) + enumerateNode(frag, this) + } + } + } + }) + } + } + if (!root.outerHTML && window.HTMLElement) { //firefox 到11时才有outerHTML + HTMLElement.prototype.__defineGetter__("outerHTML", outerHTML); + } + + /********************************************************************* + * LRU缓存工厂,代替之前的createCache * + **********************************************************************/ + var avalonCaches = {} + function cacheFactory(cacheId, capacity) { + if (cacheId in avalonCaches) { + throw Error(cacheId + "已经存在") + } + var size = 0, + data = {}, + lruHash = {}, + freshEnd = null, + staleEnd = null + capacity = typeof capacity === "number" ? capacity : Number.MAX_VALUE + //生成一个LRU缓体实体 + return avalonCaches[cacheId] = { + set: function(key, value) { + if (capacity < Number.MAX_VALUE) { + var lruEntry = lruHash[key] || (lruHash[key] = {key: key}) + refresh(lruEntry) + } + + if (value === void 0) + return + if (!(key in data)) + size++ + data[key] = value + if (size > capacity) { + this.remove(staleEnd.key) + } + + return value + }, + get: function(key) { + if (capacity < Number.MAX_VALUE) { + var lruEntry = lruHash[key] + if (!lruEntry) + return + refresh(lruEntry) + } + + return data[key] + }, + remove: function(key) { + if (capacity < Number.MAX_VALUE) { + var lruEntry = lruHash[key] + if (!lruEntry) + return + if (lruEntry == freshEnd) + freshEnd = lruEntry.p + if (lruEntry == staleEnd) + staleEnd = lruEntry.n + link(lruEntry.n, lruEntry.p) + delete lruHash[key] + } + + delete data[key] + size-- + } + }; + + function refresh(entry) { + if (entry != freshEnd) { + if (!staleEnd) { + staleEnd = entry + } else if (staleEnd == entry) { + staleEnd = entry.n + } + + link(entry.n, entry.p) + link(entry, freshEnd) + freshEnd = entry + freshEnd.n = null + } + } + + function link(nextEntry, prevEntry) { + if (nextEntry != prevEntry) { + if (nextEntry) + nextEntry.p = prevEntry //p stands for previous, 'prev' didn't minify + if (prevEntry) + prevEntry.n = nextEntry //n stands for next, 'next' didn't minify + } + } + } + + + /********************************************************************* + * 配置系统 * + **********************************************************************/ + function kernel(settings) { + for (var p in settings) { + if (!ohasOwn.call(settings, p)) + continue + var val = settings[p] + if (typeof kernel.plugins[p] === "function") { + kernel.plugins[p](val) + } else if (typeof kernel[p] === "object") { + avalon.mix(kernel[p], val) + } else { + kernel[p] = val + } + } + return this + } + var openTag, closeTag, rexpr, rexprg, rbind, rregexp = /[-.*+?^${}()|[\]\/\\]/g + + function escapeRegExp(target) { + //http://stevenlevithan.com/regex/xregexp/ + //将字符串安全格式化为正则表达式的源码 + return (target + "").replace(rregexp, "\\$&") + } + var plugins = { + loader: function(builtin) { + window.define = builtin ? innerRequire.define : otherDefine + window.require = builtin ? innerRequire : otherRequire + }, + interpolate: function(array) { + openTag = array[0] + closeTag = array[1] + if (openTag === closeTag) { + throw new SyntaxError("openTag!==closeTag") + } else if (array + "" === "") { + kernel.commentInterpolate = true + } else { + var test = openTag + "test" + closeTag + cinerator.innerHTML = test + if (cinerator.innerHTML !== test && cinerator.innerHTML.indexOf("<") >= 0) { + throw new SyntaxError("此定界符不合法") + } + cinerator.innerHTML = "" + } + var o = escapeRegExp(openTag), + c = escapeRegExp(closeTag) + rexpr = new RegExp(o + "(.*?)" + c) + rexprg = new RegExp(o + "(.*?)" + c, "g") + rbind = new RegExp(o + ".*?" + c + "|\\sms-") + } + } + + kernel.debug = true + kernel.plugins = plugins + kernel.plugins['interpolate'](["{{", "}}"]) + kernel.paths = {} + kernel.shim = {} + kernel.maxRepeatSize = 100 + avalon.config = kernel + + /********************************************************************* + * avalon的原型方法定义区 * + **********************************************************************/ + + function hyphen(target) { + //转换为连字符线风格 + return target.replace(/([a-z\d])([A-Z]+)/g, "$1-$2").toLowerCase() + } + + function camelize(target) { + //转换为驼峰风格 + if (target.indexOf("-") < 0 && target.indexOf("_") < 0) { + return target //提前判断,提高getStyle等的效率 + } + return target.replace(/[-_][^-_]/g, function(match) { + return match.charAt(1).toUpperCase() + }) + } + + var ClassListMethods = { + _toString: function() { + var node = this.node//IE6,7元素节点不存在hasAttribute方法 + var cls = node.className + var str = typeof cls === "string" ? cls : cls.baseVal + return str.split(/\s+/).join(" ") + }, + _contains: function(cls) { + return (" " + this + " ").indexOf(" " + cls + " ") > -1 + }, + _add: function(cls) { + if (!this.contains(cls)) { + this._set(this + " " + cls) + } + }, + _remove: function(cls) { + this._set((" " + this + " ").replace(" " + cls + " ", " ").trim()) + }, + __set: function(cls) { + var node = this.node + if (typeof node.className === "string") { + node.className = cls + } else {//SVG元素的className是一个对象 SVGAnimatedString { baseVal="", animVal=""},只能通过set/getAttribute操作 + node.setAttribute("class", cls) + } + }//toggle存在版本差异,因此不使用它 + } + function ClassList(node) { + if (!("classList" in node)) { + node.classList = { + node: node + } + for (var k in ClassListMethods) { + node.classList[k.slice(1)] = ClassListMethods[k] + } + } + return node.classList + } + + + "add,remove".replace(rword, function(method) { + avalon.fn[method + "Class"] = function(cls) { + var el = this[0] + //https://developer.mozilla.org/zh-CN/docs/Mozilla/Firefox/Releases/26 + if (cls && typeof cls === "string" && el && el.nodeType === 1) { + cls.replace(/\S+/g, function(c) { + ClassList(el)[method](c) + }) + } + return this + } + }) + avalon.fn.mix({ + hasClass: function(cls) { + var el = this[0] || {} + return el.nodeType === 1 && ClassList(el).contains(cls) + }, + toggleClass: function(value, stateVal) { + var className, i = 0 + var classNames = value.split(/\s+/) + var isBool = typeof stateVal === "boolean" + while ((className = classNames[i++])) { + var state = isBool ? stateVal : !this.hasClass(className) + this[state ? "addClass" : "removeClass"](className) + } + return this + }, + attr: function(name, value) { + if (arguments.length === 2) { + this[0].setAttribute(name, value) + return this + } else { + return this[0].getAttribute(name) + } + }, + data: function(name, value) { + name = "data-" + hyphen(name || "") + switch (arguments.length) { + case 2: + this.attr(name, value) + return this + case 1: + var val = this.attr(name) + return parseData(val) + case 0: + var ret = {} + ap.forEach.call(this[0].attributes, function(attr) { + if (attr) { + name = attr.name + if (!name.indexOf("data-")) { + name = camelize(name.slice(5)) + ret[name] = parseData(attr.value) + } + } + }) + return ret + } + }, + removeData: function(name) { + name = "data-" + hyphen(name) + this[0].removeAttribute(name) + return this + }, + css: function(name, value) { + if (avalon.isPlainObject(name)) { + for (var i in name) { + avalon.css(this, i, name[i]) + } + } else { + var ret = avalon.css(this, name, value) + } + return ret !== void 0 ? ret : this + }, + position: function() { + var offsetParent, offset, + elem = this[0], + parentOffset = { + top: 0, + left: 0 + } + if (!elem) { + return + } + if (this.css("position") === "fixed") { + offset = elem.getBoundingClientRect() + } else { + offsetParent = this.offsetParent() //得到真正的offsetParent + offset = this.offset() // 得到正确的offsetParent + if (offsetParent[0].tagName !== "HTML") { + parentOffset = offsetParent.offset() + } + parentOffset.top += avalon.css(offsetParent[0], "borderTopWidth", true) + parentOffset.left += avalon.css(offsetParent[0], "borderLeftWidth", true) + } + return { + top: offset.top - parentOffset.top - avalon.css(elem, "marginTop", true), + left: offset.left - parentOffset.left - avalon.css(elem, "marginLeft", true) + } + }, + offsetParent: function() { + var offsetParent = this[0].offsetParent || root + while (offsetParent && (offsetParent.tagName !== "HTML") && avalon.css(offsetParent, "position") === "static") { + offsetParent = offsetParent.offsetParent + } + return avalon(offsetParent || root) + }, + bind: function(type, fn, phase) { + if (this[0]) { //此方法不会链 + return avalon.bind(this[0], type, fn, phase) + } + }, + unbind: function(type, fn, phase) { + if (this[0]) { + avalon.unbind(this[0], type, fn, phase) + } + return this + }, + val: function(value) { + var node = this[0] + if (node && node.nodeType === 1) { + var get = arguments.length === 0 + var access = get ? ":get" : ":set" + var fn = valHooks[getValType(node) + access] + if (fn) { + var val = fn(node, value) + } else if (get) { + return (node.value || "").replace(/\r/g, "") + } else { + node.value = value + } + } + return get ? val : this + } + }) + + function parseData(data) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : +data + "" === data ? +data : rbrace.test(data) ? avalon.parseJSON(data) : data + } catch (e) { + } + return data + } + var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/, + rvalidchars = /^[\],:{}\s]*$/, + rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, + rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g, + rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g + avalon.parseJSON = window.JSON ? JSON.parse : function(data) { + if (typeof data === "string") { + data = data.trim() + if (data) { + if (rvalidchars.test(data.replace(rvalidescape, "@") + .replace(rvalidtokens, "]") + .replace(rvalidbraces, ""))) { + return (new Function("return " + data))() + } + } + avalon.error("Invalid JSON: " + data) + } + } + + //生成avalon.fn.scrollLeft, avalon.fn.scrollTop方法 + avalon.each({ + scrollLeft: "pageXOffset", + scrollTop: "pageYOffset" + }, function(method, prop) { + avalon.fn[method] = function(val) { + var node = this[0] || {}, win = getWindow(node), + top = method === "scrollTop" + if (!arguments.length) { + return win ? (prop in win) ? win[prop] : root[method] : node[method] + } else { + if (win) { + win.scrollTo(!top ? val : avalon(win).scrollLeft(), top ? val : avalon(win).scrollTop()) + } else { + node[method] = val + } + } + } + }) + + function getWindow(node) { + return node.window && node.document ? node : node.nodeType === 9 ? node.defaultView || node.parentWindow : false + } + //=============================css相关======================= + var cssHooks = avalon.cssHooks = {} + var prefixes = ["", "-webkit-", "-o-", "-moz-", "-ms-"] + var cssMap = { + "float": "cssFloat", + background: "backgroundColor" + } + avalon.cssNumber = oneObject("columnCount,order,fillOpacity,fontWeight,lineHeight,opacity,orphans,widows,zIndex,zoom") + + avalon.cssName = function(name, host, camelCase) { + if (cssMap[name]) { + return cssMap[name] + } + host = host || root.style + for (var i = 0, n = prefixes.length; i < n; i++) { + camelCase = camelize(prefixes[i] + name) + if (camelCase in host) { + return (cssMap[name] = camelCase) + } + } + return null + } + cssHooks["@:set"] = function(node, name, value) { + try { //node.style.width = NaN;node.style.width = "xxx";node.style.width = undefine 在旧式IE下会抛异常 + node.style[name] = value + } catch (e) { + } + } + if (window.getComputedStyle) { + cssHooks["@:get"] = function(node, name) { + if (!node || !node.style) { + throw new Error("getComputedStyle要求传入一个节点 " + node) + } + var ret, computed = getComputedStyle(node, null) + if (computed) { + ret = name === "filter" ? computed.getPropertyValue(name) : computed[name] + if (ret === "") { + ret = node.style[name] //其他浏览器需要我们手动取内联样式 + } + } + return ret + } + cssHooks["opacity:get"] = function(node) { + var ret = cssHooks["@:get"](node, "opacity") + return ret === "" ? "1" : ret + } + } else { + var rnumnonpx = /^-?(?:\d*\.)?\d+(?!px)[^\d\s]+$/i + var rposition = /^(top|right|bottom|left)$/ + var ie8 = !!window.XDomainRequest + var salpha = "DXImageTransform.Microsoft.Alpha" + var border = { + thin: ie8 ? '1px' : '2px', + medium: ie8 ? '3px' : '4px', + thick: ie8 ? '5px' : '6px' + } + cssHooks["@:get"] = function(node, name) { + //取得精确值,不过它有可能是带em,pc,mm,pt,%等单位 + var currentStyle = node.currentStyle + var ret = currentStyle[name] + if ((rnumnonpx.test(ret) && !rposition.test(ret))) { + //①,保存原有的style.left, runtimeStyle.left, + var style = node.style, + left = style.left, + rsLeft = node.runtimeStyle.left + //②由于③处的style.left = xxx会影响到currentStyle.left, + //因此把它currentStyle.left放到runtimeStyle.left, + //runtimeStyle.left拥有最高优先级,不会style.left影响 + node.runtimeStyle.left = currentStyle.left + //③将精确值赋给到style.left,然后通过IE的另一个私有属性 style.pixelLeft + //得到单位为px的结果;fontSize的分支见http://bugs.jquery.com/ticket/760 + style.left = name === 'fontSize' ? '1em' : (ret || 0) + ret = style.pixelLeft + "px" + //④还原 style.left,runtimeStyle.left + style.left = left + node.runtimeStyle.left = rsLeft + } + if (ret === "medium") { + name = name.replace("Width", "Style") + //border width 默认值为medium,即使其为0" + if (currentStyle[name] === "none") { + ret = "0px" + } + } + return ret === "" ? "auto" : border[ret] || ret + } + cssHooks["opacity:set"] = function(node, name, value) { + var style = node.style + var filter = style.filter || "" + if (filter.indexOf(salpha) === -1) { + style.filter += "progid:" + salpha + "(opacity=100)" + } + var alpha = node.filters[salpha] || {} + if (value <= 1 && isFinite(value)) { + style.zoom = alpha.enabled = 1 + alpha.opacity = value * 100 + } else { + alpha.enabled = 0 + } + } + cssHooks["opacity:get"] = function(node) { + //这是最快的获取IE透明值的方式,不需要动用正则了! + var alpha = node.filters.alpha || node.filters[salpha], + op = alpha && alpha.enabled ? alpha.opacity : 100 + return (op / 100) + "" //确保返回的是字符串 + } + } + + "top,left".replace(rword, function(name) { + cssHooks[name + ":get"] = function(node) { + var computed = cssHooks["@:get"](node, name) + return /px$/.test(computed) ? computed : + avalon(node).position()[name] + "px" + } + }) + + var cssShow = { + position: "absolute", + visibility: "hidden", + display: "block" + } + + var rdisplayswap = /^(none|table(?!-c[ea]).+)/ + + function showHidden(node, array) { + //http://www.cnblogs.com/rubylouvre/archive/2012/10/27/2742529.html + if (node.offsetWidth <= 0) { //opera.offsetWidth可能小于0 + if (rdisplayswap.test(cssHooks["@:get"](node, "display"))) { + var obj = { + node: node + } + for (var name in cssShow) { + obj[name] = node.style[name] + node.style[name] = cssShow[name] + } + array.push(obj) + } + var parent = node.parentNode + if (parent && parent.nodeType === 1) { + showHidden(parent, array) + } + } + } + "Width,Height".replace(rword, function(name) {//fix 481 + var method = name.toLowerCase(), + clientProp = "client" + name, + scrollProp = "scroll" + name, + offsetProp = "offset" + name + cssHooks[method + ":get"] = function(node, which, override) { + var boxSizing = -4 + if (typeof override === "number") { + boxSizing = override + } + which = name === "Width" ? ["Left", "Right"] : ["Top", "Bottom"] + var ret = node[offsetProp] // border-box 0 + if (boxSizing === 2) { // margin-box 2 + return ret + + avalon.css(node, "margin" + which[0], true) + + avalon.css(node, "margin" + which[1], true) + } + if (boxSizing < 0) { // padding-box -2 + ret = ret + - avalon.css(node, "border" + which[0] + "Width", true) + - avalon.css(node, "border" + which[1] + "Width", true) + } + if (boxSizing === -4) { // content-box -4 + ret = ret + - avalon.css(node, "padding" + which[0], true) + - avalon.css(node, "padding" + which[1], true) + } + return ret + } + cssHooks[method + "&get"] = function(node) { + var hidden = [] + showHidden(node, hidden) + var val = cssHooks[method + ":get"](node) + for (var i = 0, obj; obj = hidden[i++]; ) { + node = obj.node + for (var n in obj) { + if (typeof obj[n] === "string") { + node.style[n] = obj[n] + } + } + } + return val + } + avalon.fn[method] = function(value) { //会忽视其display + var node = this[0] + if (arguments.length === 0) { + if (node.setTimeout) { //取得窗口尺寸,IE9后可以用node.innerWidth /innerHeight代替 + return node["inner" + name] || node.document.documentElement[clientProp] + } + if (node.nodeType === 9) { //取得页面尺寸 + var doc = node.documentElement + //FF chrome html.scrollHeight< body.scrollHeight + //IE 标准模式 : html.scrollHeight> body.scrollHeight + //IE 怪异模式 : html.scrollHeight 最大等于可视窗口多一点? + return Math.max(node.body[scrollProp], doc[scrollProp], node.body[offsetProp], doc[offsetProp], doc[clientProp]) + } + return cssHooks[method + "&get"](node) + } else { + return this.css(method, value) + } + } + avalon.fn["inner" + name] = function() { + return cssHooks[method + ":get"](this[0], void 0, -2) + } + avalon.fn["outer" + name] = function(includeMargin) { + return cssHooks[method + ":get"](this[0], void 0, includeMargin === true ? 2 : 0) + } + }) + avalon.fn.offset = function() { //取得距离页面左右角的坐标 + var node = this[0], box = { + left: 0, + top: 0 + } + if (!node || !node.tagName || !node.ownerDocument) { + return box + } + var doc = node.ownerDocument, + body = doc.body, + root = doc.documentElement, + win = doc.defaultView || doc.parentWindow + if (!avalon.contains(root, node)) { + return box + } + //http://hkom.blog1.fc2.com/?mode=m&no=750 body的偏移量是不包含margin的 + //我们可以通过getBoundingClientRect来获得元素相对于client的rect. + //http://msdn.microsoft.com/en-us/library/ms536433.aspx + if (node.getBoundingClientRect) { + box = node.getBoundingClientRect() // BlackBerry 5, iOS 3 (original iPhone) + } + //chrome/IE6: body.scrollTop, firefox/other: root.scrollTop + var clientTop = root.clientTop || body.clientTop, + clientLeft = root.clientLeft || body.clientLeft, + scrollTop = Math.max(win.pageYOffset || 0, root.scrollTop, body.scrollTop), + scrollLeft = Math.max(win.pageXOffset || 0, root.scrollLeft, body.scrollLeft) + // 把滚动距离加到left,top中去。 + // IE一些版本中会自动为HTML元素加上2px的border,我们需要去掉它 + // http://msdn.microsoft.com/en-us/library/ms533564(VS.85).aspx + return { + top: box.top + scrollTop - clientTop, + left: box.left + scrollLeft - clientLeft + } + } + + //==================================val相关============================ + + function getValType(el) { + var ret = el.tagName.toLowerCase() + return ret === "input" && /checkbox|radio/.test(el.type) ? "checked" : ret + } + var roption = /^]+))?)*\s+value[\s=]/i + var valHooks = { + "option:get": function(node) { + //在IE11及W3C,如果没有指定value,那么node.value默认为node.text(存在trim作),但IE9-10则是取innerHTML(没trim操作) + if (node.hasAttribute) { + return node.hasAttribute("value") ? node.value : node.text.trim() + } + //specified并不可靠,因此通过分析outerHTML判定用户有没有显示定义value + return roption.test(node.outerHTML) ? node.value : node.text + }, + "select:get": function(node, value) { + var option, options = node.options, + index = node.selectedIndex, + getter = valHooks["option:get"], + one = node.type === "select-one" || index < 0, + values = one ? null : [], + max = one ? index + 1 : options.length, + i = index < 0 ? max : one ? index : 0 + for (; i < max; i++) { + option = options[i] + //旧式IE在reset后不会改变selected,需要改用i === index判定 + //我们过滤所有disabled的option元素,但在safari5下,如果设置select为disable,那么其所有孩子都disable + //因此当一个元素为disable,需要检测其是否显式设置了disable及其父节点的disable情况 + if ((option.selected || i === index) && !option.disabled) { + value = getter(option) + if (one) { + return value + } + //收集所有selected值组成数组返回 + values.push(value) + } + } + return values + }, + "select:set": function(node, values, optionSet) { + values = [].concat(values) //强制转换为数组 + var getter = valHooks["option:get"] + for (var i = 0, el; el = node.options[i++]; ) { + if ((el.selected = values.indexOf(getter(el)) >= 0)) { + optionSet = true + } + } + if (!optionSet) { + node.selectedIndex = -1 + } + } + } + + /************************************************************************ + * HTML处理(parseHTML, innerHTML, clearHTML) * + ************************************************************************/ + var rtagName = /<([\w:]+)/, + //取得其tagName + rxhtml = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, + rcreate = W3C ? /[^\d\D]/ : /(<(?:script|link|style|meta|noscript))/ig, + scriptTypes = oneObject("text/javascript", "text/ecmascript", "application/ecmascript", "application/javascript", "text/vbscript"), + //需要处理套嵌关系的标签 + rnest = /<(?:tb|td|tf|th|tr|col|opt|leg|cap|area)/ + //parseHTML的辅助变量 + var tagHooks = { + area: [1, ""], + param: [1, ""], + col: [2, "", "
    "], + legend: [1, "
    "], + option: [1, "上要求对应一个数组") + } + } else { + if (element.multiple) { + log("ms-duplex在"], + thead: [1, "", "
    "], + tr: [2, ""], + td: [3, "
    "], + text: [1, '', ''], + //IE6-8在用innerHTML生成节点时,不能直接创建no-scope元素与HTML5的新标签 + _default: W3C ? [0, ""] : [1, "X
    "] //div可以不用闭合 + } + + tagHooks.optgroup = tagHooks.option + tagHooks.tbody = tagHooks.tfoot = tagHooks.colgroup = tagHooks.caption = tagHooks.thead + tagHooks.th = tagHooks.td +//处理SVG + tagHooks.circle = tagHooks.ellipse = tagHooks.line = tagHooks.path = + tagHooks.polygon = tagHooks.polyline = tagHooks.rect = tagHooks.text + var script = DOC.createElement("script") + avalon.parseHTML = function(html) { + if (typeof html !== "string") { + html = html + "" + } + html = html.replace(rxhtml, "<$1>").trim() + var tag = (rtagName.exec(html) || ["", ""])[1].toLowerCase(), + //取得其标签名 + wrap = tagHooks[tag] || tagHooks._default, + fragment = hyperspace.cloneNode(false), + wrapper = cinerator, + firstChild, neo + if (!W3C) { //fix IE + html = html.replace(rcreate, "
    $1") //在link style script等标签之前添加一个补丁 + } + wrapper.innerHTML = wrap[1] + html + (wrap[2] || "") + var els = wrapper.getElementsByTagName("script") + if (els.length) { //使用innerHTML生成的script节点不会发出请求与执行text属性 + for (var i = 0, el; el = els[i++]; ) { + if (!el.type || scriptTypes[el.type]) { //如果script节点的MIME能让其执行脚本 + neo = script.cloneNode(false) //FF不能省略参数 + ap.forEach.call(el.attributes, function(attr) { + if (attr && attr.specified) { + neo[attr.name] = attr.value //复制其属性 + } + }) + neo.text = el.text //必须指定,因为无法在attributes中遍历出来 + el.parentNode.replaceChild(neo, el) //替换节点 + } + } + } + //移除我们为了符合套嵌关系而添加的标签 + for (i = wrap[0]; i--; wrapper = wrapper.lastChild) { + } + if (!W3C) { //fix IE + for (els = wrapper["getElementsByTagName"]("br"), i = 0; el = els[i++]; ) { + if (el.className && el.className === "msNoScope") { + el.parentNode.removeChild(el) + } + } + for (els = wrapper.all, i = 0; el = els[i++]; ) {//fix VML + if (isVML(el)) { + fixVML(el) + } + } + } + while (firstChild = wrapper.firstChild) { // 将wrapper上的节点转移到文档碎片上! + fragment.appendChild(firstChild) + } + return fragment + } + function isVML(src) { + var nodeName = src.nodeName + return nodeName.toLowerCase() === nodeName && src.scopeName && src.outerText === "" + } + function fixVML(node) { + if (node.currentStyle.behavior !== "url(#default#VML)") { + node.style.behavior = "url(#default#VML)" + node.style.display = "inline-block" + node.style.zoom = 1 //hasLayout + } + } + avalon.innerHTML = function(node, html) { + if (!W3C && (!rcreate.test(html) && !rnest.test(html))) { + try { + node.innerHTML = html + return + } catch (e) { + } + } + var a = this.parseHTML(html) + this.clearHTML(node).appendChild(a) + } + avalon.clearHTML = function(node) { + node.textContent = "" + while (node.firstChild) { + node.removeChild(node.firstChild) + } + return node + } + /********************************************************************* + * 事件管理器 * + **********************************************************************/ + var EventManager = { + $watch: function(type, callback) { + if (typeof callback === "function") { + var callbacks = this.$events[type] + if (callbacks) { + callbacks.push(callback) + } else { + this.$events[type] = [callback] + } + } else { //重新开始监听此VM的第一重简单属性的变动 + this.$events = this.$watch.backup + } + return this + }, + $unwatch: function(type, callback) { + var n = arguments.length + if (n === 0) { //让此VM的所有$watch回调无效化 + this.$watch.backup = this.$events + this.$events = {} + } else if (n === 1) { + this.$events[type] = [] + } else { + var callbacks = this.$events[type] || [] + var i = callbacks.length + while (~--i < 0) { + if (callbacks[i] === callback) { + return callbacks.splice(i, 1) + } + } + } + return this + }, + $fire: function(type) { + var special + if (/^(\w+)!(\S+)$/.test(type)) { + special = RegExp.$1 + type = RegExp.$2 + } + var events = this.$events + var callbacks = events[type] || [] + var all = events.$all || [] + var args = aslice.call(arguments, 1) + for (var i = 0, callback; callback = callbacks[i++]; ) { + if (isFunction(callback)) + callback.apply(this, args) + } + for (var i = 0, callback; callback = all[i++]; ) { + if (isFunction(callback)) + callback.apply(this, arguments) + } + var element = events.expr && findNode(events.expr) + if (element) { + var detail = [type].concat(args) + if (special === "up" || special === "down" || special === "all") { + for (var i in avalon.vmodels) { + var v = avalon.vmodels[i] + if (v && v.$events && v.$events.expr) { + if (v !== this) { + var node = findNode(v.$events.expr) + if (!node) { + continue + } + var ok = special === "all" ? 1 : //全局广播 + special === "down" ? element.contains(node) : //向下捕获 + node.contains(element)//向上冒泡 + if (ok) { + node._avalon = v//符合条件的加一个标识 + } + } + } + } + var nodes = DOC.getElementsByTagName("*")//实现节点排序 + var alls = [] + Array.prototype.forEach.call(nodes, function(el) { + if (el._avalon) { + alls.push(el._avalon) + el._avalon = "" + el.removeAttribute("_avalon") + } + }) + if (special === "up") { + alls.reverse() + } + alls.forEach(function(v) { + v.$fire.apply(v, detail) + }) + } + } + } + } + var ravalon = /(\w+)\[(avalonctrl)="(\S+)"\]/ + var findNode = DOC.querySelector ? function(str) { + return DOC.querySelector(str) + } : function(str) { + var match = str.match(ravalon) + var all = DOC.getElementsByTagName(match[1]) + for (var i = 0, el; el = all[i++]; ) { + if (el.getAttribute(match[2]) === match[3]) { + return el + } + } + } + /********************************************************************* + * 依赖调度系统 * + **********************************************************************/ + var ronduplex = /^(duplex|on)$/ + function registerSubscriber(data) { + Registry[expose] = data //暴光此函数,方便collectSubscribers收集 + avalon.openComputedCollect = true + var fn = data.evaluator + if (fn) { //如果是求值函数 + try { + var c = ronduplex.test(data.type) ? data : fn.apply(0, data.args) + data.handler(c, data.element, data) + } catch (e) { + log("warning:exception throwed in [registerSubscriber] " + e) + delete data.evaluator + var node = data.element + if (node.nodeType === 3) { + var parent = node.parentNode + if (kernel.commentInterpolate) { + parent.replaceChild(DOC.createComment(data.value), node) + } else { + node.data = openTag + data.value + closeTag + } + } + } + } + avalon.openComputedCollect = false + delete Registry[expose] + } + + function collectSubscribers(list) { //收集依赖于这个访问器的订阅者 + var data = Registry[expose] + if (list && data && avalon.Array.ensure(list, data) && data.element) { //只有数组不存在此元素才push进去 + $$subscribers.push({ + data: data, list: list + }) + } + } + var $$subscribers = [], $startIndex = 0, $maxIndex = 200 + function removeSubscribers() { + for (var i = $startIndex, n = $startIndex + $maxIndex; i < n; i++) { + var obj = $$subscribers[i] + if (!obj) { + break + } + var data = obj.data + var el = data.element + var remove = el === null ? 1 : (el.nodeType === 1 ? typeof el.sourceIndex === "number" ? + el.sourceIndex === 0 : !root.contains(el) : !avalon.contains(root, el)) + if (remove) { //如果它没有在DOM树 + $$subscribers.splice(i, 1) + avalon.Array.remove(obj.list, data) + //log("debug: remove " + data.type) + if (data.type === "if" && data.template && data.template.parentNode === head) { + head.removeChild(data.template) + } + for (var key in data) { + data[key] = null + } + obj.data = obj.list = null + i-- + n-- + } + } + obj = $$subscribers[i] + if (obj) { + $startIndex = n + } else { + $startIndex = 0 + } + } + var beginTime = new Date(), removeID + function notifySubscribers(list) { //通知依赖于这个访问器的订阅者更新自身 + var currentTime = new Date() + clearTimeout(removeID) + if (currentTime - beginTime > 333) { + removeSubscribers() + beginTime = new Date() + } else { + removeID = setTimeout(removeSubscribers, 333) + } + if (list && list.length) { + var args = aslice.call(arguments, 1) + for (var i = list.length, fn; fn = list[--i]; ) { + var el = fn.element + if (fn.$repeat) { + fn.handler.apply(fn, args) //处理监控数组的方法 + } else if (fn.element && fn.type !== "on") {//事件绑定只能由用户触发,不能由程序触发 + var fun = fn.evaluator || noop + fn.handler(fun.apply(0, fn.args || []), el, fn) + } + } + } + } + + /********************************************************************* + * 扫描系统 * + **********************************************************************/ + avalon.scan = function(elem, vmodel) { + elem = elem || root + var vmodels = vmodel ? [].concat(vmodel) : [] + scanTag(elem, vmodels) + } + + //http://www.w3.org/TR/html5/syntax.html#void-elements + var stopScan = oneObject("area,base,basefont,br,col,command,embed,hr,img,input,link,meta,param,source,track,wbr,noscript,script,style,textarea".toUpperCase()) + + //确保元素的内容被完全扫描渲染完毕才调用回调 + var interval = W3C ? 15 : 50 + + function checkScan(elem, callback) { + var innerHTML = NaN, + id = setInterval(function() { + var currHTML = elem.innerHTML + if (currHTML === innerHTML) { + clearInterval(id) + callback() + } else { + innerHTML = currHTML + } + }, interval) + } + + + function scanTag(elem, vmodels, node) { + //扫描顺序 ms-skip(0) --> ms-important(1) --> ms-controller(2) --> ms-if(10) --> ms-repeat(100) + //--> ms-if-loop(110) --> ms-attr(970) ...--> ms-each(1400)-->ms-with(1500)--〉ms-duplex(2000)垫后 + var a = elem.getAttribute("ms-skip") + //#360 在旧式IE中 Object标签在引入Flash等资源时,可能出现没有getAttributeNode,innerHTML的情形 + if (!elem.getAttributeNode) { + return log("warning " + elem.tagName + " no getAttributeNode method") + } + var b = elem.getAttributeNode("ms-important") + var c = elem.getAttributeNode("ms-controller") + if (typeof a === "string") { + return + } else if (node = b || c) { + var newVmodel = VMODELS[node.value] + if (!newVmodel) { + return + } + //ms-important不包含父VM,ms-controller相反 + vmodels = node === b ? [newVmodel] : [newVmodel].concat(vmodels) + var name = node.name + elem.removeAttribute(name) //removeAttributeNode不会刷新[ms-controller]样式规则 + elem.setAttribute("avalonctrl", node.value) + newVmodel.$events.expr = elem.tagName + '[avalonctrl="' + node.value + '"]' + avalon(elem).removeClass(name) + + } + scanAttr(elem, vmodels) //扫描特性节点 + } + + function scanNodeList(parent, vmodels) { + var node = parent.firstChild + while (node) { + var nextNode = node.nextSibling + scanNode(node, node.nodeType, vmodels) + node = nextNode + } + } + + function scanNodeArray(nodes, vmodels) { + for (var i = 0, node; node = nodes[i++]; ) { + scanNode(node, node.nodeType, vmodels) + } + } + function scanNode(node, nodeType, vmodels) { + if (nodeType === 1) { + scanTag(node, vmodels) //扫描元素节点 + } else if (nodeType === 3 && rexpr.test(node.data)) { + scanText(node, vmodels) //扫描文本节点 + } else if (kernel.commentInterpolate && nodeType === 8 && !rexpr.test(node.nodeValue)) { + scanText(node, vmodels) //扫描注释节点 + } + } + + function scanText(textNode, vmodels) { + var bindings = [] + if (textNode.nodeType === 8) { + var leach = [] + var value = trimFilter(textNode.nodeValue, leach) + var token = { + expr: true, + value: value + } + if (leach.length) { + token.filters = leach + } + var tokens = [token] + } else { + tokens = scanExpr(textNode.data) + } + if (tokens.length) { + for (var i = 0, token; token = tokens[i++]; ) { + var node = DOC.createTextNode(token.value) //将文本转换为文本节点,并替换原来的文本节点 + if (token.expr) { + var filters = token.filters + var binding = { + type: "text", + element: node, + value: token.value, + filters: filters + } + if (filters && filters.indexOf("html") !== -1) { + avalon.Array.remove(filters, "html") + binding.type = "html" + binding.group = 1 + if (!filters.length) { + delete bindings.filters + } + } + bindings.push(binding) //收集带有插值表达式的文本 + } + hyperspace.appendChild(node) + } + textNode.parentNode.replaceChild(hyperspace, textNode) + if (bindings.length) + executeBindings(bindings, vmodels) + } + } + + var rmsAttr = /ms-(\w+)-?(.*)/ + var priorityMap = { + "if": 10, + "repeat": 90, + "data": 100, + "widget": 110, + "each": 1400, + "with": 1500, + "duplex": 2000, + "on": 3000 + } + var events = oneObject("animationend,blur,change,input,click,dblclick,focus,keydown,keypress,keyup,mousedown,mouseenter,mouseleave,mousemove,mouseout,mouseover,mouseup,scan,scroll,submit") + function bindingSorter(a, b) { + return a.priority - b.priority + } + function scanAttr(elem, vmodels) { + //防止setAttribute, removeAttribute时 attributes自动被同步,导致for循环出错 + var attributes = getAttributes ? getAttributes(elem) : avalon.slice(elem.attributes) + var bindings = [], + msData = {}, + match + for (var i = 0, attr; attr = attributes[i++]; ) { + if (attr.specified) { + if (match = attr.name.match(rmsAttr)) { + //如果是以指定前缀命名的 + var type = match[1] + var param = match[2] || "" + var value = attr.value + var name = attr.name + msData[name] = value + if (events[type]) { + param = type + type = "on" + } else if (type === "enabled") {//吃掉ms-enabled绑定,用ms-disabled代替 + type = "disabled" + value = "!(" + value + ")" + } + //吃掉以下几个绑定,用ms-attr-*绑定代替 + if (type === "checked" || type === "selected" || type === "disabled" || type === "readonly") { + param = type + type = "attr" + elem.removeAttribute(name) + name = "ms-attr-" + param + elem.setAttribute(name, value) + match = [name] + msData[name] = value + } + if (typeof bindingHandlers[type] === "function") { + var binding = { + type: type, + param: param, + element: elem, + name: match[0], + value: value, + priority: type in priorityMap ? priorityMap[type] : type.charCodeAt(0) * 10 + (Number(param) || 0) + } + if (type === "if" && param.indexOf("loop") > -1) { + binding.priority += 100 + } + if (vmodels.length) { + bindings.push(binding) + if (type === "widget") { + elem.msData = elem.msData || msData + } + } + } + } + } + } + bindings.sort(bindingSorter) + if (msData["ms-checked"] && msData["ms-duplex"]) { + log("warning!一个元素上不能同时定义ms-checked与ms-duplex") + } + var firstBinding = bindings[0] || {} + switch (firstBinding.type) { + case "if": + case "repeat": + case "widget": + executeBindings([firstBinding], vmodels) + break + default: + executeBindings(bindings, vmodels) + if (!stopScan[elem.tagName] && rbind.test(elem.innerHTML.replace(rlt, "<").replace(rgt, ">"))) { + scanNodeList(elem, vmodels) //扫描子孙元素 + } + break; + } + + + } + //IE67下,在循环绑定中,一个节点如果是通过cloneNode得到,自定义属性的specified为false,无法进入里面的分支, + //但如果我们去掉scanAttr中的attr.specified检测,一个元素会有80+个特性节点(因为它不区分固有属性与自定义属性),很容易卡死页面 + if (!"1" [0]) { + var cacheAttrs = createCache(512) + var rattrs = /\s+(ms-[^=\s]+)(?:=("[^"]*"|'[^']*'|[^\s>]+))?/g, + rquote = /^['"]/, + rtag = /<\w+\b(?:(["'])[^"]*?(\1)|[^>])*>/i, + ramp = /&/g + //IE6-8解析HTML5新标签,会将它分解两个元素节点与一个文本节点 + //
    ddd
    + // window.onload = function() { + // var body = document.body + // for (var i = 0, el; el = body.children[i++]; ) { + // avalon.log(el.outerHTML) + // } + // } + //依次输出
    ,
    + var getAttributes = function(elem) { + var html = elem.outerHTML + //处理IE6-8解析HTML5新标签的情况,及
    等半闭合标签outerHTML为空的情况 + if (html.slice(0, 2) === " 0) { // 抽取过滤器 先替换掉所有短路与 + value = value.replace(r11a, "U2hvcnRDaXJjdWl0") //btoa("ShortCircuit") + value = value.replace(rfilters, function(c, d, e) { + leach.push(d + (e || "")) + return "" + }) + value = value.replace(r11b, "||") //还原短路与 + } + return value + } + + function scanExpr(str) { + var tokens = [], + value, start = 0, + stop + do { + stop = str.indexOf(openTag, start) + if (stop === -1) { + break + } + value = str.slice(start, stop) + if (value) { // {{ 左边的文本 + tokens.push({ + value: value, + expr: false + }) + } + start = stop + openTag.length + stop = str.indexOf(closeTag, start) + if (stop === -1) { + break + } + value = str.slice(start, stop) + if (value) { //处理{{ }}插值表达式 + var leach = [] + value = trimFilter(value, leach) + tokens.push({ + value: value, + expr: true, + filters: leach.length ? leach : void 0 + }) + } + start = stop + closeTag.length + } while (1) + value = str.slice(start) + if (value) { //}} 右边的文本 + tokens.push({ + value: value, + expr: false + }) + } + + return tokens + } + /********************************************************************* + * 编译系统 * + **********************************************************************/ + var keywords = + // 关键字 + "break,case,catch,continue,debugger,default,delete,do,else,false" + + ",finally,for,function,if,in,instanceof,new,null,return,switch,this" + + ",throw,true,try,typeof,var,void,while,with" + // 保留字 + + ",abstract,boolean,byte,char,class,const,double,enum,export,extends" + + ",final,float,goto,implements,import,int,interface,long,native" + + ",package,private,protected,public,short,static,super,synchronized" + + ",throws,transient,volatile" + // ECMA 5 - use strict + + ",arguments,let,yield" + + ",undefined" + var rrexpstr = /\/\*[\w\W]*?\*\/|\/\/[^\n]*\n|\/\/[^\n]*$|"(?:[^"\\]|\\[\w\W])*"|'(?:[^'\\]|\\[\w\W])*'|[\s\t\n]*\.[\s\t\n]*[$\w\.]+/g + var rsplit = /[^\w$]+/g + var rkeywords = new RegExp(["\\b" + keywords.replace(/,/g, '\\b|\\b') + "\\b"].join('|'), 'g') + var rnumber = /\b\d[^,]*/g + var rcomma = /^,+|,+$/g + var cacheVars = createCache(512) + var getVariables = function(code) { + var key = "," + code.trim() + if (cacheVars[key]) { + return cacheVars[key] + } + var match = code + .replace(rrexpstr, "") + .replace(rsplit, ",") + .replace(rkeywords, "") + .replace(rnumber, "") + .replace(rcomma, "") + .split(/^$|,+/) + return cacheVars(key, uniqSet(match)) + } + /*添加赋值语句*/ + function addAssign(vars, scope, name, data) { + var ret = [], + prefix = " = " + name + "." + for (var i = vars.length, prop; prop = vars[--i]; ) { + if (scope.hasOwnProperty(prop)) { + ret.push(prop + prefix + prop) + if (data.type === "duplex") { + vars.get = name + "." + prop + } + vars.splice(i, 1) + } + } + return ret + + } + + function uniqSet(array) { + var ret = [], unique = {} + for (var i = 0; i < array.length; i++) { + var el = array[i] + var id = el && typeof el.$id === "string" ? el.$id : el + if (!unique[id]) { + unique[id] = ret.push(el) + } + } + return ret + } + + + function createCache(maxLength) { + var keys = [] + function cache(key, value) { + if (keys.push(key) > maxLength) { + delete cache[keys.shift()] + } + return cache[key] = value; + } + return cache; + } + //缓存求值函数,以便多次利用 + var cacheExprs = createCache(128) + //取得求值函数及其传参 + var rduplex = /\w\[.*\]|\w\.\w/ + var rproxy = /(\$proxy\$[a-z]+)\d+$/ + + function parseExpr(code, scopes, data) { + var dataType = data.type + var filters = data.filters ? data.filters.join("") : "" + var exprId = scopes.map(function(el) { + return el.$id.replace(rproxy, "$1") + }) + code + dataType + filters + var vars = getVariables(code).concat(), + assigns = [], + names = [], + args = [], + prefix = "" + //args 是一个对象数组, names 是将要生成的求值函数的参数 + scopes = uniqSet(scopes) + for (var i = 0, sn = scopes.length; i < sn; i++) { + if (vars.length) { + var name = "vm" + expose + "_" + i + names.push(name) + args.push(scopes[i]) + assigns.push.apply(assigns, addAssign(vars, scopes[i], name, data)) + } + } + if (!assigns.length && dataType === "duplex") { + return + } + //---------------args---------------- + if (filters) { + args.push(avalon.filters) + } + data.args = args + //---------------cache---------------- + var fn = cacheExprs[exprId] //直接从缓存,免得重复生成 + if (fn) { + data.evaluator = fn + return + } + var prefix = assigns.join(", ") + if (prefix) { + prefix = "var " + prefix + } + if (filters) { //文本绑定,双工绑定才有过滤器 + code = "\nvar ret" + expose + " = " + code + var textBuffer = [], + fargs + textBuffer.push(code, "\r\n") + for (var i = 0, fname; fname = data.filters[i++]; ) { + var start = fname.indexOf("(") + if (start !== -1) { + fargs = fname.slice(start + 1, fname.lastIndexOf(")")).trim() + fargs = "," + fargs + fname = fname.slice(0, start).trim() + } else { + fargs = "" + } + textBuffer.push(" if(filters", expose, ".", fname, "){\n\ttry{\nret", expose, + " = filters", expose, ".", fname, "(ret", expose, fargs, ")\n\t}catch(e){} \n}\n") + } + code = textBuffer.join("") + code += "\nreturn ret" + expose + names.push("filters" + expose) + } else if (dataType === "duplex") { //双工绑定 + var _body = "'use strict';\nreturn function(vvv){\n\t" + + prefix + + ";\n\tif(!arguments.length){\n\t\treturn " + + code + + "\n\t}\n\t" + (!rduplex.test(code) ? vars.get : code) + + "= vvv;\n} " + try { + fn = Function.apply(noop, names.concat(_body)) + data.evaluator = cacheExprs(exprId, fn) + } catch (e) { + log("debug: parse error," + e.message) + } + return + } else if (dataType === "on") { //事件绑定 + if (code.indexOf("(") === -1) { + code += ".call(this, $event)" + } else { + code = code.replace("(", ".call(this,") + } + names.push("$event") + code = "\nreturn " + code + ";" //IE全家 Function("return ")出错,需要Function("return ;") + var lastIndex = code.lastIndexOf("\nreturn") + var header = code.slice(0, lastIndex) + var footer = code.slice(lastIndex) + code = header + "\n" + footer + } else { //其他绑定 + code = "\nreturn " + code + ";" //IE全家 Function("return ")出错,需要Function("return ;") + } + try { + fn = Function.apply(noop, names.concat("'use strict';\n" + prefix + code)) + data.evaluator = cacheExprs(exprId, fn) + } catch (e) { + log("debug: parse error," + e.message) + } finally { + vars = textBuffer = names = null //释放内存 + } + } + + var meta = { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"': '\\"', + '\\': '\\\\' + } + var quote = window.JSON && JSON.stringify || function(str) { + return '"' + str.replace(/[\\\"\x00-\x1f]/g, function(a) { + var c = meta[a]; + return typeof c === 'string' ? c : + '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"' + } + //parseExpr的智能引用代理 + function parseExprProxy(code, scopes, data, tokens) { + if (Array.isArray(tokens)) { + code = tokens.map(function(el) { + return el.expr ? "(" + el.value + ")" : quote(el.value) + }).join(" + ") + } + parseExpr(code, scopes, data) + if (data.evaluator) { + data.handler = bindingExecutors[data.handlerName || data.type] + data.evaluator.toString = function() { + return data.type + " binding to eval(" + code + ")" + } + //方便调试 + //这里非常重要,我们通过判定视图刷新函数的element是否在DOM树决定 + //将它移出订阅者列表 + registerSubscriber(data) + } + } + avalon.parseExprProxy = parseExprProxy + /********************************************************************* + * 绑定处理系统 * + **********************************************************************/ + var propMap = {//属性名映射 + "accept-charset": "acceptCharset", + "char": "ch", + "charoff": "chOff", + "class": "className", + "for": "htmlFor", + "http-equiv": "httpEquiv" + } + + var anomaly = "accessKey,bgColor,cellPadding,cellSpacing,codeBase,codeType,colSpan," + + "dateTime,defaultValue,frameBorder,longDesc,maxLength,marginWidth,marginHeight," + + "rowSpan,tabIndex,useMap,vSpace,valueType,vAlign" + anomaly.replace(rword, function(name) { + propMap[name.toLowerCase()] = name + }) + + var cssText = "" + head.insertBefore(avalon.parseHTML(cssText), head.firstChild) //避免IE6 base标签BUG + var rnoscripts = /(?:[\s\S]+?)<\/noscript>/img + var rnoscriptText = /([\s\S]+?)<\/noscript>/im + + var getXHR = function() { + return new (window.XMLHttpRequest || ActiveXObject)("Microsoft.XMLHTTP") + } + var getBindingCallback = function(elem, name, vmodels) { + var callback = elem.getAttribute(name) + if (callback) { + for (var i = 0, vm; vm = vmodels[i++]; ) { + if (vm.hasOwnProperty(callback) && typeof vm[callback] === "function") { + return vm[callback] + } + } + } + } + var cacheTmpls = avalon.templateCache = {} + + avalon.contains = fixContains + + var bools = "autofocus,autoplay,async,allowTransparency,checked,controls,declare,disabled,defer,defaultChecked,defaultSelected" + + "contentEditable,isMap,loop,multiple,noHref,noResize,noShade,open,readOnly,selected" + var boolMap = {} + bools.replace(rword, function(name) { + boolMap[name.toLowerCase()] = name + }) + + //这里的函数每当VM发生改变后,都会被执行(操作方为notifySubscribers) + var bindingExecutors = avalon.bindingExecutors = { + "attr": function(val, elem, data) { + var method = data.type, + attrName = data.param + if (method === "css") { + avalon(elem).css(attrName, val) + } else if (method === "attr") { + // ms-attr-class="xxx" vm.xxx="aaa bbb ccc"将元素的className设置为aaa bbb ccc + // ms-attr-class="xxx" vm.xxx=false 清空元素的所有类名 + // ms-attr-name="yyy" vm.yyy="ooo" 为元素设置name属性 + if (boolMap[attrName]) { + var bool = boolMap[attrName] + if (typeof elem[bool] === "boolean") { + return elem[bool] = !!val + } + } + var toRemove = (val === false) || (val === null) || (val === void 0) + if (!W3C && propMap[attrName]) {//旧式IE下需要进行名字映射 + attrName = propMap[attrName] + var isInnate = true + } + if (toRemove) { + return elem.removeAttribute(attrName) + } + if (window.VBArray && !isInnate) {//IE下需要区分固有属性与自定义属性 + if (isVML(elem)) { + isInnate = true + } else if (!rsvg.test(elem)) { + var attrs = elem.attributes || {} + var attr = attrs[attrName] + isInnate = attr ? attr.expando === false : attr === null + } + } + if (isInnate) { + elem[attrName] = val + } else { + elem.setAttribute(attrName, val) + } + } else if (method === "include" && val) { + var vmodels = data.vmodels + var rendered = getBindingCallback(elem, "data-include-rendered", vmodels) + var loaded = getBindingCallback(elem, "data-include-loaded", vmodels) + + function scanTemplate(text) { + if (loaded) { + text = loaded.apply(elem, [text].concat(vmodels)) + } + avalon.innerHTML(elem, text) + scanNodeList(elem, vmodels) + rendered && checkScan(elem, function() { + rendered.call(elem) + }) + } + if (data.param === "src") { + if (cacheTmpls[val]) { + avalon.nextTick(function() { + scanTemplate(cacheTmpls[val]) + }) + } else { + var xhr = getXHR() + xhr.onreadystatechange = function() { + if (xhr.readyState === 4) { + var s = xhr.status + if (s >= 200 && s < 300 || s === 304 || s === 1223) { + scanTemplate(cacheTmpls[val] = xhr.responseText) + } + } + } + xhr.open("GET", val, true) + if ("withCredentials" in xhr) { + xhr.withCredentials = true + } + xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest") + xhr.send(null) + } + } else { + //IE系列与够新的标准浏览器支持通过ID取得元素(firefox14+) + //http://tjvantoll.com/2012/07/19/dom-element-references-as-global-variables/ + var el = val && val.nodeType === 1 ? val : DOC.getElementById(val) + if (el) { + if (el.tagName === "NOSCRIPT" && !(el.innerHTML || el.fixIE78)) { //IE7-8 innerText,innerHTML都无法取得其内容,IE6能取得其innerHTML + var xhr = getXHR() //IE9-11与chrome的innerHTML会得到转义的内容,它们的innerText可以 + xhr.open("GET", location, false) //谢谢Nodejs 乱炖群 深圳-纯属虚构 + xhr.send(null) + //http://bbs.csdn.net/topics/390349046?page=1#post-393492653 + var noscripts = DOC.getElementsByTagName("noscript") + var array = (xhr.responseText || "").match(rnoscripts) || [] + var n = array.length + for (var i = 0; i < n; i++) { + var tag = noscripts[i] + if (tag) { //IE6-8中noscript标签的innerHTML,innerText是只读的 + tag.style.display = "none" //http://haslayout.net/css/noscript-Ghost-Bug + tag.fixIE78 = (array[i].match(rnoscriptText) || ["", " "])[1] + } + } + } + avalon.nextTick(function() { + scanTemplate(el.fixIE78 || el.value || el.innerText || el.innerHTML) + }) + } + } + } else { + if (!root.hasAttribute && typeof val === "string" && (method === "src" || method === "href")) { + val = val.replace(/&/g, "&") //处理IE67自动转义的问题 + } + elem[method] = val + } + }, + "class": function(val, elem, data) { + var $elem = avalon(elem), + method = data.type + if (method === "class" && data.oldStyle) { //如果是旧风格 + $elem.toggleClass(data.oldStyle, !!val) + } else { + //如果存在冒号就有求值函数 + data.toggleClass = data._evaluator ? !!data._evaluator.apply(elem, data._args) : true + data.newClass = data.immobileClass || val + if (data.oldClass && data.newClass !== data.oldClass) { + $elem.removeClass(data.oldClass) + } + data.oldClass = data.newClass + switch (method) { + case "class": + $elem.toggleClass(data.newClass, data.toggleClass) + break + case "hover": + case "active": + if (!data.hasBindEvent) { //确保只绑定一次 + var activate = "mouseenter" //在移出移入时切换类名 + var abandon = "mouseleave" + if (method === "active") {//在聚焦失焦中切换类名 + elem.tabIndex = elem.tabIndex || -1 + activate = "mousedown" + abandon = "mouseup" + $elem.bind("mouseleave", function() { + data.toggleClass && $elem.removeClass(data.newClass) + }) + } + $elem.bind(activate, function() { + data.toggleClass && $elem.addClass(data.newClass) + }) + $elem.bind(abandon, function() { + data.toggleClass && $elem.removeClass(data.newClass) + }) + data.hasBindEvent = true + } + break; + } + } + }, + "data": function(val, elem, data) { + var key = "data-" + data.param + if (val && typeof val === "object") { + elem[key] = val + } else { + elem.setAttribute(key, String(val)) + } + }, + "repeat": function(method, pos, el) { + if (method) { + var data = this + var parent = data.element.parentNode + var proxies = data.proxies + var transation = hyperspace.cloneNode(false) + + if (method === "del" || method === "move") { + var locatedNode = locateFragment(data, pos) + } + var group = data.group + switch (method) { + case "add": //在pos位置后添加el数组(pos为数字,el为数组) + var arr = el + var last = data.$repeat.length - 1 + var fragments = [] + for (var i = 0, n = arr.length; i < n; i++) { + var ii = i + pos + var proxy = getEachProxy(ii, arr[i], data, last) + proxies.splice(ii, 0, proxy) + shimController(data, transation, proxy, fragments) + } + locatedNode = locateFragment(data, pos) + parent.insertBefore(transation, locatedNode) + for (var i = 0, fragment; fragment = fragments[i++]; ) { + scanNodeArray(fragment.nodes, fragment.vmodels) + fragment.nodes = fragment.vmodels = null + } + calculateFragmentGroup(data) + break + case "del": //将pos后的el个元素删掉(pos, el都是数字) + var removed = proxies.splice(pos, el) + var transation = removeFragment(locatedNode, group, el) + avalon.clearHTML(transation) + recycleEachProxies(removed) + break + case "index": //将proxies中的第pos个起的所有元素重新索引(pos为数字,el用作循环变量) + var last = proxies.length - 1 + for (; el = proxies[pos]; pos++) { + el.$index = pos + el.$first = pos === 0 + el.$last = pos === last + } + break + case "clear": + var size = "proxySize" in data ? data.proxySize : proxies.length + if (size) { + var n = size * group, k = 0 + while (true) { + var nextNode = data.element.nextSibling + if (nextNode && k < n) { + parent.removeChild(nextNode) + k++ + } else { + break + } + } + recycleEachProxies(proxies) + } + break + case "move": //将proxies中的第pos个元素移动el位置上(pos, el都是数字) + var t = proxies.splice(pos, 1)[0] + if (t) { + proxies.splice(el, 0, t) + transation = removeFragment(locatedNode, group) + locatedNode = locateFragment(data, el) + parent.insertBefore(transation, locatedNode) + } + break + case "set": //将proxies中的第pos个元素的VM设置为el(pos为数字,el任意) + var proxy = proxies[pos] + if (proxy) { + proxy[proxy.$itemName] = el + } + break + case "append": //将pos的键值对从el中取出(pos为一个普通对象,el为预先生成好的代理VM对象池) + var pool = el + var keys = [] + var fragments = [] + for (var key in pos) { //得到所有键名 + if (pos.hasOwnProperty(key) && key !== "hasOwnProperty") { + keys.push(key) + } + } + if (data.sortedCallback) { //如果有回调,则让它们排序 + var keys2 = data.sortedCallback.call(parent, keys) + if (keys2 && Array.isArray(keys2) && keys2.length) { + keys = keys2 + } + } + for (var i = 0, key; key = keys[i++]; ) { + if (key !== "hasOwnProperty") { + shimController(data, transation, pool[key], fragments) + } + } + data.proxySize = keys.length + parent.insertBefore(transation, data.element.nextSibling) + for (var i = 0, fragment; fragment = fragments[i++]; ) { + scanNodeArray(fragment.nodes, fragment.vmodels) + fragment.nodes = fragment.vmodels = null + } + calculateFragmentGroup(data) + break + } + var callback = data.renderedCallback || noop, args = arguments + checkScan(parent, function() { + callback.apply(parent, args) + if (parent.oldValue && parent.tagName === "SELECT" && method === "index") {//fix #503 + avalon(parent).val(parent.oldValue.split(",")) + } + }) + } + }, + "html": function(val, elem, data) { + val = val == null ? "" : val + var parent = "group" in data ? elem.parentNode : elem + if ("group" in data) { + var fragment, nodes + //将值转换为文档碎片,原值可以为元素节点,文档碎片,NodeList,字符串 + if (val.nodeType === 11) { + fragment = val + } else if (val.nodeType === 1 || val.item) { + nodes = val.nodeType === 1 ? val.childNodes : val.item ? val : [] + fragment = hyperspace.cloneNode(true) + while (nodes[0]) { + fragment.appendChild(nodes[0]) + } + } else { + fragment = avalon.parseHTML(val) + } + nodes = avalon.slice(fragment.childNodes) + if (nodes.length === 0) { + var comment = DOC.createComment("ms-html") + fragment.appendChild(comment) + nodes = [comment] + } + parent.insertBefore(fragment, elem) //fix IE6-8 insertBefore的第2个参数只能为节点或null + var length = data.group + while (elem) { + var nextNode = elem.nextSibling + parent.removeChild(elem) + length-- + if (length === 0 || nextNode === null) + break + elem = nextNode + } + data.element = nodes[0] + data.group = nodes.length + } else { + avalon.innerHTML(parent, val) + } + avalon.nextTick(function() { + scanNodeList(parent, data.vmodels) + }) + }, + "if": function(val, elem, data) { + if (val) { //插回DOM树 + if (elem.nodeType === 8) { + elem.parentNode.replaceChild(data.template, elem) + elem = data.element = data.template + } + if (elem.getAttribute(data.name)) { + elem.removeAttribute(data.name) + scanAttr(elem, data.vmodels) + } + } else { //移出DOM树,并用注释节点占据原位置 + if (elem.nodeType === 1) { + var node = data.element = DOC.createComment("ms-if") + elem.parentNode.replaceChild(node, elem) + data.template = elem //元素节点 + head.appendChild(elem) + } + } + }, + "on": function(callback, elem, data) { + data.type = "on" + callback = function(e) { + var fn = data.evaluator || noop + return fn.apply(this, data.args.concat(e)) + } + var eventType = data.param.replace(/-\d+$/, "") // ms-on-mousemove-10 + if (eventType === "scan") { + callback.call(elem, {type: eventType}) + } else if (typeof data.specialBind === "function") { + data.specialBind(elem, callback) + } else { + var removeFn = avalon.bind(elem, eventType, callback) + } + data.rollback = function() { + if (typeof data.specialUnbind === "function") { + data.specialUnbind() + } else { + avalon.unbind(elem, eventType, removeFn) + } + } + }, + "text": function(val, elem) { + val = val == null ? "" : val //不在页面上显示undefined null + if (elem.nodeType === 3) { //绑定在文本节点上 + try {//IE对游离于DOM树外的节点赋值会报错 + elem.data = val + } catch (e) { + } + } else { //绑定在特性节点上 + if ("textContent" in elem) { + elem.textContent = val + } else { + elem.innerText = val + } + } + }, + "visible": function(val, elem, data) { + elem.style.display = val ? data.display : "none" + }, + "widget": noop + } + + var rdash = /\(([^)]*)\)/ + + function parseDisplay(nodeName, val) { + //用于取得此类标签的默认display值 + var key = "_" + nodeName + if (!parseDisplay[key]) { + var node = DOC.createElement(nodeName) + root.appendChild(node) + if (W3C) { + val = getComputedStyle(node, null).display + } else { + val = node.currentStyle.display + } + root.removeChild(node) + parseDisplay[key] = val + } + return parseDisplay[key] + } + + avalon.parseDisplay = parseDisplay + //这里的函数只会在第一次被扫描后被执行一次,并放进行对应VM属性的subscribers数组内(操作方为registerSubscriber) + var bindingHandlers = avalon.bindingHandlers = { + //这是一个字符串属性绑定的范本, 方便你在title, alt, src, href, include, css添加插值表达式 + // + "attr": function(data, vmodels) { + var text = data.value.trim(), + simple = true + if (text.indexOf(openTag) > -1 && text.indexOf(closeTag) > 2) { + simple = false + if (rexpr.test(text) && RegExp.rightContext === "" && RegExp.leftContext === "") { + simple = true + text = RegExp.$1 + } + } + data.handlerName = "attr" //handleName用于处理多种绑定共用同一种bindingExecutor的情况 + parseExprProxy(text, vmodels, data, (simple ? null : scanExpr(data.value))) + }, + //根据VM的属性值或表达式的值切换类名,ms-class="xxx yyy zzz:flag" + //http://www.cnblogs.com/rubylouvre/archive/2012/12/17/2818540.html + "class": function(data, vmodels) { + var oldStyle = data.param, + text = data.value, + rightExpr + data.handlerName = "class" + if (!oldStyle || isFinite(oldStyle)) { + data.param = "" //去掉数字 + var noExpr = text.replace(rexprg, function(a) { + return Math.pow(10, a.length - 1) //将插值表达式插入10的N-1次方来占位 + }) + var colonIndex = noExpr.indexOf(":") //取得第一个冒号的位置 + if (colonIndex === -1) { // 比如 ms-class="aaa bbb ccc" 的情况 + var className = text + } else { // 比如 ms-class-1="ui-state-active:checked" 的情况 + className = text.slice(0, colonIndex) + rightExpr = text.slice(colonIndex + 1) + parseExpr(rightExpr, vmodels, data) //决定是添加还是删除 + if (!data.evaluator) { + log("debug: ms-class '" + (rightExpr || "").trim() + "' 不存在于VM中") + return false + } else { + data._evaluator = data.evaluator + data._args = data.args + } + } + var hasExpr = rexpr.test(className) //比如ms-class="width{{w}}"的情况 + if (!hasExpr) { + data.immobileClass = className + } + parseExprProxy("", vmodels, data, (hasExpr ? scanExpr(className) : null)) + } else { + data.immobileClass = data.oldStyle = data.param + parseExprProxy(text, vmodels, data) + } + }, + "duplex": function(data, vmodels) { + var elem = data.element, + tagName = elem.tagName + if (typeof duplexBinding[tagName] === "function") { + data.changed = getBindingCallback(elem, "data-duplex-changed", vmodels) || noop + //由于情况特殊,不再经过parseExprProxy + parseExpr(data.value, vmodels, data, "duplex") + if (data.evaluator && data.args) { + var form = elem.form + if (form && form.msValidate) { + form.msValidate(elem) + } + data.msType = data.param || "" + if (data.msType === "bool") { + data.msType = "boolean" + log("ms-duplex-bool已经更名为ms-duplex-boolean") + } else if (data.msType === "text") { + data.msType = "string" + log("ms-duplex-text已经更名为ms-duplex-string") + } + if (data.msType === "radio") { + log("ms-duplex-radio将在2.0废掉,请尽量不要用") + } + if (!/boolean|string|number/.test(data.msType)) { + data.msType = "" + } + data.bound = function(type, callback) { + if (elem.addEventListener) { + elem.addEventListener(type, callback, false) + } else { + elem.attachEvent("on" + type, callback) + } + var old = data.rollback + data.rollback = function() { + avalon.unbind(elem, type, callback) + old && old() + } + } + duplexBinding[elem.tagName](elem, data.evaluator.apply(null, data.args), data) + } + } + }, + "repeat": function(data, vmodels) { + var type = data.type + parseExpr(data.value, vmodels, data) + data.proxies = [] + var freturn = false + try { + var $repeat = data.$repeat = data.evaluator.apply(0, data.args || []) + var xtype = avalon.type($repeat) + if (xtype !== "object" && xtype !== "array") { + freturn = true + avalon.log("warning:" + data.value + "对应类型不正确") + } + } catch (e) { + freturn = true + avalon.log("warning:" + data.value + "编译出错") + } + var elem = data.element + elem.removeAttribute(data.name) + data.sortedCallback = getBindingCallback(elem, "data-with-sorted", vmodels) + data.renderedCallback = getBindingCallback(elem, "data-" + type + "-rendered", vmodels) + + var comment = data.element = DOC.createComment("ms-repeat") + if (type === "each" || type === "with") { + data.template = elem.innerHTML.trim() + avalon.clearHTML(elem).appendChild(comment) + } else { + data.template = elem.outerHTML.trim() + data.group = 1 + elem.parentNode.replaceChild(comment, elem) + } + + data.rollback = function() {//只用于list为对象的情况 + bindingExecutors.repeat.call(data, "clear") + var elem = data.element + var parentNode = elem.parentNode + var content = avalon.parseHTML(data.template) + var target = content.firstChild + parentNode.replaceChild(content, elem) + target = data.element = data.type === "repeat" ? target : parentNode + data.group = null + target.setAttribute(data.name, data.value) + } + var arr = data.value.split(".") || [] + if (arr.length > 1) { + arr.pop() + var n = arr[0] + for (var i = 0, v; v = vmodels[i++]; ) { + if (v && v.hasOwnProperty(n)) { + var events = v[n].$events + events[subscribers] = events[subscribers] || [] + events[subscribers].push(data) + break + } + } + } + if (freturn) { + return + } + + data.handler = bindingExecutors.repeat + data.$outer = {} + var check0 = "$key", + check1 = "$val" + if (Array.isArray($repeat)) { + check0 = "$first" + check1 = "$last" + } + for (var i = 0, p; p = vmodels[i++]; ) { + if (p.hasOwnProperty(check0) && p.hasOwnProperty(check1)) { + data.$outer = p + break + } + } + var $list = ($repeat.$events || {})[subscribers] + if ($list && avalon.Array.ensure($list, data)) { + $$subscribers.push({ + data: data, list: $list + }) + } + if (!Array.isArray($repeat) && type !== "each") { + var pool = withProxyPool[$repeat.$id] + if (!pool) { + withProxyCount++ + pool = withProxyPool[$repeat.$id] = {} + for (var key in $repeat) { + if ($repeat.hasOwnProperty(key) && key !== "hasOwnProperty") { + (function(k, v) { + pool[k] = createWithProxy(k, v, {}) + pool[k].$watch("$val", function(val) { + $repeat[k] = val //#303 + }) + })(key, $repeat[key]) + } + } + } + data.handler("append", $repeat, pool) + } else { + data.handler("add", 0, $repeat) + } + }, + "html": function(data, vmodels) { + parseExprProxy(data.value, vmodels, data) + }, + "on": function(data, vmodels) { + var value = data.value + var eventType = data.param.replace(/-\d+$/, "") // ms-on-mousemove-10 + if (typeof bindingHandlers.on[eventType + "Hook"] === "function") { + bindingHandlers.on[eventType + "Hook"](data) + } + if (value.indexOf("(") > 0 && value.indexOf(")") > -1) { + var matched = (value.match(rdash) || ["", ""])[1].trim() + if (matched === "" || matched === "$event") { // aaa() aaa($event)当成aaa处理 + value = value.replace(rdash, "") + } + } + parseExprProxy(value, vmodels, data) + }, + "visible": function(data, vmodels) { + var elem = avalon(data.element) + var display = elem.css("display") + if (display === "none") { + var style = elem[0].style + var has = /visibility/i.test(style.cssText) + var visible = elem.css("visibility") + style.display = "" + style.visibility = "hidden" + display = elem.css("display") + if (display === "none") { + display = parseDisplay(elem[0].nodeName) + } + style.visibility = has ? visible : "" + } + data.display = display + parseExprProxy(data.value, vmodels, data) + }, + "widget": function(data, vmodels) { + var args = data.value.match(rword) + var elem = data.element + var widget = args[0] + if (args[1] === "$" || !args[1]) { + args[1] = widget + setTimeout("1") + } + data.value = args.join(",") + var constructor = avalon.ui[widget] + if (typeof constructor === "function") { //ms-widget="tabs,tabsAAA,optname" + vmodels = elem.vmodels || vmodels + var optName = args[2] || widget //尝试获得配置项的名字,没有则取widget的名字 + for (var i = 0, v; v = vmodels[i++]; ) { + if (v.hasOwnProperty(optName) && typeof v[optName] === "object") { + var nearestVM = v + break + } + } + if (nearestVM) { + var vmOptions = nearestVM[optName] + vmOptions = vmOptions.$model || vmOptions + var id = vmOptions[widget + "Id"] + if (typeof id === "string") { + args[1] = id + } + } + var widgetData = avalon.getWidgetData(elem, args[0]) //抽取data-tooltip-text、data-tooltip-attr属性,组成一个配置对象 + data[widget + "Id"] = args[1] + data[widget + "Options"] = avalon.mix({}, constructor.defaults, vmOptions || {}, widgetData) + elem.removeAttribute("ms-widget") + var vmodel = constructor(elem, data, vmodels) || {} //防止组件不返回VM + data.evaluator = noop + elem.msData["ms-widget-id"] = vmodel.$id || "" + if (vmodel.hasOwnProperty("$init")) { + vmodel.$init() + } + if (vmodel.hasOwnProperty("$remove")) { + function offTree() { + if (!elem.msRetain && !root.contains(elem)) { + vmodel.$remove() + elem.msData = {} + delete VMODELS[vmodel.$id] + return false + } + } + if (window.chrome) { + elem.addEventListener("DOMNodeRemovedFromDocument", function() { + setTimeout(offTree) + }) + } else { + avalon.tick(offTree) + } + } + } else if (vmodels.length) { //如果该组件还没有加载,那么保存当前的vmodels + elem.vmodels = vmodels + } + } + } + //============================ class preperty binding ======================= + "hover,active".replace(rword, function(method) { + bindingHandlers[method] = bindingHandlers["class"] + }) + "with,each".replace(rword, function(name) { + bindingHandlers[name] = bindingHandlers.repeat + }) + bindingHandlers["if"] = bindingHandlers.data = bindingHandlers.text = bindingHandlers.html + //============================= string preperty binding ======================= + //与href绑定器 用法差不多的其他字符串属性的绑定器 + //建议不要直接在src属性上修改,这样会发出无效的请求,请使用ms-src + "title,alt,src,value,css,include,href".replace(rword, function(name) { + bindingHandlers[name] = bindingHandlers.attr + }) + //============================= model binding ======================= + + //将模型中的字段与input, textarea的value值关联在一起 + var duplexBinding = bindingHandlers.duplex + //如果一个input标签添加了model绑定。那么它对应的字段将与元素的value连结在一起 + //字段变,value就变;value变,字段也跟着变。默认是绑定input事件, + duplexBinding.INPUT = function(element, evaluator, data) { + var type = element.type, + bound = data.bound, + $elem = avalon(element), + firstTigger = false, + composing = false + function callback(value) { + firstTigger = true + data.changed.call(this, value) + } + function compositionStart() { + composing = true + } + function compositionEnd() { + composing = false + } + //当value变化时改变model的值 + function updateVModel() { + if (composing)//处理中文输入法在minlengh下引发的BUG + return + var val = element.oldValue = element.value //防止递归调用形成死循环 + var typedVal = getTypedValue(data, val) //尝式转换为正确的格式 + if ($elem.data("duplex-observe") !== false) { + evaluator(typedVal) + callback.call(element, typedVal) + if ($elem.data("duplex-focus")) { + avalon.nextTick(function() { + element.focus() + }) + } + } + } + + //当model变化时,它就会改变value的值 + data.handler = function() { + var val = evaluator() + val = val == null ? "" : val + "" + if (val !== element.value) { + element.value = val + } + } + + if (type === "checkbox" && data.param === "radio") { + type = "radio" + } + if (type === "radio") { + var IE6 = !window.XMLHttpRequest + updateVModel = function() { + if ($elem.data("duplex-observe") !== false) { + var val = element.value + var typedValue = data.msType ? getTypedValue(data, val) : !element.oldValue + evaluator(typedValue) + callback.call(element, typedValue) + } + } + data.handler = function() { + var val = evaluator() + var checked = data.msType ? val + "" === element.value : !!val + element.oldValue = checked + if (IE6) { + setTimeout(function() { + //IE8 checkbox, radio是使用defaultChecked控制选中状态, + //并且要先设置defaultChecked后设置checked + //并且必须设置延迟 + element.defaultChecked = checked + element.checked = checked + }, 100) + } else { + element.checked = checked + } + } + bound(IE6 ? "mouseup" : "click", updateVModel) + } else if (type === "checkbox") { + updateVModel = function() { + if ($elem.data("duplex-observe") !== false) { + var method = element.checked ? "ensure" : "remove" + var array = evaluator() + if (!Array.isArray(array)) { + log("ms-duplex应用于checkbox上要对应一个数组") + array = [array] + } + var typedValue = getTypedValue(data, element.value) + avalon.Array[method](array, typedValue) + callback.call(element, array) + } + } + + data.handler = function() { + var array = [].concat(evaluator()) //强制转换为数组 + element.checked = array.indexOf(getTypedValue(data, element.value)) >= 0 + } + + bound(W3C ? "change" : "click", updateVModel) + + } else { + var event = element.attributes["data-duplex-event"] || element.attributes["data-event"] || {} + if (element.attributes["data-event"]) { + log("data-event指令已经废弃,请改用data-duplex-event") + } + event = event.value + if (event === "change") { + bound("change", updateVModel) + } else { + if (W3C && DOC.documentMode !== 9) { //IE10+, W3C + bound("input", updateVModel) + bound("compositionstart", compositionStart) + bound("compositionend", compositionEnd) + } else { + var events = ["keyup", "paste", "cut", "change"] + + function removeFn(e) { + var key = e.keyCode + // command modifiers arrows + if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) + return + if (e.type === "cut") { + avalon.nextTick(updateVModel) + } else { + updateVModel() + } + } + + events.forEach(function(type) { + element.attachEvent("on" + type, removeFn) + }) + + data.rollback = function() { + events.forEach(function(type) { + element.detachEvent("on" + type, removeFn) + }) + } + } + } + } + element.oldValue = element.value + launch(function() { + if (avalon.contains(root, element)) { + onTree.call(element) + } else if (!element.msRetain) { + return false + } + }) + registerSubscriber(data) + var timer = setTimeout(function() { + if (!firstTigger) { + callback.call(element, element.value) + } + clearTimeout(timer) + }, 31) + } + + function getTypedValue(data, val) { + switch (data.msType) { + case "boolean": + return val === "true" + case "number": + return isFinite(val) || val === "" ? parseFloat(val) || 0 : val + default: + return val + } + } + + var TimerID, ribbon = [], + launch = noop + function W3CFire(el, name, detail) { + var event = DOC.createEvent("Events") + event.initEvent(name, true, true) + if (detail) { + event.detail = detail + } + el.dispatchEvent(event) + } + + function onTree() { //disabled状态下改动不触发input事件 + if (!this.disabled && this.oldValue !== this.value) { + if (W3C) { + W3CFire(this, "input") + } else { + this.fireEvent("onchange") + } + } + } + + avalon.tick = function(fn) { + if (ribbon.push(fn) === 1) { + TimerID = setInterval(ticker, 60) + } + } + + function ticker() { + for (var n = ribbon.length - 1; n >= 0; n--) { + var el = ribbon[n] + if (el() === false) { + ribbon.splice(n, 1) + } + } + if (!ribbon.length) { + clearInterval(TimerID) + } + } + + function newSetter(newValue) { + oldSetter.call(this, newValue) + if (newValue !== this.oldValue) { + W3CFire(this, "input") + } + } + try { + var inputProto = HTMLInputElement.prototype + Object.getOwnPropertyNames(inputProto)//故意引发IE6-8等浏览器报错 + var oldSetter = Object.getOwnPropertyDescriptor(inputProto, "value").set //屏蔽chrome, safari,opera + Object.defineProperty(inputProto, "value", { + set: newSetter + }) + } catch (e) { + launch = avalon.tick + } + + duplexBinding.SELECT = function(element, evaluator, data) { + var $elem = avalon(element) + function updateVModel() { + if ($elem.data("duplex-observe") !== false) { + var val = $elem.val() //字符串或字符串数组 + if (Array.isArray(val)) { + val = val.map(function(v) { + return getTypedValue(data, v) + }) + } else { + val = getTypedValue(data, val) + } + if (val + "" !== element.oldValue) { + evaluator(val) + } + data.changed.call(element, val) + } + } + data.handler = function() { + var val = evaluator() + val = val && val.$model || val + //必须变成字符串后才能比较 + if (Array.isArray(val)) { + if (!element.multiple) { + log("ms-duplex在不能对应一个数组") + } + } + val = Array.isArray(val) ? val.map(String) : val + "" + if (val + "" !== element.oldValue) { + $elem.val(val) + element.oldValue = val + "" + } + } + data.bound("change", updateVModel) + var innerHTML = NaN + var id = setInterval(function() { + var currHTML = element.innerHTML + if (currHTML === innerHTML) { + clearInterval(id) + //先等到select里的option元素被扫描后,才根据model设置selected属性 + registerSubscriber(data) + data.changed.call(element, evaluator()) + } else { + innerHTML = currHTML + } + }, 20) + } + duplexBinding.TEXTAREA = duplexBinding.INPUT + //============================= event binding ======================= + + function fixEvent(event) { + var ret = {} + for (var i in event) { + ret[i] = event[i] + } + var target = ret.target = event.srcElement + if (event.type.indexOf("key") === 0) { + ret.which = event.charCode != null ? event.charCode : event.keyCode + } else if (/mouse|click/.test(event.type)) { + var doc = target.ownerDocument || DOC + var box = doc.compatMode === "BackCompat" ? doc.body : doc.documentElement + ret.pageX = event.clientX + (box.scrollLeft >> 0) - (box.clientLeft >> 0) + ret.pageY = event.clientY + (box.scrollTop >> 0) - (box.clientTop >> 0) + ret.wheelDeltaY = ret.wheelDelta + ret.wheelDeltaX = 0 + } + ret.timeStamp = new Date - 0 + ret.originalEvent = event + ret.preventDefault = function() { //阻止默认行为 + event.returnValue = false + } + ret.stopPropagation = function() { //阻止事件在DOM树中的传播 + event.cancelBubble = true + } + return ret + } + + var eventHooks = avalon.eventHooks + //针对firefox, chrome修正mouseenter, mouseleave + if (!("onmouseenter" in root)) { + avalon.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" + }, function(origType, fixType) { + eventHooks[origType] = { + type: fixType, + deel: function(elem, fn) { + return function(e) { + var t = e.relatedTarget + if (!t || (t !== elem && !(elem.compareDocumentPosition(t) & 16))) { + delete e.type + e.type = origType + return fn.call(elem, e) + } + } + } + } + }) + } + //针对IE9+, w3c修正animationend + avalon.each({ + AnimationEvent: "animationend", + WebKitAnimationEvent: "webkitAnimationEnd" + }, function(construct, fixType) { + if (window[construct] && !eventHooks.animationend) { + eventHooks.animationend = { + type: fixType + } + } + }) + //针对IE6-8修正input + if (!("oninput" in DOC.createElement("input"))) { + eventHooks.input = { + type: "propertychange", + deel: function(elem, fn) { + return function(e) { + if (e.propertyName === "value") { + e.type = "input" + return fn.call(elem, e) + } + } + } + } + } + if (DOC.onmousewheel === void 0) { + /* IE6-11 chrome mousewheel wheelDetla 下 -120 上 120 + firefox DOMMouseScroll detail 下3 上-3 + firefox wheel detlaY 下3 上-3 + IE9-11 wheel deltaY 下40 上-40 + chrome wheel deltaY 下100 上-100 */ + var fixWheelType = DOC.onwheel !== void 0 ? "wheel" : "DOMMouseScroll" + var fixWheelDelta = fixWheelType === "wheel" ? "deltaY" : "detail" + eventHooks.mousewheel = { + type: fixWheelType, + deel: function(elem, fn) { + return function(e) { + e.wheelDeltaY = e.wheelDelta = e[fixWheelDelta] > 0 ? -120 : 120 + e.wheelDeltaX = 0 + if (Object.defineProperty) { + Object.defineProperty(e, "type", { + value: "mousewheel" + }) + } + fn.call(elem, e) + } + } + } + } + + /********************************************************************* + * 监控数组(与ms-each, ms-repeat配合使用) * + **********************************************************************/ + + function Collection(model) { + var array = [] + array.$id = generateID() + array.$model = model //数据模型 + array.$events = {} + array.$events[subscribers] = [] + array._ = modelFactory({ + length: model.length + }) + array._.$watch("length", function(a, b) { + array.$fire("length", a, b) + }) + for (var i in EventManager) { + array[i] = EventManager[i] + } + avalon.mix(array, CollectionPrototype) + return array + } + + var _splice = ap.splice + var CollectionPrototype = { + _splice: _splice, + _fire: function(method, a, b) { + notifySubscribers(this.$events[subscribers], method, a, b) + }, + _add: function(arr, pos) { //在第pos个位置上,添加一组元素 + var oldLength = this.length + pos = typeof pos === "number" ? pos : oldLength + var added = [] + for (var i = 0, n = arr.length; i < n; i++) { + added[i] = convert(arr[i]) + } + _splice.apply(this, [pos, 0].concat(added)) + this._fire("add", pos, added) + if (!this._stopFireLength) { + return this._.length = this.length + } + }, + _del: function(pos, n) { //在第pos个位置上,删除N个元素 + var ret = this._splice(pos, n) + if (ret.length) { + this._fire("del", pos, n) + if (!this._stopFireLength) { + this._.length = this.length + } + } + return ret + }, + push: function() { + ap.push.apply(this.$model, arguments) + var n = this._add(arguments) + this._fire("index", n > 2 ? n - 2 : 0) + return n + }, + pushArray: function(array) { + return this.push.apply(this, array) + }, + unshift: function() { + ap.unshift.apply(this.$model, arguments) + this._add(arguments, 0) + this._fire("index", arguments.length) + return this.$model.length //IE67的unshift不会返回长度 + }, + shift: function() { + var el = this.$model.shift() + this._del(0, 1) + this._fire("index", 0) + return el //返回被移除的元素 + }, + pop: function() { + var el = this.$model.pop() + this._del(this.length - 1, 1) + return el //返回被移除的元素 + }, + splice: function(a, b) { + // 必须存在第一个参数,需要大于-1, 为添加或删除元素的基点 + a = _number(a, this.length) + var removed = _splice.apply(this.$model, arguments), + ret = [], change + this._stopFireLength = true //确保在这个方法中 , $watch("length",fn)只触发一次 + if (removed.length) { + ret = this._del(a, removed.length) + change = true + } + if (arguments.length > 2) { + this._add(aslice.call(arguments, 2), a) + change = true + } + this._stopFireLength = false + this._.length = this.length + if (change) { + this._fire("index", 0) + } + return ret //返回被移除的元素 + }, + contains: function(el) { //判定是否包含 + return this.indexOf(el) !== -1 + }, + size: function() { //取得数组长度,这个函数可以同步视图,length不能 + return this._.length + }, + remove: function(el) { //移除第一个等于给定值的元素 + return this.removeAt(this.indexOf(el)) + }, + removeAt: function(index) { //移除指定索引上的元素 + return index >= 0 ? this.splice(index, 1) : [] + }, + clear: function() { + this.$model.length = this.length = this._.length = 0 //清空数组 + this._fire("clear", 0) + return this + }, + removeAll: function(all) { //移除N个元素 + if (Array.isArray(all)) { + all.forEach(function(el) { + this.remove(el) + }, this) + } else if (typeof all === "function") { + for (var i = this.length - 1; i >= 0; i--) { + var el = this[i] + if (all(el, i)) { + this.splice(i, 1) + } + } + } else { + this.clear() + } + }, + ensure: function(el) { + if (!this.contains(el)) { //只有不存在才push + this.push(el) + } + return this + }, + set: function(index, val) { + if (index >= 0) { + var valueType = avalon.type(val) + if (val && val.$model) { + val = val.$model + } + var target = this[index] + if (valueType === "object") { + for (var i in val) { + if (target.hasOwnProperty(i)) { + target[i] = val[i] + } + } + } else if (valueType === "array") { + target.clear().push.apply(target, val) + } else if (target !== val) { + this[index] = val + this.$model[index] = val + this._fire("set", index, val) + } + } + return this + } + } + "sort,reverse".replace(rword, function(method) { + CollectionPrototype[method] = function() { + var aaa = this.$model, + bbb = aaa.slice(0), + sorted = false + ap[method].apply(aaa, arguments) //先移动model + for (var i = 0, n = bbb.length; i < n; i++) { + var a = aaa[i], + b = bbb[i] + if (!isEqual(a, b)) { + sorted = true + var index = bbb.indexOf(a, i) + var remove = this._splice(index, 1)[0] + var remove2 = bbb.splice(index, 1)[0] + this._splice(i, 0, remove) + bbb.splice(i, 0, remove2) + this._fire("move", index, i) + } + } + bbb = void 0 + if (sorted) { + this._fire("index", 0) + } + return this + } + }) + + function convert(val) { + if (rcomplexType.test(avalon.type(val))) { + val = val.$id ? val : modelFactory(val) + } + return val + } + + //============ each/repeat/with binding 用到的辅助函数与对象 ====================== + + //为ms-each, ms-with, ms-repeat要循环的元素外包一个msloop临时节点,ms-controller的值为代理VM的$id + function shimController(data, transation, proxy, fragments) { + var dom = avalon.parseHTML(data.template) + var nodes = avalon.slice(dom.childNodes) + transation.appendChild(dom) + proxy.$outer = data.$outer + var fragment = { + nodes: nodes, + vmodels: [proxy].concat(data.vmodels) + } + fragments.push(fragment) + } + // 取得用于定位的节点。比如data.group = 3, 结构为 + //






    + // 当pos为0时,返回 br#first + // 当pos为1时,返回 br#second + // 当pos为2时,返回 null + function locateFragment(data, pos) { + var comment = data.element + if (data.type == "repeat") {//ms-repeat,data.group为1 + var node = comment.nextSibling + for (var i = 0, n = pos; i < n; i++) { + if (node) { + node = node.nextSibling + } else { + break + } + } + } else { + var nodes = avalon.slice(comment.parentNode.childNodes, 1) + var group = data.group || nodes.length / data.proxies.length + node = nodes[group * pos] + } + return node || null + } + + function removeFragment(node, group, pos) { + var n = group * (pos || 1) + var nodes = [node], i = 1 + var view = hyperspace + while (i < n) { + node = node.nextSibling + if (node) { + nodes[i++] = node + } + } + for (var i = 0; node = nodes[i++]; ) { + view.appendChild(node) + } + return view + } + + function calculateFragmentGroup(data) { + if (!isFinite(data.group)) { + var nodes = avalon.slice(data.element.parentNode.childNodes, 1) + var n = "proxySize" in data ? data.proxySize : data.proxies.length + data.group = nodes.length / n + } + } + // 为ms-each, ms-repeat创建一个代理对象,通过它们能使用一些额外的属性与功能($index,$first,$last,$remove,$key,$val,$outer) + var watchEachOne = oneObject("$index,$first,$last") + + function createWithProxy(key, val, $outer) { + var proxy = modelFactory({ + $key: key, + $outer: $outer, + $val: val + }, { + $val: 1, + $key: 1 + }) + proxy.$id = ("$proxy$with" + Math.random()).replace(/0\./, "") + return proxy + } + var eachProxyPool = [] + function getEachProxy(index, item, data, last) { + var param = data.param || "el", proxy + var source = { + $remove: function() { + return data.$repeat.removeAt(proxy.$index) + }, + $itemName: param, + $index: index, + $outer: data.$outer, + $first: index === 0, + $last: index === last + } + source[param] = item + for (var i = 0, n = eachProxyPool.length; i < n; i++) { + var proxy = eachProxyPool[i] + if (proxy.hasOwnProperty(param)) { + for (var k in source) { + proxy[k] = source[k] + } + eachProxyPool.splice(i, 1) + return proxy + } + } + if (rcomplexType.test(avalon.type(item))) { + source.$skipArray = [param] + } + proxy = modelFactory(source, watchEachOne) + proxy.$watch(param, function(val) { + data.$repeat.set(proxy.$index, val) + }) + proxy.$id = ("$proxy$" + data.type + Math.random()).replace(/0\./, "") + return proxy + } + + function recycleEachProxies(array) { + for (var i = 0, el; el = array[i++]; ) { + recycleEachProxy(el) + } + array.length = 0 + } + + function recycleEachProxy(proxy) { + for (var i in proxy.$events) { + if (Array.isArray(proxy.$events[i])) { + proxy.$events[i].length = 0 + } + } + if (eachProxyPool.unshift(proxy) > kernel.maxRepeatSize) { + eachProxyPool.pop() + } + } + /********************************************************************* + * 自带过滤器 * + **********************************************************************/ + var rscripts = /]*>([\S\s]*?)<\/script\s*>/gim + var ron = /\s+(on[^=\s]+)(?:=("[^"]*"|'[^']*'|[^\s>]+))?/g + var ropen = /<\w+\b(?:(["'])[^"]*?(\1)|[^>])*>/ig + var rsanitize = { + a: /\b(href)\=("javascript[^"]*"|'javascript[^']*')/ig, + img: /\b(src)\=("javascript[^"]*"|'javascript[^']*')/ig, + form: /\b(action)\=("javascript[^"]*"|'javascript[^']*')/ig + } + var rsurrogate = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g + var rnoalphanumeric = /([^\#-~| |!])/g; + var filters = avalon.filters = { + uppercase: function(str) { + return str.toUpperCase() + }, + lowercase: function(str) { + return str.toLowerCase() + }, + truncate: function(target, length, truncation) { + //length,新字符串长度,truncation,新字符串的结尾的字段,返回新字符串 + length = length || 30 + truncation = truncation === void(0) ? "..." : truncation + return target.length > length ? target.slice(0, length - truncation.length) + truncation : String(target) + }, + camelize: camelize, + //https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet + //
    chrome + // chrome + // IE67chrome + // IE67chrome + // IE67chrome + sanitize: function(str) { + return str.replace(rscripts, "").replace(ropen, function(a, b) { + var match = a.toLowerCase().match(/<(\w+)\s/) + if (match) {//处理a标签的href属性,img标签的src属性,form标签的action属性 + var reg = rsanitize[match[1]] + if (reg) { + a = a.replace(reg, function(s, name, value) { + var quote = value.charAt(0) + return name + "=" + quote + "javascript:void(0)" + quote + }) + } + } + return a.replace(ron, " ").replace(/\s+/g, " ")//移除onXXX事件 + }) + }, + escape: function(html) { + //将字符串经过 html 转义得到适合在页面中显示的内容, 例如替换 < 为 < + return String(html). + replace(/&/g, '&'). + replace(rsurrogate, function(value) { + var hi = value.charCodeAt(0) + var low = value.charCodeAt(1) + return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';' + }). + replace(rnoalphanumeric, function(value) { + return '&#' + value.charCodeAt(0) + ';' + }). + replace(//g, '>') + }, + currency: function(number, symbol) { + symbol = symbol || "\uFFE5" + return symbol + avalon.filters.number(number) + }, + number: function(number, decimals, dec_point, thousands_sep) { + //与PHP的number_format完全兼容 + //number 必需,要格式化的数字 + //decimals 可选,规定多少个小数位。 + //dec_point 可选,规定用作小数点的字符串(默认为 . )。 + //thousands_sep 可选,规定用作千位分隔符的字符串(默认为 , ),如果设置了该参数,那么所有其他参数都是必需的。 + // http://kevin.vanzonneveld.net + number = (number + "").replace(/[^0-9+\-Ee.]/g, "") + var n = !isFinite(+number) ? 0 : +number, + prec = !isFinite(+decimals) ? 0 : Math.abs(decimals), + sep = thousands_sep || ",", + dec = dec_point || ".", + s = "", + toFixedFix = function(n, prec) { + var k = Math.pow(10, prec) + return "" + Math.round(n * k) / k + } + // Fix for IE parseFloat(0.55).toFixed(0) = 0 + s = (prec ? toFixedFix(n, prec) : "" + Math.round(n)).split('.') + if (s[0].length > 3) { + s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, sep) + } + if ((s[1] || "").length < prec) { + s[1] = s[1] || "" + s[1] += new Array(prec - s[1].length + 1).join("0") + } + return s.join(dec) + } + } + /* + 'yyyy': 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010) + 'yy': 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10) + 'y': 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199) + 'MMMM': Month in year (January-December) + 'MMM': Month in year (Jan-Dec) + 'MM': Month in year, padded (01-12) + 'M': Month in year (1-12) + 'dd': Day in month, padded (01-31) + 'd': Day in month (1-31) + 'EEEE': Day in Week,(Sunday-Saturday) + 'EEE': Day in Week, (Sun-Sat) + 'HH': Hour in day, padded (00-23) + 'H': Hour in day (0-23) + 'hh': Hour in am/pm, padded (01-12) + 'h': Hour in am/pm, (1-12) + 'mm': Minute in hour, padded (00-59) + 'm': Minute in hour (0-59) + 'ss': Second in minute, padded (00-59) + 's': Second in minute (0-59) + 'a': am/pm marker + 'Z': 4 digit (+sign) representation of the timezone offset (-1200-+1200) + format string can also be one of the following predefined localizable formats: + + 'medium': equivalent to 'MMM d, y h:mm:ss a' for en_US locale (e.g. Sep 3, 2010 12:05:08 pm) + 'short': equivalent to 'M/d/yy h:mm a' for en_US locale (e.g. 9/3/10 12:05 pm) + 'fullDate': equivalent to 'EEEE, MMMM d,y' for en_US locale (e.g. Friday, September 3, 2010) + 'longDate': equivalent to 'MMMM d, y' for en_US locale (e.g. September 3, 2010 + 'mediumDate': equivalent to 'MMM d, y' for en_US locale (e.g. Sep 3, 2010) + 'shortDate': equivalent to 'M/d/yy' for en_US locale (e.g. 9/3/10) + 'mediumTime': equivalent to 'h:mm:ss a' for en_US locale (e.g. 12:05:08 pm) + 'shortTime': equivalent to 'h:mm a' for en_US locale (e.g. 12:05 pm) + */ + new function() { + function toInt(str) { + return parseInt(str, 10) + } + + function padNumber(num, digits, trim) { + var neg = "" + if (num < 0) { + neg = '-' + num = -num + } + num = "" + num + while (num.length < digits) + num = "0" + num + if (trim) + num = num.substr(num.length - digits) + return neg + num + } + + function dateGetter(name, size, offset, trim) { + return function(date) { + var value = date["get" + name]() + if (offset > 0 || value > -offset) + value += offset + if (value === 0 && offset === -12) { + value = 12 + } + return padNumber(value, size, trim) + } + } + + function dateStrGetter(name, shortForm) { + return function(date, formats) { + var value = date["get" + name]() + var get = (shortForm ? ("SHORT" + name) : name).toUpperCase() + return formats[get][value] + } + } + + function timeZoneGetter(date) { + var zone = -1 * date.getTimezoneOffset() + var paddedZone = (zone >= 0) ? "+" : "" + paddedZone += padNumber(Math[zone > 0 ? "floor" : "ceil"](zone / 60), 2) + padNumber(Math.abs(zone % 60), 2) + return paddedZone + } + //取得上午下午 + + function ampmGetter(date, formats) { + return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1] + } + var DATE_FORMATS = { + yyyy: dateGetter("FullYear", 4), + yy: dateGetter("FullYear", 2, 0, true), + y: dateGetter("FullYear", 1), + MMMM: dateStrGetter("Month"), + MMM: dateStrGetter("Month", true), + MM: dateGetter("Month", 2, 1), + M: dateGetter("Month", 1, 1), + dd: dateGetter("Date", 2), + d: dateGetter("Date", 1), + HH: dateGetter("Hours", 2), + H: dateGetter("Hours", 1), + hh: dateGetter("Hours", 2, -12), + h: dateGetter("Hours", 1, -12), + mm: dateGetter("Minutes", 2), + m: dateGetter("Minutes", 1), + ss: dateGetter("Seconds", 2), + s: dateGetter("Seconds", 1), + sss: dateGetter("Milliseconds", 3), + EEEE: dateStrGetter("Day"), + EEE: dateStrGetter("Day", true), + a: ampmGetter, + Z: timeZoneGetter + } + var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/, + NUMBER_STRING = /^\d+$/ + var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/ + // 1 2 3 4 5 6 7 8 9 10 11 + + function jsonStringToDate(string) { + var match + if (match = string.match(R_ISO8601_STR)) { + var date = new Date(0), + tzHour = 0, + tzMin = 0, + dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear, + timeSetter = match[8] ? date.setUTCHours : date.setHours + if (match[9]) { + tzHour = toInt(match[9] + match[10]) + tzMin = toInt(match[9] + match[11]) + } + dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3])) + var h = toInt(match[4] || 0) - tzHour + var m = toInt(match[5] || 0) - tzMin + var s = toInt(match[6] || 0) + var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000) + timeSetter.call(date, h, m, s, ms) + return date + } + return string + } + var rfixFFDate = /^(\d+)-(\d+)-(\d{4})$/ + var rfixIEDate = /^(\d+)\s+(\d+),(\d{4})$/ + filters.date = function(date, format) { + var locate = filters.date.locate, + text = "", + parts = [], + fn, match + format = format || "mediumDate" + format = locate[format] || format + if (typeof date === "string") { + if (NUMBER_STRING.test(date)) { + date = toInt(date) + } else { + var trimDate = date.trim() + if (trimDate.match(rfixFFDate) || trimDate.match(rfixIEDate)) { + date = RegExp.$3 + "/" + RegExp.$1 + "/" + RegExp.$2 + } + date = jsonStringToDate(date) + } + date = new Date(date) + } + if (typeof date === "number") { + date = new Date(date) + } + if (avalon.type(date) !== "date") { + return + } + while (format) { + match = DATE_FORMATS_SPLIT.exec(format) + if (match) { + parts = parts.concat(match.slice(1)) + format = parts.pop() + } else { + parts.push(format) + format = null + } + } + parts.forEach(function(value) { + fn = DATE_FORMATS[value] + text += fn ? fn(date, locate) : value.replace(/(^'|'$)/g, "").replace(/''/g, "'") + }) + return text + } + var locate = { + AMPMS: { + 0: "上午", + 1: "下午" + }, + DAY: { + 0: "星期日", + 1: "星期一", + 2: "星期二", + 3: "星期三", + 4: "星期四", + 5: "星期五", + 6: "星期六" + }, + MONTH: { + 0: "1月", + 1: "2月", + 2: "3月", + 3: "4月", + 4: "5月", + 5: "6月", + 6: "7月", + 7: "8月", + 8: "9月", + 9: "10月", + 10: "11月", + 11: "12月" + }, + SHORTDAY: { + "0": "周日", + "1": "周一", + "2": "周二", + "3": "周三", + "4": "周四", + "5": "周五", + "6": "周六" + }, + fullDate: "y年M月d日EEEE", + longDate: "y年M月d日", + medium: "yyyy-M-d ah:mm:ss", + mediumDate: "yyyy-M-d", + mediumTime: "ah:mm:ss", + "short": "yy-M-d ah:mm", + shortDate: "yy-M-d", + shortTime: "ah:mm" + } + locate.SHORTMONTH = locate.MONTH + filters.date.locate = locate + } + + + /********************************************************************* + * END * + **********************************************************************/ + avalon.ready = noop + avalon.bind(window, "load", function() { + avalon.scan(DOC.body) + }) + avalon.config({ + loader: false + }) + +})(document) +/** + http://www.cnblogs.com/henryzhu/p/mvvm-1-why-mvvm.ht + http://dev.oupeng.com/wp-content/uploads/20131109-kennyluck-optimizing-js-games.html#controls-slide + http://ps.p12345.com/ + */ diff --git a/examples/class.html b/examples/class.html index 226fc789b..81ec870e2 100644 --- a/examples/class.html +++ b/examples/class.html @@ -1,73 +1,43 @@ - - ms-class - + 新风格 + + + - - -

    ms-class

    -
    点我切换类名
    -
    添加多个类名,并且移上去会有hover效果
    -
    类名通过插值表达式生成{{w}} * {{h}} -

    -

    + +
    点我
    +
    它的名类是aaa bbb ccc
    +
    它的名类是bbb ccc aaa
    +
    它的名类是bbb aaa ccc
    +
    它的名类是xxx yyy zzz
    +
    它的名类是XXX YYY ZZZ
    diff --git a/examples/class1.html b/examples/class1.html new file mode 100644 index 000000000..d51e3d194 --- /dev/null +++ b/examples/class1.html @@ -0,0 +1,65 @@ + + + + + ms-class + + + + + + +
    测试:active
    +
    测试:hover
    +
    + 类名通过插值表达式生成
    + {{w}} * {{h}}
    + +
    +

    +
    + +
    + + diff --git a/examples/class3.html b/examples/class3.html new file mode 100644 index 000000000..1f27a0654 --- /dev/null +++ b/examples/class3.html @@ -0,0 +1,39 @@ + + + + ms-class + + + + + + + +
    + +
    {{$index}}{{ new Date - 0 | date("yyyy-MM-dd")}}
    + + diff --git a/examples/css1.html b/examples/css1.html index 40eec61e1..90cf91eae 100644 --- a/examples/css1.html +++ b/examples/css1.html @@ -6,25 +6,24 @@ -

    ms-css

    -

    {{ w }} x {{ h }}

    W:

    H:

    - \ No newline at end of file diff --git a/examples/css2.html b/examples/css2.html index 61889bfa8..60420e6a7 100644 --- a/examples/css2.html +++ b/examples/css2.html @@ -10,9 +10,7 @@ }) - +
    - \ No newline at end of file diff --git a/examples/css3.html b/examples/css3.html index 965f30a87..2113a0ff0 100644 --- a/examples/css3.html +++ b/examples/css3.html @@ -8,6 +8,7 @@ avalon.define("test", function(vm) { vm.o = 0.5 }) + + + + +
    TODO write content
    + + + + diff --git a/examples/dialog1.html b/examples/dialog1.html new file mode 100644 index 000000000..1cb48b7c7 --- /dev/null +++ b/examples/dialog1.html @@ -0,0 +1 @@ +
    这是弹出层
    \ No newline at end of file diff --git a/examples/dialog2.html b/examples/dialog2.html new file mode 100644 index 000000000..71e319e39 --- /dev/null +++ b/examples/dialog2.html @@ -0,0 +1,14 @@ +
    +
    {{username}}
    +
    {{password}}
    + +
    \ No newline at end of file diff --git a/examples/duplex4.html b/examples/duplex4.html index 739171535..da4fb07d9 100644 --- a/examples/duplex4.html +++ b/examples/duplex4.html @@ -4,7 +4,7 @@ 测试同步到$model - + - - - - - -
    -
    -
    - - - - -
    表格1第{{el}}行表格3
    -
    -
    -
    - - \ No newline at end of file diff --git a/examples/href.html b/examples/href.html index 0f334fd91..2b3ea98a9 100644 --- a/examples/href.html +++ b/examples/href.html @@ -18,6 +18,38 @@ }); +
    @@ -26,6 +58,12 @@

    link2

    link3

    +
    +
    + + \ No newline at end of file diff --git a/examples/if1.html b/examples/if1.html index 502ded639..435217924 100644 --- a/examples/if1.html +++ b/examples/if1.html @@ -4,7 +4,7 @@ ms-if - + + + + + + +
    +
      +
    • {{el.name}}
    • +
    + +
    + + \ No newline at end of file diff --git a/examples/include.html b/examples/include.html index 09b4b5be0..e93df6d62 100644 --- a/examples/include.html +++ b/examples/include.html @@ -4,17 +4,18 @@ ms-include - + \ No newline at end of file diff --git a/examples/index.html b/examples/index.html index 149aa3865..d2240ee3e 100644 --- a/examples/index.html +++ b/examples/index.html @@ -22,7 +22,7 @@
    • ms-duplex {{el}}
    • ms-if {{el}}
    • -
    • ms-class
    • +
    • ms-class
    • ms-css-* 0
    • ms-css-* 1
    • ms-css-* 2
    • @@ -36,7 +36,7 @@
    • ms-href
    • ms-visible
    • ms-include
    • -
    • ms-repeat {{el}}
    • +
    • ms-repeat {{el}}
    • skipArray
    • todos 示例
    • grid 示例
    • diff --git a/examples/on0.html b/examples/on0.html index d56aaa25f..26ac1c744 100644 --- a/examples/on0.html +++ b/examples/on0.html @@ -6,24 +6,25 @@ @@ -40,9 +41,9 @@

      ms-on-*

      有关事件回调传参
      {{status}}
      - {{field}} + {{field}}
      -
      点我
      +
      点我

      {{el}}

      diff --git a/examples/on1.html b/examples/on1.html index 9e406892c..eabf65985 100644 --- a/examples/on1.html +++ b/examples/on1.html @@ -7,18 +7,19 @@ diff --git a/examples/on2.html b/examples/on2.html index 7f4cbcaa4..083d311ac 100644 --- a/examples/on2.html +++ b/examples/on2.html @@ -6,25 +6,26 @@ -
      diff --git a/examples/on3.html b/examples/on3.html index 5dc5acef5..7b8fc6874 100644 --- a/examples/on3.html +++ b/examples/on3.html @@ -7,15 +7,15 @@ - + {{a}} \ No newline at end of file diff --git a/examples/on4.html b/examples/on4.html index 086ccccf4..7a8f2b01d 100644 --- a/examples/on4.html +++ b/examples/on4.html @@ -6,14 +6,14 @@ -
      {{aaa|antixss|html}}
      -
      +
      {{text}}
      - - \ No newline at end of file diff --git a/examples/repeat1.html b/examples/repeat1.html index 34fea0efa..57d374f5e 100644 --- a/examples/repeat1.html +++ b/examples/repeat1.html @@ -4,7 +4,7 @@ ms-repeat - + + + + + +
        +
      • {{el.checked}}
      • +
      +
      +
        +
      • {{$key}}
      • +
      +
      +
        +
      1. {{$key}}
      2. +
      + + + + + + + +
      全选{{$one}}
      xxxxxxxxxxxx
      + + + diff --git a/test4.html b/examples/repeat16.html similarity index 92% rename from test4.html rename to examples/repeat16.html index b8af0336d..0cd325b0a 100644 --- a/test4.html +++ b/examples/repeat16.html @@ -3,7 +3,7 @@ ms-repeat - + + + diff --git a/examples/repeat5.html b/examples/repeat5.html index 79316f365..f24477561 100644 --- a/examples/repeat5.html +++ b/examples/repeat5.html @@ -4,7 +4,7 @@ ms-repeat - + + @@ -22,7 +27,7 @@

    下面是ms-each与ms-if的示例

      -
    • {{el}}
    • +
    • {{el}}
    diff --git a/examples/repeat7.html b/examples/repeat7.html index 5385503f7..d962a00d0 100644 --- a/examples/repeat7.html +++ b/examples/repeat7.html @@ -3,7 +3,7 @@ ms-repeat - + - - -

    fix svg innerHTML and outerHTML

    -
    - - - - - - - -

    - -

    -
    - - \ No newline at end of file diff --git a/examples/svg.html b/examples/svg.html index 54900722d..4d427102d 100644 --- a/examples/svg.html +++ b/examples/svg.html @@ -2,48 +2,22 @@ - - + - - - - - - - +
    + + + +
    + \ No newline at end of file diff --git a/examples/tabs.html b/examples/tabs.html new file mode 100644 index 000000000..1a0f54f74 --- /dev/null +++ b/examples/tabs.html @@ -0,0 +1,36 @@ + + + + TODO supply a title + + + + + + + +
    + +
    +
    + 第{{$index + 1}}个面板: {{el}} +
    + + diff --git a/examples/test1.html b/examples/test1.html new file mode 100644 index 000000000..b8f6075fc --- /dev/null +++ b/examples/test1.html @@ -0,0 +1,106 @@ + + + + avalon入门 + + + + + + + +
    +

    First name:

    +

    Last name: {{lastName | html}}

    +

    Hello,

    + +
    这是显示的内容{{firstName +" | "+ lastName }} lastName尝试输入<em>test</em>看看
    +
      +
    • 全选
    • +
    • {{el}} {{$index}}
    • +
    +
      +
    1. ms-each {{el}}
    2. +
    +
    +
    现在悬浮色为{{color}}
    +
    +
    hello,{{html | html}}!
    +
    + + + diff --git a/examples/test2.html b/examples/test2.html new file mode 100644 index 000000000..dce0752be --- /dev/null +++ b/examples/test2.html @@ -0,0 +1,31 @@ + + + + + 测试用例 + + + + + +
    {{$index}} +
    + {{el.aa.d}}{{el.text}} +
    + + +
    + + + \ No newline at end of file diff --git a/examples/test3.html b/examples/test3.html new file mode 100644 index 000000000..e9076ff53 --- /dev/null +++ b/examples/test3.html @@ -0,0 +1,50 @@ + + + + + 测试用例 + + + + + + + \ No newline at end of file diff --git a/examples/test4.html b/examples/test4.html new file mode 100644 index 000000000..ea33524d8 --- /dev/null +++ b/examples/test4.html @@ -0,0 +1,75 @@ + + + + + + ms-duplex + + + + +
    radio +

    根据当前checked属性取反设置其checked,只应用于单个控件

    +

    + + 根据其value属性设置其checked

    +

    + + 根据其value属性设置其checked,要求应用于复数个控件,类型转换为number

    +

    + + 根据其value属性设置其checked,要求应用于复数个控件,类型转换为boolean,原来的ms-duplex-bool

    +

    + + 根据其value属性设置其checked,要求应用于复数个控件,类型转换为string,原来的ms-duplex-text

    +
    +
    checkbox +

    根据当前checked属性取反设置其checked,只应用于单个控件

    +

    + + 根据其value属性设置其checked,要求应用于复数个控件,类型转换为number

    +

    + + 根据其value属性设置其checked,要求应用于复数个控件,类型转换为boolean

    +

    + + 根据其value属性设置其checked,要求应用于复数个控件,类型转换为string

    +
    +
    text,texteara, password +

    {{typeof aaa}}

    +

    {{typeof bbb}}

    +
    +
    select + +
    + + diff --git a/examples/test5.html b/examples/test5.html new file mode 100644 index 000000000..087c043f0 --- /dev/null +++ b/examples/test5.html @@ -0,0 +1,40 @@ + + + + + 测试用例 + + +
    + 下面两组测试数据,按顺序从左到右勾选checkbox,第一组结果正确,第二组结果错误 +
    +
    + x && y:{{f1()}} +
    + x:{{x}} + y:{{y}} +

    + a && b:{{f2()}},{{a && b}} +
    + a:{{a}} + b:{{b}} +
    + + + + + \ No newline at end of file diff --git a/examples/text6.html b/examples/text6.html new file mode 100644 index 000000000..3acbec932 --- /dev/null +++ b/examples/text6.html @@ -0,0 +1,27 @@ + + + + ms-attr-* + + + + + + + + + + +
    + +
    + + diff --git a/examples/todos.html b/examples/todos.html index b25e69be2..f8e6ddf5a 100644 --- a/examples/todos.html +++ b/examples/todos.html @@ -118,7 +118,7 @@ box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0; } - + + + + + +
    + +
    + + +
      +
    • +
      + + + +
      +
      + +
      +
    • +
    +
    +
    + + {{remainingCount}} + item{{remainingCount>1 ? "s" : ""}} left + + + +
    +
    + + + \ No newline at end of file diff --git a/examples/tree.html b/examples/tree.html index 8291b6c9a..3d1791be4 100644 --- a/examples/tree.html +++ b/examples/tree.html @@ -4,7 +4,7 @@ - + - - - - - -
    -
    - -
    - - -

    sss

    - -
    - - diff --git a/test2.html b/test2.html deleted file mode 100644 index bd5c6a02a..000000000 --- a/test2.html +++ /dev/null @@ -1,93 +0,0 @@ - - - - TODO supply a title - - - - - - - -
    TODO write content
    - - diff --git a/test3.html b/test3.html deleted file mode 100644 index e4b4c4ec8..000000000 --- a/test3.html +++ /dev/null @@ -1,132 +0,0 @@ - - - - - 路由器的相关测试 - - - - - - - - -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    名字默认值说明
    配置参数
    view"" 容器的名字, ms-view="name"中的name
    element null 容器元素,即ms-view所定义的元素节点
    template null 字符串或函数,如果是函数会将入路由的参数与将路由对象作为this, - 最后返回容器要添加的HTML模板
    templateUrl null字符串或函数,如果是函数会将入路由的参数与将路由对象作为this, - 最后将返回一个URL地址,框架通过它得到template。template会缓存到路由对象上
    templates {}如果存在templateUrl,它会通过ajax请求加载对应的模块,缓存在这里
    regexp 路由规则对应的正则
    path 地址中被配匹的部分
    args []抽取路由规则中的参数名对应的实际值放在这里
    params{} 将路由规则中的参数名与对应的实际值 以键值对形式 构成一个对象
    callback(params) 当模板插入后,执行的回调函数,参数为args,值为路由对象
    query {} 地址栏上的query组成的对象
    -
    - - - diff --git a/test5.html b/test5.html deleted file mode 100644 index bb70688ba..000000000 --- a/test5.html +++ /dev/null @@ -1,41 +0,0 @@ - - - - ms-attr-* - - - - - - - - - -
    - - - - - - - - - -
    - - diff --git a/test6.html b/test6.html deleted file mode 100644 index b110f732c..000000000 --- a/test6.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - TODO supply a title - - - - - - - - -
    -
      -
    • {{elem}}
    • -
    -
    - - \ No newline at end of file From 5ce535a406fb8863b3904b3cc5f6543ba15080b2 Mon Sep 17 00:00:00 2001 From: ilife5 Date: Fri, 21 Nov 2014 17:22:18 +0800 Subject: [PATCH 5/6] =?UTF-8?q?fix=20=E5=A4=9A=E8=A7=86=E5=9B=BE=E4=BA=8B?= =?UTF-8?q?=E4=BB=B6=E4=BC=A0=E6=92=AD=E6=97=A0=E6=B3=95=E4=BC=A0=E6=92=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- avalon.js | 103 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 62 insertions(+), 41 deletions(-) diff --git a/avalon.js b/avalon.js index fad070006..ca43fa592 100644 --- a/avalon.js +++ b/avalon.js @@ -1775,64 +1775,85 @@ type = RegExp.$2 } var events = this.$events - var callbacks = events[type] || [] - var all = events.$all || [] var args = aslice.call(arguments, 1) - for (var i = 0, callback; callback = callbacks[i++]; ) { - if (isFunction(callback)) - callback.apply(this, args) - } - for (var i = 0, callback; callback = all[i++]; ) { - if (isFunction(callback)) - callback.apply(this, arguments) - } - var element = events.expr && findNode(events.expr) - if (element) { - var detail = [type].concat(args) - var alls = [] - if (special === "up" || special === "down" || special === "all") { - for (var i in avalon.vmodels) { - var v = avalon.vmodels[i] - if (v && v.$events && v.$events.expr) { - if (v !== this) { - var node = findNode(v.$events.expr) - if (!node) { - continue - } - var ok = special === "all" ? 1 : //全局广播 - special === "down" ? element.contains(node) : //向下捕获 - node.contains(element)//向上冒泡 - if (ok) { - alls.push([node, v]) - } + var detail = [type].concat(args) + if (special === "all") { + for (var i in avalon.vmodels) { + var v = avalon.vmodels[i] + if (v !== this) { + v.$fire.apply(v, detail) + } + } + } else if (special === "up" || special === "down") { + var elements = events.expr && findNodes(events.expr) + if (elements.length === 0) + return + for (var i in avalon.vmodels) { + var v = avalon.vmodels[i] + if (v !== this) { + if (v.$events.expr) { + var eventNodes = findNodes(v.$events.expr) + if (eventNodes.length === 0) { + continue } + //循环两个vmodel中的节点,查找匹配(向上匹配或者向下匹配)的节点并设置标识 + avalon.each(eventNodes, function(i, node) { + avalon.each(elements, function(j, element) { + var ok = special === "down" ? element.contains(node) : //向下捕获 + node.contains(element) //向上冒泡 + + if (ok) { + node._avalon = v //符合条件的加一个标识 + } + }); + }) } } - var nodes = DOC.getElementsByTagName("*")//实现节点排序 - alls.sort(function(a, b) { - return Array.prototype.indexOf.call(nodes, a[0]) - Array.prototype.indexOf.call(nodes, b[0]) - }) - if (special === "up") { - alls.reverse() + } + var nodes = DOC.getElementsByTagName("*") //实现节点排序 + var alls = [] + Array.prototype.forEach.call(nodes, function(el) { + if (el._avalon) { + alls.push(el._avalon) + el._avalon = "" + el.removeAttribute("_avalon") } - alls.forEach(function(v) { - v[1].$fire.apply(v[1], detail) - }) + }) + if (special === "up") { + alls.reverse() + } + for (var i = 0, el; el = alls[i++]; ) { + if (el.$fire.apply(el, detail) === false) { + break + } + } + } else { + var callbacks = events[type] || [] + var all = events.$all || [] + for (var i = 0, callback; callback = callbacks[i++]; ) { + if (isFunction(callback)) + callback.apply(this, args) + } + for (var i = 0, callback; callback = all[i++]; ) { + if (isFunction(callback)) + callback.apply(this, arguments) } } } } var ravalon = /(\w+)\[(avalonctrl)="(\S+)"\]/ - var findNode = DOC.querySelector ? function(str) { - return DOC.querySelector(str) + var findNodes = DOC.querySelector ? function(str) { + return DOC.querySelector(str) } : function(str) { var match = str.match(ravalon) var all = DOC.getElementsByTagName(match[1]) + var nodes = [] for (var i = 0, el; el = all[i++]; ) { if (el.getAttribute(match[2]) === match[3]) { - return el + nodes.push(el) } } + return nodes } /********************************************************************* * 依赖调度系统 * From bcf4310e88ed33606277be6230167239a32995da Mon Sep 17 00:00:00 2001 From: ilife5 Date: Fri, 21 Nov 2014 17:49:08 +0800 Subject: [PATCH 6/6] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=A4=9A=E8=A7=86?= =?UTF-8?q?=E5=9B=BE=E4=BA=8B=E4=BB=B6=E4=BC=A0=E6=92=AD=E4=B8=8D=E5=88=B0?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- avalon.js | 32 ++++++++++++++++++++------------ examples/$fire3.html | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 12 deletions(-) create mode 100644 examples/$fire3.html diff --git a/avalon.js b/avalon.js index 7f4beb80c..b734bd885 100644 --- a/avalon.js +++ b/avalon.js @@ -1798,22 +1798,28 @@ } } } else if (special === "up" || special === "down") { - var element = events.expr && findNode(events.expr) - if (!element) + var elements = events.expr && findNodes(events.expr) + if (elements.length === 0) return for (var i in avalon.vmodels) { var v = avalon.vmodels[i] if (v !== this) { if (v.$events.expr) { - var node = findNode(v.$events.expr) - if (!node) { + var eventNodes = findNodes(v.$events.expr) + if (eventNodes.length === 0) { continue } - var ok = special === "down" ? element.contains(node) : //向下捕获 - node.contains(element) //向上冒泡 - if (ok) { - node._avalon = v //符合条件的加一个标识 - } + //循环两个vmodel中的节点,查找匹配(向上匹配或者向下匹配)的节点并设置标识 + avalon.each(eventNodes, function(i, node) { + avalon.each(elements, function(j, element) { + var ok = special === "down" ? element.contains(node) : //向下捕获 + node.contains(element) //向上冒泡 + + if (ok) { + node._avalon = v //符合条件的加一个标识 + } + }); + }) } } } @@ -1849,16 +1855,18 @@ } } var ravalon = /(\w+)\[(avalonctrl)="(\S+)"\]/ - var findNode = DOC.querySelector ? function(str) { - return DOC.querySelector(str) + var findNodes = DOC.querySelectorAll ? function(str) { + return DOC.querySelectorAll(str) } : function(str) { var match = str.match(ravalon) var all = DOC.getElementsByTagName(match[1]) + var nodes = [] for (var i = 0, el; el = all[i++]; ) { if (el.getAttribute(match[2]) === match[3]) { - return el + nodes.push(el) } } + return nodes } /********************************************************************* * 依赖调度系统 * diff --git a/examples/$fire3.html b/examples/$fire3.html new file mode 100644 index 000000000..97cbc062e --- /dev/null +++ b/examples/$fire3.html @@ -0,0 +1,36 @@ + + + + 事件在多视图中间的传播 + + + + + +
    +
    +
    +
    + +
    +
    + + + \ No newline at end of file