diff --git a/MIT-LICENSE.txt b/MIT-LICENSE.txt deleted file mode 100644 index f16992e2f..000000000 --- a/MIT-LICENSE.txt +++ /dev/null @@ -1,20 +0,0 @@ -Copyright 2012 RubyLouvre http://www.cnblogs.com/rubylouvre/ - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index 4ca52bc88..69177d03a 100644 --- a/README.md +++ b/README.md @@ -5,39 +5,40 @@ 当下的前端开发就是一边填坑一边写业务中进行。avalon的诞生改变了这一切,让我们摆脱DOM的掣肘,专注于需求本身,将可变的数据与操作数据的方法封装成模型。 在更高的层次上组织代码,提高软件的可维护性,可扩展性和可重用性。


- -

优势

- -
-

学习教程

-

avalon学习教程

-

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

+* [avalon](https://github.com/RubyLouvre/avalon)现在有三个分支:avalon.js 兼容IE6,及标准浏览器;avalon.modern.js 则只支持IE10及其以上版本,及标准浏览器,主流山寨浏览器(QQ, 猎豹, 搜狗, 360, 傲游) ;avalon.observe是用于研究es6的新特性,使用Object.observe实现的; +* [avalon](https://github.com/RubyLouvre/avalon)拥有强大的组件库,现在由去哪儿网前端架构组在维护与升级,[这里](http://ued.qunar.com/);首先是三柱臣,想使用路由器,可以用[mmRouter](https://github.com/RubyLouvre/mmRouter), 想使用动画,可以用[mmAnimate](https://github.com/RubyLouvre/mmAnimate), 想使用AJAX,可以用[mmRequest](https://github.com/RubyLouvre/mmRequest); 其是是OniUI,树组件差不多开发完毕,届时就有一个拥有2个Grid,1个树,1 个验证插件等总数近50个UI组件的库了。 +* avalon的测试比较庞大,放在独立的仓库中——[avalon.test](https://github.com/RubyLouvre/avalon.test) + +优势 +====== +* 使用简单,在HTML中添加绑定,在JS中用avalon.define定义ViewModel,再调用avalon.scan方法,它就能动了! +* 兼容到 **IE6** (其他MVVM框架,KnockoutJS(IE6), AngularJS(IE9), EmberJS(IE8), WinJS(IE9) ),另有avalon.mobile,它可以更高效地运行于IE10等新版本浏览器中 +* 没有任何依赖,不到5000行,压缩后不到50KiB +* 支持管道符风格的过滤函数,方便格式化输出 +* 局部刷新的颗粒度已细化到一个文本节点,特性节点 +* 要操作的节点,在第一次扫描就与视图刷新函数相绑定,并缓存起来,因此没有选择器出场的余地。 +* 让DOM操作的代码近乎绝迹 +* 使用类似CSS的重叠覆盖机制,让各个ViewModel分区交替地渲染页面 +* 节点移除时,智能卸载对应的视图刷新函数,节约内存 +* **操作数据即操作DOM**,对ViewModel的操作都会同步到View与Model去 +* 自带AMD模块加载器,省得与其他加载器进行整合 + +学习教程 +====== +* [avalon学习教程](http://www.html-js.com/article/column/234) +* [avalon-learning](http://limodou.github.io/avalon-learning/zh_CN/index.html) +* [入门教程](http://www.cnblogs.com/rubylouvre/p/3181291.html) +* [HTML5交流会有关avalon的PPT](http://vdisk.weibo.com/s/aMO9PyIQCnLOF/1375154475) +* [avalon最佳实践](http://www.cnblogs.com/rubylouvre/p/3385373.html) + + +运行github中的示例 +===== +将项目下载到本地,里面有一个叫server.exe的.Net小型服务器(可以需要安装[.Net4.0](http://dl.pconline.com.cn/download/54972.html)), +点击它然后打开里面与index开头的HTML文件,一边看运行效果,一边看源码进行学习。 +![Alt text](https://raw.github.com/RubyLouvre/avalon/master/examples/images/example.jpg) +

运行github中的示例

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

@@ -133,3 +134,7 @@ java -jar compiler.jar --js avalon.modern.js --js_output_file avalon.modern.min. - 自带过滤器 - AMD加载器 - DOMReady + +

LOGO来历

+ +http://tieba.baidu.com/p/1350048586 \ No newline at end of file diff --git a/avalon.iscroll.js b/avalon.iscroll.js deleted file mode 100644 index c5b2fc2c0..000000000 --- a/avalon.iscroll.js +++ /dev/null @@ -1,1178 +0,0 @@ -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 fad070006..53928681a 100644 --- a/avalon.js +++ b/avalon.js @@ -1,11 +1,11 @@ /*================================================== - Copyright 2013-2014 司徒正美 and other contributors + Copyright (c) 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.6 2014.10.10 + avalon 1.3.7 2014.11.17 support IE6+ and other browsers ==================================================*/ (function(DOC) { /********************************************************************* @@ -13,13 +13,13 @@ **********************************************************************/ 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") + //http://stackoverflow.com/questions/7290086/javascript-use-strict-and-nicks-find-global-function + var window = Function("return this")() var otherRequire = window.require var otherDefine = window.define var stopRepeatAssign = false var rword = /[^, ]+/g //切割字符串为一个个小块,以空格或豆号分开它们,结合replace实现字符串的forEach - var rnative = /\[native code\]/ //判定是否原生函数 + var rnative = /\[native code\]/ //判定是否原生函数 var rcomplexType = /^(?:object|array)$/ var rsvg = /^\[object SVG\w*Element\]$/ var rwindow = /^\[object (?:Window|DOMWindow|global)\]$/ @@ -62,7 +62,8 @@ 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) } @@ -78,7 +79,7 @@ } avalon.fn = avalon.prototype = avalon.init.prototype - avalon.type = function(obj) {//取得目标的类型 + avalon.type = function(obj) { //取得目标的类型 if (obj == null) { return String(obj) } @@ -117,19 +118,17 @@ for (enu in avalon({})) { break } - var enumerateBUG = enu !== "0"//IE6下为true, 其他为false + 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")) { + try { //IE内置对象没有constructor + if (obj.constructor && !ohasOwn.call(obj, "constructor") && !ohasOwn.call(obj.constructor.prototype, "isPrototypeOf")) { return false; } - } catch (e) {//IE8 9会在这里抛错 + } catch (e) { //IE8 9会在这里抛错 return false; } if (enumerateBUG) { @@ -146,7 +145,7 @@ 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] || {}, @@ -161,24 +160,24 @@ 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] try { - copy = options[name]//当options为VBS对象时报错 + copy = options[name] //当options为VBS对象时报错 } catch (e) { continue } @@ -309,6 +308,9 @@ name = avalon.cssName(prop) || prop if (value === void 0 || typeof value === "boolean") { //获取样式 var fn = cssHooks[prop + ":get"] || cssHooks["@:get"] + if (name === "background") { + name = "backgroundColor" + } var val = fn(node, name) return value === true ? parseFloat(val) || 0 : val } else if (value === "") { //请除样式 @@ -376,6 +378,7 @@ }) /*判定类数组,如节点集合,纯数组,arguments与拥有非负整数的length属性的纯JS对象*/ + function isArrayLike(obj) { if (obj && typeof obj === "object" && !avalon.isWindow(obj)) { var n = obj.length @@ -401,13 +404,13 @@ * modelFactory * **********************************************************************/ //avalon最核心的方法的两个方法之一(另一个是avalon.scan),返回一个ViewModel(VM) - var VMODELS = avalon.vmodels = {}//所有vmodel都储存在这里 + var VMODELS = avalon.vmodels = {} //所有vmodel都储存在这里 avalon.define = function(id, factory) { var $id = id.$id || id if (!$id) { log("warning: vm必须指定$id") } - if (VMODELS[id]) { + if (VMODELS[$id]) { log("warning: " + $id + " 已经存在于avalon.vmodels中") } if (typeof id === "object") { @@ -428,6 +431,7 @@ //一些不需要被监听的属性 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 @@ -456,23 +460,23 @@ if (typeof $scope.nodeType === "number") { return $scope } - if ($scope.$id && $scope.$model && $scope.$events) {//fix IE6-8 createWithProxy $val: val引发的BUG + 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 || {}//强制要监听的属性 + $scope.$skipArray.$special = $special || {} //强制要监听的属性 var $vmodel = {} //要返回的对象, 它在IE6-8下可能被偷龙转凤 - $model = $model || {} //vmodels.$model属性 + $model = $model || {} //vmodels.$model属性 var $events = {} //vmodel.$events属性 var watchedProperties = {} //监控属性 - var computedProperties = [] //计算属性 + var computedProperties = [] //计算属性 for (var i in $scope) { (function(name, val) { $model[name] = val if (!isObservable(name, val, $scope.$skipArray)) { - return //过滤所有非监控属性 + return //过滤所有非监控属性 } //总共产生三种accessor var accessor @@ -535,7 +539,7 @@ newValue = $model[name] = childVmodel.$model //同步$model var fn = rebindings[childVmodel.$id] fn && fn() //同步视图 - safeFire($vmodel, name, newValue, oldValue) //触发$watch回调 + safeFire($vmodel, name, newValue, oldValue) //触发$watch回调 } } else { collectSubscribers($events[name]) //收集视图函数 @@ -567,7 +571,7 @@ $$skipArray.forEach(function(name) { delete $scope[name] - delete $model[name] //这些特殊属性不应该在$model中出现 + delete $model[name] //这些特殊属性不应该在$model中出现 }) $vmodel = defineProperties($vmodel, descriptorFactory(watchedProperties), $scope) //生成一个空的ViewModel @@ -581,7 +585,7 @@ $vmodel.$model = $model $vmodel.$events = $events for (var i in EventManager) { - var fn = EventManager [i] + var fn = EventManager[i] if (!W3C) { //在IE6-8下,VB对象的方法里的this并不指向自身,需要用bind处理一下 fn = fn.bind($vmodel) } @@ -591,7 +595,7 @@ $vmodel.hasOwnProperty = function(name) { return name in $vmodel.$model } - computedProperties.forEach(function(collect) {//收集依赖 + computedProperties.forEach(function(collect) { //收集依赖 collect() }) return $vmodel @@ -640,6 +644,7 @@ } } //应用于第2种accessor + function updateChild(parent, name, value, valueType) { //a为原来的VM, b为新数组或新对象 var son = parent[name] @@ -657,6 +662,7 @@ delete withProxyPool[son.$id] } var ret = modelFactory(value) + ret.$events[subscribers] = iterators rebindings[ret.$id] = function(data) { while (data = iterators.shift()) { (function(el) { @@ -810,7 +816,9 @@ return this.replace(rtrim, "") } } - var hasDontEnumBug = !({'toString': null}).propertyIsEnumerable('toString'), + var hasDontEnumBug = !({ + 'toString': null + }).propertyIsEnumerable('toString'), hasProtoEnumBug = (function() { }).propertyIsEnumerable('prototype'), dontEnums = [ @@ -921,7 +929,7 @@ **********************************************************************/ function fixContains(root, el) { - try {//IE6-8,游离于DOM树外的文本节点,访问parentNode有时会抛错 + try { //IE6-8,游离于DOM树外的文本节点,访问parentNode有时会抛错 while ((el = el.parentNode)) if (el === root) return true; @@ -943,6 +951,7 @@ return fixContains(DOC, b) } } + function outerHTML() { return new XMLSerializer().serializeToString(this) } @@ -952,7 +961,7 @@ var svgns = "http://www.w3.org/2000/svg" var svg = DOC.createElementNS(svgns, "svg") svg.innerHTML = '' - if (!rsvg.test(svg.firstChild)) {// #409 + if (!rsvg.test(svg.firstChild)) { // #409 function enumerateNode(node, targetNode) { if (node && node.childNodes) { var nodes = node.childNodes @@ -961,7 +970,7 @@ var svg = DOC.createElementNS(svgns, el.tagName.toLowerCase()) ap.forEach.call(el.attributes, function(attr) { - svg.setAttribute(attr.name, attr.value)//复制属性 + svg.setAttribute(attr.name, attr.value) //复制属性 }) // 递归处理子节点 enumerateNode(el, svg) @@ -998,10 +1007,10 @@ 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, "") + return s.replace(ropen, "").replace(rclose, "") }, set: function(html) { - if (avalon.clearHTM) { + if (avalon.clearHTML) { avalon.clearHTML(this) var frag = avalon.parseHTML(html) enumerateNode(frag, this) @@ -1018,6 +1027,7 @@ /********************************************************************* * 配置系统 * **********************************************************************/ + function kernel(settings) { for (var p in settings) { if (!ohasOwn.call(settings, p)) @@ -1040,6 +1050,7 @@ //将字符串安全格式化为正则表达式的源码 return (target + "").replace(rregexp, "\\$&") } + var innerRequire = noop var plugins = { loader: function(builtin) { window.define = builtin ? innerRequire.define : otherDefine @@ -1097,7 +1108,7 @@ var ClassListMethods = { _toString: function() { - var node = this.node//IE6,7元素节点不存在hasAttribute方法 + var node = this.node var cls = node.className var str = typeof cls === "string" ? cls : cls.baseVal return str.split(/\s+/).join(" ") @@ -1117,11 +1128,12 @@ var node = this.node if (typeof node.className === "string") { node.className = cls - } else {//SVG元素的className是一个对象 SVGAnimatedString { baseVal="", animVal=""},只能通过set/getAttribute操作 + } else { //SVG元素的className是一个对象 SVGAnimatedString { baseVal="", animVal=""},只能通过set/getAttribute操作 node.setAttribute("class", cls) } - }//toggle存在版本差异,因此不使用它 + } //toggle存在版本差异,因此不使用它 } + function ClassList(node) { if (!("classList" in node)) { node.classList = { @@ -1325,8 +1337,7 @@ var cssHooks = avalon.cssHooks = {} var prefixes = ["", "-webkit-", "-o-", "-moz-", "-ms-"] var cssMap = { - "float": "cssFloat", - background: "backgroundColor" + "float": "cssFloat" } avalon.cssNumber = oneObject("columnCount,order,fillOpacity,fontWeight,lineHeight,opacity,orphans,widows,zIndex,zoom") @@ -1465,7 +1476,7 @@ } } } - "Width,Height".replace(rword, function(name) {//fix 481 + "Width,Height".replace(rword, function(name) { //fix 481 var method = name.toLowerCase(), clientProp = "client" + name, scrollProp = "scroll" + name, @@ -1476,21 +1487,15 @@ 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) + 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 } @@ -1534,10 +1539,11 @@ } }) avalon.fn.offset = function() { //取得距离页面左右角的坐标 - var node = this[0], box = { - left: 0, - top: 0 - } + var node = this[0], + box = { + left: 0, + top: 0 + } if (!node || !node.tagName || !node.ownerDocument) { return box } @@ -1576,13 +1582,12 @@ } var roption = /^]+))?)*\s+value[\s=]/i var valHooks = { - "option:get": function(node) { + "option:get": window.VBArray ? 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 + return roption.test(node.outerHTML) ? node.value : node.text.trim() + } : function(node) { + return node.value }, "select:get": function(node, value) { var option, options = node.options, @@ -1625,13 +1630,7 @@ /************************************************************************ * 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, ""], @@ -1642,7 +1641,7 @@ thead: [1, "", "
"], tr: [2, ""], td: [3, "
"], - text: [1, '', ''], + g: [1, '', ''], //IE6-8在用innerHTML生成节点时,不能直接创建no-scope元素与HTML5的新标签 _default: W3C ? [0, ""] : [1, "X
"] //div可以不用闭合 } @@ -1650,9 +1649,14 @@ 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 + String("circle,defs,ellipse,image,line,path,polygon,polyline,rect,symbol,text,use").replace(rword, function(tag) { + tagHooks[tag] = tagHooks.g //处理SVG + }) + var rtagName = /<([\w:]+)/ //取得其tagName + var rxhtml = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig + var rcreate = W3C ? /[^\d\D]/ : /(<(?:script|link|style|meta|noscript))/ig + var scriptTypes = oneObject(["", "text/javascript", "text/ecmascript", "application/ecmascript", "application/javascript"]) + var rnest = /<(?:tb|td|tf|th|tr|col|opt|leg|cap|area)/ //需要处理套嵌关系的标签 var script = DOC.createElement("script") avalon.parseHTML = function(html) { if (typeof html !== "string") { @@ -1672,14 +1676,16 @@ 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能让其执行脚本 + if (scriptTypes[el.type]) { + //以偷龙转凤方式恢复执行脚本功能 neo = script.cloneNode(false) //FF不能省略参数 ap.forEach.call(el.attributes, function(attr) { if (attr && attr.specified) { neo[attr.name] = attr.value //复制其属性 + neo.setAttribute(attr.name, attr.value) } }) - neo.text = el.text //必须指定,因为无法在attributes中遍历出来 + neo.text = el.text el.parentNode.replaceChild(neo, el) //替换节点 } } @@ -1691,23 +1697,35 @@ for (els = wrapper["getElementsByTagName"]("br"), i = 0; el = els[i++]; ) { if (el.className && el.className === "msNoScope") { el.parentNode.removeChild(el) + i-- } } - for (els = wrapper.all, i = 0; el = els[i++]; ) {//fix VML + for (els = wrapper.all, i = 0; el = els[i++]; ) { //fix VML if (isVML(el)) { fixVML(el) } } + if (tag === "tr") { + for (els = wrapper.children, i = 0; el = els[i++]; ) { + // IE6-8,如果动态生成tr元素,必须会在后面添加早已废弃caption的标签,其nodeName,innerHTML都为"" + if (el.nodeName == "") { + el.parentNode.removeChild(el) + i-- + } + } + } } 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 === "" + return nodeName.toLowerCase() === nodeName && src.scopeName && src.outerText === "" } + function fixVML(node) { if (node.currentStyle.behavior !== "url(#default#VML)") { node.style.behavior = "url(#default#VML)" @@ -1733,6 +1751,7 @@ } return node } + /********************************************************************* * 事件管理器 * **********************************************************************/ @@ -1775,56 +1794,69 @@ 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 element = events.expr && findNode(events.expr) + if (!element) + 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) { + continue + } + 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) + return DOC.querySelector(str) } : function(str) { var match = str.match(ravalon) var all = DOC.getElementsByTagName(match[1]) @@ -1838,6 +1870,7 @@ * 依赖调度系统 * **********************************************************************/ var ronduplex = /^(duplex|on)$/ + function registerSubscriber(data) { Registry[expose] = data //暴光此函数,方便collectSubscribers收集 avalon.openComputedCollect = true @@ -1867,12 +1900,31 @@ function collectSubscribers(list) { //收集依赖于这个访问器的订阅者 var data = Registry[expose] if (list && data && avalon.Array.ensure(list, data) && data.element) { //只有数组不存在此元素才push进去 - $$subscribers.push({ - data: data, list: list - }) + addSubscribers(data, list) + } + } + + function addSubscribers(data, list) { + data.$uuid = data.$uuid || generateID() + list.$uuid = list.$uuid || generateID() + var obj = { + data: data, + list: list, + toString: function() { + return data.$uuid + " " + list.$uuid + } + } + if (!$$subscribers[obj]) { + $$subscribers[obj] = 1 + $$subscribers.push(obj) } } - var $$subscribers = [], $startIndex = 0, $maxIndex = 200 + var $$subscribers = [], + $startIndex = 0, + $maxIndex = 200, + beginTime = new Date(), + removeID + function removeSubscribers() { for (var i = $startIndex, n = $startIndex + $maxIndex; i < n; i++) { var obj = $$subscribers[i] @@ -1885,10 +1937,11 @@ el.sourceIndex === 0 : !root.contains(el) : !avalon.contains(root, el)) if (remove) { //如果它没有在DOM树 $$subscribers.splice(i, 1) + delete $$subscribers[obj] 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) + if (data.type === "if" && data.template && data.template.parentNode === ifGroup) { + ifGroup.removeChild(data.template) } for (var key in data) { data[key] = null @@ -1896,6 +1949,7 @@ obj.data = obj.list = null i-- n-- + } } obj = $$subscribers[i] @@ -1904,26 +1958,27 @@ } else { $startIndex = 0 } + beginTime = new Date() } - var beginTime = new Date(), removeID + function notifySubscribers(list) { //通知依赖于这个访问器的订阅者更新自身 - var currentTime = new Date() clearTimeout(removeID) - if (currentTime - beginTime > 333) { + if (new Date() - beginTime > 444) { removeSubscribers() - beginTime = new Date() } else { - removeID = setTimeout(removeSubscribers, 333) + removeID = setTimeout(removeSubscribers, 444) } 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) + if (el && el.parentNode) { + if (fn.$repeat) { + fn.handler.apply(fn, args) //处理监控数组的方法 + } else if (fn.type !== "on") { //事件绑定只能由用户触发,不能由程序触发 + var fun = fn.evaluator || noop + fn.handler(fun.apply(0, fn.args || []), el, fn) + } } } } @@ -1932,32 +1987,58 @@ /********************************************************************* * 扫描系统 * **********************************************************************/ - avalon.scan = function(elem, vmodel) { + var scanObject = {} + avalon.scanCallback = function(fn, group) { + group = group || "$all" + var array = scanObject[group] || (scanObject[group] = []) + array.push(fn) + } + avalon.scan = function(elem, vmodel, group) { elem = elem || root + group = group || "$all" + var array = scanObject[group] || [] var vmodels = vmodel ? [].concat(vmodel) : [] + var scanIndex = 0; + var scanAll = false + var fn + var dirty = false + function cb(i) { + scanIndex += i + dirty = true + setTimeout(function() { + if (scanIndex <= 0 && !scanAll) { + scanAll = true + while (fn = array.shift()) { + fn() + } + } + }) + } + vmodels.cb = cb scanTag(elem, vmodels) + //html, include, widget + if (!dirty) { + while (fn = array.shift()) { + fn() + } + } } //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 checkScan(elem, callback, innerHTML) { + var id = setTimeout(function() { + var currHTML = elem.innerHTML + clearTimeout(id) + if (currHTML === innerHTML) { + callback() + } else { + checkScan(elem, callback, currHTML) + } + }) } - 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)垫后 @@ -1976,17 +2057,24 @@ return } //ms-important不包含父VM,ms-controller相反 + var cb = vmodels.cb vmodels = node === b ? [newVmodel] : [newVmodel].concat(vmodels) + vmodels.cb = cb var name = node.name elem.removeAttribute(name) //removeAttributeNode不会刷新[ms-controller]样式规则 - elem.setAttribute("avalonctrl", node.value) - newVmodel.$events.expr = elem.tagName + '[avalonctrl="' + node.value + '"]' + createSignalTower(elem, newVmodel) avalon(elem).removeClass(name) } scanAttr(elem, vmodels) //扫描特性节点 } + function createSignalTower(elem, vmodel) { + var id = elem.getAttribute("avalonctrl") || vmodel.$id + elem.setAttribute("avalonctrl", id) + vmodel.$events.expr = elem.tagName + '[avalonctrl="' + id + '"]' + } + function scanNodeList(parent, vmodels) { var node = parent.firstChild while (node) { @@ -2001,6 +2089,7 @@ scanNode(node, node.nodeType, vmodels) } } + function scanNode(node, nodeType, vmodels) { if (nodeType === 1) { scanTag(node, vmodels) //扫描元素节点 @@ -2068,9 +2157,11 @@ "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 } + var obsoleteAttrs = oneObject("value,title,alt,checked,selected,disabled,readonly,enabled") function scanAttr(elem, vmodels) { //防止setAttribute, removeAttribute时 attributes自动被同步,导致for循环出错 var attributes = getAttributes ? getAttributes(elem) : avalon.slice(elem.attributes) @@ -2089,12 +2180,12 @@ 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") { + } else if (obsoleteAttrs[type]) { + log("ms-" + type + "已经被废弃,请使用ms-attr-*代替") + if (type === "enabled") { //吃掉ms-enabled绑定,用ms-disabled代替 + type = "disabled" + value = "!(" + value + ")" + } param = type type = "attr" elem.removeAttribute(name) @@ -2130,6 +2221,7 @@ log("warning!一个元素上不能同时定义ms-checked与ms-duplex") } var firstBinding = bindings[0] || {} + switch (firstBinding.type) { case "if": case "repeat": @@ -2141,10 +2233,8 @@ if (!stopScan[elem.tagName] && rbind.test(elem.innerHTML.replace(rlt, "<").replace(rgt, ">"))) { scanNodeList(elem, vmodels) //扫描子孙元素 } - break; + break } - - } //IE67下,在循环绑定中,一个节点如果是通过cloneNode得到,自定义属性的specified为false,无法进入里面的分支, //但如果我们去掉scanAttr中的attr.specified检测,一个元素会有80+个特性节点(因为它不区分固有属性与自定义属性),很容易卡死页面 @@ -2195,6 +2285,9 @@ } function executeBindings(bindings, vmodels) { + if (bindings.length) + vmodels.cb(bindings.length) + for (var i = 0, data; data = bindings[i++]; ) { data.vmodels = vmodels bindingHandlers[data.type](data, vmodels) @@ -2212,6 +2305,7 @@ r11b = /U2hvcnRDaXJjdWl0/g, rlt = /</g, rgt = />/g + function trimFilter(value, leach) { if (value.indexOf("|") > 0) { // 抽取过滤器 先替换掉所有短路与 value = value.replace(r11a, "U2hvcnRDaXJjdWl0") //btoa("ShortCircuit") @@ -2281,8 +2375,7 @@ ",package,private,protected,public,short,static,super,synchronized" + ",throws,transient,volatile" // ECMA 5 - use strict - + ",arguments,let,yield" - + ",undefined" + + ",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') @@ -2304,6 +2397,7 @@ return cacheVars(key, uniqSet(match)) } /*添加赋值语句*/ + function addAssign(vars, scope, name, data) { var ret = [], prefix = " = " + name + "." @@ -2321,7 +2415,8 @@ } function uniqSet(array) { - var ret = [], unique = {} + 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 @@ -2335,6 +2430,7 @@ function createCache(maxLength) { var keys = [] + function cache(key, value) { if (keys.push(key) > maxLength) { delete cache[keys.shift()] @@ -2457,21 +2553,23 @@ '\\': '\\\\' } var quote = window.JSON && JSON.stringify || function(str) { - return '"' + str.replace(/[\\\"\x00-\x1f]/g, function(a) { + 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) { + + function parseExprProxy(code, scopes, data, tokens, noregister) { + scopes.cb(-1) 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) { + if (data.evaluator && !noregister) { data.handler = bindingExecutors[data.handlerName || data.type] data.evaluator.toString = function() { return data.type + " binding to eval(" + code + ")" @@ -2495,15 +2593,12 @@ "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" + 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 ifGroup = head.insertBefore(document.createElement("avalon"), head.firstChild) //避免IE6 base标签BUG + ifGroup.innerHTML = "X" var rnoscripts = /(?:[\s\S]+?)<\/noscript>/img var rnoscriptText = /([\s\S]+?)<\/noscript>/im @@ -2545,26 +2640,20 @@ if (boolMap[attrName]) { var bool = boolMap[attrName] if (typeof elem[bool] === "boolean") { + // IE6-11不支持动态设置fieldset的disabled属性,IE11下样式是生效了,但无法阻止用户对其底下的input元素进行设值…… return elem[bool] = !!val } } var toRemove = (val === false) || (val === null) || (val === void 0) - if (!W3C && propMap[attrName]) {//旧式IE下需要进行名字映射 + + 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 - } - } + //SVG只能使用setAttribute(xxx, yyy), VML只能使用elem.xxx = yyy ,HTML的固有属性必须elem.xxx = yyy + var isInnate = rsvg.test(elem) ? false : (DOC.namespaces && isVML(elem)) ? true : attrName in elem.cloneNode(false) if (isInnate) { elem[attrName] = val } else { @@ -2572,18 +2661,33 @@ } } 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) - + var rendered = data.includeRendered + var loaded = data.includeLoaded + var replace = data.includeReplaced + var target = replace ? elem.parentNode : elem + vmodels.cb(1) function scanTemplate(text) { if (loaded) { - text = loaded.apply(elem, [text].concat(vmodels)) + text = loaded.apply(target, [text].concat(vmodels)) } - avalon.innerHTML(elem, text) - scanNodeList(elem, vmodels) - rendered && checkScan(elem, function() { - rendered.call(elem) - }) + if (rendered) { + checkScan(target, function() { + rendered.call(target) + }, NaN) + } + while (true) { + var node = data.startInclude.nextSibling + if (node && node !== data.endInclude) { + target.removeChild(node) + } else { + break + } + } + var dom = avalon.parseHTML(text) + var nodes = avalon.slice(dom.childNodes) + target.insertBefore(dom, data.endInclude) + scanNodeArray(nodes, vmodels) + vmodels.cb(-1) } if (data.param === "src") { if (cacheTmpls[val]) { @@ -2639,7 +2743,7 @@ } elem[method] = val if (window.chrome && elem.tagName === "EMBED") { - var parent = elem.parentNode//#525 chrome1-37下embed标签动态设置src不能发生请求 + var parent = elem.parentNode //#525 chrome1-37下embed标签动态设置src不能发生请求 var comment = document.createComment("ms-src") parent.replaceChild(comment, elem) parent.replaceChild(elem, comment) @@ -2668,7 +2772,7 @@ if (!data.hasBindEvent) { //确保只绑定一次 var activate = "mouseenter" //在移出移入时切换类名 var abandon = "mouseleave" - if (method === "active") {//在聚焦失焦中切换类名 + if (method === "active") { //在聚焦失焦中切换类名 elem.tabIndex = elem.tabIndex || -1 activate = "mousedown" abandon = "mouseup" @@ -2741,20 +2845,15 @@ } 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 - } + while (true) { + var node = data.element.nextSibling + if (node && node !== data.endRepeat) { + parent.removeChild(node) + } else { + break } - recycleEachProxies(proxies) } + recycleEachProxies(proxies) break case "move": //将proxies中的第pos个元素移动el位置上(pos, el都是数字) var t = proxies.splice(pos, 1)[0] @@ -2800,13 +2899,14 @@ calculateFragmentGroup(data) break } - var callback = data.renderedCallback || noop, args = 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 + if (parent.oldValue && parent.tagName === "SELECT" && method === "index") { //fix #503 avalon(parent).val(parent.oldValue.split(",")) } - }) + }, NaN) } }, "html": function(val, elem, data) { @@ -2847,15 +2947,17 @@ } else { avalon.innerHTML(parent, val) } + data.vmodels.cb(1) avalon.nextTick(function() { scanNodeList(parent, data.vmodels) + data.vmodels.cb(-1) }) }, "if": function(val, elem, data) { if (val) { //插回DOM树 if (elem.nodeType === 8) { elem.parentNode.replaceChild(data.template, elem) - elem = data.element = data.template + elem = data.element = data.template //这时可能为null } if (elem.getAttribute(data.name)) { elem.removeAttribute(data.name) @@ -2866,7 +2968,7 @@ var node = data.element = DOC.createComment("ms-if") elem.parentNode.replaceChild(node, elem) data.template = elem //元素节点 - head.appendChild(elem) + ifGroup.appendChild(elem) } } }, @@ -2878,7 +2980,9 @@ } var eventType = data.param.replace(/-\d+$/, "") // ms-on-mousemove-10 if (eventType === "scan") { - callback.call(elem, {type: eventType}) + callback.call(elem, { + type: eventType + }) } else if (typeof data.specialBind === "function") { data.specialBind(elem, callback) } else { @@ -2895,7 +2999,7 @@ "text": function(val, elem) { val = val == null ? "" : val //不在页面上显示undefined null if (elem.nodeType === 3) { //绑定在文本节点上 - try {//IE对游离于DOM树外的节点赋值会报错 + try { //IE对游离于DOM树外的节点赋值会报错 elem.data = val } catch (e) { } @@ -2947,8 +3051,24 @@ text = RegExp.$1 } } + if (data.type === "include") { + var elem = data.element + data.includeRendered = getBindingCallback(elem, "data-include-rendered", vmodels) + data.includeLoaded = getBindingCallback(elem, "data-include-loaded", vmodels) + var outer = data.includeReplaced = !!avalon(elem).data("includeReplace") + data.startInclude = DOC.createComment("ms-include") + data.endInclude = DOC.createComment("ms-include-end") + if (outer) { + data.element = data.startInclude + elem.parentNode.insertBefore(data.startInclude, elem) + elem.parentNode.insertBefore(data.endInclude, elem.nextSibling) + } else { + elem.insertBefore(data.startInclude, elem.firstChild) + elem.appendChild(data.endInclude) + } + } data.handlerName = "attr" //handleName用于处理多种绑定共用同一种bindingExecutor的情况 - parseExprProxy(text, vmodels, data, (simple ? null : scanExpr(data.value))) + parseExprProxy(text, vmodels, data, (simple ? 0 : scanExpr(data.value))) }, //根据VM的属性值或表达式的值切换类名,ms-class="xxx yyy zzz:flag" //http://www.cnblogs.com/rubylouvre/archive/2012/12/17/2818540.html @@ -2981,7 +3101,7 @@ if (!hasExpr) { data.immobileClass = className } - parseExprProxy("", vmodels, data, (hasExpr ? scanExpr(className) : null)) + parseExprProxy("", vmodels, data, (hasExpr ? scanExpr(className) : 0)) } else { data.immobileClass = data.oldStyle = data.param parseExprProxy(text, vmodels, data) @@ -2989,30 +3109,40 @@ }, "duplex": function(data, vmodels) { var elem = data.element, - tagName = elem.tagName + tagName = elem.tagName, + hasCast + parseExprProxy(data.value, vmodels, data, 0, 1) 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") + var params = [] + var casting = oneObject("string,number,boolean,checked") + if (elem.type === "radio" && data.param === "") { + data.param = "checked" } - if (data.msType === "radio") { - log("ms-duplex-radio将在2.0废掉,请尽量不要用") - } - if (!/boolean|string|number/.test(data.msType)) { - data.msType = "" + data.param.replace(/\w+/g, function(name) { + if (/^(checkbox|radio)$/.test(elem.type) && /^(radio|checked)$/.test(name)) { + if (name === "radio") + log("ms-duplex-radio已经更名为ms-duplex-checked") + name = "checked" + data.isChecked = true + } + if (name === "bool") { + name = "boolean" + log("ms-duplex-bool已经更名为ms-duplex-boolean") + } else if (name === "text") { + name = "string" + log("ms-duplex-text已经更名为ms-duplex-string") + } + if (casting[name]) { + hasCast = true + } + avalon.Array.ensure(params, name) + }) + if (!hasCast) { + params.push("string") } + data.param = params.join("-") data.bound = function(type, callback) { if (elem.addEventListener) { elem.addEventListener(type, callback, false) @@ -3025,15 +3155,22 @@ old && old() } } + for (var i in avalon.vmodels) { + var v = avalon.vmodels[i] + v.$fire("avalon-ms-duplex-init", data) + } + var cpipe = data.pipe || (data.pipe = pipe) + cpipe(null, data, "init") duplexBinding[elem.tagName](elem, data.evaluator.apply(null, data.args), data) } } }, "repeat": function(data, vmodels) { var type = data.type - parseExpr(data.value, vmodels, data) + parseExprProxy(data.value, vmodels, data, 0, 1) data.proxies = [] var freturn = false + vmodels.cb(-1) try { var $repeat = data.$repeat = data.evaluator.apply(0, data.args || []) var xtype = avalon.type($repeat) @@ -3051,25 +3188,30 @@ data.renderedCallback = getBindingCallback(elem, "data-" + type + "-rendered", vmodels) var comment = data.element = DOC.createComment("ms-repeat") + var endRepeat = data.endRepeat = DOC.createComment("ms-repeat-end") + + hyperspace.appendChild(comment) + hyperspace.appendChild(endRepeat) + if (type === "each" || type === "with") { data.template = elem.innerHTML.trim() - avalon.clearHTML(elem).appendChild(comment) + avalon.clearHTML(elem).appendChild(hyperspace) } else { data.template = elem.outerHTML.trim() + elem.parentNode.replaceChild(hyperspace, elem) data.group = 1 - elem.parentNode.replaceChild(comment, elem) } - data.rollback = function() {//只用于list为对象的情况 + 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) + parentNode.removeChild(data.endRepeat) target = data.element = data.type === "repeat" ? target : parentNode - data.group = null - target.setAttribute(data.name, data.value) + data.group = target.setAttribute(data.name, data.value) } var arr = data.value.split(".") || [] if (arr.length > 1) { @@ -3104,9 +3246,7 @@ } var $list = ($repeat.$events || {})[subscribers] if ($list && avalon.Array.ensure($list, data)) { - $$subscribers.push({ - data: data, list: $list - }) + addSubscribers(data, $list) } if (!Array.isArray($repeat) && type !== "each") { var pool = withProxyPool[$repeat.$id] @@ -3168,54 +3308,68 @@ var args = data.value.match(rword) var elem = data.element var widget = args[0] - if (args[1] === "$" || !args[1]) { - args[1] = widget + setTimeout("1") + var id = args[1] + if (!id || id === "$") {//没有定义或为$时,取组件名+随机数 + id = widget + setTimeout("1") } - data.value = args.join(",") + var optName = args[2] || widget//没有定义,取组件名 + vmodels.cb(-1) 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 + var vmOptions = v[optName] + vmOptions = vmOptions.$model || vmOptions break } } - if (nearestVM) { - var vmOptions = nearestVM[optName] - vmOptions = vmOptions.$model || vmOptions - var id = vmOptions[widget + "Id"] - if (typeof id === "string") { - args[1] = id + if (vmOptions) { + var wid = vmOptions[widget + "Id"] + if (typeof wid === "string") { + id = wid } } - 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) + //抽取data-tooltip-text、data-tooltip-attr属性,组成一个配置对象 + var widgetData = avalon.getWidgetData(elem, widget) + data.value = [widget, id, optName].join(",") + data[widget + "Id"] = id + data.evaluator = noop + var options = 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) + if (vmodel.$id) { + avalon.vmodels[id] = vmodel + createSignalTower(elem, vmodel) + if (vmodel.hasOwnProperty("$init")) { + vmodel.$init(function() { + var nv = [vmodel].concat(vmodels) + nv.cb = vmodels.cb + avalon.scan(elem, nv) + if (typeof options.onInit === "function") { + options.onInit.call(elem, vmodel, options, vmodels) + } }) - } else { - avalon.tick(offTree) } + 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 { + avalon.scan(elem, vmodels) } } else if (vmodels.length) { //如果该组件还没有加载,那么保存当前的vmodels elem.vmodels = vmodels @@ -3240,33 +3394,75 @@ //将模型中的字段与input, textarea的value值关联在一起 var duplexBinding = bindingHandlers.duplex + + function fixNull(val) { + return val == null ? "" : val + } + avalon.duplexHooks = { + checked: { + get: function(val, data) { + return !data.element.oldValue + } + }, + string: { + get: function(val) { //同步到VM + return val + }, + set: fixNull + }, + "boolean": { + get: function(val) { + return val === "true" + }, + set: fixNull + }, + number: { + get: function(val) { + return isFinite(val) ? parseFloat(val) || 0 : val + }, + set: fixNull + } + } + + function pipe(val, data, action, e) { + data.param.replace(/\w+/g, function(name) { + var hook = avalon.duplexHooks[name] + if (hook && typeof hook[action] === "function") { + val = hook[action](val, data) + } + }) + return val + } //如果一个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) + data.changed.call(this, value, data) } + function compositionStart() { composing = true } + function compositionEnd() { composing = false } //当value变化时改变model的值 + function updateVModel() { - if (composing)//处理中文输入法在minlengh下引发的BUG + if (composing) //处理中文输入法在minlengh下引发的BUG return var val = element.oldValue = element.value //防止递归调用形成死循环 - var typedVal = getTypedValue(data, val) //尝式转换为正确的格式 + var lastValue = data.pipe(val, data, "get") if ($elem.data("duplex-observe") !== false) { - evaluator(typedVal) - callback.call(element, typedVal) + evaluator(lastValue) + callback.call(element, lastValue) if ($elem.data("duplex-focus")) { avalon.nextTick(function() { element.focus() @@ -3277,29 +3473,24 @@ //当model变化时,它就会改变value的值 data.handler = function() { - var val = evaluator() - val = val == null ? "" : val + "" + var val = data.pipe(evaluator(), data, "set") if (val !== element.value) { element.value = val } } - if (type === "checkbox" && data.param === "radio") { - type = "radio" - } - if (type === "radio") { + if (data.isChecked || element.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) + var lastValue = data.pipe(element.value, data, "get") + evaluator(lastValue) + callback.call(element, lastValue) } } data.handler = function() { var val = evaluator() - var checked = data.msType ? val + "" === element.value : !!val + var checked = data.isChecked ? !!val : val + "" === element.value element.oldValue = checked if (IE6) { setTimeout(function() { @@ -3323,69 +3514,52 @@ log("ms-duplex应用于checkbox上要对应一个数组") array = [array] } - var typedValue = getTypedValue(data, element.value) - avalon.Array[method](array, typedValue) + avalon.Array[method](array, data.pipe(element.value, data, "get")) callback.call(element, array) } } data.handler = function() { var array = [].concat(evaluator()) //强制转换为数组 - element.checked = array.indexOf(getTypedValue(data, element.value)) >= 0 + element.checked = array.indexOf(data.pipe(element.value, data, "get")) >= 0 } - bound(W3C ? "change" : "click", updateVModel) - } else { - var event = element.attributes["data-duplex-event"] || element.attributes["data-event"] || {} + var events = element.getAttribute("data-duplex-event") || element.getAttribute("data-event") || "input" if (element.attributes["data-event"]) { log("data-event指令已经废弃,请改用data-duplex-event") } - event = event.value - if (event === "change") { - bound("change", updateVModel) - } else { - 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); + + function delay(e) { + setTimeout(function() { + updateVModel(e) + }) + } + events.replace(rword, function(name) { + switch (name) { + case "input": + if (W3C) { //IE9+, W3C + bound("input", updateVModel) + bound("compositionstart", compositionStart) + bound("compositionend", compositionEnd) + //http://www.cnblogs.com/rubylouvre/archive/2013/02/17/2914604.html + //http://www.matts411.com/post/internet-explorer-9-oninput/ + if (DOC.documentMode === 9) { + bound("paste", delay) + bound("cut", delay) } + } else { //onpropertychange事件无法区分是程序触发还是用户触发 + bound("propertychange", function(e) { + if (e.propertyName === "value") + updateVModel() + }) } - bound("focus", selectionchange) - bound("blur", selectionchange) - } - } 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) - }) - } + break + default: + bound(name, updateVModel) + break } - } + }) } element.oldValue = element.value launch(function() { @@ -3396,42 +3570,34 @@ } }) 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 - } + callback.call(element, element.value) } var TimerID, ribbon = [], launch = noop + function W3CFire(el, name, detail) { var event = DOC.createEvent("Events") event.initEvent(name, true, true) + event.isTrusted = false if (detail) { event.detail = detail } el.dispatchEvent(event) } - function onTree() { //disabled状态下改动不触发input事件 - if (!this.disabled && this.oldValue !== this.value) { + function onTree(value) { //disabled状态下改动不触发input事件 + var newValue = arguments.length ? value : this.value + if (!this.disabled && this.oldValue !== newValue) { + var type = this.getAttribute("data-duplex-event") || "input" + type = type.match(rword).shift() if (W3C) { - W3CFire(this, "input") + W3CFire(this, type) } else { - this.fireEvent("onchange") + try { + this.fireEvent("on" + type) + } catch (e) { + } } } } @@ -3454,16 +3620,14 @@ } } - function newSetter(newValue) { - oldSetter.call(this, newValue) - if (newValue !== this.oldValue) { - W3CFire(this, "input") - } + function newSetter(value) { + onSetter.call(this, value) + onTree.call(this, value) } try { var inputProto = HTMLInputElement.prototype - Object.getOwnPropertyNames(inputProto)//故意引发IE6-8等浏览器报错 - var oldSetter = Object.getOwnPropertyDescriptor(inputProto, "value").set //屏蔽chrome, safari,opera + Object.getOwnPropertyNames(inputProto) //故意引发IE6-8等浏览器报错 + var onSetter = Object.getOwnPropertyDescriptor(inputProto, "value").set //屏蔽chrome, safari,opera Object.defineProperty(inputProto, "value", { set: newSetter }) @@ -3473,20 +3637,21 @@ 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) + return data.pipe(v, data, "get") }) } else { - val = getTypedValue(data, val) + val = data.pipe(val, data, "get") } if (val + "" !== element.oldValue) { evaluator(val) } - data.changed.call(element, val) + data.changed.call(element, val, data) } } data.handler = function() { @@ -3509,18 +3674,11 @@ } } 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) + checkScan(element,function() { + //先等到select里的option元素被扫描后,才根据model设置selected属性 + registerSubscriber(data) + data.changed.call(element, evaluator(), data) + }, NaN) } duplexBinding.TEXTAREA = duplexBinding.INPUT //============================= event binding ======================= @@ -3631,7 +3789,7 @@ function Collection(model) { var array = [] array.$id = generateID() - array.$model = model //数据模型 + array.$model = model //数据模型 array.$events = {} array.$events[subscribers] = [] array._ = modelFactory({ @@ -3658,7 +3816,7 @@ pos = typeof pos === "number" ? pos : oldLength var added = [] for (var i = 0, n = arr.length; i < n; i++) { - added[i] = convert(arr[i], this.$model[i]) + added[i] = convert(arr[i], this.$model[pos + i]) } _splice.apply(this, [pos, 0].concat(added)) this._fire("add", pos, added) @@ -3706,7 +3864,8 @@ // 必须存在第一个参数,需要大于-1, 为添加或删除元素的基点 a = _number(a, this.length) var removed = _splice.apply(this.$model, arguments), - ret = [], change + ret = [], + change this._stopFireLength = true //确保在这个方法中 , $watch("length",fn)只触发一次 if (removed.length) { ret = this._del(a, removed.length) @@ -3823,44 +3982,49 @@ //============ 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 ov = data.vmodels + var nv = [proxy].concat(ov) + nv.cb = ov.cb var fragment = { nodes: nodes, - vmodels: [proxy].concat(data.vmodels) + vmodels: nv } fragments.push(fragment) } - // 取得用于定位的节点。比如data.group = 3, 结构为 - //






+ //如果ms-repeat紧挨着ms-repeat-end,那么就返回ms-repeat-end + // 取得用于定位的节点。比如group = 3, 结构为 + //






// 当pos为0时,返回 br#first // 当pos为1时,返回 br#second - // 当pos为2时,返回 null + // 当pos为2时,返回 ms-repeat-end + 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 + var startRepeat = data.element + var endRepeat = data.endRepeat + var nodes = [] + var node = startRepeat.nextSibling + if (node !== endRepeat) { + do { + if (node !== endRepeat) { + nodes.push(node) } else { break } - } - } else { - var nodes = avalon.slice(comment.parentNode.childNodes, 1) - var group = data.group || nodes.length / data.proxies.length - node = nodes[group * pos] + } while (node = node.nextSibling) } - return node || null + return nodes[data.group * pos] || endRepeat } function removeFragment(node, group, pos) { var n = group * (pos || 1) - var nodes = [node], i = 1 + var nodes = [node], + i = 1 var view = hyperspace while (i < n) { node = node.nextSibling @@ -3876,9 +4040,10 @@ function calculateFragmentGroup(data) { if (!isFinite(data.group)) { - var nodes = avalon.slice(data.element.parentNode.childNodes, 1) + var nodes = data.element.parentNode.childNodes + var length = nodes.length - 2 //去掉两个注释节点 var n = "proxySize" in data ? data.proxySize : data.proxies.length - data.group = nodes.length / n + data.group = length / n } } // 为ms-each, ms-repeat创建一个代理对象,通过它们能使用一些额外的属性与功能($index,$first,$last,$remove,$key,$val,$outer) @@ -3897,8 +4062,10 @@ return proxy } var eachProxyPool = [] + function getEachProxy(index, item, data, last) { - var param = data.param || "el", proxy + var param = data.param || "el", + proxy var source = { $remove: function() { return data.$repeat.removeAt(proxy.$index) @@ -3912,7 +4079,7 @@ source[param] = item for (var i = 0, n = eachProxyPool.length; i < n; i++) { var proxy = eachProxyPool[i] - if (proxy.hasOwnProperty(param)) { + if (proxy.hasOwnProperty(param) && (avalon.type(proxy[param]) === avalon.type(item))) { for (var k in source) { proxy[k] = source[k] } @@ -3984,16 +4151,16 @@ 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属性 + 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 name + "=" + quote + "javascript:void(0)" + quote }) } } - return a.replace(ron, " ").replace(/\s+/g, " ")//移除onXXX事件 + return a.replace(ron, " ").replace(/\s+/g, " ") //移除onXXX事件 }) }, escape: function(html) { @@ -4178,8 +4345,7 @@ } return string } - var rfixFFDate = /^(\d+)-(\d+)-(\d{4})$/ - var rfixIEDate = /^(\d+)\s+(\d+),(\d{4})$/ + var rfixYMD = /^(\d+)\D(\d+)\D(\d+)/ filters.date = function(date, format) { var locate = filters.date.locate, text = "", @@ -4192,9 +4358,10 @@ date = toInt(date) } else { var trimDate = date.trim() - if (trimDate.match(rfixFFDate) || trimDate.match(rfixIEDate)) { - date = RegExp.$3 + "/" + RegExp.$1 + "/" + RegExp.$2 - } + date = trimDate.replace(rfixYMD, function(a, b, c, d) { + var array = d.length === 4 ? [d, b, c] : [b, c, d] + return array.join("/") + }) date = jsonStringToDate(date) } date = new Date(date) @@ -4271,12 +4438,9 @@ filters.date.locate = locate } - /********************************************************************* * AMD加载器 * **********************************************************************/ - - var innerRequire var modules = avalon.modules = { "ready!": { exports: avalon @@ -4638,6 +4802,7 @@ **********************************************************************/ var readyList = [] + function fireReady() { if (DOC.body) { // 在IE8 iframe中doScrollCheck可能不正确 if (innerRequire) { @@ -4699,4 +4864,4 @@ 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/ - */ + */ \ No newline at end of file diff --git a/avalon.min.js b/avalon.min.js index cbfbfdd8d..4126c3bc4 100644 --- a/avalon.min.js +++ b/avalon.min.js @@ -1,121 +1,125 @@ -(function(p){function A(){}function x(){r.console&&avalon.config.debug&&Function.apply.call(console.log,console,arguments)}function T(b,c){"string"===typeof b&&(b=b.match(D)||[]);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 Sb(b,c, -d){if(U(c)||c&&c.nodeType||-1!==d.indexOf(b)||-1!==ta.indexOf(b))return!1;c=d.$special;return b&&"$"===b.charAt(0)&&!c[b]?!1:!0}function J(b,c,d){if(Array.isArray(b)){var e=b.concat();b.length=0;c=Tb(b);c.pushArray(e);return c}if("number"===typeof b.nodeType||b.$id&&b.$model&&b.$events)return b;Array.isArray(b.$skipArray)||(b.$skipArray=[]);b.$skipArray.$special=c||{};var f={};d=d||{};var g={},h={},k=[];for(e in b)(function(c,e){d[c]=e;if(Sb(c,e,b.$skipArray)){var q,l=avalon.type(e);g[c]=[];if("object"=== -l&&U(e.get)&&2>=Object.keys(e).length){var H=e.set,K=e.get;q=function(b){var e=f.$events,g=d[c];if(arguments.length){if(ka)return;if(U(H)){var h=e[c];e[c]=[];H.call(f,b);e[c]=h}}else avalon.openComputedCollect&&la(e[c]);b=d[c]=K.call(f);if(!ma(g,b)){if(na&&(h=V[f.$id])&&h[c])h[c].$val=b;ua(e[c]);f.$events&&O.$fire.call(f,c,b,g)}return b};k.push(function(){Z[z]={evaluator:q,element:F,type:"computed::"+c,handler:A,args:[]};q();la(g[c]);delete Z[z]})}else va.test(l)?(q=function(b){var e=q.child,h=d[c]; -if(arguments.length)!ka&&!ma(h,b)&&(e=q.child=Ub(f,c,b,l),b=d[c]=e.$model,(e=wa[e.$id])&&e(),f.$events&&O.$fire.call(f,c,b,h));else return la(g[c]),e},(q.child=J(e,0,d[c])).$events[G]=g[c]):q=function(b){var e=d[c];if(arguments.length){if(!ma(e,b)){d[c]=b;if(na){var h=V[f.$id];h&&h[c]&&(h[c].$val=b)}ua(g[c]);f.$events&&O.$fire.call(f,c,b,e)}}else return la(g[c]),e};h[c]=q}})(e,b[e]);ta.forEach(function(c){delete b[c];delete d[c]});var f=oa(f,Vb(h),b),l;for(l in b)h[l]||(f[l]=b[l]);f.$id=Va();f.$model= -d;f.$events=g;for(e in O)c=O[e],v||(c=c.bind(f)),f[e]=c;f.hasOwnProperty=function(b){return b in f.$model};k.forEach(function(b){b()});return f}function Ub(b,c,d,e){var f=b[c];if("array"===e){if(!Array.isArray(d)||f===d)return f;f.clear();f.pushArray(d.concat());return f}var g=b.$events[c];V[f.$id]&&(na--,delete V[f.$id]);var h=J(d);wa[h.$id]=function(b){for(;b=g.shift();)(function(b){b.type&&avalon.nextTick(function(){b.rollback&&b.rollback();C[b.type](b,b.vmodels)})})(b);delete wa[h.$id]};return h} -function $(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 Xa(b,c){try{for(;c=c.parentNode;)if(c===b)return!0;return!1}catch(d){return!1}}function Ya(){return(new XMLSerializer).serializeToString(this)}function t(b){for(var c in b)if(P.call(b,c)){var d=b[c];if("function"===typeof t.plugins[c])t.plugins[c](d);else"object"===typeof t[c]?avalon.mix(t[c],d):t[c]=d}return this}function Za(b){return b.replace(/([a-z\d])([A-Z]+)/g, -"$1-$2").toLowerCase()}function pa(b){return 0>b.indexOf("-")&&0>b.indexOf("_")?b:b.replace(/[-_][^-_]/g,function(b){return b.charAt(1).toUpperCase()})}function $a(b){if(!("classList"in b)){b.classList={node:b};for(var c in ab)b.classList[c.slice(1)]=ab[c]}return b.classList}function bb(b){try{b="true"===b?!0:"false"===b?!1:"null"===b?null:+b+""===b?+b:Wb.test(b)?avalon.parseJSON(b):b}catch(c){}return b}function cb(b,c){if(0>=b.offsetWidth){if(Xb.test(w["@:get"](b,"display"))){var d={node:b},e;for(e in db)d[e]= -b.style[e],b.style[e]=db[e];c.push(d)}(d=b.parentNode)&&1===d.nodeType&&cb(d,c)}}function eb(b){var c=b.nodeName;return c.toLowerCase()===c&&b.scopeName&&""===b.outerText}function Yb(b){"url(#default#VML)"!==b.currentStyle.behavior&&(b.style.behavior="url(#default#VML)",b.style.display="inline-block",b.style.zoom=1)}function xa(b){Z[z]=b;avalon.openComputedCollect=!0;var c=b.evaluator;if(c)try{var d=Zb.test(b.type)?b:c.apply(0,b.args);b.handler(d,b.element,b)}catch(e){x("warning:exception throwed in [registerSubscriber] "+ -e),delete b.evaluator,c=b.element,3===c.nodeType&&(d=c.parentNode,t.commentInterpolate?d.replaceChild(p.createComment(b.value),c):c.data=L+b.value+M)}avalon.openComputedCollect=!1;delete Z[z]}function la(b){var c=Z[z];b&&(c&&avalon.Array.ensure(b,c)&&c.element)&&aa.push({data:c,list:b})}function fb(){for(var b=ya,c=ya+$b;b"))&&za(b,c)}}function Ba(b,c){for(var d=0,e;e=b[d++];)e.vmodels=c,C[e.type](e,c),e.evaluator&&(e.element&&1===e.element.nodeType)&&e.element.removeAttribute(e.name);b.length=0}function ob(b,c){0b&&delete c[d.shift()];return c[e]=f}var d=[];return c}function ra(b,c,d){var e=d.type,f=d.filters?d.filters.join(""):"",g=c.map(function(b){return b.$id.replace(kc,"$1")})+b+e+f,h=lc(b).concat(),k=[],l=[],n=[],m="";c=tb(c);for(var q=0,m=c.length;q>0)-(d.clientLeft>>0),c.pageY=b.clientY+(d.scrollTop>>0)-(d.clientTop>>0),c.wheelDeltaY=c.wheelDelta,c.wheelDeltaX=0);c.timeStamp=new Date-0;c.originalEvent=b;c.preventDefault=function(){b.returnValue=!1};c.stopPropagation=function(){b.cancelBubble=!0};return c}function Tb(b){var c= -[];c.$id=Va();c.$model=b;c.$events={};c.$events[G]=[];c._=J({length:b.length});c._.$watch("length",function(b,d){c.$fire("length",b,d)});for(var d in O)c[d]=O[d];avalon.mix(c,wb);return c}function xb(b,c,d,e){var f=avalon.parseHTML(b.template),g=avalon.slice(f.childNodes);c.appendChild(f);d.$outer=b.$outer;b={nodes:g,vmodels:[d].concat(b.vmodels)};e.push(b)}function Fa(b,c){var d=b.element;if("repeat"==b.type)for(var d=d.nextSibling,e=0;et.maxRepeatSize&&fa.pop()}b.length=0}function R(){p.body&&(E?(s["ready!"].state=2,E.checkDeps()):Bb.forEach(function(b){b(avalon)}),R=A)}function Cb(){try{B.doScroll("left"),R()}catch(b){setTimeout(Cb)}}var z=new Date-0,G="$"+z,r=this||(0,eval)("this"),wc=r.require,xc=r.define,ka=!1,D=/[^, ]+/g,Db=/\[native code\]/,va=/^(?:object|array)$/,Eb=/^\[object SVG\w*Element\]$/,Rb=/^\[object (?:Window|DOMWindow|global)\]$/,Ga=Object.prototype,P=Ga.hasOwnProperty,ja=Ga.toString, -I=Array.prototype,ba=I.slice,Z={},v=r.dispatchEvent,B=p.documentElement,F=p.getElementsByTagName("head")[0],W=p.createDocumentFragment(),ga=p.createElement("div"),Fb={};"Boolean Number String Function Array Date RegExp Object Error".replace(D,function(b){Fb["[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?Fb[ja.call(b)]||"object":typeof b};var U="object"===typeof alert?function(b){try{return/^\s*\bfunction\b/.test(b+"")}catch(c){return!1}}:function(b){return"[object Function]"==ja.call(b)};avalon.isFunction=U;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&&!P.call(b,"constructor")&&!P.call(b.constructor.prototype, -"isPrototypeOf"))return!1}catch(d){return!1}for(c in b);return void 0===c||P.call(b,c)};Db.test(Object.getPrototypeOf)&&(avalon.isPlainObject=function(b){return!!b&&"object"===typeof b&&Object.getPrototypeOf(b)===Ga});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,yc)}}if(!"\u53f8\u5f92\u6b63\u7f8e".trim){var zc=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;String.prototype.trim=function(){return this.replace(zc,"")}}var Ac=!{toString:null}.propertyIsEnumerable("toString"),Bc=function(){}.propertyIsEnumerable("prototype"),Gb="toString toLocaleString valueOf hasOwnProperty isPrototypeOf propertyIsEnumerable constructor".split(" "), -Cc=Gb.length;Object.keys||(Object.keys=function(b){var c=[],d=Bc&&"function"===typeof b;if("string"===typeof b||b&&b.callee)for(d=0;d -arguments.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:$("","_",""),filter:$("r=[],j=0,", -"if(_)r[j++]=this[i]","return r"),map:$("r=[],","r[i]=_","return r"),some:$("","if(_)return true","return false"),every:$("","if(!_)return false","return true")});B.contains||(Node.prototype.contains=function(b){return!!(this.compareDocumentPosition(b)&16)});p.contains||(p.contains=function(b){return Xa(p,b)});if(r.SVGElement){var Hb=p.createElementNS("http://www.w3.org/2000/svg","svg");Hb.innerHTML='';if(!Eb.test(Hb.firstChild)){var Ia=function(b,c){if(b&& -b.childNodes)for(var d=b.childNodes,e=0,f;f=d[e++];)if(f.tagName){var g=p.createElementNS("http://www.w3.org/2000/svg",f.tagName.toLowerCase());I.forEach.call(f.attributes,function(b){g.setAttribute(b.name,b.value)});Ia(f,g);c.appendChild(g)}};Object.defineProperties(SVGElement.prototype,{outerHTML:{enumerable:!0,configurable:!0,get:Ya,set:function(b){var c=this.tagName.toLowerCase(),d=this.parentNode;b=avalon.parseHTML(b);"svg"===c?d.insertBefore(b,this):(c=p.createDocumentFragment(),Ia(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.clearHTM&&(avalon.clearHTML(this),b=avalon.parseHTML(b),Ia(b,this))}}})}}!B.outerHTML&&r.HTMLElement&&HTMLElement.prototype.__defineGetter__("outerHTML",Ya);var L,M,ca,Ib,sb,Jb=/[-.*+?^${}()|[\]\/\\]/g,S={loader:function(b){r.define=b?E.define: -xc;r.require=b?E:wc},interpolate:function(b){L=b[0];M=b[1];if(L===M)throw new SyntaxError("openTag!==closeTag");if("\x3c!--,--\x3e"===b+"")t.commentInterpolate=!0;else{b=L+"test"+M;ga.innerHTML=b;if(ga.innerHTML!==b&&0<=ga.innerHTML.indexOf("<"))throw new SyntaxError("\u6b64\u5b9a\u754c\u7b26\u4e0d\u5408\u6cd5");ga.innerHTML=""}b=(L+"").replace(Jb,"\\$&");var c=(M+"").replace(Jb,"\\$&");ca=RegExp(b+"(.*?)"+c);Ib=RegExp(b+"(.*?)"+c,"g");sb=RegExp(b+".*?"+c+"|\\sms-")}};t.debug=!0;t.plugins=S;t.plugins.interpolate(["{{", -"}}"]);t.paths={};t.shim={};t.maxRepeatSize=100;avalon.config=t;var ab={_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(D, -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){$a(d)[b](c)});return this}});avalon.fn.mix({hasClass:function(b){var c=this[0]||{};return 1===c.nodeType&&$a(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-"+Za(b||"");switch(arguments.length){case 2:return this.attr(b,c),this;case 1:var d=this.attr(b);return bb(d);case 0:var e={};I.forEach.call(this[0].attributes,function(c){c&&(b=c.name,b.indexOf("data-")||(b=pa(b.slice(5)),e[b]=bb(c.value)))});return e}},removeData:function(b){b="data-"+Za(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||B;b&&"HTML"!==b.tagName&&"static"===avalon.css(b,"position");)b= -b.offsetParent;return avalon(b||B)},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=Ja,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 Wb=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/, -Dc=/^[\],:{}\s]*$/,Ec=/(?:^|:|,)(?:\s*\[)+/g,Fc=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,Gc=/"[^"\\\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())&&Dc.test(b.replace(Fc,"@").replace(Gc,"]").replace(Ec,"")))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]:B[b]:e[b]}});var w=avalon.cssHooks={},Kb=["","-webkit-","-o-","-moz-","-ms-"],Ka={"float":"cssFloat",background:"backgroundColor"};avalon.cssNumber=T("columnCount,order,fillOpacity,fontWeight,lineHeight,opacity,orphans,widows,zIndex,zoom");avalon.cssName=function(b,c,d){if(Ka[b])return Ka[b];c=c||B.style; -for(var e=0,f=Kb.length;e=d?"alpha(opacity="+100*d+")":"";c=b.filter||"";b.zoom=1;b.filter=(Lb.test(c)?c.replace(Lb,d):c+" "+d).trim();b.filter||b.removeAttribute("filter")};w["opacity:get"]=function(b){b=b.filters.alpha||b.filters["DXImageTransform.Microsoft.Alpha"];return(b&&b.enabled?b.opacity:100)/100+""}}"top,left".replace(D,function(b){w[b+":get"]=function(c){var d=w["@:get"](c,b);return/px$/.test(d)?d:avalon(c).position()[b]+"px"}});var db={position:"absolute",visibility:"hidden",display:"block"}, -Xb=/^(none|table(?!-c[ea]).+)/;"Width,Height".replace(D,function(b){var c=b.toLowerCase(),d="client"+b,e="scroll"+b,f="offset"+b;w[c+":get"]=function(c,d,e){var l=-4;"number"===typeof e&&(l=e);d="Width"===b?["Left","Right"]:["Top","Bottom"];e=c[f];if(2===l)return e+avalon.css(c,"margin"+d[0],!0)+avalon.css(c,"margin"+d[1],!0);0>l&&(e=e-avalon.css(c,"border"+d[0]+"Width",!0)-avalon.css(c,"border"+d[1]+"Width",!0));-4===l&&(e=e-avalon.css(c,"padding"+d[0],!0)-avalon.css(c,"padding"+d[1],!0));return e}; -w[c+"&get"]=function(b){var d=[];cb(b,d);for(var e=w[c+":get"](b),f=0,n;n=d[f++];){b=n.node;for(var m in n)"string"===typeof n[m]&&(b.style[m]=n[m])}return e};avalon.fn[c]=function(g){var h=this[0];if(0===arguments.length){if(h.setTimeout)return h["inner"+b]||h.document.documentElement[d];if(9===h.nodeType){var k=h.documentElement;return Math.max(h.body[e],k[e],h.body[f],k[f],k[d])}return w[c+"&get"](h)}return this.css(c,g)};avalon.fn["inner"+b]=function(){return w[c+":get"](this[0],void 0,-2)};avalon.fn["outer"+ -b]=function(b){return w[c+":get"](this[0],void 0,!0===b?2:0)}});avalon.fn.offset=function(){var b=this[0],c={left:0,top:0};if(!b||!b.tagName||!b.ownerDocument)return c;var d=b.ownerDocument,e=d.body,f=d.documentElement,d=d.defaultView||d.parentWindow;if(!avalon.contains(f,b))return c;b.getBoundingClientRect&&(c=b.getBoundingClientRect());var b=f.clientTop||e.clientTop,g=f.clientLeft||e.clientLeft,h=Math.max(d.pageYOffset||0,f.scrollTop,e.scrollTop),e=Math.max(d.pageXOffset||0,f.scrollLeft,e.scrollLeft); -return{top:c.top+h-b,left:c.left+e-g}};var Kc=/^]+))?)*\s+value[\s=]/i,Ja={"option:get":function(b){return b.hasAttribute?b.hasAttribute("value")?b.value:b.text.trim():Kc.test(b.outerHTML)?b.value:b.text},"select:get":function(b,c){for(var d,e=b.options,f=b.selectedIndex,g=Ja["option:get"],h="select-one"===b.type||0>f,k=h?null:[],l=h?f+1:e.length,n=0>f?l:h?f:0;n]*)\/>/ig,Mb=v?/[^\d\D]/:/(<(?:script|link|style|meta|noscript))/ig,Nc=T("text/javascript","text/ecmascript","application/ecmascript","application/javascript","text/vbscript"),Oc=/<(?:tb|td|tf|th|tr|col|opt|leg|cap|area)/,y={area:[1,""],param:[1,""], -col:[2,"
","
"],legend:[1,"
"],option:[1,"\u4e0a\u8981\u6c42\u5bf9\u5e94\u4e00\u4e2a\u6570\u7ec4"):b.multiple&&x("ms-duplex\u5728"],thead:[1,"","
"],tr:[2,""],td:[3,"
"],g:[1,'',""],_default:w? +[0,""]:[1,"X
"]};E.optgroup=E.option;E.tbody=E.tfoot=E.colgroup=E.caption=E.thead;E.th=E.td;"circle,defs,ellipse,image,line,path,polygon,polyline,rect,symbol,text,use".replace(z,function(b){E[b]=E.g});var Rc=/<([\w:]+)/,Sc=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Qb=w?/[^\d\D]/:/(<(?:script|link|style|meta|noscript))/ig,Tc=M(["","text/javascript","text/ecmascript","application/ecmascript","application/javascript"]),Uc=/<(?:tb|td|tf|th|tr|col|opt|leg|cap|area)/, +Vc=p.createElement("script");avalon.parseHTML=function(b){"string"!==typeof b&&(b+="");b=b.replace(Sc,"<$1>").trim();var c=(Rc.exec(b)||["",""])[1].toLowerCase(),d=E[c]||E._default,e=I.cloneNode(!1),f=ea,g;w||(b=b.replace(Qb,"
$1"));f.innerHTML=d[1]+b+(d[2]||"");b=f.getElementsByTagName("script");if(b.length)for(var h=0,k;k=b[h++];)Tc[k.type]&&(g=Vc.cloneNode(!1),L.forEach.call(k.attributes,function(b){b&&b.specified&&(g[b.name]=b.value,g.setAttribute(b.name,b.value))}),g.text= +k.text,k.parentNode.replaceChild(g,k));for(h=d[0];h--;f=f.lastChild);if(!w){b=f.getElementsByTagName("br");for(h=0;k=b[h++];)k.className&&"msNoScope"===k.className&&(k.parentNode.removeChild(k),h--);b=f.all;for(h=0;k=b[h++];)gb(k)&&bc(k);if("tr"===c){b=f.children;for(h=0;k=b[h++];)""==k.nodeName&&(k.parentNode.removeChild(k),h--)}}for(;c=f.firstChild;)e.appendChild(c);return e};avalon.innerHTML=function(b,c){if(!w&&!Qb.test(c)&&!Uc.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 R={$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);var d=this.$events,e=$.call(arguments,1),f=[b].concat(e);if("all"===c)for(var g in avalon.vmodels)e=avalon.vmodels[g],e!==this&&e.$fire.apply(e,f);else if("up"===c||"down"===c){if(d=d.expr&&Rb(d.expr)){for(g in avalon.vmodels)if(e=avalon.vmodels[g],e!==this&&e.$events.expr){var h=Rb(e.$events.expr);if(h&&("down"===c?d.contains(h):h.contains(d)))h._avalon=e}g=p.getElementsByTagName("*");var k=[];Array.prototype.forEach.call(g, +function(b){b._avalon&&(k.push(b._avalon),b._avalon="",b.removeAttribute("_avalon"))});"up"===c&&k.reverse();for(g=0;(c=k[g++])&&!1!==c.$fire.apply(c,f););}}else{f=d[b]||[];c=d.$all||[];for(g=0;d=f[g++];)V(d)&&d.apply(this,e);for(g=0;d=c[g++];)V(d)&&d.apply(this,arguments)}}},Wc=/(\w+)\[(avalonctrl)="(\S+)"\]/,Rb=p.querySelector?function(b){return p.querySelector(b)}:function(b){b=b.match(Wc);for(var c=p.getElementsByTagName(b[1]),d=0,e;e=c[d++];)if(e.getAttribute(b[2])===b[3])return e},cc=/^(duplex|on)$/, +S=[],za=0,dc=200,jb=new Date,kb,Oa={};avalon.scanCallback=function(b,c){c=c||"$all";(Oa[c]||(Oa[c]=[])).push(b)};avalon.scan=function(b,c,d){b=b||B;var e=Oa[d||"$all"]||[];c=c?[].concat(c):[];var f=0,g=!1,h,k=!1;c.cb=function(b){f+=b;k=!0;setTimeout(function(){if(0>=f&&!g)for(g=!0;h=e.shift();)h()})};lb(b,c);if(!k)for(;h=e.shift();)h()};var hc=M("AREA,BASE,BASEFONT,BR,COL,COMMAND,EMBED,HR,IMG,INPUT,LINK,META,PARAM,SOURCE,TRACK,WBR,NOSCRIPT,SCRIPT,STYLE,TEXTAREA"),tb=/ms-(\w+)-?(.*)/,ub={"if":10,repeat:90, +data:100,widget:110,each:1400,"with":1500,duplex:2E3,on:3E3},fc=M("animationend,blur,change,input,click,dblclick,focus,keydown,keypress,keyup,mousedown,mouseenter,mouseleave,mousemove,mouseout,mouseover,mouseup,scan,scroll,submit"),gc=M("value,title,alt,checked,selected,disabled,readonly,enabled");if(!"1"[0])var Pa=Da(512),Xc=/\s+(ms-[^=\s]+)(?:=("[^"]*"|'[^']*'|[^\s>]+))?/g,Yc=/^['"]/,Zc=/<\w+\b(?:(["'])[^"]*?(\1)|[^>])*>/i,$c=/&/g,sb=function(b){b=b.outerHTML;if(".avalonHide{ display: none!important }";var gd=/(?:[\s\S]+?)<\/noscript>/img,hd=/([\s\S]+?)<\/noscript>/im,Sa=function(){return new (r.XMLHttpRequest||ActiveXObject)("Microsoft.XMLHTTP")},fa=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]}},Ta=avalon.templateCache={};avalon.contains=Za;var Ua={};"autofocus,autoplay,async,allowTransparency,checked,controls,declare,disabled,defer,defaultChecked,defaultSelectedcontentEditable,isMap,loop,multiple,noHref,noResize,noShade,open,readOnly,selected".replace(z, +function(b){Ua[b.toLowerCase()]=b});var Fa=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(Ua[f]&&(e=Ua[f],"boolean"===typeof c[e]))return c[e]=!!b;!w&&Ra[f]&&(f=Ra[f]);if(!1===b||null===b||void 0===b)return c.removeAttribute(f);(Ib.test(c)?0:p.namespaces&&gb(c)||f in c.cloneNode(!1))?c[f]=b:c.setAttribute(f,b)}else if("include"===e&&b){var g=d.vmodels,h=d.includeRendered,k=d.includeLoaded,l=d.includeReplaced?c.parentNode: +c;g.cb(1);var m=function(b){k&&(b=k.apply(l,[b].concat(g)));for(h&&qa(l,function(){h.call(l)},NaN);;){var c=d.startInclude.nextSibling;if(c&&c!==d.endInclude)l.removeChild(c);else break}b=avalon.parseHTML(b);c=avalon.slice(b.childNodes);l.insertBefore(b,d.endInclude);Aa(c,g);g.cb(-1)};if("src"===d.param)if(Ta[b])avalon.nextTick(function(){m(Ta[b])});else{var n=Sa();n.onreadystatechange=function(){if(4===n.readyState){var c=n.status;if(200<=c&&300>c||304===c||1223===c)m(Ta[b]=n.responseText)}};n.open("GET", +b,!0);"withCredentials"in n&&(n.withCredentials=!0);n.setRequestHeader("X-Requested-With","XMLHttpRequest");n.send(null)}else{var q=b&&1===b.nodeType?b:p.getElementById(b);if(q){if("NOSCRIPT"===q.tagName&&!q.innerHTML&&!q.fixIE78){n=Sa();n.open("GET",location,!1);n.send(null);c=p.getElementsByTagName("noscript");for(var f=(n.responseText||"").match(gd)||[],e=f.length,t=0;t\u4e0a\u8981\u6c42\u5bf9\u5e94\u4e00\u4e2a\u6570\u7ec4"):b.multiple&&v("ms-duplex\u5728
", "
"], - legend: [1, "
"], - option: [1, ""], - 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 - tagHooks.circle = tagHooks.ellipse = tagHooks.line = tagHooks.path = //处理SVG - 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) { - 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 (typeof callback === "function") - callback.apply(this, args) - } - for (var i = 0, callback; callback = all[i++]; ) { - if (typeof callback === "function") - 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 = document.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+)\[(avalonid)="(\S+)"\]/ - var findNode = document.querySelector ? function(str) { - return document.querySelector(str) - } : function(str) { - var match = str.match(ravalon) - var all = document.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, val) { - 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 - } - } - } - } - } - - function collectSubscribers(scope, prop, data) {//收集依赖于这个访问器的订阅者 - var obj = scope.$events - if (obj) { - var list = obj[prop] || (obj[prop] = []) - if (avalon.Array.ensure(list, data) && data.element) { - $$subscribers.push({ - data: data, list: list - }) - } - } - } - - - var $$subscribers = [], $startIndex = 0, $maxIndex = 300 - 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) { - clearTimeout(removeID) - if (new Date() - 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]; ) { - 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 || []), fn.element, 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 addId(elem, value) { - if (typeof elem.getAttribute("avalonid") !== "string") { - elem.setAttribute("avalonid", value) - } - } - - 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]样式规则 - addId(elem, node.value) - newVmodel.$events.expr = elem.tagName + '[avalonid="' + 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 = cacheFactory("attrs", 256) - 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 robjectProperty = /([\w\.\_$])\s*\[['"]([^'"]+)['"]\]/ - //处理注释及字符串 - var rstringComment = /\/\*[\w\W]*?\*\/|\/\/[^\n]*\n|\/\/[^\n]*$|"(?:[^"\\]|\\[\w\W])*"|'(?:[^'\\]|\\[\w\W])*'/g - //处理加减乘除小括号等运算符 - var roperator = /[^\w\.$]+/g - //处理数字 - var rnumber = /\b\d[^,]*/g - //处理最前面或最后面逗号 - var rcommaOfFirstOrLast = /^,+|,+$/g - //处理位于中间的逗号 - var rcommaInMiddle = /,+/ - //去掉所有关键字保留字 - var rkeywords = new RegExp(["\\b" + keywords.replace(/,/g, '\\b|\\b') + "\\b"].join('|'), 'g') - var cacheVars = cacheFactory("vars", 128) - var getVariables = function(str) { - var key = "," + str.trim() - var has = cacheVars.get(key) - if (has !== void 0) { - return has - } - while (robjectProperty.test(str)) { - str = str.replace(robjectProperty, function(match, obj, prop) { - return obj + '.' + prop - }) - } - var vars = str.replace(rstringComment, "") - .replace(roperator, ",") - .replace(rkeywords, ",") - .replace(rnumber, ",") - .replace(rcommaOfFirstOrLast, "") - .split(rcommaInMiddle) - return cacheVars.set(key, uniqSet(vars)) - } - - 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 - } - - void function() { - var test = [1, 2, 3, 1] - if (typeof Set === "function" && (new Set(test)).size === 3) { - var uniqSet = function(arr) {//重写uniqSet - var set = new Set(arr), ret = [] - set.forEach(function(el) { - ret.push(el) - }) - return ret - } - } - }() - - - function inObject(obj, array) { - if (!obj.hasOwnProperty(array[0])) { - return 0 - } - for (var i = 1, el; el = array[i++]; ) { - if (!obj.hasOwnProperty(el)) { - return (obj && typeof obj === "object") ? 1 : 0 - } else { - obj = obj[el] - } - } - return 2 - } - /*添加赋值语句*/ - var rvariable = /[$\w][$\w]*\./g - function addAssign(vars, scope, name, data) { - var ret = [], - prefix = " = " + name + "." - for (var i = vars.length, path; path = vars[--i]; ) { - var arr = path.split(".") - var flag = inObject(scope, arr) - if (flag) { - var prop = arr.shift() - if (typeof scope[prop] === "function") { - var _vars = getVariables(scope[prop].toString().replace(rvariable, "")).concat() - avalon.Array.remove(_vars, prop) - addAssign(_vars, scope, name, data) - } else { - collectSubscribers(scope, prop, data) - } - ret.push(prop + prefix + prop) - if (data.type === "duplex") { - vars.get = name + "." + prop - } - var subscope = scope - do {//处理子对象 - var parentList = scope.$events[prop] - var parentName = prop - subscope = subscope[prop] - if (subscope && typeof subscope === "object") { - prop = arr.shift() - if ((prop === void 0 || prop === "length") && Array.isArray(subscope)) { - var sonEvents = subscope.$events - if (!sonEvents) - continue -// var sonList = sonEvents[subscribers] -// if (sonList !== parentList) { -// if (sonList && sonList.length) { -// for (var j = 0, fn; fn = sonList[j++]; ) { -// avalon.Array.ensure(parentList, fn) -// } -// } -// sonEvents[subscribers] = parentList -// prop = subscribers -// } - prop = subscribers - } - collectSubscribers(subscope, prop, data) - } else { - break - } - } while (arr.length) - if (flag > 0) - vars.splice(i, 1) - } - } - return ret - } - - - //缓存求值函数,以便多次利用 - var cacheExprs = cacheFactory("epxrs", 124) - //取得求值函数及其传参 - 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.get(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.set(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.set(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]) { - 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 - 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.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)) - } - }, - "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" - var fn = data.evaluator || noop - 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) - } - } - data.evaluator = data.handler = noop - }, - "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) - 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 = [] - try { - var $repeat = data.$repeat = data.evaluator.apply(0, data.args || []) - var xtype = avalon.type($repeat) - if (xtype !== "object" && xtype !== "array") { - return avalon.log("warning:" + data.value + "对应类型不正确") - } - } catch (e) { - return 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) - } - - 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 - } - } - - 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"] || {} - event = event.value - if (event === "change") { - bound("change", updateVModel) - } else { - 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"] - - 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) - } - 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") - } - } - } - - 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 - } - } - - 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 document.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 (document.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 = document.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() //它在父VM中的名字 - array.$model = model //数据模型 - array.$events = {} - array.$events[subscribers] = [] - //在监控数组中,它没有用处,只是基于VM的规范全部统一添加 - array._ = modelFactory({ - size: model.length - }) - array._.$watch("size", 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], this.$model[i]) - } - _splice.apply(this, [pos, 0].concat(added)) - this._fire("add", pos, added) - if (!this._stopFireLength) { - return this._.size = 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._.size = 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._.size = this.length - if (change) { - this._fire("index", 0) - } - return ret //返回被移除的元素 - }, - contains: function(el) { //判定是否包含 - return this.indexOf(el) !== -1 - }, - size: function() { //取得数组长度,这个函数可以同步视图,length不能 - return this._.size - }, - 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._.size = 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, $model) { - if (rcomplexType.test(avalon.type(val))) { - val = val.$id ? val : modelFactory(val, 0, $model) - } - return val - } - - - bindingExecutors.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(",")) - } - }) - } - } - - //============ each/repeat/with binding 用到的辅助函数与对象 ====================== - 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) - } - // 取得用于定位的节点。比如group = 3, 结构为 - //






- // 当pos为0时,返回 br#first - // 当pos为1时,返回 br#second - // 当pos为2时,返回 null - function locateFragment(data, pos) { - if (data.type == "repeat") {//ms-repeat,group为1 - var node = data.element.nextSibling - for (var i = 0, n = pos; i < n; i++) { - if (node) { - node = node.nextSibling - } else { - break - } - } - } else { - var nodes = avalon.slice(data.element.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 getFirstThursdayOfYear(year) { - // 0 = index of January - var dayOfWeekOnFirst = (new Date(year, 0, 1)).getDay(); - // 4 = index of Thursday (+1 to account for 1st = 5) - // 11 = index of *next* Thursday (+1 account for 1st = 12) - return new Date(year, 0, ((dayOfWeekOnFirst <= 4) ? 5 : 12) - dayOfWeekOnFirst); - } - - function getThursdayThisWeek(datetime) { - return new Date(datetime.getFullYear(), datetime.getMonth(), - // 4 = index of Thursday - datetime.getDate() + (4 - datetime.getDay())); - } - - function weekGetter(size) { - return function(date) { - var firstThurs = getFirstThursdayOfYear(date.getFullYear()), - thisThurs = getThursdayThisWeek(date); - - var diff = +thisThurs - +firstThurs, - result = 1 + Math.round(diff / 6.048e8); // 6.048e8 ms per week - - return padNumber(result, size); - }; - } - - - //取得上午下午 - - 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, - ww: weekGetter(2), - w: weekGetter(1) - } - var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEw']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|w+))(.*)/, - 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, timezone) { - 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 - } - } - if (timezone && timezone === 'UTC') { - date = new Date(date.getTime()); - date.setMinutes(date.getMinutes() + date.getTimezoneOffset()); - } - 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 - } - - - /********************************************************************* - * AMD加载器 * - **********************************************************************/ - - var innerRequire - var modules = avalon.modules = { - "ready!": { - exports: avalon - }, - "avalon": { - exports: avalon, - state: 2 - } - } - - - new function() { - var loadings = [] //正在加载中的模块列表 - var factorys = [] //储存需要绑定ID与factory对应关系的模块(标准浏览器下,先parse的script节点会先onload) - var basepath - - function cleanUrl(url) { - return (url || "").replace(/[?#].*/, "") - } - - plugins.js = function(url, shim) { - var id = cleanUrl(url) - if (!modules[id]) { //如果之前没有加载过 - modules[id] = { - id: id, - exports: {} - } - if (shim) { //shim机制 - innerRequire(shim.deps || "", function() { - loadJS(url, id, function() { - modules[id].state = 2 - if (shim.exports) - modules[id].exports = typeof shim.exports === "function" ? - shim.exports() : window[shim.exports] - innerRequire.checkDeps() - }) - }) - } else { - loadJS(url, id) - } - } - return id - } - plugins.css = function(url) { - var id = url.replace(/(#.+|\W)/g, "") ////用于处理掉href中的hash与所有特殊符号 - if (!DOC.getElementById(id)) { - var node = DOC.createElement("link") - node.rel = "stylesheet" - node.href = url - node.id = id - head.insertBefore(node, head.firstChild) - } - } - plugins.css.ext = ".css" - plugins.js.ext = ".js" - - plugins.text = function(url) { - var xhr = getXHR() - var id = url.replace(/[?#].*/, "") - modules[id] = {} - xhr.onreadystatechange = function() { - if (xhr.readyState === 4) { - var status = xhr.status; - if (status > 399 && status < 600) { - avalon.error(url + " 对应资源不存在或没有开启 CORS") - } else { - modules[id].state = 2 - modules[id].exports = xhr.responseText - innerRequire.checkDeps() - } - } - } - xhr.open("GET", url, true) - if ("withCredentials" in xhr) { - xhr.withCredentials = true - } - xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest") - xhr.send() - return id - } - - - var cur = getCurrentScript(true) - if (!cur) { //处理window safari的Error没有stack的问题 - cur = avalon.slice(DOC.scripts).pop().src - } - var url = cleanUrl(cur) - basepath = kernel.base = url.slice(0, url.lastIndexOf("/") + 1) - - function getCurrentScript(base) { - // 参考 https://github.com/samyk/jiagra/blob/master/jiagra.js - var stack - try { - a.b.c() //强制报错,以便捕获e.stack - } catch (e) { //safari的错误对象只有line,sourceId,sourceURL - stack = e.stack - if (!stack && window.opera) { - //opera 9没有e.stack,但有e.Backtrace,但不能直接取得,需要对e对象转字符串进行抽取 - stack = (String(e).match(/of linked script \S+/g) || []).join(" ") - } - } - if (stack) { - /**e.stack最后一行在所有支持的浏览器大致如下: - *chrome23: - * at http://113.93.50.63/data.js:4:1 - *firefox17: - *@http://113.93.50.63/query.js:4 - *opera12:http://www.oldapps.com/opera.php?system=Windows_XP - *@http://113.93.50.63/data.js:4 - *IE10: - * at Global code (http://113.93.50.63/data.js:4:1) - * //firefox4+ 可以用document.currentScript - */ - stack = stack.split(/[@ ]/g).pop() //取得最后一行,最后一个空格或@之后的部分 - stack = stack[0] === "(" ? stack.slice(1, -1) : stack.replace(/\s/, "") //去掉换行符 - return stack.replace(/(:\d+)?:\d+$/i, "") //去掉行号与或许存在的出错字符起始位置 - } - var nodes = (base ? DOC : head).getElementsByTagName("script") //只在head标签中寻找 - for (var i = nodes.length, node; node = nodes[--i]; ) { - if ((base || node.className === subscribers) && node.readyState === "interactive") { - return node.className = node.src - } - } - } - - function checkCycle(deps, nick) { - //检测是否存在循环依赖 - for (var id in deps) { - if (deps[id] === "司徒正美" && modules[id].state !== 2 && (id === nick || checkCycle(modules[id].deps, nick))) { - return true - } - } - } - - function checkDeps() { - //检测此JS模块的依赖是否都已安装完毕,是则安装自身 - loop: for (var i = loadings.length, id; id = loadings[--i]; ) { - - var obj = modules[id], - deps = obj.deps - for (var key in deps) { - if (ohasOwn.call(deps, key) && modules[key].state !== 2) { - continue loop - } - } - //如果deps是空对象或者其依赖的模块的状态都是2 - if (obj.state !== 2) { - loadings.splice(i, 1) //必须先移除再安装,防止在IE下DOM树建完后手动刷新页面,会多次执行它 - fireFactory(obj.id, obj.args, obj.factory) - checkDeps() //如果成功,则再执行一次,以防有些模块就差本模块没有安装好 - } - } - } - - function checkFail(node, onError, fuckIE) { - var id = cleanUrl(node.src) //检测是否死链 - node.onload = node.onreadystatechange = node.onerror = null - if (onError || (fuckIE && !modules[id].state)) { - setTimeout(function() { - head.removeChild(node) - node = null // 处理旧式IE下的循环引用问题 - }) - log("debug: 加载 " + id + " 失败" + onError + " " + (!modules[id].state)) - } else { - return true - } - } - var rdeuce = /\/\w+\/\.\./ - - function loadResources(url, parent, ret, shim) { - //1. 特别处理mass|ready标识符 - if (url === "ready!" || (modules[url] && modules[url].state === 2)) { - return url - } - //2. 处理text! css! 等资源 - var plugin - url = url.replace(/^\w+!/, function(a) { - plugin = a.slice(0, -1) - return "" - }) - plugin = plugin || "js" - plugin = plugins[plugin] || noop - //3. 转化为完整路径 - if (typeof kernel.shim[url] === "object") { - shim = kernel.shim[url] - } - if (kernel.paths[url]) { //别名机制 - url = kernel.paths[url] - } - - //4. 补全路径 - if (/^(\w+)(\d)?:.*/.test(url)) { - ret = url - } else { - parent = parent.substr(0, parent.lastIndexOf("/")) - var tmp = url.charAt(0) - if (tmp !== "." && tmp !== "/") { //相对于根路径 - ret = basepath + url - } else if (url.slice(0, 2) === "./") { //相对于兄弟路径 - ret = parent + url.slice(1) - } else if (url.slice(0, 2) === "..") { //相对于父路径 - ret = parent + "/" + url - while (rdeuce.test(ret)) { - ret = ret.replace(rdeuce, "") - } - } else if (tmp === "/") { - ret = url //相对于根路径 - } else { - avalon.error("不符合模块标识规则: " + url) - } - } - //5. 补全扩展名 - url = cleanUrl(ret) - var ext = plugin.ext - if (ext) { - if (url.slice(0 - ext.length) !== ext) { - ret += ext - } - } - //6. 缓存处理 - if (kernel.nocache) { - ret += (ret.indexOf("?") === -1 ? "?" : "&") + (new Date - 0) - } - return plugin(ret, shim) - } - - function loadJS(url, id, callback) { - //通过script节点加载目标模块 - var node = DOC.createElement("script") - node.className = subscribers //让getCurrentScript只处理类名为subscribers的script节点 - node[W3C ? "onload" : "onreadystatechange"] = function() { - if (W3C || /loaded|complete/i.test(node.readyState)) { - //mass Framework会在_checkFail把它上面的回调清掉,尽可能释放回存,尽管DOM0事件写法在IE6下GC无望 - var factory = factorys.pop() - factory && factory.delay(id) - if (callback) { - callback() - } - if (checkFail(node, false, !W3C)) { - log("debug: 已成功加载 " + url) - } - } - } - node.onerror = function() { - checkFail(node, true) - } - node.src = url //插入到head的第一个节点前,防止IE6下head标签没闭合前使用appendChild抛错 - head.insertBefore(node, head.firstChild) //chrome下第二个参数不能为null - log("debug: 正准备加载 " + url) //更重要的是IE6下可以收窄getCurrentScript的寻找范围 - } - - innerRequire = avalon.require = function(list, factory, parent) { - // 用于检测它的依赖是否都为2 - var deps = {}, - // 用于保存依赖模块的返回值 - args = [], - // 需要安装的模块数 - dn = 0, - // 已安装完的模块数 - cn = 0, - id = parent || "callback" + setTimeout("1") - parent = parent || basepath - String(list).replace(rword, function(el) { - var url = loadResources(el, parent) - if (url) { - dn++ - if (modules[url] && modules[url].state === 2) { - cn++ - } - if (!deps[url]) { - args.push(url) - deps[url] = "司徒正美" //去重 - } - } - }) - modules[id] = {//创建一个对象,记录模块的加载情况与其他信息 - id: id, - factory: factory, - deps: deps, - args: args, - state: 1 - } - if (dn === cn) { //如果需要安装的等于已安装好的 - fireFactory(id, args, factory) //安装到框架中 - } else { - //放到检测列队中,等待checkDeps处理 - loadings.unshift(id) - } - checkDeps() - } - - /** - * 定义模块 - * @param {String} id ? 模块ID - * @param {Array} deps ? 依赖列表 - * @param {Function} factory 模块工厂 - * @api public - */ - innerRequire.define = function(id, deps, factory) { //模块名,依赖列表,模块本身 - var args = aslice.call(arguments) - - if (typeof id === "string") { - var _id = args.shift() - } - if (typeof args[0] === "function") { - args.unshift([]) - } //上线合并后能直接得到模块ID,否则寻找当前正在解析中的script节点的src作为模块ID - //现在除了safari外,我们都能直接通过getCurrentScript一步到位得到当前执行的script节点, - //safari可通过onload+delay闭包组合解决 - var name = modules[_id] && modules[_id].state >= 1 ? _id : cleanUrl(getCurrentScript()) - if (!modules[name] && _id) { - modules[name] = { - id: name, - factory: factory, - state: 1 - } - } - factory = args[1] - factory.id = _id //用于调试 - factory.delay = function(d) { - args.push(d) - var isCycle = true - try { - isCycle = checkCycle(modules[d].deps, d) - } catch (e) { - } - if (isCycle) { - avalon.error(d + "模块与之前的模块存在循环依赖,请不要直接用script标签引入" + d + "模块") - } - delete factory.delay //释放内存 - innerRequire.apply(null, args) //0,1,2 --> 1,2,0 - } - - if (name) { - factory.delay(name, args) - } else { //先进先出 - factorys.push(factory) - } - } - innerRequire.define.amd = modules - - function fireFactory(id, deps, factory) { - for (var i = 0, array = [], d; d = deps[i++]; ) { - array.push(modules[d].exports) - } - var module = Object(modules[id]), - ret = factory.apply(window, array) - module.state = 2 - if (ret !== void 0) { - modules[id].exports = ret - } - return ret - } - innerRequire.config = kernel - innerRequire.checkDeps = checkDeps - } - /********************************************************************* - * DOMReady * - **********************************************************************/ - - var readyList = [] - function fireReady() { - if (DOC.body) { // 在IE8 iframe中doScrollCheck可能不正确 - if (innerRequire) { - modules["ready!"].state = 2 - innerRequire.checkDeps() - } else { - readyList.forEach(function(a) { - a(avalon) - }) - } - fireReady = noop //隋性函数,防止IE9二次调用_checkDeps - } - } - - function doScrollCheck() { - try { //IE下通过doScrollCheck检测DOM树是否建完 - root.doScroll("left") - fireReady() - } catch (e) { - setTimeout(doScrollCheck) - } - } - - if (DOC.readyState === "complete") { - setTimeout(fireReady) //如果在domReady之外加载 - } else if (W3C) { - DOC.addEventListener("DOMContentLoaded", fireReady) - } else { - DOC.attachEvent("onreadystatechange", function() { - if (DOC.readyState === "complete") { - fireReady() - } - }) - 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() { - 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/examples/avalon.shim.js b/examples/avalon.shim.js index f265564f3..0e443fd06 100644 --- a/examples/avalon.shim.js +++ b/examples/avalon.shim.js @@ -1,11 +1,11 @@ /*================================================== - Copyright 2013-2014 司徒正美 and other contributors + Copyright (c) 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.6 2014.10.10 + avalon 1.3.7 2014.11.17 support IE6+ and other browsers ==================================================*/ (function(DOC) { /********************************************************************* @@ -13,13 +13,13 @@ **********************************************************************/ 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") + //http://stackoverflow.com/questions/7290086/javascript-use-strict-and-nicks-find-global-function + var window = Function("return this")() var otherRequire = window.require var otherDefine = window.define var stopRepeatAssign = false var rword = /[^, ]+/g //切割字符串为一个个小块,以空格或豆号分开它们,结合replace实现字符串的forEach - var rnative = /\[native code\]/ //判定是否原生函数 + var rnative = /\[native code\]/ //判定是否原生函数 var rcomplexType = /^(?:object|array)$/ var rsvg = /^\[object SVG\w*Element\]$/ var rwindow = /^\[object (?:Window|DOMWindow|global)\]$/ @@ -62,7 +62,8 @@ 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) } @@ -78,7 +79,7 @@ } avalon.fn = avalon.prototype = avalon.init.prototype - avalon.type = function(obj) {//取得目标的类型 + avalon.type = function(obj) { //取得目标的类型 if (obj == null) { return String(obj) } @@ -113,30 +114,38 @@ 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")) { + try { //IE内置对象没有constructor + if (obj.constructor && !ohasOwn.call(obj, "constructor") && !ohasOwn.call(obj.constructor.prototype, "isPrototypeOf")) { return false; } - } catch (e) {//IE8 9会在这里抛错 + } 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] || {}, @@ -151,23 +160,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) { @@ -295,6 +308,9 @@ name = avalon.cssName(prop) || prop if (value === void 0 || typeof value === "boolean") { //获取样式 var fn = cssHooks[prop + ":get"] || cssHooks["@:get"] + if (name === "background") { + name = "backgroundColor" + } var val = fn(node, name) return value === true ? parseFloat(val) || 0 : val } else if (value === "") { //请除样式 @@ -362,6 +378,7 @@ }) /*判定类数组,如节点集合,纯数组,arguments与拥有非负整数的length属性的纯JS对象*/ + function isArrayLike(obj) { if (obj && typeof obj === "object" && !avalon.isWindow(obj)) { var n = obj.length @@ -387,13 +404,13 @@ * modelFactory * **********************************************************************/ //avalon最核心的方法的两个方法之一(另一个是avalon.scan),返回一个ViewModel(VM) - var VMODELS = avalon.vmodels = {}//所有vmodel都储存在这里 + var VMODELS = avalon.vmodels = {} //所有vmodel都储存在这里 avalon.define = function(id, factory) { var $id = id.$id || id if (!$id) { log("warning: vm必须指定$id") } - if (VMODELS[id]) { + if (VMODELS[$id]) { log("warning: " + $id + " 已经存在于avalon.vmodels中") } if (typeof id === "object") { @@ -414,6 +431,7 @@ //一些不需要被监听的属性 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 @@ -431,7 +449,7 @@ return true } - function modelFactory($scope, $special) { + function modelFactory($scope, $special, $model) { if (Array.isArray($scope)) { var arr = $scope.concat() $scope.length = 0 @@ -442,23 +460,23 @@ if (typeof $scope.nodeType === "number") { return $scope } - if ($scope.$id && $scope.$model && $scope.$events) {//fix IE6-8 createWithProxy $val: val引发的BUG + 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 || {}//强制要监听的属性 + $scope.$skipArray.$special = $special || {} //强制要监听的属性 var $vmodel = {} //要返回的对象, 它在IE6-8下可能被偷龙转凤 - var $model = {} //vmodels.$model属性 + $model = $model || {} //vmodels.$model属性 var $events = {} //vmodel.$events属性 var watchedProperties = {} //监控属性 - var computedProperties = [] //计算属性 + var computedProperties = [] //计算属性 for (var i in $scope) { (function(name, val) { $model[name] = val if (!isObservable(name, val, $scope.$skipArray)) { - return //过滤所有非监控属性 + return //过滤所有非监控属性 } //总共产生三种accessor var accessor @@ -521,16 +539,15 @@ newValue = $model[name] = childVmodel.$model //同步$model var fn = rebindings[childVmodel.$id] fn && fn() //同步视图 - safeFire($vmodel, name, newValue, oldValue) //触发$watch回调 + safeFire($vmodel, name, newValue, oldValue) //触发$watch回调 } } else { collectSubscribers($events[name]) //收集视图函数 return childVmodel } } - var childVmodel = accessor.child = modelFactory(val) + var childVmodel = accessor.child = modelFactory(val, 0, $model[name]) childVmodel.$events[subscribers] = $events[name] - $model[name] = childVmodel.$model } else { //第3种对应简单的数据类型,自变量,监控属性 accessor = function(newValue) { @@ -551,9 +568,10 @@ watchedProperties[name] = accessor })(i, $scope[i]) } + $$skipArray.forEach(function(name) { - $scope[name] = true //为用户定义的对象再添加一些特殊属性 - delete $model[name] //这些特殊属性不应该在$model中出现 + delete $scope[name] + delete $model[name] //这些特殊属性不应该在$model中出现 }) $vmodel = defineProperties($vmodel, descriptorFactory(watchedProperties), $scope) //生成一个空的ViewModel @@ -567,7 +585,7 @@ $vmodel.$model = $model $vmodel.$events = $events for (var i in EventManager) { - var fn = EventManager [i] + var fn = EventManager[i] if (!W3C) { //在IE6-8下,VB对象的方法里的this并不指向自身,需要用bind处理一下 fn = fn.bind($vmodel) } @@ -577,7 +595,7 @@ $vmodel.hasOwnProperty = function(name) { return name in $vmodel.$model } - computedProperties.forEach(function(collect) {//收集依赖 + computedProperties.forEach(function(collect) { //收集依赖 collect() }) return $vmodel @@ -626,12 +644,13 @@ } } //应用于第2种accessor + function updateChild(parent, name, value, valueType) { //a为原来的VM, b为新数组或新对象 var son = parent[name] if (valueType === "array") { - if (!Array.isArray(value)) { - return parent //fix https://github.com/RubyLouvre/avalon/issues/261 + if (!Array.isArray(value) || son === value) { + return son //fix https://github.com/RubyLouvre/avalon/issues/261 } son.clear() son.pushArray(value.concat()) @@ -643,6 +662,7 @@ delete withProxyPool[son.$id] } var ret = modelFactory(value) + ret.$events[subscribers] = iterators rebindings[ret.$id] = function(data) { while (data = iterators.shift()) { (function(el) { @@ -744,6 +764,11 @@ buffer.push("\tPublic [" + name + "]") } } + $$skipArray.forEach(function(name) { + if (!accessors.hasOwnProperty(name)) { + buffer.push("\tPublic [" + name + "]") + } + }) buffer.push("\tPublic [" + 'hasOwnProperty' + "]") //添加访问器属性 for (name in accessors) { @@ -791,7 +816,9 @@ return this.replace(rtrim, "") } } - var hasDontEnumBug = !({'toString': null}).propertyIsEnumerable('toString'), + var hasDontEnumBug = !({ + 'toString': null + }).propertyIsEnumerable('toString'), hasProtoEnumBug = (function() { }).propertyIsEnumerable('prototype'), dontEnums = [ @@ -902,7 +929,7 @@ **********************************************************************/ function fixContains(root, el) { - try {//IE6-8,游离于DOM树外的文本节点,访问parentNode有时会抛错 + try { //IE6-8,游离于DOM树外的文本节点,访问parentNode有时会抛错 while ((el = el.parentNode)) if (el === root) return true; @@ -924,6 +951,7 @@ return fixContains(DOC, b) } } + function outerHTML() { return new XMLSerializer().serializeToString(this) } @@ -933,7 +961,7 @@ var svgns = "http://www.w3.org/2000/svg" var svg = DOC.createElementNS(svgns, "svg") svg.innerHTML = '' - if (!rsvg.test(svg.firstChild)) {// #409 + if (!rsvg.test(svg.firstChild)) { // #409 function enumerateNode(node, targetNode) { if (node && node.childNodes) { var nodes = node.childNodes @@ -942,7 +970,7 @@ var svg = DOC.createElementNS(svgns, el.tagName.toLowerCase()) ap.forEach.call(el.attributes, function(attr) { - svg.setAttribute(attr.name, attr.value)//复制属性 + svg.setAttribute(attr.name, attr.value) //复制属性 }) // 递归处理子节点 enumerateNode(el, svg) @@ -979,10 +1007,10 @@ 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, "") + return s.replace(ropen, "").replace(rclose, "") }, set: function(html) { - if (avalon.clearHTM) { + if (avalon.clearHTML) { avalon.clearHTML(this) var frag = avalon.parseHTML(html) enumerateNode(frag, this) @@ -999,6 +1027,7 @@ /********************************************************************* * 配置系统 * **********************************************************************/ + function kernel(settings) { for (var p in settings) { if (!ohasOwn.call(settings, p)) @@ -1021,6 +1050,7 @@ //将字符串安全格式化为正则表达式的源码 return (target + "").replace(rregexp, "\\$&") } + var innerRequire = noop var plugins = { loader: function(builtin) { window.define = builtin ? innerRequire.define : otherDefine @@ -1078,7 +1108,7 @@ var ClassListMethods = { _toString: function() { - var node = this.node//IE6,7元素节点不存在hasAttribute方法 + var node = this.node var cls = node.className var str = typeof cls === "string" ? cls : cls.baseVal return str.split(/\s+/).join(" ") @@ -1098,11 +1128,12 @@ var node = this.node if (typeof node.className === "string") { node.className = cls - } else {//SVG元素的className是一个对象 SVGAnimatedString { baseVal="", animVal=""},只能通过set/getAttribute操作 + } else { //SVG元素的className是一个对象 SVGAnimatedString { baseVal="", animVal=""},只能通过set/getAttribute操作 node.setAttribute("class", cls) } - }//toggle存在版本差异,因此不使用它 + } //toggle存在版本差异,因此不使用它 } + function ClassList(node) { if (!("classList" in node)) { node.classList = { @@ -1306,8 +1337,7 @@ var cssHooks = avalon.cssHooks = {} var prefixes = ["", "-webkit-", "-o-", "-moz-", "-ms-"] var cssMap = { - "float": "cssFloat", - background: "backgroundColor" + "float": "cssFloat" } avalon.cssNumber = oneObject("columnCount,order,fillOpacity,fontWeight,lineHeight,opacity,orphans,widows,zIndex,zoom") @@ -1446,7 +1476,7 @@ } } } - "Width,Height".replace(rword, function(name) {//fix 481 + "Width,Height".replace(rword, function(name) { //fix 481 var method = name.toLowerCase(), clientProp = "client" + name, scrollProp = "scroll" + name, @@ -1457,21 +1487,15 @@ 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) + 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 } @@ -1515,10 +1539,11 @@ } }) avalon.fn.offset = function() { //取得距离页面左右角的坐标 - var node = this[0], box = { - left: 0, - top: 0 - } + var node = this[0], + box = { + left: 0, + top: 0 + } if (!node || !node.tagName || !node.ownerDocument) { return box } @@ -1557,13 +1582,12 @@ } var roption = /^]+))?)*\s+value[\s=]/i var valHooks = { - "option:get": function(node) { + "option:get": window.VBArray ? 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 + return roption.test(node.outerHTML) ? node.value : node.text.trim() + } : function(node) { + return node.value }, "select:get": function(node, value) { var option, options = node.options, @@ -1606,13 +1630,7 @@ /************************************************************************ * 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, ""], @@ -1623,7 +1641,7 @@ thead: [1, "
", "
"], tr: [2, ""], td: [3, "
"], - text: [1, '', ''], + g: [1, '', ''], //IE6-8在用innerHTML生成节点时,不能直接创建no-scope元素与HTML5的新标签 _default: W3C ? [0, ""] : [1, "X
"] //div可以不用闭合 } @@ -1631,9 +1649,14 @@ 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 + String("circle,defs,ellipse,image,line,path,polygon,polyline,rect,symbol,text,use").replace(rword, function(tag) { + tagHooks[tag] = tagHooks.g //处理SVG + }) + var rtagName = /<([\w:]+)/ //取得其tagName + var rxhtml = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig + var rcreate = W3C ? /[^\d\D]/ : /(<(?:script|link|style|meta|noscript))/ig + var scriptTypes = oneObject(["", "text/javascript", "text/ecmascript", "application/ecmascript", "application/javascript"]) + var rnest = /<(?:tb|td|tf|th|tr|col|opt|leg|cap|area)/ //需要处理套嵌关系的标签 var script = DOC.createElement("script") avalon.parseHTML = function(html) { if (typeof html !== "string") { @@ -1653,14 +1676,16 @@ 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能让其执行脚本 + if (scriptTypes[el.type]) { + //以偷龙转凤方式恢复执行脚本功能 neo = script.cloneNode(false) //FF不能省略参数 ap.forEach.call(el.attributes, function(attr) { if (attr && attr.specified) { neo[attr.name] = attr.value //复制其属性 + neo.setAttribute(attr.name, attr.value) } }) - neo.text = el.text //必须指定,因为无法在attributes中遍历出来 + neo.text = el.text el.parentNode.replaceChild(neo, el) //替换节点 } } @@ -1669,26 +1694,36 @@ for (i = wrap[0]; i--; wrapper = wrapper.lastChild) { } if (!W3C) { //fix IE - for (els = wrapper["getElementsByTagName"]("br"), i = 0; el = els[i++]; ) { + for (els = avalon.slice(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 + for (els = wrapper.all, i = 0; el = els[i++]; ) { //fix VML if (isVML(el)) { fixVML(el) } } + if (tag === "tr") { + for (els = avalon.slice(wrapper.children), i = 0; el = els[i++]; ) { + // IE6-8,如果动态生成tr元素,必须会在后面添加早已废弃caption的标签,其nodeName,innerHTML都为"" + if (el.nodeName == "") { + el.parentNode.removeChild(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 === "" + return nodeName.toLowerCase() === nodeName && src.scopeName && src.outerText === "" } + function fixVML(node) { if (node.currentStyle.behavior !== "url(#default#VML)") { node.style.behavior = "url(#default#VML)" @@ -1714,6 +1749,7 @@ } return node } + /********************************************************************* * 事件管理器 * **********************************************************************/ @@ -1756,60 +1792,69 @@ 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 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 element = events.expr && findNode(events.expr) + if (!element) + 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) { + continue + } + var ok = 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() + } + 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.$fire.apply(v, 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) + return DOC.querySelector(str) } : function(str) { var match = str.match(ravalon) var all = DOC.getElementsByTagName(match[1]) @@ -1823,6 +1868,7 @@ * 依赖调度系统 * **********************************************************************/ var ronduplex = /^(duplex|on)$/ + function registerSubscriber(data) { Registry[expose] = data //暴光此函数,方便collectSubscribers收集 avalon.openComputedCollect = true @@ -1852,12 +1898,31 @@ function collectSubscribers(list) { //收集依赖于这个访问器的订阅者 var data = Registry[expose] if (list && data && avalon.Array.ensure(list, data) && data.element) { //只有数组不存在此元素才push进去 - $$subscribers.push({ - data: data, list: list - }) + addSubscribers(data, list) + } + } + + function addSubscribers(data, list) { + data.$uuid = data.$uuid || generateID() + list.$uuid = list.$uuid || generateID() + var obj = { + data: data, + list: list, + toString: function() { + return data.$uuid + " " + list.$uuid + } + } + if (!$$subscribers[obj]) { + $$subscribers[obj] = 1 + $$subscribers.push(obj) } } - var $$subscribers = [], $startIndex = 0, $maxIndex = 200 + var $$subscribers = [], + $startIndex = 0, + $maxIndex = 200, + beginTime = new Date(), + removeID + function removeSubscribers() { for (var i = $startIndex, n = $startIndex + $maxIndex; i < n; i++) { var obj = $$subscribers[i] @@ -1870,10 +1935,11 @@ el.sourceIndex === 0 : !root.contains(el) : !avalon.contains(root, el)) if (remove) { //如果它没有在DOM树 $$subscribers.splice(i, 1) + delete $$subscribers[obj] 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) + if (data.type === "if" && data.template && data.template.parentNode === ifGroup) { + ifGroup.removeChild(data.template) } for (var key in data) { data[key] = null @@ -1881,6 +1947,7 @@ obj.data = obj.list = null i-- n-- + } } obj = $$subscribers[i] @@ -1889,26 +1956,27 @@ } else { $startIndex = 0 } + beginTime = new Date() } - var beginTime = new Date(), removeID + function notifySubscribers(list) { //通知依赖于这个访问器的订阅者更新自身 - var currentTime = new Date() clearTimeout(removeID) - if (currentTime - beginTime > 333) { + if (new Date() - beginTime > 444) { removeSubscribers() - beginTime = new Date() } else { - removeID = setTimeout(removeSubscribers, 333) + removeID = setTimeout(removeSubscribers, 444) } 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) + if (el && el.parentNode) { + if (fn.$repeat) { + fn.handler.apply(fn, args) //处理监控数组的方法 + } else if (fn.type !== "on") { //事件绑定只能由用户触发,不能由程序触发 + var fun = fn.evaluator || noop + fn.handler(fun.apply(0, fn.args || []), el, fn) + } } } } @@ -1917,31 +1985,46 @@ /********************************************************************* * 扫描系统 * **********************************************************************/ - avalon.scan = function(elem, vmodel) { + var scanObject = {} + avalon.scanCallback = function(fn, group) { + group = group || "$all" + var array = scanObject[group] || (scanObject[group] = []) + array.push(fn) + } + avalon.scan = function(elem, vmodel, group) { elem = elem || root + group = group || "$all" + var array = scanObject[group] || [] var vmodels = vmodel ? [].concat(vmodel) : [] + var scanIndex = 0; + var scanAll = false + var fn + var dirty = false + function cb(i) { + scanIndex += i + dirty = true + setTimeout(function() { + if (scanIndex <= 0 && !scanAll) { + scanAll = true + while (fn = array.shift()) { + fn() + } + } + }) + } + vmodels.cb = cb scanTag(elem, vmodels) + //html, include, widget + if (!dirty) { + while (fn = array.shift()) { + fn() + } + } } //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) @@ -1961,17 +2044,24 @@ return } //ms-important不包含父VM,ms-controller相反 + var cb = vmodels.cb vmodels = node === b ? [newVmodel] : [newVmodel].concat(vmodels) + vmodels.cb = cb var name = node.name elem.removeAttribute(name) //removeAttributeNode不会刷新[ms-controller]样式规则 - elem.setAttribute("avalonctrl", node.value) - newVmodel.$events.expr = elem.tagName + '[avalonctrl="' + node.value + '"]' + createSignalTower(elem, newVmodel) avalon(elem).removeClass(name) } scanAttr(elem, vmodels) //扫描特性节点 } + function createSignalTower(elem, vmodel) { + var id = elem.getAttribute("avalonctrl") || vmodel.$id + elem.setAttribute("avalonctrl", id) + vmodel.$events.expr = elem.tagName + '[avalonctrl="' + id + '"]' + } + function scanNodeList(parent, vmodels) { var node = parent.firstChild while (node) { @@ -1986,6 +2076,7 @@ scanNode(node, node.nodeType, vmodels) } } + function scanNode(node, nodeType, vmodels) { if (nodeType === 1) { scanTag(node, vmodels) //扫描元素节点 @@ -2053,9 +2144,11 @@ "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 } + var obsoleteAttrs = oneObject("value,title,alt,checked,selected,disabled,readonly,enabled") function scanAttr(elem, vmodels) { //防止setAttribute, removeAttribute时 attributes自动被同步,导致for循环出错 var attributes = getAttributes ? getAttributes(elem) : avalon.slice(elem.attributes) @@ -2074,12 +2167,12 @@ 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") { + } else if (obsoleteAttrs[type]) { + log("ms-" + type + "已经被废弃,请使用ms-attr-*代替") + if (type === "enabled") { //吃掉ms-enabled绑定,用ms-disabled代替 + type = "disabled" + value = "!(" + value + ")" + } param = type type = "attr" elem.removeAttribute(name) @@ -2115,6 +2208,7 @@ log("warning!一个元素上不能同时定义ms-checked与ms-duplex") } var firstBinding = bindings[0] || {} + switch (firstBinding.type) { case "if": case "repeat": @@ -2126,10 +2220,8 @@ if (!stopScan[elem.tagName] && rbind.test(elem.innerHTML.replace(rlt, "<").replace(rgt, ">"))) { scanNodeList(elem, vmodels) //扫描子孙元素 } - break; + break } - - } //IE67下,在循环绑定中,一个节点如果是通过cloneNode得到,自定义属性的specified为false,无法进入里面的分支, //但如果我们去掉scanAttr中的attr.specified检测,一个元素会有80+个特性节点(因为它不区分固有属性与自定义属性),很容易卡死页面 @@ -2180,6 +2272,9 @@ } function executeBindings(bindings, vmodels) { + if (bindings.length) + vmodels.cb(bindings.length) + for (var i = 0, data; data = bindings[i++]; ) { data.vmodels = vmodels bindingHandlers[data.type](data, vmodels) @@ -2197,6 +2292,7 @@ r11b = /U2hvcnRDaXJjdWl0/g, rlt = /</g, rgt = />/g + function trimFilter(value, leach) { if (value.indexOf("|") > 0) { // 抽取过滤器 先替换掉所有短路与 value = value.replace(r11a, "U2hvcnRDaXJjdWl0") //btoa("ShortCircuit") @@ -2266,8 +2362,7 @@ ",package,private,protected,public,short,static,super,synchronized" + ",throws,transient,volatile" // ECMA 5 - use strict - + ",arguments,let,yield" - + ",undefined" + + ",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') @@ -2289,6 +2384,7 @@ return cacheVars(key, uniqSet(match)) } /*添加赋值语句*/ + function addAssign(vars, scope, name, data) { var ret = [], prefix = " = " + name + "." @@ -2306,7 +2402,8 @@ } function uniqSet(array) { - var ret = [], unique = {} + 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 @@ -2320,6 +2417,7 @@ function createCache(maxLength) { var keys = [] + function cache(key, value) { if (keys.push(key) > maxLength) { delete cache[keys.shift()] @@ -2442,21 +2540,23 @@ '\\': '\\\\' } var quote = window.JSON && JSON.stringify || function(str) { - return '"' + str.replace(/[\\\"\x00-\x1f]/g, function(a) { + 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) { + + function parseExprProxy(code, scopes, data, tokens, noregister) { + scopes.cb(-1) 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) { + if (data.evaluator && !noregister) { data.handler = bindingExecutors[data.handlerName || data.type] data.evaluator.toString = function() { return data.type + " binding to eval(" + code + ")" @@ -2480,15 +2580,12 @@ "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" + 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 ifGroup = head.insertBefore(document.createElement("avalon"), head.firstChild) //避免IE6 base标签BUG + ifGroup.innerHTML = "X" var rnoscripts = /(?:[\s\S]+?)<\/noscript>/img var rnoscriptText = /([\s\S]+?)<\/noscript>/im @@ -2530,26 +2627,20 @@ if (boolMap[attrName]) { var bool = boolMap[attrName] if (typeof elem[bool] === "boolean") { + // IE6-11不支持动态设置fieldset的disabled属性,IE11下样式是生效了,但无法阻止用户对其底下的input元素进行设值…… return elem[bool] = !!val } } var toRemove = (val === false) || (val === null) || (val === void 0) - if (!W3C && propMap[attrName]) {//旧式IE下需要进行名字映射 + + 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 - } - } + //SVG只能使用setAttribute(xxx, yyy), VML只能使用elem.xxx = yyy ,HTML的固有属性必须elem.xxx = yyy + var isInnate = rsvg.test(elem) ? false : (DOC.namespaces && isVML(elem)) ? true : attrName in elem.cloneNode(false) if (isInnate) { elem[attrName] = val } else { @@ -2557,18 +2648,34 @@ } } 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) - + var rendered = data.includeRendered + var loaded = data.includeLoaded + var replace = data.includeReplaced + var target = replace ? elem.parentNode : elem + vmodels.cb(1) function scanTemplate(text) { if (loaded) { - text = loaded.apply(elem, [text].concat(vmodels)) + text = loaded.apply(target, [text].concat(vmodels)) } - avalon.innerHTML(elem, text) - scanNodeList(elem, vmodels) - rendered && checkScan(elem, function() { - rendered.call(elem) - }) + if (rendered) { + avalon.scanCallback(function() { + rendered.call(target) + }) + } + avalon.scan(target) + while (true) { + var node = data.startInclude.nextSibling + if (node && node !== data.endInclude) { + target.removeChild(node) + } else { + break + } + } + var dom = avalon.parseHTML(text) + var nodes = avalon.slice(dom.childNodes) + target.insertBefore(dom, data.endInclude) + scanNodeArray(nodes, vmodels) + vmodels.cb(-1) } if (data.param === "src") { if (cacheTmpls[val]) { @@ -2623,6 +2730,12 @@ 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) { @@ -2647,7 +2760,7 @@ if (!data.hasBindEvent) { //确保只绑定一次 var activate = "mouseenter" //在移出移入时切换类名 var abandon = "mouseleave" - if (method === "active") {//在聚焦失焦中切换类名 + if (method === "active") { //在聚焦失焦中切换类名 elem.tabIndex = elem.tabIndex || -1 activate = "mousedown" abandon = "mouseup" @@ -2720,20 +2833,15 @@ } 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 - } + while (true) { + var node = data.element.nextSibling + if (node && node !== data.endRepeat) { + parent.removeChild(node) + } else { + break } - recycleEachProxies(proxies) } + recycleEachProxies(proxies) break case "move": //将proxies中的第pos个元素移动el位置上(pos, el都是数字) var t = proxies.splice(pos, 1)[0] @@ -2779,13 +2887,15 @@ calculateFragmentGroup(data) break } - var callback = data.renderedCallback || noop, args = arguments - checkScan(parent, function() { + var callback = data.renderedCallback || noop, + args = arguments + avalon.scanCallback(function() { callback.apply(parent, args) - if (parent.oldValue && parent.tagName === "SELECT" && method === "index") {//fix #503 + if (parent.oldValue && parent.tagName === "SELECT" && method === "index") { //fix #503 avalon(parent).val(parent.oldValue.split(",")) } }) + avalon.scan(parent) } }, "html": function(val, elem, data) { @@ -2826,15 +2936,17 @@ } else { avalon.innerHTML(parent, val) } + data.vmodels.cb(1) avalon.nextTick(function() { scanNodeList(parent, data.vmodels) + data.vmodels.cb(-1) }) }, "if": function(val, elem, data) { if (val) { //插回DOM树 if (elem.nodeType === 8) { elem.parentNode.replaceChild(data.template, elem) - elem = data.element = data.template + elem = data.element = data.template //这时可能为null } if (elem.getAttribute(data.name)) { elem.removeAttribute(data.name) @@ -2845,7 +2957,7 @@ var node = data.element = DOC.createComment("ms-if") elem.parentNode.replaceChild(node, elem) data.template = elem //元素节点 - head.appendChild(elem) + ifGroup.appendChild(elem) } } }, @@ -2857,7 +2969,9 @@ } var eventType = data.param.replace(/-\d+$/, "") // ms-on-mousemove-10 if (eventType === "scan") { - callback.call(elem, {type: eventType}) + callback.call(elem, { + type: eventType + }) } else if (typeof data.specialBind === "function") { data.specialBind(elem, callback) } else { @@ -2874,7 +2988,7 @@ "text": function(val, elem) { val = val == null ? "" : val //不在页面上显示undefined null if (elem.nodeType === 3) { //绑定在文本节点上 - try {//IE对游离于DOM树外的节点赋值会报错 + try { //IE对游离于DOM树外的节点赋值会报错 elem.data = val } catch (e) { } @@ -2926,8 +3040,24 @@ text = RegExp.$1 } } + if (data.type === "include") { + var elem = data.element + data.includeRendered = getBindingCallback(elem, "data-include-rendered", vmodels) + data.includeLoaded = getBindingCallback(elem, "data-include-loaded", vmodels) + var outer = data.includeReplaced = !!avalon(elem).data("includeReplace") + data.startInclude = DOC.createComment("ms-include") + data.endInclude = DOC.createComment("ms-include-end") + if (outer) { + data.element = data.startInclude + elem.parentNode.insertBefore(data.startInclude, elem) + elem.parentNode.insertBefore(data.endInclude, elem.nextSibling) + } else { + elem.insertBefore(data.startInclude, elem.firstChild) + elem.appendChild(data.endInclude) + } + } data.handlerName = "attr" //handleName用于处理多种绑定共用同一种bindingExecutor的情况 - parseExprProxy(text, vmodels, data, (simple ? null : scanExpr(data.value))) + parseExprProxy(text, vmodels, data, (simple ? 0 : scanExpr(data.value))) }, //根据VM的属性值或表达式的值切换类名,ms-class="xxx yyy zzz:flag" //http://www.cnblogs.com/rubylouvre/archive/2012/12/17/2818540.html @@ -2960,7 +3090,7 @@ if (!hasExpr) { data.immobileClass = className } - parseExprProxy("", vmodels, data, (hasExpr ? scanExpr(className) : null)) + parseExprProxy("", vmodels, data, (hasExpr ? scanExpr(className) : 0)) } else { data.immobileClass = data.oldStyle = data.param parseExprProxy(text, vmodels, data) @@ -2968,30 +3098,40 @@ }, "duplex": function(data, vmodels) { var elem = data.element, - tagName = elem.tagName + tagName = elem.tagName, + hasCast + parseExprProxy(data.value, vmodels, data, 0, 1) 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) + var params = [] + var casting = oneObject("string,number,boolean,checked") + if (elem.type === "radio" && data.param === "") { + data.param = "checked" } - 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.param.replace(/\w+/g, function(name) { + if (/^(checkbox|radio)$/.test(elem.type) && /^(radio|checked)$/.test(name)) { + if (name === "radio") + log("ms-duplex-radio已经更名为ms-duplex-checked") + name = "checked" + data.isChecked = true + } + if (name === "bool") { + name = "boolean" + log("ms-duplex-bool已经更名为ms-duplex-boolean") + } else if (name === "text") { + name = "string" + log("ms-duplex-text已经更名为ms-duplex-string") + } + if (casting[name]) { + hasCast = true + } + avalon.Array.ensure(params, name) + }) + if (!hasCast) { + params.push("string") } + data.param = params.join("-") data.bound = function(type, callback) { if (elem.addEventListener) { elem.addEventListener(type, callback, false) @@ -3004,15 +3144,22 @@ old && old() } } + for (var i in avalon.vmodels) { + var v = avalon.vmodels[i] + v.$fire("avalon-ms-duplex-init", data) + } + var cpipe = data.pipe || (data.pipe = pipe) + cpipe(null, data, "init") duplexBinding[elem.tagName](elem, data.evaluator.apply(null, data.args), data) } } }, "repeat": function(data, vmodels) { var type = data.type - parseExpr(data.value, vmodels, data) + parseExprProxy(data.value, vmodels, data, 0, 1) data.proxies = [] var freturn = false + vmodels.cb(-1) try { var $repeat = data.$repeat = data.evaluator.apply(0, data.args || []) var xtype = avalon.type($repeat) @@ -3030,25 +3177,30 @@ data.renderedCallback = getBindingCallback(elem, "data-" + type + "-rendered", vmodels) var comment = data.element = DOC.createComment("ms-repeat") + var endRepeat = data.endRepeat = DOC.createComment("ms-repeat-end") + + hyperspace.appendChild(comment) + hyperspace.appendChild(endRepeat) + if (type === "each" || type === "with") { data.template = elem.innerHTML.trim() - avalon.clearHTML(elem).appendChild(comment) + avalon.clearHTML(elem).appendChild(hyperspace) } else { data.template = elem.outerHTML.trim() + elem.parentNode.replaceChild(hyperspace, elem) data.group = 1 - elem.parentNode.replaceChild(comment, elem) } - data.rollback = function() {//只用于list为对象的情况 + 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) + parentNode.removeChild(data.endRepeat) target = data.element = data.type === "repeat" ? target : parentNode - data.group = null - target.setAttribute(data.name, data.value) + data.group = target.setAttribute(data.name, data.value) } var arr = data.value.split(".") || [] if (arr.length > 1) { @@ -3083,9 +3235,7 @@ } var $list = ($repeat.$events || {})[subscribers] if ($list && avalon.Array.ensure($list, data)) { - $$subscribers.push({ - data: data, list: $list - }) + addSubscribers(data, $list) } if (!Array.isArray($repeat) && type !== "each") { var pool = withProxyPool[$repeat.$id] @@ -3147,54 +3297,68 @@ var args = data.value.match(rword) var elem = data.element var widget = args[0] - if (args[1] === "$" || !args[1]) { - args[1] = widget + setTimeout("1") + var id = args[1] + if (!id || id === "$") {//没有定义或为$时,取组件名+随机数 + id = widget + setTimeout("1") } - data.value = args.join(",") + var optName = args[2] || widget//没有定义,取组件名 + vmodels.cb(-1) 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 + var vmOptions = v[optName] + vmOptions = vmOptions.$model || vmOptions break } } - if (nearestVM) { - var vmOptions = nearestVM[optName] - vmOptions = vmOptions.$model || vmOptions - var id = vmOptions[widget + "Id"] - if (typeof id === "string") { - args[1] = id + if (vmOptions) { + var wid = vmOptions[widget + "Id"] + if (typeof wid === "string") { + id = wid } } - 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) + //抽取data-tooltip-text、data-tooltip-attr属性,组成一个配置对象 + var widgetData = avalon.getWidgetData(elem, widget) + data.value = [widget, id, optName].join(",") + data[widget + "Id"] = id + data.evaluator = noop + var options = 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) + if (vmodel.$id) { + avalon.vmodels[id] = vmodel + createSignalTower(elem, vmodel) + if (vmodel.hasOwnProperty("$init")) { + vmodel.$init(function() { + var nv = [vmodel].concat(vmodels) + nv.cb = vmodels.cb + avalon.scan(elem, nv) + if (typeof options.onInit === "function") { + options.onInit.call(elem, vmodel, options, vmodels) + } }) - } else { - avalon.tick(offTree) } + 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 { + avalon.scan(elem, vmodels) } } else if (vmodels.length) { //如果该组件还没有加载,那么保存当前的vmodels elem.vmodels = vmodels @@ -3219,33 +3383,75 @@ //将模型中的字段与input, textarea的value值关联在一起 var duplexBinding = bindingHandlers.duplex + + function fixNull(val) { + return val == null ? "" : val + } + avalon.duplexHooks = { + checked: { + get: function(val, data) { + return !data.element.oldValue + } + }, + string: { + get: function(val) { //同步到VM + return val + }, + set: fixNull + }, + "boolean": { + get: function(val) { + return val === "true" + }, + set: fixNull + }, + number: { + get: function(val) { + return isFinite(val) ? parseFloat(val) || 0 : val + }, + set: fixNull + } + } + + function pipe(val, data, action, e) { + data.param.replace(/\w+/g, function(name) { + var hook = avalon.duplexHooks[name] + if (hook && typeof hook[action] === "function") { + val = hook[action](val, data) + } + }) + return val + } //如果一个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) + data.changed.call(this, value, data) } + function compositionStart() { composing = true } + function compositionEnd() { composing = false } //当value变化时改变model的值 + function updateVModel() { - if (composing)//处理中文输入法在minlengh下引发的BUG + if (composing) //处理中文输入法在minlengh下引发的BUG return var val = element.oldValue = element.value //防止递归调用形成死循环 - var typedVal = getTypedValue(data, val) //尝式转换为正确的格式 + var lastValue = data.pipe(val, data, "get") if ($elem.data("duplex-observe") !== false) { - evaluator(typedVal) - callback.call(element, typedVal) + evaluator(lastValue) + callback.call(element, lastValue) if ($elem.data("duplex-focus")) { avalon.nextTick(function() { element.focus() @@ -3256,29 +3462,24 @@ //当model变化时,它就会改变value的值 data.handler = function() { - var val = evaluator() - val = val == null ? "" : val + "" + var val = data.pipe(evaluator(), data, "set") if (val !== element.value) { element.value = val } } - if (type === "checkbox" && data.param === "radio") { - type = "radio" - } - if (type === "radio") { + if (data.isChecked || element.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) + var lastValue = data.pipe(element.value, data, "get") + evaluator(lastValue) + callback.call(element, lastValue) } } data.handler = function() { var val = evaluator() - var checked = data.msType ? val + "" === element.value : !!val + var checked = data.isChecked ? !!val : val + "" === element.value element.oldValue = checked if (IE6) { setTimeout(function() { @@ -3302,58 +3503,52 @@ log("ms-duplex应用于checkbox上要对应一个数组") array = [array] } - var typedValue = getTypedValue(data, element.value) - avalon.Array[method](array, typedValue) + avalon.Array[method](array, data.pipe(element.value, data, "get")) callback.call(element, array) } } data.handler = function() { var array = [].concat(evaluator()) //强制转换为数组 - element.checked = array.indexOf(getTypedValue(data, element.value)) >= 0 + element.checked = array.indexOf(data.pipe(element.value, data, "get")) >= 0 } - bound(W3C ? "change" : "click", updateVModel) - } else { - var event = element.attributes["data-duplex-event"] || element.attributes["data-event"] || {} + var events = element.getAttribute("data-duplex-event") || element.getAttribute("data-event") || "input" 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) - }) - } - } + function delay(e) { + setTimeout(function() { + updateVModel(e) + }) } + events.replace(rword, function(name) { + switch (name) { + case "input": + if (W3C) { //IE9+, W3C + bound("input", updateVModel) + bound("compositionstart", compositionStart) + bound("compositionend", compositionEnd) + //http://www.cnblogs.com/rubylouvre/archive/2013/02/17/2914604.html + //http://www.matts411.com/post/internet-explorer-9-oninput/ + if (DOC.documentMode === 9) { + bound("paste", delay) + bound("cut", delay) + } + } else { //onpropertychange事件无法区分是程序触发还是用户触发 + bound("propertychange", function(e) { + if (e.propertyName === "value") + updateVModel() + }) + } + break + default: + bound(name, updateVModel) + break + } + }) } element.oldValue = element.value launch(function() { @@ -3364,42 +3559,34 @@ } }) 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 - } + callback.call(element, element.value) } var TimerID, ribbon = [], launch = noop + function W3CFire(el, name, detail) { var event = DOC.createEvent("Events") event.initEvent(name, true, true) + event.isTrusted = false if (detail) { event.detail = detail } el.dispatchEvent(event) } - function onTree() { //disabled状态下改动不触发input事件 - if (!this.disabled && this.oldValue !== this.value) { + function onTree(value) { //disabled状态下改动不触发input事件 + var newValue = arguments.length ? value : this.value + if (!this.disabled && this.oldValue !== newValue) { + var type = this.getAttribute("data-duplex-event") || "input" + type = type.match(rword).shift() if (W3C) { - W3CFire(this, "input") + W3CFire(this, type) } else { - this.fireEvent("onchange") + try { + this.fireEvent("on" + type) + } catch (e) { + } } } } @@ -3422,16 +3609,14 @@ } } - function newSetter(newValue) { - oldSetter.call(this, newValue) - if (newValue !== this.oldValue) { - W3CFire(this, "input") - } + function newSetter(value) { + onSetter.call(this, value) + onTree.call(this, value) } try { var inputProto = HTMLInputElement.prototype - Object.getOwnPropertyNames(inputProto)//故意引发IE6-8等浏览器报错 - var oldSetter = Object.getOwnPropertyDescriptor(inputProto, "value").set //屏蔽chrome, safari,opera + Object.getOwnPropertyNames(inputProto) //故意引发IE6-8等浏览器报错 + var onSetter = Object.getOwnPropertyDescriptor(inputProto, "value").set //屏蔽chrome, safari,opera Object.defineProperty(inputProto, "value", { set: newSetter }) @@ -3441,20 +3626,21 @@ 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) + return data.pipe(v, data, "get") }) } else { - val = getTypedValue(data, val) + val = data.pipe(val, data, "get") } if (val + "" !== element.oldValue) { evaluator(val) } - data.changed.call(element, val) + data.changed.call(element, val, data) } } data.handler = function() { @@ -3477,18 +3663,12 @@ } } 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) + avalon.scanCallback(function() { + //先等到select里的option元素被扫描后,才根据model设置selected属性 + registerSubscriber(data) + data.changed.call(element, evaluator(), data) + }) + avalon.scan(element) } duplexBinding.TEXTAREA = duplexBinding.INPUT //============================= event binding ======================= @@ -3599,7 +3779,7 @@ function Collection(model) { var array = [] array.$id = generateID() - array.$model = model //数据模型 + array.$model = model //数据模型 array.$events = {} array.$events[subscribers] = [] array._ = modelFactory({ @@ -3626,7 +3806,7 @@ 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[pos + i]) } _splice.apply(this, [pos, 0].concat(added)) this._fire("add", pos, added) @@ -3674,7 +3854,8 @@ // 必须存在第一个参数,需要大于-1, 为添加或删除元素的基点 a = _number(a, this.length) var removed = _splice.apply(this.$model, arguments), - ret = [], change + ret = [], + change this._stopFireLength = true //确保在这个方法中 , $watch("length",fn)只触发一次 if (removed.length) { ret = this._del(a, removed.length) @@ -3781,9 +3962,9 @@ } }) - function convert(val) { + function convert(val, $model) { if (rcomplexType.test(avalon.type(val))) { - val = val.$id ? val : modelFactory(val) + val = val.$id ? val : modelFactory(val, 0, $model) } return val } @@ -3791,44 +3972,49 @@ //============ 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 ov = data.vmodels + var nv = [proxy].concat(ov) + nv.cb = ov.cb var fragment = { nodes: nodes, - vmodels: [proxy].concat(data.vmodels) + vmodels: nv } fragments.push(fragment) } - // 取得用于定位的节点。比如data.group = 3, 结构为 - //






+ //如果ms-repeat紧挨着ms-repeat-end,那么就返回ms-repeat-end + // 取得用于定位的节点。比如group = 3, 结构为 + //






// 当pos为0时,返回 br#first // 当pos为1时,返回 br#second - // 当pos为2时,返回 null + // 当pos为2时,返回 ms-repeat-end + 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 + var startRepeat = data.element + var endRepeat = data.endRepeat + var nodes = [] + var node = startRepeat.nextSibling + if (node !== endRepeat) { + do { + if (node !== endRepeat) { + nodes.push(node) } else { break } - } - } else { - var nodes = avalon.slice(comment.parentNode.childNodes, 1) - var group = data.group || nodes.length / data.proxies.length - node = nodes[group * pos] + } while (node = node.nextSibling) } - return node || null + return nodes[data.group * pos] || endRepeat } function removeFragment(node, group, pos) { var n = group * (pos || 1) - var nodes = [node], i = 1 + var nodes = [node], + i = 1 var view = hyperspace while (i < n) { node = node.nextSibling @@ -3844,9 +4030,10 @@ function calculateFragmentGroup(data) { if (!isFinite(data.group)) { - var nodes = avalon.slice(data.element.parentNode.childNodes, 1) + var nodes = data.element.parentNode.childNodes + var length = nodes.length - 2 //去掉两个注释节点 var n = "proxySize" in data ? data.proxySize : data.proxies.length - data.group = nodes.length / n + data.group = length / n } } // 为ms-each, ms-repeat创建一个代理对象,通过它们能使用一些额外的属性与功能($index,$first,$last,$remove,$key,$val,$outer) @@ -3865,8 +4052,10 @@ return proxy } var eachProxyPool = [] + function getEachProxy(index, item, data, last) { - var param = data.param || "el", proxy + var param = data.param || "el", + proxy var source = { $remove: function() { return data.$repeat.removeAt(proxy.$index) @@ -3880,7 +4069,7 @@ source[param] = item for (var i = 0, n = eachProxyPool.length; i < n; i++) { var proxy = eachProxyPool[i] - if (proxy.hasOwnProperty(param)) { + if (proxy.hasOwnProperty(param) && (avalon.type(proxy[param]) === avalon.type(item))) { for (var k in source) { proxy[k] = source[k] } @@ -3946,22 +4135,22 @@ //https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet // chrome // chrome - // IE67chrome + // 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属性 + 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 name + "=" + quote + "javascript:void(0)" + quote }) } } - return a.replace(ron, " ").replace(/\s+/g, " ")//移除onXXX事件 + return a.replace(ron, " ").replace(/\s+/g, " ") //移除onXXX事件 }) }, escape: function(html) { @@ -3985,10 +4174,10 @@ }, number: function(number, decimals, dec_point, thousands_sep) { //与PHP的number_format完全兼容 - //number 必需,要格式化的数字 - //decimals 可选,规定多少个小数位。 - //dec_point 可选,规定用作小数点的字符串(默认为 . )。 - //thousands_sep 可选,规定用作千位分隔符的字符串(默认为 , ),如果设置了该参数,那么所有其他参数都是必需的。 + //number 必需,要格式化的数字 + //decimals 可选,规定多少个小数位。 + //dec_point 可选,规定用作小数点的字符串(默认为 . )。 + //thousands_sep 可选,规定用作千位分隔符的字符串(默认为 , ),如果设置了该参数,那么所有其他参数都是必需的。 // http://kevin.vanzonneveld.net number = (number + "").replace(/[^0-9+\-Ee.]/g, "") var n = !isFinite(+number) ? 0 : +number, @@ -4146,8 +4335,7 @@ } return string } - var rfixFFDate = /^(\d+)-(\d+)-(\d{4})$/ - var rfixIEDate = /^(\d+)\s+(\d+),(\d{4})$/ + var rfixYMD = /^(\d+)\D(\d+)\D(\d+)/ filters.date = function(date, format) { var locate = filters.date.locate, text = "", @@ -4160,9 +4348,10 @@ date = toInt(date) } else { var trimDate = date.trim() - if (trimDate.match(rfixFFDate) || trimDate.match(rfixIEDate)) { - date = RegExp.$3 + "/" + RegExp.$1 + "/" + RegExp.$2 - } + date = trimDate.replace(rfixYMD, function(a, b, c, d) { + var array = d.length === 4 ? [d, b, c] : [b, c, d] + return array.join("/") + }) date = jsonStringToDate(date) } date = new Date(date) @@ -4238,15 +4427,19 @@ locate.SHORTMONTH = locate.MONTH filters.date.locate = locate } - - /********************************************************************* * END * **********************************************************************/ avalon.ready = noop - avalon.bind(window, "load", function() { - avalon.scan(DOC.body) - }) + if (W3C) { + DOC.addEventListener("DOMContentLoaded", function() { + avalon.scan(DOC.body) + }) + } else { + avalon.bind(window, "load", function() { + avalon.scan(DOC.body) + }) + } avalon.config({ loader: false }) @@ -4256,4 +4449,4 @@ 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/ - */ + */ \ No newline at end of file diff --git a/examples/class1.html b/examples/class1.html index d51e3d194..78b9aee78 100644 --- a/examples/class1.html +++ b/examples/class1.html @@ -28,6 +28,9 @@ } diff --git a/examples/css3.html b/examples/css3.html index 2113a0ff0..d82823860 100644 --- a/examples/css3.html +++ b/examples/css3.html @@ -37,6 +37,8 @@

在旧式IE下,如果父元素是定位元素,但没有设置它的top, le
+
+
\ No newline at end of file diff --git a/examples/duplex1.html b/examples/duplex1.html index c935b9abc..081da83fc 100644 --- a/examples/duplex1.html +++ b/examples/duplex1.html @@ -69,7 +69,7 @@

ms-duplex

- +
+ + + + + +
+

ms-duplex-checked{{a}}

+

ms-duplex-checked{{b}}

+

ms-duplex-string{{c}}

+

ms-duplex-string{{d}}

+

ms-duplex-number{{e}}

+

ms-duplex-boolean{{f}}

+
+

ms-duplex-string + + + + + {{g}}

+

ms-duplex-number + + + + + {{h}}

+

ms-duplex-boolean + + + {{i}}

+

ms-duplex-string/ms-duplex + +

+

ms-duplex-number + +

+

ms-duplex-boolean + +

+
+

ms-duplex-string/ms-duplex
+ +

+

ms-duplex-number
+ +

+
+ + + diff --git a/examples/if2.html b/examples/if2.html index 194d19042..28d0747bb 100644 --- a/examples/if2.html +++ b/examples/if2.html @@ -23,6 +23,7 @@ +
测试avalonHide
能动态添加移除
{{data.toggle}}

diff --git a/examples/include.html b/examples/include.html index e93df6d62..b00313ec9 100644 --- a/examples/include.html +++ b/examples/include.html @@ -4,7 +4,7 @@ ms-include - + + + + + + +
+
+ + diff --git a/examples/include2aaa.html b/examples/include2aaa.html new file mode 100644 index 000000000..a28e43513 --- /dev/null +++ b/examples/include2aaa.html @@ -0,0 +1,2 @@ +
xxxxxxxxxxxxx
+
yyyyyyyyyyyy
\ No newline at end of file diff --git a/examples/include2bbb.html b/examples/include2bbb.html new file mode 100644 index 000000000..e9fbc827e --- /dev/null +++ b/examples/include2bbb.html @@ -0,0 +1,3 @@ +

aaaaaaaaaaa

+

bbbbbbbbbbb

+

ccccccccccc

\ No newline at end of file diff --git a/examples/include3.html b/examples/include3.html new file mode 100644 index 000000000..9161ad1a9 --- /dev/null +++ b/examples/include3.html @@ -0,0 +1,56 @@ + + + + + ms-include、 html filter and scanCallback + + + + + + + + +

{{title}}

+
{{b|html }}
+
+
{{title}}
+ +
{{el}}
+ + + diff --git a/examples/includeTemplate1.html b/examples/includeTemplate1.html index 0fb0550c5..bc4bbfec1 100644 --- a/examples/includeTemplate1.html +++ b/examples/includeTemplate1.html @@ -7,6 +7,9 @@

这是模板1

生成于{{ "3 14,2000" | date("yyyy MM dd:HH:mm:ss")}}

生成于{{ 1373021259229 | date("yyyy MM dd:HH:mm:ss")}}

生成于{{ "1373021259229" | date("yyyy MM dd:HH:mm:ss")}}

+

生成于{{ "2014-06-10 15:21:2" | date("yyyy!MM!dd:HH:mm:ss!")}}

+ +

值得注意的是,new Date可传的格式类型非常多,但不是所有浏览器都支持这么多,详看这里

{{hhh}} diff --git a/examples/jquery.js b/examples/jquery.js new file mode 100644 index 000000000..d4b67f7e6 --- /dev/null +++ b/examples/jquery.js @@ -0,0 +1,10308 @@ +/*! + * jQuery JavaScript Library v1.11.1 + * http://jquery.com/ + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * + * Copyright 2005, 2014 jQuery Foundation, Inc. and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2014-05-01T17:42Z + */ + +(function( global, factory ) { + + if ( typeof module === "object" && typeof module.exports === "object" ) { + // For CommonJS and CommonJS-like environments where a proper window is present, + // execute the factory and get jQuery + // For environments that do not inherently posses a window with a document + // (such as Node.js), expose a jQuery-making factory as module.exports + // This accentuates the need for the creation of a real window + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Can't do this because several apps including ASP.NET trace +// the stack via arguments.caller.callee and Firefox dies if +// you try to trace through "use strict" call chains. (#13335) +// Support: Firefox 18+ +// + +var deletedIds = []; + +var slice = deletedIds.slice; + +var concat = deletedIds.concat; + +var push = deletedIds.push; + +var indexOf = deletedIds.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var support = {}; + + + +var + version = "1.11.1", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }, + + // Support: Android<4.1, IE<9 + // Make sure we trim BOM and NBSP + rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, + + // Matches dashed string for camelizing + rmsPrefix = /^-ms-/, + rdashAlpha = /-([\da-z])/gi, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return letter.toUpperCase(); + }; + +jQuery.fn = jQuery.prototype = { + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // Start with an empty selector + selector: "", + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num != null ? + + // Return just the one element from the set + ( num < 0 ? this[ num + this.length ] : this[ num ] ) : + + // Return all the elements in a clean array + slice.call( this ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + ret.context = this.context; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: deletedIds.sort, + splice: deletedIds.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var src, copyIsArray, copy, name, options, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return jQuery.type(obj) === "function"; + }, + + isArray: Array.isArray || function( obj ) { + return jQuery.type(obj) === "array"; + }, + + isWindow: function( obj ) { + /* jshint eqeqeq: false */ + return obj != null && obj == obj.window; + }, + + isNumeric: function( obj ) { + // parseFloat NaNs numeric-cast false positives (null|true|false|"") + // ...but misinterprets leading-number strings, particularly hex literals ("0x...") + // subtraction forces infinities to NaN + return !jQuery.isArray( obj ) && obj - parseFloat( obj ) >= 0; + }, + + isEmptyObject: function( obj ) { + var name; + for ( name in obj ) { + return false; + } + return true; + }, + + isPlainObject: function( obj ) { + var key; + + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + try { + // Not own constructor property must be Object + if ( obj.constructor && + !hasOwn.call(obj, "constructor") && + !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + } catch ( e ) { + // IE8,9 Will throw exceptions on certain host objects #9897 + return false; + } + + // Support: IE<9 + // Handle iteration over inherited properties before own properties. + if ( support.ownLast ) { + for ( key in obj ) { + return hasOwn.call( obj, key ); + } + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + for ( key in obj ) {} + + return key === undefined || hasOwn.call( obj, key ); + }, + + type: function( obj ) { + if ( obj == null ) { + return obj + ""; + } + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call(obj) ] || "object" : + typeof obj; + }, + + // Evaluates a script in a global context + // Workarounds based on findings by Jim Driscoll + // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context + globalEval: function( data ) { + if ( data && jQuery.trim( data ) ) { + // We use execScript on Internet Explorer + // We use an anonymous function so that context is window + // rather than jQuery in Firefox + ( window.execScript || function( data ) { + window[ "eval" ].call( window, data ); + } )( data ); + } + }, + + // Convert dashed to camelCase; used by the css and data modules + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + }, + + // args is for internal usage only + each: function( obj, callback, args ) { + var value, + i = 0, + length = obj.length, + isArray = isArraylike( obj ); + + if ( args ) { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } + } + + return obj; + }, + + // Support: Android<4.1, IE<9 + trim: function( text ) { + return text == null ? + "" : + ( text + "" ).replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArraylike( Object(arr) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + var len; + + if ( arr ) { + if ( indexOf ) { + return indexOf.call( arr, elem, i ); + } + + len = arr.length; + i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; + + for ( ; i < len; i++ ) { + // Skip accessing in sparse arrays + if ( i in arr && arr[ i ] === elem ) { + return i; + } + } + } + + return -1; + }, + + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + while ( j < len ) { + first[ i++ ] = second[ j++ ]; + } + + // Support: IE<9 + // Workaround casting of .length to NaN on otherwise arraylike objects (e.g., NodeLists) + if ( len !== len ) { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var value, + i = 0, + length = elems.length, + isArray = isArraylike( elems ), + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + var args, proxy, tmp; + + if ( typeof context === "string" ) { + tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + args = slice.call( arguments, 2 ); + proxy = function() { + return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || jQuery.guid++; + + return proxy; + }, + + now: function() { + return +( new Date() ); + }, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +}); + +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +function isArraylike( obj ) { + var length = obj.length, + type = jQuery.type( obj ); + + if ( type === "function" || jQuery.isWindow( obj ) ) { + return false; + } + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v1.10.19 + * http://sizzlejs.com/ + * + * Copyright 2013 jQuery Foundation, Inc. and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2014-04-18 + */ +(function( window ) { + +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + -(new Date()), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // General-purpose constants + strundefined = typeof undefined, + MAX_NEGATIVE = 1 << 31, + + // Instance methods + hasOwn = ({}).hasOwnProperty, + arr = [], + pop = arr.pop, + push_native = arr.push, + push = arr.push, + slice = arr.slice, + // Use a stripped-down indexOf if we can't use a native one + indexOf = arr.indexOf || function( elem ) { + var i = 0, + len = this.length; + for ( ; i < len; i++ ) { + if ( this[i] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + // http://www.w3.org/TR/css3-syntax/#characters + characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", + + // Loosely modeled on CSS identifier characters + // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors + // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = characterEncoding.replace( "w", "w#" ), + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + characterEncoding + ")(?:" + whitespace + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + + "*\\]", + + pseudos = ":(" + characterEncoding + ")(?:\\((" + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), + + rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + characterEncoding + ")" ), + "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ), + "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + rescape = /'|\\/g, + + // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), + funescape = function( _, escaped, escapedWhitespace ) { + var high = "0x" + escaped - 0x10000; + // NaN means non-codepoint + // Support: Firefox<24 + // Workaround erroneous numeric interpretation of +"0x" + return high !== high || escapedWhitespace ? + escaped : + high < 0 ? + // BMP codepoint + String.fromCharCode( high + 0x10000 ) : + // Supplemental Plane codepoint (surrogate pair) + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }; + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + (arr = slice.call( preferredDoc.childNodes )), + preferredDoc.childNodes + ); + // Support: Android<4.0 + // Detect silently failing push.apply + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + push_native.apply( target, slice.call(els) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + // Can't trust NodeList.length + while ( (target[j++] = els[i++]) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var match, elem, m, nodeType, + // QSA vars + i, groups, old, nid, newContext, newSelector; + + if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { + setDocument( context ); + } + + context = context || document; + results = results || []; + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) { + return []; + } + + if ( documentIsHTML && !seed ) { + + // Shortcuts + if ( (match = rquickExpr.exec( selector )) ) { + // Speed-up: Sizzle("#ID") + if ( (m = match[1]) ) { + if ( nodeType === 9 ) { + elem = context.getElementById( m ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document (jQuery #6963) + if ( elem && elem.parentNode ) { + // Handle the case where IE, Opera, and Webkit return items + // by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + } else { + // Context is not a document + if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) && + contains( context, elem ) && elem.id === m ) { + results.push( elem ); + return results; + } + } + + // Speed-up: Sizzle("TAG") + } else if ( match[2] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Speed-up: Sizzle(".CLASS") + } else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) { + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // QSA path + if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { + nid = old = expando; + newContext = context; + newSelector = nodeType === 9 && selector; + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + groups = tokenize( selector ); + + if ( (old = context.getAttribute("id")) ) { + nid = old.replace( rescape, "\\$&" ); + } else { + context.setAttribute( "id", nid ); + } + nid = "[id='" + nid + "'] "; + + i = groups.length; + while ( i-- ) { + groups[i] = nid + toSelector( groups[i] ); + } + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context; + newSelector = groups.join(","); + } + + if ( newSelector ) { + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch(qsaError) { + } finally { + if ( !old ) { + context.removeAttribute("id"); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {Function(string, Object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return (cache[ key + " " ] = value); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created div and expects a boolean result + */ +function assert( fn ) { + var div = document.createElement("div"); + + try { + return !!fn( div ); + } catch (e) { + return false; + } finally { + // Remove from its parent by default + if ( div.parentNode ) { + div.parentNode.removeChild( div ); + } + // release memory in IE + div = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split("|"), + i = attrs.length; + + while ( i-- ) { + Expr.attrHandle[ arr[i] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + ( ~b.sourceIndex || MAX_NEGATIVE ) - + ( ~a.sourceIndex || MAX_NEGATIVE ); + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( (cur = cur.nextSibling) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction(function( argument ) { + argument = +argument; + return markFunction(function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ (j = matchIndexes[i]) ] ) { + seed[j] = !(matches[j] = seed[j]); + } + } + }); + }); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== strundefined && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = elem && (elem.ownerDocument || elem).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, + doc = node ? node.ownerDocument || node : preferredDoc, + parent = doc.defaultView; + + // If no document and documentElement is available, return + if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Set our document + document = doc; + docElem = doc.documentElement; + + // Support tests + documentIsHTML = !isXML( doc ); + + // Support: IE>8 + // If iframe document is assigned to "document" variable and if iframe has been reloaded, + // IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936 + // IE6-8 do not support the defaultView property so parent will be undefined + if ( parent && parent !== parent.top ) { + // IE11 does not have attachEvent, so all must suffer + if ( parent.addEventListener ) { + parent.addEventListener( "unload", function() { + setDocument(); + }, false ); + } else if ( parent.attachEvent ) { + parent.attachEvent( "onunload", function() { + setDocument(); + }); + } + } + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties (excepting IE8 booleans) + support.attributes = assert(function( div ) { + div.className = "i"; + return !div.getAttribute("className"); + }); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert(function( div ) { + div.appendChild( doc.createComment("") ); + return !div.getElementsByTagName("*").length; + }); + + // Check if getElementsByClassName can be trusted + support.getElementsByClassName = rnative.test( doc.getElementsByClassName ) && assert(function( div ) { + div.innerHTML = "
"; + + // Support: Safari<4 + // Catch class over-caching + div.firstChild.className = "i"; + // Support: Opera<10 + // Catch gEBCN failure to find non-leading classes + return div.getElementsByClassName("i").length === 2; + }); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert(function( div ) { + docElem.appendChild( div ).id = expando; + return !doc.getElementsByName || !doc.getElementsByName( expando ).length; + }); + + // ID find and filter + if ( support.getById ) { + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== strundefined && documentIsHTML ) { + var m = context.getElementById( id ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [ m ] : []; + } + }; + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute("id") === attrId; + }; + }; + } else { + // Support: IE6/7 + // getElementById is not reliable as a find shortcut + delete Expr.find["ID"]; + + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id"); + return node && node.value === attrId; + }; + }; + } + + // Tag + Expr.find["TAG"] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== strundefined ) { + return context.getElementsByTagName( tag ); + } + } : + function( tag, context ) { + var elem, + tmp = [], + i = 0, + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( (elem = results[i++]) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== strundefined && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See http://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) { + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert(function( div ) { + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // http://bugs.jquery.com/ticket/12359 + div.innerHTML = ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( div.querySelectorAll("[msallowclip^='']").length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !div.querySelectorAll("[selected]").length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":checked").length ) { + rbuggyQSA.push(":checked"); + } + }); + + assert(function( div ) { + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = doc.createElement("input"); + input.setAttribute( "type", "hidden" ); + div.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( div.querySelectorAll("[name=d]").length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":enabled").length ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Opera 10-11 does not throw on post-comma invalid pseudos + div.querySelectorAll("*,:x"); + rbuggyQSA.push(",.*:"); + }); + } + + if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector) )) ) { + + assert(function( div ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( div, "div" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( div, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + }); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully does not implement inclusive descendent + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } : + function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { + + // Choose the first element that is related to our preferred document + if ( a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { + return -1; + } + if ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + return a === doc ? -1 : + b === doc ? 1 : + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( (cur = cur.parentNode) ) { + ap.unshift( cur ); + } + cur = b; + while ( (cur = cur.parentNode) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[i] === bp[i] ) { + i++; + } + + return i ? + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[i], bp[i] ) : + + // Otherwise nodes in our document sort first + ap[i] === preferredDoc ? -1 : + bp[i] === preferredDoc ? 1 : + 0; + }; + + return doc; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + // Make sure that attribute selectors are quoted + expr = expr.replace( rattributeQuotes, "='$1']" ); + + if ( support.matchesSelector && documentIsHTML && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch(e) {} + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + // Set document vars if needed + if ( ( context.ownerDocument || context ) !== document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + (val = elem.getAttributeNode(name)) && val.specified ? + val.value : + null; +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( (elem = results[i++]) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + // If no nodeType, this is expected to be an array + while ( (node = elem[i++]) ) { + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[1] = match[1].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); + + if ( match[2] === "~=" ) { + match[3] = " " + match[3] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[1] = match[1].toLowerCase(); + + if ( match[1].slice( 0, 3 ) === "nth" ) { + // nth-* requires argument + if ( !match[3] ) { + Sizzle.error( match[0] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); + match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + + // other types prohibit arguments + } else if ( match[3] ) { + Sizzle.error( match[0] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[6] && match[2]; + + if ( matchExpr["CHILD"].test( match[0] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[3] ) { + match[2] = match[4] || match[5] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + // Get excess from tokenize (recursively) + (excess = tokenize( unquoted, true )) && + // advance to the next closing parenthesis + (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + + // excess is a negative index + match[0] = match[0].slice( 0, excess ); + match[2] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { return true; } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && + classCache( className, function( elem ) { + return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== strundefined && elem.getAttribute("class") || "" ); + }); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + }; + }, + + "CHILD": function( type, what, argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, context, xml ) { + var cache, outerCache, node, diff, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( (node = node[ dir ]) ) { + if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { + return false; + } + } + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + // Seek `elem` from a previously-cached index + outerCache = parent[ expando ] || (parent[ expando ] = {}); + cache = outerCache[ type ] || []; + nodeIndex = cache[0] === dirruns && cache[1]; + diff = cache[0] === dirruns && cache[2]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( (node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + (diff = nodeIndex = 0) || start.pop()) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + outerCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + // Use previously-cached element index if available + } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) { + diff = cache[1]; + + // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...) + } else { + // Use the same loop as above to seek `elem` from the start + while ( (node = ++nodeIndex && node && node[ dir ] || + (diff = nodeIndex = 0) || start.pop()) ) { + + if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { + // Cache the index of each encountered element + if ( useCache ) { + (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction(function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf.call( seed, matched[i] ); + seed[ idx ] = !( matches[ idx ] = matched[i] ); + } + }) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + // Potentially complex pseudos + "not": markFunction(function( selector ) { + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction(function( seed, matches, context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( (elem = unmatched[i]) ) { + seed[i] = !(matches[i] = elem); + } + } + }) : + function( elem, context, xml ) { + input[0] = elem; + matcher( input, null, xml, results ); + return !results.pop(); + }; + }), + + "has": markFunction(function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + }), + + "contains": markFunction(function( text ) { + return function( elem ) { + return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; + }; + }), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + // lang value must be a valid identifier + if ( !ridentifier.test(lang || "") ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( (elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); + return false; + }; + }), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + }, + + // Boolean properties + "enabled": function( elem ) { + return elem.disabled === false; + }, + + "disabled": function( elem ) { + return elem.disabled === true; + }, + + "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + }, + + "selected": function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos["empty"]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo(function() { + return [ 0 ]; + }), + + "last": createPositionalPseudo(function( matchIndexes, length ) { + return [ length - 1 ]; + }), + + "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + }), + + "even": createPositionalPseudo(function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "odd": createPositionalPseudo(function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }) + } +}; + +Expr.pseudos["nth"] = Expr.pseudos["eq"]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( match ) { + // Don't consume trailing commas as valid + soFar = soFar.slice( match[0].length ) || soFar; + } + groups.push( (tokens = []) ); + } + + matched = false; + + // Combinators + if ( (match = rcombinators.exec( soFar )) ) { + matched = match.shift(); + tokens.push({ + value: matched, + // Cast descendant combinators to space + type: match[0].replace( rtrim, " " ) + }); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || + (match = preFilters[ type ]( match ))) ) { + matched = match.shift(); + tokens.push({ + value: matched, + type: type, + matches: match + }); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +}; + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[i].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + checkNonElements = base && dir === "parentNode", + doneName = done++; + + return combinator.first ? + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching + if ( xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || (elem[ expando ] = {}); + if ( (oldCache = outerCache[ dir ]) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return (newCache[ 2 ] = oldCache[ 2 ]); + } else { + // Reuse newcache so results back-propagate to previous elements + outerCache[ dir ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { + return true; + } + } + } + } + } + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[i]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[0]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[i], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( (elem = unmatched[i]) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction(function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( (elem = temp[i]) ) { + matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) ) { + // Restore matcherIn since elem is not yet a final match + temp.push( (matcherIn[i] = elem) ); + } + } + postFinder( null, (matcherOut = []), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) && + (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) { + + seed[temp] = !(results[temp] = elem); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + }); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[0].type ], + implicitRelative = leadingRelative || Expr.relative[" "], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf.call( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + return ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + (checkContext = context).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + } ]; + + for ( ; i < len; i++ ) { + if ( (matcher = Expr.relative[ tokens[i].type ]) ) { + matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; + } else { + matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[j].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), + len = elems.length; + + if ( outermost ) { + outermostContext = context !== document && context; + } + + // Add elements passing elementMatchers directly to results + // Keep `i` a string if there are no elements so `matchedCount` will be "00" below + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && (elem = elems[i]) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + while ( (matcher = elementMatchers[j++]) ) { + if ( matcher( elem, context, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + // They will have gone through all possible matchers + if ( (elem = !matcher && elem) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // Apply set filters to unmatched elements + matchedCount += i; + if ( bySet && i !== matchedCount ) { + j = 0; + while ( (matcher = setMatchers[j++]) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !(unmatched[i] || setMatched[i]) ) { + setMatched[i] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[i] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +}; + +/** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +select = Sizzle.select = function( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( (selector = compiled.selector || selector) ); + + results = results || []; + + // Try to minimize operations if there is no seed and only one group + if ( match.length === 1 ) { + + // Take a shortcut and set the context if the root selector is an ID + tokens = match[0] = match[0].slice( 0 ); + if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && + support.getById && context.nodeType === 9 && documentIsHTML && + Expr.relative[ tokens[1].type ] ) { + + context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[i]; + + // Abort if we hit a combinator + if ( Expr.relative[ (type = token.type) ] ) { + break; + } + if ( (find = Expr.find[ type ]) ) { + // Search, expanding context for leading sibling combinators + if ( (seed = find( + token.matches[0].replace( runescape, funescape ), + rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context + )) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +}; + +// One-time assignments + +// Sort stability +support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; + +// Support: Chrome<14 +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert(function( div1 ) { + // Should return 1, but returns 4 (following) + return div1.compareDocumentPosition( document.createElement("div") ) & 1; +}); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert(function( div ) { + div.innerHTML = ""; + return div.firstChild.getAttribute("href") === "#" ; +}) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + }); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert(function( div ) { + div.innerHTML = ""; + div.firstChild.setAttribute( "value", "" ); + return div.firstChild.getAttribute( "value" ) === ""; +}) ) { + addHandle( "value", function( elem, name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + }); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert(function( div ) { + return div.getAttribute("disabled") == null; +}) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + (val = elem.getAttributeNode( name )) && val.specified ? + val.value : + null; + } + }); +} + +return Sizzle; + +})( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.pseudos; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + + +var rneedsContext = jQuery.expr.match.needsContext; + +var rsingleTag = (/^<(\w+)\s*\/?>(?:<\/\1>|)$/); + + + +var risSimple = /^.[^:#\[\.,]*$/; + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + /* jshint -W018 */ + return !!qualifier.call( elem, i, elem ) !== not; + }); + + } + + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + }); + + } + + if ( typeof qualifier === "string" ) { + if ( risSimple.test( qualifier ) ) { + return jQuery.filter( qualifier, elements, not ); + } + + qualifier = jQuery.filter( qualifier, elements ); + } + + return jQuery.grep( elements, function( elem ) { + return ( jQuery.inArray( elem, qualifier ) >= 0 ) !== not; + }); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 && elem.nodeType === 1 ? + jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] : + jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + })); +}; + +jQuery.fn.extend({ + find: function( selector ) { + var i, + ret = [], + self = this, + len = self.length; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter(function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + }) ); + } + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + // Needed because $( selector, context ) becomes $( context ).find( selector ) + ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret ); + ret.selector = this.selector ? this.selector + " " + selector : selector; + return ret; + }, + filter: function( selector ) { + return this.pushStack( winnow(this, selector || [], false) ); + }, + not: function( selector ) { + return this.pushStack( winnow(this, selector || [], true) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +}); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // Use the correct document accordingly with window argument (sandbox) + document = window.document, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/, + + init = jQuery.fn.init = function( selector, context ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; + + // scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[1], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + // Properties of context are called as methods if possible + if ( jQuery.isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[2] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || rootjQuery ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return typeof rootjQuery.ready !== "undefined" ? + rootjQuery.ready( selector ) : + // Execute immediately if ready is not present + selector( jQuery ); + } + + if ( selector.selector !== undefined ) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.extend({ + dir: function( elem, dir, until ) { + var matched = [], + cur = elem[ dir ]; + + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); + +jQuery.fn.extend({ + has: function( target ) { + var i, + targets = jQuery( target, this ), + len = targets.length; + + return this.filter(function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ? + jQuery( selectors, context || this.context ) : + 0; + + for ( ; i < l; i++ ) { + for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) { + // Always skip document fragments + if ( cur.nodeType < 11 && (pos ? + pos.index(cur) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector(cur, selectors)) ) { + + matched.push( cur ); + break; + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.unique( matched ) : matched ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[0] && this[0].parentNode ) ? this.first().prevAll().length : -1; + } + + // index in selector + if ( typeof elem === "string" ) { + return jQuery.inArray( this[0], jQuery( elem ) ); + } + + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.unique( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter(selector) + ); + } +}); + +function sibling( cur, dir ) { + do { + cur = cur[ dir ]; + } while ( cur && cur.nodeType !== 1 ); + + return cur; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + if ( this.length > 1 ) { + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + ret = jQuery.unique( ret ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + } + + return this.pushStack( ret ); + }; +}); +var rnotwhite = (/\S+/g); + + + +// String to Object options format cache +var optionsCache = {}; + +// Convert String-formatted options into Object-formatted ones and store in cache +function createOptions( options ) { + var object = optionsCache[ options ] = {}; + jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) { + object[ flag ] = true; + }); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + ( optionsCache[ options ] || createOptions( options ) ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + // Last fire value (for non-forgettable lists) + memory, + // Flag to know if list was already fired + fired, + // End of the loop when firing + firingLength, + // Index of currently firing callback (modified by remove if needed) + firingIndex, + // First callback to fire (used internally by add and fireWith) + firingStart, + // Actual callback list + list = [], + // Stack of fire calls for repeatable lists + stack = !options.once && [], + // Fire callbacks + fire = function( data ) { + memory = options.memory && data; + fired = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + firing = true; + for ( ; list && firingIndex < firingLength; firingIndex++ ) { + if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { + memory = false; // To prevent further calls using add + break; + } + } + firing = false; + if ( list ) { + if ( stack ) { + if ( stack.length ) { + fire( stack.shift() ); + } + } else if ( memory ) { + list = []; + } else { + self.disable(); + } + } + }, + // Actual Callbacks object + self = { + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + // First, we save the current length + var start = list.length; + (function add( args ) { + jQuery.each( args, function( _, arg ) { + var type = jQuery.type( arg ); + if ( type === "function" ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && type !== "string" ) { + // Inspect recursively + add( arg ); + } + }); + })( arguments ); + // Do we need to add the callbacks to the + // current firing batch? + if ( firing ) { + firingLength = list.length; + // With memory, if we're not firing then + // we should call right away + } else if ( memory ) { + firingStart = start; + fire( memory ); + } + } + return this; + }, + // Remove a callback from the list + remove: function() { + if ( list ) { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + // Handle firing indexes + if ( firing ) { + if ( index <= firingLength ) { + firingLength--; + } + if ( index <= firingIndex ) { + firingIndex--; + } + } + } + }); + } + return this; + }, + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length ); + }, + // Remove all callbacks from the list + empty: function() { + list = []; + firingLength = 0; + return this; + }, + // Have the list do nothing anymore + disable: function() { + list = stack = memory = undefined; + return this; + }, + // Is it disabled? + disabled: function() { + return !list; + }, + // Lock the list in its current state + lock: function() { + stack = undefined; + if ( !memory ) { + self.disable(); + } + return this; + }, + // Is it locked? + locked: function() { + return !stack; + }, + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( list && ( !fired || stack ) ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + if ( firing ) { + stack.push( args ); + } else { + fire( args ); + } + } + return this; + }, + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +jQuery.extend({ + + Deferred: function( func ) { + var tuples = [ + // action, add listener, listener list, final state + [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], + [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], + [ "notify", "progress", jQuery.Callbacks("memory") ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + then: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + return jQuery.Deferred(function( newDefer ) { + jQuery.each( tuples, function( i, tuple ) { + var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; + // deferred[ done | fail | progress ] for forwarding actions to newDefer + deferred[ tuple[1] ](function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise() + .done( newDefer.resolve ) + .fail( newDefer.reject ) + .progress( newDefer.notify ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); + } + }); + }); + fns = null; + }).promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Keep pipe for back-compat + promise.pipe = promise.then; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 3 ]; + + // promise[ done | fail | progress ] = list.add + promise[ tuple[1] ] = list.add; + + // Handle state + if ( stateString ) { + list.add(function() { + // state = [ resolved | rejected ] + state = stateString; + + // [ reject_list | resolve_list ].disable; progress_list.lock + }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); + } + + // deferred[ resolve | reject | notify ] + deferred[ tuple[0] ] = function() { + deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments ); + return this; + }; + deferred[ tuple[0] + "With" ] = list.fireWith; + }); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( subordinate /* , ..., subordinateN */ ) { + var i = 0, + resolveValues = slice.call( arguments ), + length = resolveValues.length, + + // the count of uncompleted subordinates + remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, + + // the master Deferred. If resolveValues consist of only a single Deferred, just use that. + deferred = remaining === 1 ? subordinate : jQuery.Deferred(), + + // Update function for both resolve and progress values + updateFunc = function( i, contexts, values ) { + return function( value ) { + contexts[ i ] = this; + values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( values === progressValues ) { + deferred.notifyWith( contexts, values ); + + } else if ( !(--remaining) ) { + deferred.resolveWith( contexts, values ); + } + }; + }, + + progressValues, progressContexts, resolveContexts; + + // add listeners to Deferred subordinates; treat others as resolved + if ( length > 1 ) { + progressValues = new Array( length ); + progressContexts = new Array( length ); + resolveContexts = new Array( length ); + for ( ; i < length; i++ ) { + if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) { + resolveValues[ i ].promise() + .done( updateFunc( i, resolveContexts, resolveValues ) ) + .fail( deferred.reject ) + .progress( updateFunc( i, progressContexts, progressValues ) ); + } else { + --remaining; + } + } + } + + // if we're not waiting on anything, resolve the master + if ( !remaining ) { + deferred.resolveWith( resolveContexts, resolveValues ); + } + + return deferred.promise(); + } +}); + + +// The deferred used on DOM ready +var readyList; + +jQuery.fn.ready = function( fn ) { + // Add the callback + jQuery.ready.promise().done( fn ); + + return this; +}; + +jQuery.extend({ + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } + }, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.triggerHandler ) { + jQuery( document ).triggerHandler( "ready" ); + jQuery( document ).off( "ready" ); + } + } +}); + +/** + * Clean-up method for dom ready events + */ +function detach() { + if ( document.addEventListener ) { + document.removeEventListener( "DOMContentLoaded", completed, false ); + window.removeEventListener( "load", completed, false ); + + } else { + document.detachEvent( "onreadystatechange", completed ); + window.detachEvent( "onload", completed ); + } +} + +/** + * The ready event handler and self cleanup method + */ +function completed() { + // readyState === "complete" is good enough for us to call the dom ready in oldIE + if ( document.addEventListener || event.type === "load" || document.readyState === "complete" ) { + detach(); + jQuery.ready(); + } +} + +jQuery.ready.promise = function( obj ) { + if ( !readyList ) { + + readyList = jQuery.Deferred(); + + // Catch cases where $(document).ready() is called after the browser event has already occurred. + // we once tried to use readyState "interactive" here, but it caused issues like the one + // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 + if ( document.readyState === "complete" ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + setTimeout( jQuery.ready ); + + // Standards-based browsers support DOMContentLoaded + } else if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed, false ); + + // If IE event model is used + } else { + // Ensure firing before onload, maybe late but safe also for iframes + document.attachEvent( "onreadystatechange", completed ); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", completed ); + + // If IE and not a frame + // continually check to see if the document is ready + var top = false; + + try { + top = window.frameElement == null && document.documentElement; + } catch(e) {} + + if ( top && top.doScroll ) { + (function doScrollCheck() { + if ( !jQuery.isReady ) { + + try { + // Use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + top.doScroll("left"); + } catch(e) { + return setTimeout( doScrollCheck, 50 ); + } + + // detach all dom ready events + detach(); + + // and execute any waiting functions + jQuery.ready(); + } + })(); + } + } + } + return readyList.promise( obj ); +}; + + +var strundefined = typeof undefined; + + + +// Support: IE<9 +// Iteration over object's inherited properties before its own +var i; +for ( i in jQuery( support ) ) { + break; +} +support.ownLast = i !== "0"; + +// Note: most support tests are defined in their respective modules. +// false until the test is run +support.inlineBlockNeedsLayout = false; + +// Execute ASAP in case we need to set body.style.zoom +jQuery(function() { + // Minified: var a,b,c,d + var val, div, body, container; + + body = document.getElementsByTagName( "body" )[ 0 ]; + if ( !body || !body.style ) { + // Return for frameset docs that don't have a body + return; + } + + // Setup + div = document.createElement( "div" ); + container = document.createElement( "div" ); + container.style.cssText = "position:absolute;border:0;width:0;height:0;top:0;left:-9999px"; + body.appendChild( container ).appendChild( div ); + + if ( typeof div.style.zoom !== strundefined ) { + // Support: IE<8 + // Check if natively block-level elements act like inline-block + // elements when setting their display to 'inline' and giving + // them layout + div.style.cssText = "display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1"; + + support.inlineBlockNeedsLayout = val = div.offsetWidth === 3; + if ( val ) { + // Prevent IE 6 from affecting layout for positioned elements #11048 + // Prevent IE from shrinking the body in IE 7 mode #12869 + // Support: IE<8 + body.style.zoom = 1; + } + } + + body.removeChild( container ); +}); + + + + +(function() { + var div = document.createElement( "div" ); + + // Execute the test only if not already executed in another module. + if (support.deleteExpando == null) { + // Support: IE<9 + support.deleteExpando = true; + try { + delete div.test; + } catch( e ) { + support.deleteExpando = false; + } + } + + // Null elements to avoid leaks in IE. + div = null; +})(); + + +/** + * Determines whether an object can have data + */ +jQuery.acceptData = function( elem ) { + var noData = jQuery.noData[ (elem.nodeName + " ").toLowerCase() ], + nodeType = +elem.nodeType || 1; + + // Do not set data on non-element DOM nodes because it will not be cleared (#8335). + return nodeType !== 1 && nodeType !== 9 ? + false : + + // Nodes accept data unless otherwise specified; rejection can be conditional + !noData || noData !== true && elem.getAttribute("classid") === noData; +}; + + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /([A-Z])/g; + +function dataAttr( elem, key, data ) { + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + + var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); + + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + // Only convert to a number if it doesn't change the string + +data + "" === data ? +data : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + jQuery.data( elem, key, data ); + + } else { + data = undefined; + } + } + + return data; +} + +// checks a cache object for emptiness +function isEmptyDataObject( obj ) { + var name; + for ( name in obj ) { + + // if the public data object is empty, the private is still empty + if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { + continue; + } + if ( name !== "toJSON" ) { + return false; + } + } + + return true; +} + +function internalData( elem, name, data, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var ret, thisCache, + internalKey = jQuery.expando, + + // We have to handle DOM nodes and JS objects differently because IE6-7 + // can't GC object references properly across the DOM-JS boundary + isNode = elem.nodeType, + + // Only DOM nodes need the global jQuery cache; JS object data is + // attached directly to the object so GC can occur automatically + cache = isNode ? jQuery.cache : elem, + + // Only defining an ID for JS objects if its cache already exists allows + // the code to shortcut on the same path as a DOM node with no cache + id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey; + + // Avoid doing any more work than we need to when trying to get data on an + // object that has no data at all + if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && data === undefined && typeof name === "string" ) { + return; + } + + if ( !id ) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if ( isNode ) { + id = elem[ internalKey ] = deletedIds.pop() || jQuery.guid++; + } else { + id = internalKey; + } + } + + if ( !cache[ id ] ) { + // Avoid exposing jQuery metadata on plain JS objects when the object + // is serialized using JSON.stringify + cache[ id ] = isNode ? {} : { toJSON: jQuery.noop }; + } + + // An object can be passed to jQuery.data instead of a key/value pair; this gets + // shallow copied over onto the existing cache + if ( typeof name === "object" || typeof name === "function" ) { + if ( pvt ) { + cache[ id ] = jQuery.extend( cache[ id ], name ); + } else { + cache[ id ].data = jQuery.extend( cache[ id ].data, name ); + } + } + + thisCache = cache[ id ]; + + // jQuery data() is stored in a separate object inside the object's internal data + // cache in order to avoid key collisions between internal data and user-defined + // data. + if ( !pvt ) { + if ( !thisCache.data ) { + thisCache.data = {}; + } + + thisCache = thisCache.data; + } + + if ( data !== undefined ) { + thisCache[ jQuery.camelCase( name ) ] = data; + } + + // Check for both converted-to-camel and non-converted data property names + // If a data property was specified + if ( typeof name === "string" ) { + + // First Try to find as-is property data + ret = thisCache[ name ]; + + // Test for null|undefined property data + if ( ret == null ) { + + // Try to find the camelCased property + ret = thisCache[ jQuery.camelCase( name ) ]; + } + } else { + ret = thisCache; + } + + return ret; +} + +function internalRemoveData( elem, name, pvt ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var thisCache, i, + isNode = elem.nodeType, + + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + id = isNode ? elem[ jQuery.expando ] : jQuery.expando; + + // If there is already no cache entry for this object, there is no + // purpose in continuing + if ( !cache[ id ] ) { + return; + } + + if ( name ) { + + thisCache = pvt ? cache[ id ] : cache[ id ].data; + + if ( thisCache ) { + + // Support array or space separated string names for data keys + if ( !jQuery.isArray( name ) ) { + + // try the string as a key before any manipulation + if ( name in thisCache ) { + name = [ name ]; + } else { + + // split the camel cased version by spaces unless a key with the spaces exists + name = jQuery.camelCase( name ); + if ( name in thisCache ) { + name = [ name ]; + } else { + name = name.split(" "); + } + } + } else { + // If "name" is an array of keys... + // When data is initially created, via ("key", "val") signature, + // keys will be converted to camelCase. + // Since there is no way to tell _how_ a key was added, remove + // both plain key and camelCase key. #12786 + // This will only penalize the array argument path. + name = name.concat( jQuery.map( name, jQuery.camelCase ) ); + } + + i = name.length; + while ( i-- ) { + delete thisCache[ name[i] ]; + } + + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if ( pvt ? !isEmptyDataObject(thisCache) : !jQuery.isEmptyObject(thisCache) ) { + return; + } + } + } + + // See jQuery.data for more information + if ( !pvt ) { + delete cache[ id ].data; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if ( !isEmptyDataObject( cache[ id ] ) ) { + return; + } + } + + // Destroy the cache + if ( isNode ) { + jQuery.cleanData( [ elem ], true ); + + // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080) + /* jshint eqeqeq: false */ + } else if ( support.deleteExpando || cache != cache.window ) { + /* jshint eqeqeq: true */ + delete cache[ id ]; + + // When all else fails, null + } else { + cache[ id ] = null; + } +} + +jQuery.extend({ + cache: {}, + + // The following elements (space-suffixed to avoid Object.prototype collisions) + // throw uncatchable exceptions if you attempt to set expando properties + noData: { + "applet ": true, + "embed ": true, + // ...but Flash objects (which have this classid) *can* handle expandos + "object ": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" + }, + + hasData: function( elem ) { + elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; + return !!elem && !isEmptyDataObject( elem ); + }, + + data: function( elem, name, data ) { + return internalData( elem, name, data ); + }, + + removeData: function( elem, name ) { + return internalRemoveData( elem, name ); + }, + + // For internal use only. + _data: function( elem, name, data ) { + return internalData( elem, name, data, true ); + }, + + _removeData: function( elem, name ) { + return internalRemoveData( elem, name, true ); + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var i, name, data, + elem = this[0], + attrs = elem && elem.attributes; + + // Special expections of .data basically thwart jQuery.access, + // so implement the relevant behavior ourselves + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = jQuery.data( elem ); + + if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE11+ + // The attrs elements can be null (#14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = jQuery.camelCase( name.slice(5) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + jQuery._data( elem, "parsedAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + return arguments.length > 1 ? + + // Sets one value + this.each(function() { + jQuery.data( this, key, value ); + }) : + + // Gets one value + // Try to fetch any internally stored data first + elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : undefined; + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); + + +jQuery.extend({ + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = jQuery._data( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || jQuery.isArray(data) ) { + queue = jQuery._data( elem, type, jQuery.makeArray(data) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // not intended for public consumption - generates a queueHooks object, or returns the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return jQuery._data( elem, key ) || jQuery._data( elem, key, { + empty: jQuery.Callbacks("once memory").add(function() { + jQuery._removeData( elem, type + "queue" ); + jQuery._removeData( elem, key ); + }) + }); + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[0], type ); + } + + return data === undefined ? + this : + this.each(function() { + var queue = jQuery.queue( this, type, data ); + + // ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = jQuery._data( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +}); +var pnum = (/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/).source; + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var isHidden = function( elem, el ) { + // isHidden might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem ); + }; + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = jQuery.access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + length = elems.length, + bulk = key == null; + + // Sets many values + if ( jQuery.type( key ) === "object" ) { + chainable = true; + for ( i in key ) { + jQuery.access( elems, fn, i, key[i], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !jQuery.isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < length; i++ ) { + fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) ); + } + } + } + + return chainable ? + elems : + + // Gets + bulk ? + fn.call( elems ) : + length ? fn( elems[0], key ) : emptyGet; +}; +var rcheckableType = (/^(?:checkbox|radio)$/i); + + + +(function() { + // Minified: var a,b,c + var input = document.createElement( "input" ), + div = document.createElement( "div" ), + fragment = document.createDocumentFragment(); + + // Setup + div.innerHTML = "
a"; + + // IE strips leading whitespace when .innerHTML is used + support.leadingWhitespace = div.firstChild.nodeType === 3; + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + support.tbody = !div.getElementsByTagName( "tbody" ).length; + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + support.htmlSerialize = !!div.getElementsByTagName( "link" ).length; + + // Makes sure cloning an html5 element does not cause problems + // Where outerHTML is undefined, this still works + support.html5Clone = + document.createElement( "nav" ).cloneNode( true ).outerHTML !== "<:nav>"; + + // Check if a disconnected checkbox will retain its checked + // value of true after appended to the DOM (IE6/7) + input.type = "checkbox"; + input.checked = true; + fragment.appendChild( input ); + support.appendChecked = input.checked; + + // Make sure textarea (and checkbox) defaultValue is properly cloned + // Support: IE6-IE11+ + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; + + // #11217 - WebKit loses check when the name is after the checked attribute + fragment.appendChild( div ); + div.innerHTML = ""; + + // Support: Safari 5.1, iOS 5.1, Android 4.x, Android 2.3 + // old WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE<9 + // Opera does not clone events (and typeof div.attachEvent === undefined). + // IE9-10 clones events bound via attachEvent, but they don't trigger with .click() + support.noCloneEvent = true; + if ( div.attachEvent ) { + div.attachEvent( "onclick", function() { + support.noCloneEvent = false; + }); + + div.cloneNode( true ).click(); + } + + // Execute the test only if not already executed in another module. + if (support.deleteExpando == null) { + // Support: IE<9 + support.deleteExpando = true; + try { + delete div.test; + } catch( e ) { + support.deleteExpando = false; + } + } +})(); + + +(function() { + var i, eventName, + div = document.createElement( "div" ); + + // Support: IE<9 (lack submit/change bubble), Firefox 23+ (lack focusin event) + for ( i in { submit: true, change: true, focusin: true }) { + eventName = "on" + i; + + if ( !(support[ i + "Bubbles" ] = eventName in window) ) { + // Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP) + div.setAttribute( eventName, "t" ); + support[ i + "Bubbles" ] = div.attributes[ eventName ].expando === false; + } + } + + // Null elements to avoid leaks in IE. + div = null; +})(); + + +var rformElems = /^(?:input|select|textarea)$/i, + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|pointer|contextmenu)|click/, + rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)$/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + var tmp, events, t, handleObjIn, + special, eventHandle, handleObj, + handlers, type, namespaces, origType, + elemData = jQuery._data( elem ); + + // Don't attach events to noData or text/comment nodes (but allow plain objects) + if ( !elemData ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !(events = elemData.events) ) { + events = elemData.events = {}; + } + if ( !(eventHandle = elemData.handle) ) { + eventHandle = elemData.handle = function( e ) { + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== strundefined && (!e || jQuery.event.triggered !== e.type) ? + jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : + undefined; + }; + // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events + eventHandle.elem = elem; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnotwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend({ + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join(".") + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !(handlers = events[ type ]) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener/attachEvent if the special events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + var j, handleObj, tmp, + origCount, t, events, + special, handlers, type, + namespaces, origType, + elemData = jQuery.hasData( elem ) && jQuery._data( elem ); + + if ( !elemData || !(events = elemData.events) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnotwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + delete elemData.handle; + + // removeData also checks for emptiness and clears the expando if empty + // so use it instead of delete + jQuery._removeData( elem, "events" ); + } + }, + + trigger: function( event, data, elem, onlyHandlers ) { + var handle, ontype, cur, + bubbleType, special, tmp, i, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : []; + + cur = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf(".") >= 0 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf(":") < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join("."); + event.namespace_re = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === (elem.ownerDocument || document) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) { + + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && jQuery.acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) && + jQuery.acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name name as the event. + // Can't use an .isFunction() check here because IE6/7 fails that test. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + try { + elem[ type ](); + } catch ( e ) { + // IE<9 dies on focus/blur to hidden element (#1486,#12518) + // only reproducible on winXP IE8 native, not IE9 in IE8 mode + } + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + dispatch: function( event ) { + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( event ); + + var i, ret, handleObj, matched, j, + handlerQueue = [], + args = slice.call( arguments ), + handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[0] = event; + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) { + + // Triggered event must either 1) have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). + if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) + .apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( (event.result = ret) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var sel, handleObj, matches, i, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + // Black-hole SVG instance trees (#13180) + // Avoid non-left-click bubbling in Firefox (#3861) + if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) { + + /* jshint eqeqeq: false */ + for ( ; cur != this; cur = cur.parentNode || this ) { + /* jshint eqeqeq: true */ + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && (cur.disabled !== true || event.type !== "click") ) { + matches = []; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matches[ sel ] === undefined ) { + matches[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) >= 0 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matches[ sel ] ) { + matches.push( handleObj ); + } + } + if ( matches.length ) { + handlerQueue.push({ elem: cur, handlers: matches }); + } + } + } + } + + // Add the remaining (directly-bound) handlers + if ( delegateCount < handlers.length ) { + handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) }); + } + + return handlerQueue; + }, + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // Create a writable copy of the event object and normalize some properties + var i, prop, copy, + type = event.type, + originalEvent = event, + fixHook = this.fixHooks[ type ]; + + if ( !fixHook ) { + this.fixHooks[ type ] = fixHook = + rmouseEvent.test( type ) ? this.mouseHooks : + rkeyEvent.test( type ) ? this.keyHooks : + {}; + } + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + + event = new jQuery.Event( originalEvent ); + + i = copy.length; + while ( i-- ) { + prop = copy[ i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Support: IE<9 + // Fix target property (#1925) + if ( !event.target ) { + event.target = originalEvent.srcElement || document; + } + + // Support: Chrome 23+, Safari? + // Target should not be a text node (#504, #13143) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // Support: IE<9 + // For mouse/key events, metaKey==false if it's undefined (#3368, #11328) + event.metaKey = !!event.metaKey; + + return fixHook.filter ? fixHook.filter( event, originalEvent ) : event; + }, + + // Includes some event props shared by KeyEvent and MouseEvent + props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + + fixHooks: {}, + + keyHooks: { + props: "char charCode key keyCode".split(" "), + filter: function( event, original ) { + + // Add which for key events + if ( event.which == null ) { + event.which = original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + } + }, + + mouseHooks: { + props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), + filter: function( event, original ) { + var body, eventDoc, doc, + button = original.button, + fromElement = original.fromElement; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && original.clientX != null ) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); + event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && fromElement ) { + event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && button !== undefined ) { + event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); + } + + return event; + } + }, + + special: { + load: { + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + focus: { + // Fire native event if possible so blur/focus sequence is correct + trigger: function() { + if ( this !== safeActiveElement() && this.focus ) { + try { + this.focus(); + return false; + } catch ( e ) { + // Support: IE<9 + // If we error on focus to hidden element (#1486, #12518), + // let .trigger() run the handlers + } + } + }, + delegateType: "focusin" + }, + blur: { + trigger: function() { + if ( this === safeActiveElement() && this.blur ) { + this.blur(); + return false; + } + }, + delegateType: "focusout" + }, + click: { + // For checkbox, fire native event so checked state will be right + trigger: function() { + if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) { + this.click(); + return false; + } + }, + + // For cross-browser consistency, don't fire native .click() on links + _default: function( event ) { + return jQuery.nodeName( event.target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + }, + + simulate: function( type, elem, event, bubble ) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true, + originalEvent: {} + } + ); + if ( bubble ) { + jQuery.event.trigger( e, null, elem ); + } else { + jQuery.event.dispatch.call( elem, e ); + } + if ( e.isDefaultPrevented() ) { + event.preventDefault(); + } + } +}; + +jQuery.removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } + } : + function( elem, type, handle ) { + var name = "on" + type; + + if ( elem.detachEvent ) { + + // #8545, #7054, preventing memory leaks for custom events in IE6-8 + // detachEvent needed property on element, by name of that event, to properly expose it to GC + if ( typeof elem[ name ] === strundefined ) { + elem[ name ] = null; + } + + elem.detachEvent( name, handle ); + } + }; + +jQuery.Event = function( src, props ) { + // Allow instantiation without the 'new' keyword + if ( !(this instanceof jQuery.Event) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + // Support: IE < 9, Android < 4.0 + src.returnValue === false ? + returnTrue : + returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + if ( !e ) { + return; + } + + // If preventDefault exists, run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + + // Support: IE + // Otherwise set the returnValue property of the original event to false + } else { + e.returnValue = false; + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + if ( !e ) { + return; + } + // If stopPropagation exists, run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + + // Support: IE + // Set the cancelBubble property of the original event to true + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && e.stopImmediatePropagation ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Create mouseenter/leave events using mouseover/out and event-time checks +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || (related !== target && !jQuery.contains( target, related )) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +}); + +// IE submit delegation +if ( !support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Lazy-add a submit handler when a descendant form may potentially be submitted + jQuery.event.add( this, "click._submit keypress._submit", function( e ) { + // Node name check avoids a VML-related crash in IE (#9807) + var elem = e.target, + form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; + if ( form && !jQuery._data( form, "submitBubbles" ) ) { + jQuery.event.add( form, "submit._submit", function( event ) { + event._submit_bubble = true; + }); + jQuery._data( form, "submitBubbles", true ); + } + }); + // return undefined since we don't need an event listener + }, + + postDispatch: function( event ) { + // If form was submitted by the user, bubble the event up the tree + if ( event._submit_bubble ) { + delete event._submit_bubble; + if ( this.parentNode && !event.isTrigger ) { + jQuery.event.simulate( "submit", this.parentNode, event, true ); + } + } + }, + + teardown: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Remove delegated handlers; cleanData eventually reaps submit handlers attached above + jQuery.event.remove( this, "._submit" ); + } + }; +} + +// IE change delegation and checkbox/radio fix +if ( !support.changeBubbles ) { + + jQuery.event.special.change = { + + setup: function() { + + if ( rformElems.test( this.nodeName ) ) { + // IE doesn't fire change on a check/radio until blur; trigger it on click + // after a propertychange. Eat the blur-change in special.change.handle. + // This still fires onchange a second time for check/radio after blur. + if ( this.type === "checkbox" || this.type === "radio" ) { + jQuery.event.add( this, "propertychange._change", function( event ) { + if ( event.originalEvent.propertyName === "checked" ) { + this._just_changed = true; + } + }); + jQuery.event.add( this, "click._change", function( event ) { + if ( this._just_changed && !event.isTrigger ) { + this._just_changed = false; + } + // Allow triggered, simulated change events (#11500) + jQuery.event.simulate( "change", this, event, true ); + }); + } + return false; + } + // Delegated event; lazy-add a change handler on descendant inputs + jQuery.event.add( this, "beforeactivate._change", function( e ) { + var elem = e.target; + + if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "changeBubbles" ) ) { + jQuery.event.add( elem, "change._change", function( event ) { + if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { + jQuery.event.simulate( "change", this.parentNode, event, true ); + } + }); + jQuery._data( elem, "changeBubbles", true ); + } + }); + }, + + handle: function( event ) { + var elem = event.target; + + // Swallow native change events from checkbox/radio, we already triggered them above + if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { + return event.handleObj.handler.apply( this, arguments ); + } + }, + + teardown: function() { + jQuery.event.remove( this, "._change" ); + + return !rformElems.test( this.nodeName ); + } + }; +} + +// Create "bubbling" focus and blur events +if ( !support.focusinBubbles ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + var doc = this.ownerDocument || this, + attaches = jQuery._data( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + jQuery._data( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this, + attaches = jQuery._data( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + jQuery._removeData( doc, fix ); + } else { + jQuery._data( doc, fix, attaches ); + } + } + }; + }); +} + +jQuery.fn.extend({ + + on: function( types, selector, data, fn, /*INTERNAL*/ one ) { + var type, origFn; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + this.on( type, selector, data, types[ type ], one ); + } + return this; + } + + if ( data == null && fn == null ) { + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return this; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return this.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + }); + }, + one: function( types, selector, data, fn ) { + return this.on( types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each(function() { + jQuery.event.remove( this, types, fn, selector ); + }); + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + triggerHandler: function( type, data ) { + var elem = this[0]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +}); + + +function createSafeFragment( document ) { + var list = nodeNames.split( "|" ), + safeFrag = document.createDocumentFragment(); + + if ( safeFrag.createElement ) { + while ( list.length ) { + safeFrag.createElement( + list.pop() + ); + } + } + return safeFrag; +} + +var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" + + "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", + rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g, + rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"), + rleadingWhitespace = /^\s+/, + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, + rtagName = /<([\w:]+)/, + rtbody = /\s*$/g, + + // We have to close these tags to support XHTML (#13200) + wrapMap = { + option: [ 1, "" ], + legend: [ 1, "
", "
" ], + area: [ 1, "", "" ], + param: [ 1, "", "" ], + thead: [ 1, "", "
" ], + tr: [ 2, "", "
" ], + col: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + // IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags, + // unless wrapped in a div with non-breaking characters in front of it. + _default: support.htmlSerialize ? [ 0, "", "" ] : [ 1, "X
", "
" ] + }, + safeFragment = createSafeFragment( document ), + fragmentDiv = safeFragment.appendChild( document.createElement("div") ); + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +function getAll( context, tag ) { + var elems, elem, + i = 0, + found = typeof context.getElementsByTagName !== strundefined ? context.getElementsByTagName( tag || "*" ) : + typeof context.querySelectorAll !== strundefined ? context.querySelectorAll( tag || "*" ) : + undefined; + + if ( !found ) { + for ( found = [], elems = context.childNodes || context; (elem = elems[i]) != null; i++ ) { + if ( !tag || jQuery.nodeName( elem, tag ) ) { + found.push( elem ); + } else { + jQuery.merge( found, getAll( elem, tag ) ); + } + } + } + + return tag === undefined || tag && jQuery.nodeName( context, tag ) ? + jQuery.merge( [ context ], found ) : + found; +} + +// Used in buildFragment, fixes the defaultChecked property +function fixDefaultChecked( elem ) { + if ( rcheckableType.test( elem.type ) ) { + elem.defaultChecked = elem.checked; + } +} + +// Support: IE<8 +// Manipulating tables requires a tbody +function manipulationTarget( elem, content ) { + return jQuery.nodeName( elem, "table" ) && + jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ? + + elem.getElementsByTagName("tbody")[0] || + elem.appendChild( elem.ownerDocument.createElement("tbody") ) : + elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = (jQuery.find.attr( elem, "type" ) !== null) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + var match = rscriptTypeMasked.exec( elem.type ); + if ( match ) { + elem.type = match[1]; + } else { + elem.removeAttribute("type"); + } + return elem; +} + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var elem, + i = 0; + for ( ; (elem = elems[i]) != null; i++ ) { + jQuery._data( elem, "globalEval", !refElements || jQuery._data( refElements[i], "globalEval" ) ); + } +} + +function cloneCopyEvent( src, dest ) { + + if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) { + return; + } + + var type, i, l, + oldData = jQuery._data( src ), + curData = jQuery._data( dest, oldData ), + events = oldData.events; + + if ( events ) { + delete curData.handle; + curData.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + + // make the cloned public data object a copy from the original + if ( curData.data ) { + curData.data = jQuery.extend( {}, curData.data ); + } +} + +function fixCloneNodeIssues( src, dest ) { + var nodeName, e, data; + + // We do not need to do anything for non-Elements + if ( dest.nodeType !== 1 ) { + return; + } + + nodeName = dest.nodeName.toLowerCase(); + + // IE6-8 copies events bound via attachEvent when using cloneNode. + if ( !support.noCloneEvent && dest[ jQuery.expando ] ) { + data = jQuery._data( dest ); + + for ( e in data.events ) { + jQuery.removeEvent( dest, e, data.handle ); + } + + // Event data gets referenced instead of copied if the expando gets copied too + dest.removeAttribute( jQuery.expando ); + } + + // IE blanks contents when cloning scripts, and tries to evaluate newly-set text + if ( nodeName === "script" && dest.text !== src.text ) { + disableScript( dest ).text = src.text; + restoreScript( dest ); + + // IE6-10 improperly clones children of object elements using classid. + // IE10 throws NoModificationAllowedError if parent is null, #12132. + } else if ( nodeName === "object" ) { + if ( dest.parentNode ) { + dest.outerHTML = src.outerHTML; + } + + // This path appears unavoidable for IE9. When cloning an object + // element in IE9, the outerHTML strategy above is not sufficient. + // If the src has innerHTML and the destination does not, + // copy the src.innerHTML into the dest.innerHTML. #10324 + if ( support.html5Clone && ( src.innerHTML && !jQuery.trim(dest.innerHTML) ) ) { + dest.innerHTML = src.innerHTML; + } + + } else if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + // IE6-8 fails to persist the checked state of a cloned checkbox + // or radio button. Worse, IE6-7 fail to give the cloned element + // a checked appearance if the defaultChecked value isn't also set + + dest.defaultChecked = dest.checked = src.checked; + + // IE6-7 get confused and end up setting the value of a cloned + // checkbox/radio button to an empty string instead of "on" + if ( dest.value !== src.value ) { + dest.value = src.value; + } + + // IE6-8 fails to return the selected option to the default selected + // state when cloning options + } else if ( nodeName === "option" ) { + dest.defaultSelected = dest.selected = src.defaultSelected; + + // IE6-8 fails to set the defaultValue to the correct value when + // cloning other types of input fields + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +jQuery.extend({ + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var destElements, node, clone, i, srcElements, + inPage = jQuery.contains( elem.ownerDocument, elem ); + + if ( support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) { + clone = elem.cloneNode( true ); + + // IE<=8 does not properly clone detached, unknown element nodes + } else { + fragmentDiv.innerHTML = elem.outerHTML; + fragmentDiv.removeChild( clone = fragmentDiv.firstChild ); + } + + if ( (!support.noCloneEvent || !support.noCloneChecked) && + (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) { + + // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + // Fix all IE cloning issues + for ( i = 0; (node = srcElements[i]) != null; ++i ) { + // Ensure that the destination node is not null; Fixes #9587 + if ( destElements[i] ) { + fixCloneNodeIssues( node, destElements[i] ); + } + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0; (node = srcElements[i]) != null; i++ ) { + cloneCopyEvent( node, destElements[i] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + destElements = srcElements = node = null; + + // Return the cloned set + return clone; + }, + + buildFragment: function( elems, context, scripts, selection ) { + var j, elem, contains, + tmp, tag, tbody, wrap, + l = elems.length, + + // Ensure a safe fragment + safe = createSafeFragment( context ), + + nodes = [], + i = 0; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( jQuery.type( elem ) === "object" ) { + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || safe.appendChild( context.createElement("div") ); + + // Deserialize a standard representation + tag = (rtagName.exec( elem ) || [ "", "" ])[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + + tmp.innerHTML = wrap[1] + elem.replace( rxhtmlTag, "<$1>" ) + wrap[2]; + + // Descend through wrappers to the right content + j = wrap[0]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Manually add leading whitespace removed by IE + if ( !support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { + nodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[0] ) ); + } + + // Remove IE's autoinserted from table fragments + if ( !support.tbody ) { + + // String was a , *may* have spurious + elem = tag === "table" && !rtbody.test( elem ) ? + tmp.firstChild : + + // String was a bare or + wrap[1] === "
" && !rtbody.test( elem ) ? + tmp : + 0; + + j = elem && elem.childNodes.length; + while ( j-- ) { + if ( jQuery.nodeName( (tbody = elem.childNodes[j]), "tbody" ) && !tbody.childNodes.length ) { + elem.removeChild( tbody ); + } + } + } + + jQuery.merge( nodes, tmp.childNodes ); + + // Fix #12392 for WebKit and IE > 9 + tmp.textContent = ""; + + // Fix #12392 for oldIE + while ( tmp.firstChild ) { + tmp.removeChild( tmp.firstChild ); + } + + // Remember the top-level container for proper cleanup + tmp = safe.lastChild; + } + } + } + + // Fix #11356: Clear elements from fragment + if ( tmp ) { + safe.removeChild( tmp ); + } + + // Reset defaultChecked for any radios and checkboxes + // about to be appended to the DOM in IE 6/7 (#8060) + if ( !support.appendChecked ) { + jQuery.grep( getAll( nodes, "input" ), fixDefaultChecked ); + } + + i = 0; + while ( (elem = nodes[ i++ ]) ) { + + // #4087 - If origin and destination elements are the same, and this is + // that element, do not do anything + if ( selection && jQuery.inArray( elem, selection ) !== -1 ) { + continue; + } + + contains = jQuery.contains( elem.ownerDocument, elem ); + + // Append to fragment + tmp = getAll( safe.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( contains ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( (elem = tmp[ j++ ]) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + tmp = null; + + return safe; + }, + + cleanData: function( elems, /* internal */ acceptData ) { + var elem, type, id, data, + i = 0, + internalKey = jQuery.expando, + cache = jQuery.cache, + deleteExpando = support.deleteExpando, + special = jQuery.event.special; + + for ( ; (elem = elems[i]) != null; i++ ) { + if ( acceptData || jQuery.acceptData( elem ) ) { + + id = elem[ internalKey ]; + data = id && cache[ id ]; + + if ( data ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Remove cache only if it was not already removed by jQuery.event.remove + if ( cache[ id ] ) { + + delete cache[ id ]; + + // IE does not allow us to delete expando properties from nodes, + // nor does it have a removeAttribute function on Document nodes; + // we must handle all of these cases + if ( deleteExpando ) { + delete elem[ internalKey ]; + + } else if ( typeof elem.removeAttribute !== strundefined ) { + elem.removeAttribute( internalKey ); + + } else { + elem[ internalKey ] = null; + } + + deletedIds.push( id ); + } + } + } + } + } +}); + +jQuery.fn.extend({ + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) ); + }, null, value, arguments.length ); + }, + + append: function() { + return this.domManip( arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + }); + }, + + prepend: function() { + return this.domManip( arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + }); + }, + + before: function() { + return this.domManip( arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + }); + }, + + after: function() { + return this.domManip( arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + }); + }, + + remove: function( selector, keepData /* Internal Use Only */ ) { + var elem, + elems = selector ? jQuery.filter( selector, this ) : this, + i = 0; + + for ( ; (elem = elems[i]) != null; i++ ) { + + if ( !keepData && elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem ) ); + } + + if ( elem.parentNode ) { + if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) { + setGlobalEval( getAll( elem, "script" ) ); + } + elem.parentNode.removeChild( elem ); + } + } + + return this; + }, + + empty: function() { + var elem, + i = 0; + + for ( ; (elem = this[i]) != null; i++ ) { + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + } + + // Remove any remaining nodes + while ( elem.firstChild ) { + elem.removeChild( elem.firstChild ); + } + + // If this is a select, ensure that it displays empty (#12336) + // Support: IE<9 + if ( elem.options && jQuery.nodeName( elem, "select" ) ) { + elem.options.length = 0; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map(function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + }); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined ) { + return elem.nodeType === 1 ? + elem.innerHTML.replace( rinlinejQuery, "" ) : + undefined; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + ( support.htmlSerialize || !rnoshimcache.test( value ) ) && + ( support.leadingWhitespace || !rleadingWhitespace.test( value ) ) && + !wrapMap[ (rtagName.exec( value ) || [ "", "" ])[ 1 ].toLowerCase() ] ) { + + value = value.replace( rxhtmlTag, "<$1>" ); + + try { + for (; i < l; i++ ) { + // Remove element nodes and prevent memory leaks + elem = this[i] || {}; + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch(e) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var arg = arguments[ 0 ]; + + // Make the changes, replacing each context element with the new content + this.domManip( arguments, function( elem ) { + arg = this.parentNode; + + jQuery.cleanData( getAll( this ) ); + + if ( arg ) { + arg.replaceChild( elem, this ); + } + }); + + // Force removal if there was no new content (e.g., from empty arguments) + return arg && (arg.length || arg.nodeType) ? this : this.remove(); + }, + + detach: function( selector ) { + return this.remove( selector, true ); + }, + + domManip: function( args, callback ) { + + // Flatten any nested arrays + args = concat.apply( [], args ); + + var first, node, hasScripts, + scripts, doc, fragment, + i = 0, + l = this.length, + set = this, + iNoClone = l - 1, + value = args[0], + isFunction = jQuery.isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( isFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return this.each(function( index ) { + var self = set.eq( index ); + if ( isFunction ) { + args[0] = value.call( this, index, self.html() ); + } + self.domManip( args, callback ); + }); + } + + if ( l ) { + fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + if ( first ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( this[i], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) { + + if ( node.src ) { + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl ) { + jQuery._evalUrl( node.src ); + } + } else { + jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) ); + } + } + } + } + + // Fix #11809: Avoid leaking memory + fragment = first = null; + } + } + + return this; + } +}); + +jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + i = 0, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone(true); + jQuery( insert[i] )[ original ]( elems ); + + // Modern browsers can apply jQuery collections as arrays, but oldIE needs a .get() + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +}); + + +var iframe, + elemdisplay = {}; + +/** + * Retrieve the actual display of a element + * @param {String} name nodeName of the element + * @param {Object} doc Document object + */ +// Called only from within defaultDisplay +function actualDisplay( name, doc ) { + var style, + elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ), + + // getDefaultComputedStyle might be reliably used only on attached element + display = window.getDefaultComputedStyle && ( style = window.getDefaultComputedStyle( elem[ 0 ] ) ) ? + + // Use of this method is a temporary fix (more like optmization) until something better comes along, + // since it was removed from specification and supported only in FF + style.display : jQuery.css( elem[ 0 ], "display" ); + + // We don't have any data stored on the element, + // so use "detach" method as fast way to get rid of the element + elem.detach(); + + return display; +} + +/** + * Try to determine the default display value of an element + * @param {String} nodeName + */ +function defaultDisplay( nodeName ) { + var doc = document, + display = elemdisplay[ nodeName ]; + + if ( !display ) { + display = actualDisplay( nodeName, doc ); + + // If the simple way fails, read from inside an iframe + if ( display === "none" || !display ) { + + // Use the already-created iframe if possible + iframe = (iframe || jQuery( "