From 1fe8f9fd16b8e157d0ca51eb9e9bacf77929bc00 Mon Sep 17 00:00:00 2001 From: ADu80 <441489115@qq.com> Date: Tue, 13 Jun 2017 01:38:39 +0800 Subject: [PATCH] tab --- dist/bundle.js | 33650 ++++++++-------- package-lock.json | 76 +- src/components/Main/index.html | 2 +- src/components/NavLink/index.css | 3 + src/components/NavLink/index.html | 2 +- src/components/SideBar/index.css | 4 +- src/components/Tab/index.css | 60 +- src/components/Tab/index.html | 25 +- src/components/Tab/index.js | 9 + src/components/TabBar/index.css | 15 - src/components/TabBar/index.html | 5 - src/components/TabPage/index.css | 8 + src/components/TabPage/index.html | 8 +- src/components/TabPage/index.js | 2 + src/components/TabTitle/index.css | 22 + src/components/TabTitle/index.html | 3 + src/components/TabTitle/index.js | 16 + src/components/TopBar/index.css | 6 +- src/components/index.js | 3 +- src/constants/actions.js | 3 + src/pages/Page1/index.html | 22 +- src/pages/Page2/index.html | 22 +- src/stores/createStroe.js | 24 + .../TabBar/index.js => stores/store.js} | 0 24 files changed, 17240 insertions(+), 16750 deletions(-) delete mode 100644 src/components/TabBar/index.css delete mode 100644 src/components/TabBar/index.html create mode 100644 src/components/TabTitle/index.css create mode 100644 src/components/TabTitle/index.html create mode 100644 src/components/TabTitle/index.js create mode 100644 src/constants/actions.js create mode 100644 src/stores/createStroe.js rename src/{components/TabBar/index.js => stores/store.js} (100%) diff --git a/dist/bundle.js b/dist/bundle.js index ed89ac8..0e19f0d 100644 --- a/dist/bundle.js +++ b/dist/bundle.js @@ -63,7 +63,7 @@ /******/ __webpack_require__.p = ""; /******/ /******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 40); +/******/ return __webpack_require__(__webpack_require__.s = 43); /******/ }) /************************************************************************/ /******/ ([ @@ -71,19000 +71,19037 @@ /***/ (function(module, exports, __webpack_require__) { "use strict"; -/* WEBPACK VAR INJECTION */(function(module) {var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__; - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; - -/*! - * jQuery JavaScript Library v1.12.4 - * http://jquery.com/ - * - * Includes Sizzle.js - * http://sizzlejs.com/ - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license - * http://jquery.org/license - * - * Date: 2016-05-20T17:17Z - */ +/* WEBPACK VAR INJECTION */(function(Buffer) { -(function (global, factory) { +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ +// css base code, injected by the css-loader +module.exports = function (useSourceMap) { + var list = []; - if (( false ? "undefined" : _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 have a `window` with a `document` - // (such as Node.js), expose a factory as module.exports. - // This accentuates the need for the creation of a real `window`. - // e.g. var 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 the list of modules as css string + list.toString = function toString() { + return this.map(function (item) { + var content = cssWithMappingToString(item, useSourceMap); + if (item[2]) { + return "@media " + item[2] + "{" + content + "}"; + } else { + return content; } - return factory(w); - }; - } else { - factory(global); - } - - // Pass this if window is not defined yet -})(typeof window !== "undefined" ? window : undefined, function (window, noGlobal) { - - // Support: Firefox 18+ - // Can't be in strict mode, several libs including ASP.NET trace - // the stack via arguments.caller.callee and Firefox dies if - // you try to trace through "use strict" call chains. (#13335) - //"use strict"; - var deletedIds = []; - - var document = window.document; - - var _slice = deletedIds.slice; - - var concat = deletedIds.concat; - - var push = deletedIds.push; + }).join(""); + }; - var indexOf = deletedIds.indexOf; + // import a list of modules into the list + list.i = function (modules, mediaQuery) { + if (typeof modules === "string") modules = [[null, modules, ""]]; + var alreadyImportedModules = {}; + for (var i = 0; i < this.length; i++) { + var id = this[i][0]; + if (typeof id === "number") alreadyImportedModules[id] = true; + } + for (i = 0; i < modules.length; i++) { + var item = modules[i]; + // skip already imported module + // this implementation is not 100% perfect for weird media query combinations + // when a module is imported multiple times with different media queries. + // I hope this will never occur (Hey this way we have smaller bundles) + if (typeof item[0] !== "number" || !alreadyImportedModules[item[0]]) { + if (mediaQuery && !item[2]) { + item[2] = mediaQuery; + } else if (mediaQuery) { + item[2] = "(" + item[2] + ") and (" + mediaQuery + ")"; + } + list.push(item); + } + } + }; + return list; +}; - var class2type = {}; +function cssWithMappingToString(item, useSourceMap) { + var content = item[1] || ''; + var cssMapping = item[3]; + if (!cssMapping) { + return content; + } - var toString = class2type.toString; + if (useSourceMap) { + var sourceMapping = toComment(cssMapping); + var sourceURLs = cssMapping.sources.map(function (source) { + return '/*# sourceURL=' + cssMapping.sourceRoot + source + ' */'; + }); - var hasOwn = class2type.hasOwnProperty; + return [content].concat(sourceURLs).concat([sourceMapping]).join('\n'); + } - var support = {}; + return [content].join('\n'); +} - var version = "1.12.4", +// Adapted from convert-source-map (MIT) +function toComment(sourceMap) { + var base64 = new Buffer(JSON.stringify(sourceMap)).toString('base64'); + var data = 'sourceMappingURL=data:application/json;charset=utf-8;base64,' + base64; + return '/*# ' + data + ' */'; +} +/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(27).Buffer)) - // Define a local copy of jQuery - jQuery = function jQuery(selector, context) { +/***/ }), +/* 1 */ +/***/ (function(module, exports, __webpack_require__) { - // 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); +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ +var stylesInDom = {}, + memoize = function(fn) { + var memo; + return function () { + if (typeof memo === "undefined") memo = fn.apply(this, arguments); + return memo; + }; }, + isOldIE = memoize(function() { + // Test for IE <= 9 as proposed by Browserhacks + // @see http://browserhacks.com/#hack-e71d8692f65334173fee715c222cb805 + // Tests for existence of standard globals is to allow style-loader + // to operate correctly into non-standard environments + // @see https://github.com/webpack-contrib/style-loader/issues/177 + return window && document && document.all && !window.atob; + }), + getElement = (function(fn) { + var memo = {}; + return function(selector) { + if (typeof memo[selector] === "undefined") { + memo[selector] = fn.call(this, selector); + } + return memo[selector] + }; + })(function (styleTarget) { + return document.querySelector(styleTarget) + }), + singletonElement = null, + singletonCounter = 0, + styleElementsInsertedAtTop = [], + fixUrls = __webpack_require__(30); +module.exports = function(list, options) { + if(typeof DEBUG !== "undefined" && DEBUG) { + if(typeof document !== "object") throw new Error("The style-loader cannot be used in a non-browser environment"); + } - // Support: Android<4.1, IE<9 - // Make sure we trim BOM and NBSP - rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, - + options = options || {}; + options.attrs = typeof options.attrs === "object" ? options.attrs : {}; - // Matches dashed string for camelizing - rmsPrefix = /^-ms-/, - rdashAlpha = /-([\da-z])/gi, + // Force single-tag solution on IE6-9, which has a hard limit on the # of + + +
+

+
+
+ + + * + */ - detach(); - jQuery.ready(); - } - } + var none = 'none'; + function parseDisplay(elem, val) { + //用于取得此类标签的默认display值 + var doc = elem.ownerDocument; + var nodeName = elem.nodeName; + var key = '_' + nodeName; + if (!parseDisplay[key]) { + var temp = doc.body.appendChild(doc.createElement(nodeName)); + val = avalon.css(temp, 'display'); + doc.body.removeChild(temp); + if (val === none) { + val = 'block'; + } + parseDisplay[key] = val; + } + return parseDisplay[key]; + } - jQuery.ready.promise = function (obj) { - if (!readyList) { + avalon.parseDisplay = parseDisplay; + avalon.directive('visible', { + diff: function diff(newVal, oldVal) { + var n = !!newVal; + if (oldVal === void 0 || n !== oldVal) { + this.value = n; + return true; + } + }, + ready: true, + update: function update(vdom, show) { + var dom = vdom.dom; + if (dom && dom.nodeType === 1) { + var display = dom.style.display; + var value; + if (show) { + if (display === none) { + value = vdom.displayValue; + if (!value) { + dom.style.display = ''; + if (dom.style.cssText === '') { + dom.removeAttribute('style'); + } + } + } + if (dom.style.display === '' && avalon(dom).css('display') === none && + // fix firefox BUG,必须挂到页面上 + avalon.contains(dom.ownerDocument, dom)) { + value = parseDisplay(dom); + } + } else { - readyList = jQuery.Deferred(); + if (display !== none) { + value = none; + vdom.displayValue = display; + } + } + var cb = function cb() { + if (value !== void 0) { + dom.style.display = value; + } + }; - // Catch cases where $(document).ready() is called - // after the browser event has already occurred. - // Support: IE6-10 - // Older IE sometimes signals "interactive" too soon - if (document.readyState === "complete" || document.readyState !== "loading" && !document.documentElement.doScroll) { + avalon.applyEffect(dom, vdom, { + hook: show ? 'onEnterDone' : 'onLeaveDone', + cb: cb + }); + } + } + }); - // Handle it asynchronously to allow scripts the opportunity to delay ready - window.setTimeout(jQuery.ready); + avalon.directive('text', { + delay: true, + init: function init() { - // Standards-based browsers support DOMContentLoaded - } else if (document.addEventListener) { + var node = this.node; + if (node.isVoidTag) { + avalon.error('自闭合元素不能使用ms-text'); + } + var child = { nodeName: '#text', nodeValue: this.getValue() }; + node.children.splice(0, node.children.length, child); + if (inBrowser) { + avalon.clearHTML(node.dom); + node.dom.appendChild(avalon.vdom(child, 'toDOM')); + } + this.node = child; + var type = 'expr'; + this.type = this.name = type; + var directive$$1 = avalon.directives[type]; + var me = this; + this.callback = function (value) { + directive$$1.update.call(me, me.node, value); + }; + } + }); - // Use the handy event callback - document.addEventListener("DOMContentLoaded", completed); + avalon.directive('expr', { + update: function update(vdom, value) { + value = value == null || value === '' ? '\u200B' : value; + vdom.nodeValue = value; + //https://github.com/RubyLouvre/avalon/issues/1834 + if (vdom.dom) vdom.dom.data = value; + } + }); - // A fallback to window.onload, that will always work - window.addEventListener("load", completed); + avalon.directive('attr', { + diff: cssDiff, + update: function update(vdom, value) { + var props = vdom.props; + for (var i in value) { + if (!!value[i] === false) { + delete props[i]; + } else { + props[i] = value[i]; + } + } + var dom = vdom.dom; + if (dom && dom.nodeType === 1) { + updateAttrs(dom, value); + } + } + }); - // If IE event model is used - } else { + avalon.directive('html', { - // Ensure firing before onload, maybe late but safe also for iframes - document.attachEvent("onreadystatechange", completed); + update: function update(vdom, value) { + this.beforeDispose(); - // A fallback to window.onload, that will always work - window.attachEvent("onload", completed); + this.innerRender = avalon.scan('
' + value + '
', this.vm, function () { + var oldRoot = this.root; + if (vdom.children) vdom.children.length = 0; + vdom.children = oldRoot.children; + this.root = vdom; + if (vdom.dom) avalon.clearHTML(vdom.dom); + }); + }, + beforeDispose: function beforeDispose() { + if (this.innerRender) { + this.innerRender.dispose(); + } + }, + delay: true + }); - // If IE and not a frame - // continually check to see if the document is ready - var top = false; + avalon.directive('if', { + delay: true, + priority: 5, + init: function init() { + this.placeholder = createAnchor('if'); + var props = this.node.props; + delete props['ms-if']; + delete props[':if']; + this.fragment = avalon.vdom(this.node, 'toHTML'); + }, + diff: function diff(newVal, oldVal) { + var n = !!newVal; + if (oldVal === void 0 || n !== oldVal) { + this.value = n; + return true; + } + }, + update: function update(vdom, value) { + if (this.isShow === void 0 && value) { + continueScan(this, vdom); + return; + } + this.isShow = value; + var placeholder = this.placeholder; - try { - top = window.frameElement == null && document.documentElement; - } catch (e) {} + if (value) { + var p = placeholder.parentNode; + continueScan(this, vdom); + p && p.replaceChild(vdom.dom, placeholder); + } else { + //移除DOM + this.beforeDispose(); + vdom.nodeValue = 'if'; + vdom.nodeName = '#comment'; + delete vdom.children; + var dom = vdom.dom; + var p = dom && dom.parentNode; + vdom.dom = placeholder; + if (p) { + p.replaceChild(placeholder, dom); + } + } + }, + beforeDispose: function beforeDispose() { + if (this.innerRender) { + this.innerRender.dispose(); + } + } + }); - if (top && top.doScroll) { - (function doScrollCheck() { - if (!jQuery.isReady) { + function continueScan(instance, vdom) { + var innerRender = instance.innerRender = avalon.scan(instance.fragment, instance.vm); + avalon.shadowCopy(vdom, innerRender.root); + delete vdom.nodeValue; + } - try { + avalon.directive('on', { + beforeInit: function beforeInit() { + this.getter = avalon.noop; + }, + init: function init() { + var vdom = this.node; + var underline = this.name.replace('ms-on-', 'e').replace('-', '_'); + var uuid = underline + '_' + this.expr.replace(/\s/g, '').replace(/[^$a-z]/ig, function (e) { + return e.charCodeAt(0); + }); + var fn = avalon.eventListeners[uuid]; + if (!fn) { + var arr = addScope(this.expr); + var body = arr[0], + filters = arr[1]; + body = makeHandle(body); - // Use the trick by Diego Perini - // http://javascript.nwbox.com/IEContentLoaded/ - top.doScroll("left"); - } catch (e) { - return window.setTimeout(doScrollCheck, 50); - } + if (filters) { + filters = filters.replace(/__value__/g, '$event'); + filters += '\nif($event.$return){\n\treturn;\n}'; + } + var ret = ['try{', '\tvar __vmodel__ = this;', '\t' + filters, '\treturn ' + body, '}catch(e){avalon.log(e, "in on dir")}'].filter(function (el) { + return (/\S/.test(el) + ); + }); + fn = new Function('$event', ret.join('\n')); + fn.uuid = uuid; + avalon.eventListeners[uuid] = fn; + } - // detach all dom ready events - detach(); + var dom = avalon.vdom(vdom, 'toDOM'); + dom._ms_context_ = this.vm; - // and execute any waiting functions - jQuery.ready(); - } - })(); - } - } - } - return readyList.promise(obj); - }; + this.eventType = this.param.replace(/\-(\d)$/, ''); + delete this.param; + avalon(dom).bind(this.eventType, fn); + }, - // Kick off the DOM ready check even if the user does not - jQuery.ready.promise(); + beforeDispose: function beforeDispose() { + avalon(this.node.dom).unbind(this.eventType); + } + }); - // Support: IE<9 - // Iteration over object's inherited properties before its own - var i; - for (i in jQuery(support)) { - break; - } - support.ownFirst = i === "0"; + var rforAs = /\s+as\s+([$\w]+)/; + var rident = /^[$a-zA-Z_][$a-zA-Z0-9_]*$/; + var rinvalid = /^(null|undefined|NaN|window|this|\$index|\$id)$/; + var rargs = /[$\w_]+/g; + avalon.directive('for', { + delay: true, + priority: 3, + beforeInit: function beforeInit() { + var str = this.expr, + asName; + str = str.replace(rforAs, function (a, b) { + /* istanbul ignore if */ + if (!rident.test(b) || rinvalid.test(b)) { + avalon.error('alias ' + b + ' is invalid --- must be a valid JS identifier which is not a reserved name.'); + } else { + asName = b; + } + return ''; + }); - // Note: most support tests are defined in their respective modules. - // false until the test is run - support.inlineBlockNeedsLayout = false; + var arr = str.split(' in '); + var kv = arr[0].match(rargs); + if (kv.length === 1) { + //确保avalon._each的回调有三个参数 + kv.unshift('$key'); + } + this.expr = arr[1]; + this.keyName = kv[0]; + this.valName = kv[1]; + this.signature = avalon.makeHashCode('for'); + if (asName) { + this.asName = asName; + } - // Execute ASAP in case we need to set body.style.zoom - jQuery(function () { + delete this.param; + }, + init: function init() { + var cb = this.userCb; + if (typeof cb === 'string' && cb) { + var arr = addScope(cb, 'for'); + var body = makeHandle(arr[0]); + this.userCb = new Function('$event', 'var __vmodel__ = this\nreturn ' + body); + } + this.node.forDir = this; //暴露给component/index.js中的resetParentChildren方法使用 + this.fragment = ['
', this.fragment, '
'].join(''); + this.cache = {}; + }, + diff: function diff(newVal, oldVal) { + /* istanbul ignore if */ + if (this.updating) { + return; + } + this.updating = true; + var traceIds = createFragments(this, newVal); - // Minified: var a,b,c,d - var val, div, body, container; + if (this.oldTrackIds === void 0) return true; - body = document.getElementsByTagName("body")[0]; - if (!body || !body.style) { + if (this.oldTrackIds !== traceIds) { + this.oldTrackIds = traceIds; + return true; + } + }, + update: function update() { - // Return for frameset docs that don't have a body - return; - } + if (!this.preFragments) { + this.fragments = this.fragments || []; + mountList(this); + } else { + diffList(this); + updateList(this); + } - // 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 (this.userCb) { + var me = this; + setTimeout(function () { + me.userCb.call(me.vm, { + type: 'rendered', + target: me.begin.dom, + signature: me.signature + }); + }, 0); + } + delete this.updating; + }, + beforeDispose: function beforeDispose() { + this.fragments.forEach(function (el) { + el.dispose(); + }); + } + }); - if (typeof div.style.zoom !== "undefined") { + function getTraceKey(item) { + var type = typeof item === 'undefined' ? 'undefined' : _typeof(item); + return item && type === 'object' ? item.$hashcode : type + ':' + item; + } - // 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"; + //创建一组fragment的虚拟DOM + function createFragments(instance, obj) { + if (isObject(obj)) { + var array = Array.isArray(obj); + var ids = []; + var fragments = [], + i = 0; - support.inlineBlockNeedsLayout = val = div.offsetWidth === 3; - if (val) { + instance.isArray = array; + if (instance.fragments) { + instance.preFragments = instance.fragments; + avalon.each(obj, function (key, value) { + var k = array ? getTraceKey(value) : key; - // 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; - } - } + fragments.push({ + key: k, + val: value, + index: i++ + }); + ids.push(k); + }); + instance.fragments = fragments; + } else { + avalon.each(obj, function (key, value) { + if (!(key in $$skipArray)) { + var k = array ? getTraceKey(value) : key; + fragments.push(new VFragment([], k, value, i++)); + ids.push(k); + } + }); + instance.fragments = fragments; + } + return ids.join(';;'); + } else { + return NaN; + } + } - body.removeChild(container); - }); + function mountList(instance) { + var args = instance.fragments.map(function (fragment, index) { + FragmentDecorator(fragment, instance, index); + saveInCache(instance.cache, fragment); + return fragment; + }); + var list = instance.parentChildren; + var i = list.indexOf(instance.begin); + list.splice.apply(list, [i + 1, 0].concat(args)); + } - (function () { - var div = document.createElement("div"); + function diffList(instance) { + var cache = instance.cache; + var newCache = {}; + var fuzzy = []; + var list = instance.preFragments; - // Support: IE<9 - support.deleteExpando = true; - try { - delete div.test; - } catch (e) { - support.deleteExpando = false; - } + list.forEach(function (el) { + el._dispose = true; + }); - // Null elements to avoid leaks in IE. - div = null; - })(); - var acceptData = function acceptData(elem) { - var noData = jQuery.noData[(elem.nodeName + " ").toLowerCase()], - nodeType = +elem.nodeType || 1; + instance.fragments.forEach(function (c, index) { + var fragment = isInCache(cache, c.key); + //取出之前的文档碎片 + if (fragment) { + delete fragment._dispose; + fragment.oldIndex = fragment.index; + fragment.index = index; // 相当于 c.index - // Do not set data on non-element DOM nodes because it will not be cleared (#8335). - return nodeType !== 1 && nodeType !== 9 ? false : + resetVM(fragment.vm, instance.keyName); + fragment.vm[instance.valName] = c.val; + fragment.vm[instance.keyName] = instance.isArray ? index : fragment.key; + saveInCache(newCache, fragment); + } else { + //如果找不到就进行模糊搜索 + fuzzy.push(c); + } + }); + fuzzy.forEach(function (c) { + var fragment = fuzzyMatchCache(cache, c.key); + if (fragment) { + //重复利用 + fragment.oldIndex = fragment.index; + fragment.key = c.key; + var val = fragment.val = c.val; + var index = fragment.index = c.index; - // Nodes accept data unless otherwise specified; rejection can be conditional - !noData || noData !== true && elem.getAttribute("classid") === noData; - }; + fragment.vm[instance.valName] = val; + fragment.vm[instance.keyName] = instance.isArray ? index : fragment.key; + delete fragment._dispose; + } else { - var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, - rmultiDash = /([A-Z])/g; + c = new VFragment([], c.key, c.val, c.index); + fragment = FragmentDecorator(c, instance, c.index); + list.push(fragment); + } + saveInCache(newCache, fragment); + }); - function dataAttr(elem, key, data) { + instance.fragments = list; + list.sort(function (a, b) { + return a.index - b.index; + }); + instance.cache = newCache; + } - // If nothing was found internally, try to fetch any - // data from the HTML5 data-* attribute - if (data === undefined && elem.nodeType === 1) { + function resetVM(vm, a, b) { + if (avalon.config.inProxyMode) { + vm.$accessors[a].value = NaN; + } else { + vm.$accessors[a].set(NaN); + } + } - var name = "data-" + key.replace(rmultiDash, "-$1").toLowerCase(); + function updateList(instance) { + var before = instance.begin.dom; + var parent = before.parentNode; + var list = instance.fragments; + var end = instance.end.dom; + for (var i = 0, item; item = list[i]; i++) { + if (item._dispose) { + list.splice(i, 1); + i--; + item.dispose(); + continue; + } + if (item.oldIndex !== item.index) { + var f = item.toFragment(); + var isEnd = before.nextSibling === null; + parent.insertBefore(f, before.nextSibling); + if (isEnd && !parent.contains(end)) { + parent.insertBefore(end, before.nextSibling); + } + } + before = item.split; + } + var ch = instance.parentChildren; + var startIndex = ch.indexOf(instance.begin); + var endIndex = ch.indexOf(instance.end); - data = elem.getAttribute(name); + list.splice.apply(ch, [startIndex + 1, endIndex - startIndex].concat(list)); + } - if (typeof data === "string") { - try { - data = data === "true" ? true : data === "false" ? false : data === "null" ? null : + /** + * + * @param {type} fragment + * @param {type} this + * @param {type} index + * @returns { key, val, index, oldIndex, this, dom, split, vm} + */ + function FragmentDecorator(fragment, instance, index) { + var data = {}; + data[instance.keyName] = instance.isArray ? index : fragment.key; + data[instance.valName] = fragment.val; + if (instance.asName) { + data[instance.asName] = instance.value; + } + var vm = fragment.vm = platform.itemFactory(instance.vm, { + data: data + }); + if (instance.isArray) { + vm.$watch(instance.valName, function (a) { + if (instance.value && instance.value.set) { + instance.value.set(vm[instance.keyName], a); + } + }); + } else { + vm.$watch(instance.valName, function (a) { + instance.value[fragment.key] = a; + }); + } - // Only convert to a number if it doesn't change the string - +data + "" === data ? +data : rbrace.test(data) ? jQuery.parseJSON(data) : data; - } catch (e) {} + fragment.index = index; + fragment.innerRender = avalon.scan(instance.fragment, vm, function () { + var oldRoot = this.root; + ap.push.apply(fragment.children, oldRoot.children); + this.root = fragment; + }); + return fragment; + } + // 新位置: 旧位置 + function isInCache(cache, id) { + var c = cache[id]; + if (c) { + var arr = c.arr; + /* istanbul ignore if*/ + if (arr) { + var r = arr.pop(); + if (!arr.length) { + c.arr = 0; + } + return r; + } + delete cache[id]; + return c; + } + } + //[1,1,1] number1 number1_ number1__ + function saveInCache(cache, component) { + var trackId = component.key; + if (!cache[trackId]) { + cache[trackId] = component; + } else { + var c = cache[trackId]; + var arr = c.arr || (c.arr = []); + arr.push(component); + } + } - // Make sure we set the data so it isn't changed later - jQuery.data(elem, key, data); - } else { - data = undefined; - } - } + function fuzzyMatchCache(cache) { + var key; + for (var id in cache) { + var key = id; + break; + } + if (key) { + return isInCache(cache, key); + } + } - return data; - } + //根据VM的属性值或表达式的值切换类名,ms-class='xxx yyy zzz:flag' + //http://www.cnblogs.com/rubylouvre/archive/2012/12/17/2818540.html + function classNames() { + var classes = []; + for (var i = 0; i < arguments.length; i++) { + var arg = arguments[i]; + var argType = typeof arg === 'undefined' ? 'undefined' : _typeof(arg); + if (argType === 'string' || argType === 'number' || arg === true) { + classes.push(arg); + } else if (Array.isArray(arg)) { + classes.push(classNames.apply(null, arg)); + } else if (argType === 'object') { + for (var key in arg) { + if (arg.hasOwnProperty(key) && arg[key]) { + classes.push(key); + } + } + } + } - // checks a cache object for emptiness - function isEmptyDataObject(obj) { - var name; - for (name in obj) { + return classes.join(' '); + } - // 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; - } - } + avalon.directive('class', { + diff: function diff(newVal, oldVal) { + var type = this.type; + var vdom = this.node; + var classEvent = vdom.classEvent || {}; + if (type === 'hover') { + //在移出移入时切换类名 + classEvent.mouseenter = activateClass; + classEvent.mouseleave = abandonClass; + } else if (type === 'active') { + //在获得焦点时切换类名 + classEvent.tabIndex = vdom.props.tabindex || -1; + classEvent.mousedown = activateClass; + classEvent.mouseup = abandonClass; + classEvent.mouseleave = abandonClass; + } + vdom.classEvent = classEvent; - return true; - } + var className = classNames(newVal); - function internalData(elem, name, data, pvt /* Internal Use Only */) { - if (!acceptData(elem)) { - return; - } + if ((typeof oldVal === 'undefined' ? 'undefined' : _typeof(oldVal)) === void 0 || oldVal !== className) { + this.value = className; - var ret, - thisCache, - internalKey = jQuery.expando, + vdom['change-' + type] = className; + return true; + } + }, + update: function update(vdom, value) { + var dom = vdom.dom; + if (dom && dom.nodeType == 1) { + var dirType = this.type; + var change = 'change-' + dirType; + var classEvent = vdom.classEvent; + if (classEvent) { + for (var i in classEvent) { + if (i === 'tabIndex') { + dom[i] = classEvent[i]; + } else { + avalon.bind(dom, i, classEvent[i]); + } + } + vdom.classEvent = {}; + } + var names = ['class', 'hover', 'active']; + names.forEach(function (type) { + if (dirType !== type) return; + if (type === 'class') { + dom && setClass(dom, value); + } else { + var oldClass = dom.getAttribute(change); + if (oldClass) { + avalon(dom).removeClass(oldClass); + } + var name = 'change-' + type; + dom.setAttribute(name, value); + } + }); + } + } + }); - // 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, + directives.active = directives.hover = directives['class']; + var classMap = { + mouseenter: 'change-hover', + mouseleave: 'change-hover', + mousedown: 'change-active', + mouseup: 'change-active' + }; - // 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, + function activateClass(e) { + var elem = e.target; + avalon(elem).addClass(elem.getAttribute(classMap[e.type]) || ''); + } + function abandonClass(e) { + var elem = e.target; + var name = classMap[e.type]; + avalon(elem).removeClass(elem.getAttribute(name) || ''); + if (name !== 'change-active') { + avalon(elem).removeClass(elem.getAttribute('change-active') || ''); + } + } - // 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; + function setClass(dom, neo) { + var old = dom.getAttribute('change-class'); + if (old !== neo) { + avalon(dom).removeClass(old).addClass(neo); + dom.setAttribute('change-class', neo); + } + } - // 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; - } + getLongID(activateClass); + getLongID(abandonClass); - if (!id) { + function lookupOption(vdom, values) { + vdom.children && vdom.children.forEach(function (el) { + if (el.nodeName === 'option') { + setOption(el, values); + } else { + lookupOption(el, values); + } + }); + } - // 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; - } - } + function setOption(vdom, values) { + var props = vdom.props; + if (!('disabled' in props)) { + var value = getOptionValue(vdom, props); + value = String(value || '').trim(); + props.selected = values.indexOf(value) !== -1; - if (!cache[id]) { + if (vdom.dom) { + vdom.dom.selected = props.selected; + var v = vdom.dom.selected; //必须加上这个,防止移出节点selected失效 + } + } + } - // Avoid exposing jQuery metadata on plain JS objects when the object - // is serialized using JSON.stringify - cache[id] = isNode ? {} : { toJSON: jQuery.noop }; - } + function getOptionValue(vdom, props) { + if (props && 'value' in props) { + return props.value + ''; + } + var arr = []; + vdom.children.forEach(function (el) { + if (el.nodeName === '#text') { + arr.push(el.nodeValue); + } else if (el.nodeName === '#document-fragment') { + arr.push(getOptionValue(el)); + } + }); + return arr.join(''); + } - // 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 === "undefined" ? "undefined" : _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); - } - } + function getSelectedValue(vdom, arr) { + vdom.children.forEach(function (el) { + if (el.nodeName === 'option') { + if (el.props.selected === true) arr.push(getOptionValue(el, el.props)); + } else if (el.children) { + getSelectedValue(el, arr); + } + }); + return arr; + } - thisCache = cache[id]; + var updateDataActions = { + input: function input(prop) { + //处理单个value值处理 + var field = this; + prop = prop || 'value'; + var dom = field.dom; + var rawValue = dom[prop]; + var parsedValue = field.parseValue(rawValue); - // 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 = {}; - } + //有时候parse后一致,vm不会改变,但input里面的值 + field.value = rawValue; + field.setValue(parsedValue); + duplexCb(field); + var pos = field.pos; + /* istanbul ignore if */ + if (dom.caret) { + field.setCaret(dom, pos); + } + //vm.aaa = '1234567890' + //处理 {{@aaa}} 这种格式化同步不一致的情况 + }, + radio: function radio() { + var field = this; + if (field.isChecked) { + var val = !field.value; + field.setValue(val); + duplexCb(field); + } else { + updateDataActions.input.call(field); + field.value = NaN; + } + }, + checkbox: function checkbox() { + var field = this; + var array = field.value; + if (!Array.isArray(array)) { + avalon.warn('ms-duplex应用于checkbox上要对应一个数组'); + array = [array]; + } + var method = field.dom.checked ? 'ensure' : 'remove'; + if (array[method]) { + var val = field.parseValue(field.dom.value); + array[method](val); + duplexCb(field); + } + this.__test__ = array; + }, + select: function select() { + var field = this; + var val = avalon(field.dom).val(); //字符串或字符串数组 + if (val + '' !== this.value + '') { + if (Array.isArray(val)) { + //转换布尔数组或其他 + val = val.map(function (v) { + return field.parseValue(v); + }); + } else { + val = field.parseValue(val); + } + field.setValue(val); + duplexCb(field); + } + }, + contenteditable: function contenteditable() { + updateDataActions.input.call(this, 'innerHTML'); + } + }; - thisCache = thisCache.data; - } + function duplexCb(field) { + if (field.userCb) { + field.userCb.call(field.vm, { + type: 'changed', + target: field.dom + }); + } + } - if (data !== undefined) { - thisCache[jQuery.camelCase(name)] = data; - } + function updateDataHandle(event) { + var elem = this; + var field = elem._ms_duplex_; + if (elem.composing) { + //防止onpropertychange引发爆栈 + return; + } + if (elem.value === field.value) { + return; + } + /* istanbul ignore if*/ + if (elem.caret) { + try { + var pos = field.getCaret(elem); + field.pos = pos; + } catch (e) {} + } + /* istanbul ignore if*/ + if (field.debounceTime > 4) { + var timestamp = new Date(); + var left = timestamp - field.time || 0; + field.time = timestamp; + /* istanbul ignore if*/ + if (left >= field.debounceTime) { + updateDataActions[field.dtype].call(field); + /* istanbul ignore else*/ + } else { + clearTimeout(field.debounceID); + field.debounceID = setTimeout(function () { + updateDataActions[field.dtype].call(field); + }, left); + } + } else if (field.isChanged) { + setTimeout(function () { + //https://github.com/RubyLouvre/avalon/issues/1908 + updateDataActions[field.dtype].call(field); + }, 4); + } else { + updateDataActions[field.dtype].call(field); + } + } - // Check for both converted-to-camel and non-converted data property names - // If a data property was specified - if (typeof name === "string") { + var rchangeFilter = /\|\s*change\b/; + var rdebounceFilter = /\|\s*debounce(?:\(([^)]+)\))?/; + function duplexBeforeInit() { + var expr = this.expr; + if (rchangeFilter.test(expr)) { + this.isChanged = true; + expr = expr.replace(rchangeFilter, ''); + } + var match = expr.match(rdebounceFilter); + if (match) { + expr = expr.replace(rdebounceFilter, ''); + if (!this.isChanged) { + this.debounceTime = parseInt(match[1], 10) || 300; + } + } + this.expr = expr; + } + function duplexInit() { + var expr = this.expr; + var node = this.node; + var etype = node.props.type; + this.parseValue = parseValue; + //处理数据转换器 + var parsers = this.param, + dtype; + var isChecked = false; + parsers = parsers ? parsers.split('-').map(function (a) { + if (a === 'checked') { + isChecked = true; + } + return a; + }) : []; + node.duplex = this; + if (rcheckedType.test(etype) && isChecked) { + //如果是radio, checkbox,判定用户使用了checked格式函数没有 + parsers = []; + dtype = 'radio'; + this.isChecked = isChecked; + } + this.parsers = parsers; + if (!/input|textarea|select/.test(node.nodeName)) { + if ('contenteditable' in node.props) { + dtype = 'contenteditable'; + } + } else if (!dtype) { + dtype = node.nodeName === 'select' ? 'select' : etype === 'checkbox' ? 'checkbox' : etype === 'radio' ? 'radio' : 'input'; + } + this.dtype = dtype; - // First Try to find as-is property data - ret = thisCache[name]; + //判定是否使用了 change debounce 过滤器 + // this.isChecked = /boolean/.test(parsers) + if (dtype !== 'input' && dtype !== 'contenteditable') { + delete this.isChanged; + delete this.debounceTime; + } else if (!this.isChecked) { + this.isString = true; + } - // Test for null|undefined property data - if (ret == null) { + var cb = node.props['data-duplex-changed']; + if (cb) { + var arr = addScope(cb, 'xx'); + var body = makeHandle(arr[0]); + this.userCb = new Function('$event', 'var __vmodel__ = this\nreturn ' + body); + } + } + function duplexDiff(newVal, oldVal) { + if (Array.isArray(newVal)) { + if (newVal + '' !== this.compareVal) { + this.compareVal = newVal + ''; + return true; + } + } else { + newVal = this.parseValue(newVal); + if (!this.isChecked) { + this.value = newVal += ''; + } + if (newVal !== this.compareVal) { + this.compareVal = newVal; + return true; + } + } + } - // Try to find the camelCased property - ret = thisCache[jQuery.camelCase(name)]; - } - } else { - ret = thisCache; - } + function duplexBind(vdom, addEvent) { + var dom = vdom.dom; + this.dom = dom; + this.vdom = vdom; + this.duplexCb = updateDataHandle; + dom._ms_duplex_ = this; + //绑定事件 + addEvent(dom, this); + } - return ret; - } + var valueHijack = true; + try { + //#272 IE9-IE11, firefox + var setters = {}; + var aproto = HTMLInputElement.prototype; + var bproto = HTMLTextAreaElement.prototype; + var newSetter = function newSetter(value) { + // jshint ignore:line + setters[this.tagName].call(this, value); + var data = this._ms_duplex_; + if (!this.caret && data && data.isString) { + data.duplexCb.call(this, { type: 'setter' }); + } + }; + var inputProto = HTMLInputElement.prototype; + Object.getOwnPropertyNames(inputProto); //故意引发IE6-8等浏览器报错 + setters['INPUT'] = Object.getOwnPropertyDescriptor(aproto, 'value').set; - function internalRemoveData(elem, name, pvt) { - if (!acceptData(elem)) { - return; - } + Object.defineProperty(aproto, 'value', { + set: newSetter + }); + setters['TEXTAREA'] = Object.getOwnPropertyDescriptor(bproto, 'value').set; + Object.defineProperty(bproto, 'value', { + set: newSetter + }); + valueHijack = false; + } catch (e) { + //在chrome 43中 ms-duplex终于不需要使用定时器实现双向绑定了 + // http://updates.html5rocks.com/2015/04/DOM-attributes-now-on-the-prototype + // https://docs.google.com/document/d/1jwA8mtClwxI-QJuHT7872Z0pxpZz8PBkf2bGAbsUtqs/edit?pli=1 + } - var thisCache, - i, - isNode = elem.nodeType, + function parseValue(val) { + for (var i = 0, k; k = this.parsers[i++];) { + var fn = avalon.parsers[k]; + if (fn) { + val = fn.call(this, val); + } + } + return val; + } + var updateView = { + input: function input() { + //处理单个value值处理 + var vdom = this.node; + var value = this.value + ''; + vdom.dom.value = vdom.props.value = value; + }, + updateChecked: function updateChecked(vdom, checked) { + if (vdom.dom) { + vdom.dom.defaultChecked = vdom.dom.checked = checked; + } + }, + radio: function radio() { + //处理单个checked属性 + var node = this.node; + var nodeValue = node.props.value; + var checked; + if (this.isChecked) { + checked = !!this.value; + } else { + checked = this.value + '' === nodeValue; + } + node.props.checked = checked; + updateView.updateChecked(node, checked); + }, + checkbox: function checkbox() { + //处理多个checked属性 + var node = this.node; + var props = node.props; + var value = props.value + ''; + var values = [].concat(this.value); + var checked = values.some(function (el) { + return el + '' === value; + }); - // See jQuery.data for more information - cache = isNode ? jQuery.cache : elem, - id = isNode ? elem[jQuery.expando] : jQuery.expando; + props.defaultChecked = props.checked = checked; + updateView.updateChecked(node, checked); + }, + select: function select() { + //处理子级的selected属性 + var a = Array.isArray(this.value) ? this.value.map(String) : this.value + ''; + lookupOption(this.node, a); + }, + contenteditable: function contenteditable() { + //处理单个innerHTML - // If there is already no cache entry for this object, there is no - // purpose in continuing - if (!cache[id]) { - return; - } + var vnodes = fromString(this.value); + var fragment = createFragment(); + for (var i = 0, el; el = vnodes[i++];) { + var child = avalon.vdom(el, 'toDOM'); + fragment.appendChild(child); + } + avalon.clearHTML(this.dom).appendChild(fragment); + var list = this.node.children; + list.length = 0; + Array.prototype.push.apply(list, vnodes); - if (name) { + this.duplexCb.call(this.dom); + } + }; - thisCache = pvt ? cache[id] : cache[id].data; + /* + * 通过绑定事件同步vmodel + * 总共有三种方式同步视图 + * 1. 各种事件 input, change, click, propertychange, keydown... + * 2. value属性重写 + * 3. 定时器轮询 + */ - if (thisCache) { + function updateDataEvents(dom, data) { + var events = {}; + //添加需要监听的事件 + switch (data.dtype) { + case 'radio': + case 'checkbox': + events.click = updateDataHandle; + break; + case 'select': + events.change = updateDataHandle; + break; + case 'contenteditable': + /* istanbul ignore if */ + if (data.isChanged) { + events.blur = updateDataHandle; + /* istanbul ignore else */ + } else { + /* istanbul ignore if*/ - // Support array or space separated string names for data keys - if (!jQuery.isArray(name)) { + if (avalon.modern) { + if (window$1.webkitURL) { + // http://code.metager.de/source/xref/WebKit/LayoutTests/fast/events/ + // https://bugs.webkit.org/show_bug.cgi?id=110742 + events.webkitEditableContentChanged = updateDataHandle; + } else if (window$1.MutationEvent) { + events.DOMCharacterDataModified = updateDataHandle; + } + events.input = updateDataHandle; + /* istanbul ignore else */ + } else { + events.keydown = updateModelKeyDown; + events.paste = updateModelDelay; + events.cut = updateModelDelay; + events.focus = closeComposition; + events.blur = openComposition; + } + } + break; + case 'input': + /* istanbul ignore if */ + if (data.isChanged) { + events.change = updateDataHandle; + /* istanbul ignore else */ + } else { + //http://www.cnblogs.com/rubylouvre/archive/2013/02/17/2914604.html + //http://www.matts411.com/post/internet-explorer-9-oninput/ + if (msie < 10) { + //IE6-8的propertychange有问题,第一次用JS修改值时不会触发,而且你是全部清空value也不会触发 + //IE9的propertychange不支持自动完成,退格,删除,复制,贴粘,剪切或点击右边的小X的清空操作 + events.propertychange = updateModelHack; + events.paste = updateModelDelay; + events.cut = updateModelDelay; + //IE9在第一次删除字符时不会触发oninput + events.keyup = updateModelKeyDown; + } else { + events.input = updateDataHandle; + events.compositionstart = openComposition; + //微软拼音输入法的问题需要在compositionend事件中处理 + events.compositionend = closeComposition; + //https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray + //处理低版本的标准浏览器,通过Int8Array进行区分 + if (!/\[native code\]/.test(window$1.Int8Array)) { + events.keydown = updateModelKeyDown; //safari < 5 opera < 11 + events.paste = updateModelDelay; //safari < 5 + events.cut = updateModelDelay; //safari < 5 + if (window$1.netscape) { + // Firefox <= 3.6 doesn't fire the 'input' event when text is filled in through autocomplete + events.DOMAutoComplete = updateDataHandle; + } + } + } + } + break; + } - // try the string as a key before any manipulation - if (name in thisCache) { - name = [name]; - } else { + if (/password|text/.test(dom.type)) { + events.focus = openCaret; //判定是否使用光标修正功能 + events.blur = closeCaret; + data.getCaret = getCaret; + data.setCaret = setCaret; + } - // 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 { + for (var name in events) { + avalon.bind(dom, name, events[name]); + } + } - // 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)); - } + function updateModelHack(e) { + if (e.propertyName === 'value') { + updateDataHandle.call(this, e); + } + } - i = name.length; - while (i--) { - delete thisCache[name[i]]; - } + function updateModelDelay(e) { + var elem = this; + setTimeout(function () { + updateDataHandle.call(elem, e); + }, 0); + } - // 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; - } - } - } + function openCaret() { + this.caret = true; + } + /* istanbul ignore next */ + function closeCaret() { + this.caret = false; + } + /* istanbul ignore next */ + function openComposition() { + this.composing = true; + } + /* istanbul ignore next */ + function closeComposition(e) { + this.composing = false; + updateModelDelay.call(this, e); + } + /* istanbul ignore next */ + function updateModelKeyDown(e) { + var key = e.keyCode; + // ignore + // command modifiers arrows + if (key === 91 || 15 < key && key < 19 || 37 <= key && key <= 40) return; + updateDataHandle.call(this, e); + } - // See jQuery.data for more information - if (!pvt) { - delete cache[id].data; + getShortID(openCaret); + getShortID(closeCaret); + getShortID(openComposition); + getShortID(closeComposition); + getShortID(updateDataHandle); + getShortID(updateModelHack); + getShortID(updateModelDelay); + getShortID(updateModelKeyDown); - // Don't destroy the parent cache unless the internal data object - // had been the only thing left in it - if (!isEmptyDataObject(cache[id])) { - return; - } - } + //IE6-8要处理光标时需要异步 + var mayBeAsync = function mayBeAsync(fn) { + setTimeout(fn, 0); + }; + /* istanbul ignore next */ + function setCaret(target, cursorPosition) { + var range$$1; + if (target.createTextRange) { + mayBeAsync(function () { + target.focus(); + range$$1 = target.createTextRange(); + range$$1.collapse(true); + range$$1.moveEnd('character', cursorPosition); + range$$1.moveStart('character', cursorPosition); + range$$1.select(); + }); + } else { + target.focus(); + if (target.selectionStart !== undefined) { + target.setSelectionRange(cursorPosition, cursorPosition); + } + } + } + /* istanbul ignore next*/ + function getCaret(target) { + var start = 0; + var normalizedValue; + var range$$1; + var textInputRange; + var len; + var endRange; - // Destroy the cache - if (isNode) { - jQuery.cleanData([elem], true); + if (target.selectionStart + target.selectionEnd > -1) { + start = target.selectionStart; + } else { + range$$1 = document$1.selection.createRange(); - // 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]; + if (range$$1 && range$$1.parentElement() === target) { + len = target.value.length; + normalizedValue = target.value.replace(/\r\n/g, '\n'); - // When all else fails, undefined - } else { - cache[id] = undefined; - } - } + textInputRange = target.createTextRange(); + textInputRange.moveToBookmark(range$$1.getBookmark()); - jQuery.extend({ - cache: {}, + endRange = target.createTextRange(); + endRange.collapse(false); - // 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, + if (textInputRange.compareEndPoints('StartToEnd', endRange) > -1) { + start = len; + } else { + start = -textInputRange.moveStart('character', -len); + start += normalizedValue.slice(0, start).split('\n').length - 1; + } + } + } - // ...but Flash objects (which have this classid) *can* handle expandos - "object ": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" - }, + return start; + } - hasData: function hasData(elem) { - elem = elem.nodeType ? jQuery.cache[elem[jQuery.expando]] : elem[jQuery.expando]; - return !!elem && !isEmptyDataObject(elem); - }, + avalon.directive('duplex', { + priority: 9999999, + beforeInit: duplexBeforeInit, + init: duplexInit, + diff: duplexDiff, + update: function update(vdom, value) { + if (!this.dom) { + duplexBind.call(this, vdom, updateDataEvents); + } + //如果不支持input.value的Object.defineProperty的属性支持, + //需要通过轮询同步, chrome 42及以下版本需要这个hack + pollValue.call(this, avalon.msie, valueHijack); + //更新视图 - data: function data(elem, name, _data) { - return internalData(elem, name, _data); - }, + updateView[this.dtype].call(this); + } + }); - removeData: function removeData(elem, name) { - return internalRemoveData(elem, name); - }, + function pollValue(isIE, valueHijack$$1) { + var dom = this.dom; + if (this.isString && valueHijack$$1 && !isIE && !dom.valueHijack) { + dom.valueHijack = updateDataHandle; + var intervalID = setInterval(function () { + if (!avalon.contains(avalon.root, dom)) { + clearInterval(intervalID); + } else { + dom.valueHijack({ type: 'poll' }); + } + }, 30); + return intervalID; + } + } + avalon.__pollValue = pollValue; //export to test + /* istanbul ignore if */ + if (avalon.msie < 8) { + var oldUpdate = updateView.updateChecked; + updateView.updateChecked = function (vdom, checked) { + var dom = vdom.dom; + if (dom) { + setTimeout(function () { + oldUpdate(vdom, checked); + dom.firstCheckedIt = 1; + }, dom.firstCheckedIt ? 31 : 16); + //IE6,7 checkbox, radio是使用defaultChecked控制选中状态, + //并且要先设置defaultChecked后设置checked + //并且必须设置延迟(因为必须插入DOM树才生效) + } + }; + } - // For internal use only. - _data: function _data(elem, name, data) { - return internalData(elem, name, data, true); - }, + avalon.directive('rules', { + diff: function diff(rules) { + if (isObject(rules)) { + var vdom = this.node; + vdom.rules = platform.toJson(rules); + return true; + } + } + }); + function isRegExp(value) { + return avalon.type(value) === 'regexp'; + } + var rmail = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/i; + var rurl = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/; + function isCorrectDate(value) { + if (typeof value === "string" && value) { + //是字符串但不能是空字符 + var arr = value.split("-"); //可以被-切成3份,并且第1个是4个字符 + if (arr.length === 3 && arr[0].length === 4) { + var year = ~~arr[0]; //全部转换为非负整数 + var month = ~~arr[1] - 1; + var date = ~~arr[2]; + var d = new Date(year, month, date); + return d.getFullYear() === year && d.getMonth() === month && d.getDate() === date; + } + } + return false; + } + //https://github.com/adform/validator.js/blob/master/validator.js + avalon.shadowCopy(avalon.validators, { + pattern: { + message: '必须匹配{{pattern}}这样的格式', + get: function get(value, field, next) { + var elem = field.dom; + var data = field.data; + if (!isRegExp(data.pattern)) { + var h5pattern = elem.getAttribute("pattern"); + data.pattern = new RegExp('^(?:' + h5pattern + ')$'); + } + next(data.pattern.test(value)); + return value; + } + }, + digits: { + message: '必须整数', + get: function get(value, field, next) { + //整数 + next(/^\-?\d+$/.test(value)); + return value; + } + }, + number: { + message: '必须数字', + get: function get(value, field, next) { + //数值 + next(!!value && isFinite(value)); // isFinite('') --> true + return value; + } + }, + norequired: { + message: '', + get: function get(value, field, next) { + next(true); + return value; + } + }, + required: { + message: '必须填写', + get: function get(value, field, next) { + next(value !== ''); + return value; + } + }, + equalto: { + message: '密码输入不一致', + get: function get(value, field, next) { + var id = String(field.data.equalto); + var other = avalon(document.getElementById(id)).val() || ""; + next(value === other); + return value; + } + }, + date: { + message: '日期格式不正确', + get: function get(value, field, next) { + var data = field.data; + if (isRegExp(data.date)) { + next(data.date.test(value)); + } else { + next(isCorrectDate(value)); + } + return value; + } + }, + url: { + message: 'URL格式不正确', + get: function get(value, field, next) { + next(rurl.test(value)); + return value; + } + }, + email: { + message: 'email格式不正确', + get: function get(value, field, next) { + next(rmail.test(value)); + return value; + } + }, + minlength: { + message: '最少输入{{minlength}}个字', + get: function get(value, field, next) { + var num = parseInt(field.data.minlength, 10); + next(value.length >= num); + return value; + } + }, + maxlength: { + message: '最多输入{{maxlength}}个字', + get: function get(value, field, next) { + var num = parseInt(field.data.maxlength, 10); + next(value.length <= num); + return value; + } + }, + min: { + message: '输入值不能小于{{min}}', + get: function get(value, field, next) { + var num = parseInt(field.data.min, 10); + next(parseFloat(value) >= num); + return value; + } + }, + max: { + message: '输入值不能大于{{max}}', + get: function get(value, field, next) { + var num = parseInt(field.data.max, 10); + next(parseFloat(value) <= num); + return value; + } + }, + chs: { + message: '必须是中文字符', + get: function get(value, field, next) { + next(/^[\u4e00-\u9fa5]+$/.test(value)); + return value; + } + } + }); + + var valiDir = avalon.directive('validate', { + diff: function diff(validator) { + var vdom = this.node; + if (vdom.validator) { + return; + } + if (isObject(validator)) { + //注意,这个Form标签的虚拟DOM有两个验证对象 + //一个是vmValidator,它是用户VM上的那个原始子对象,也是一个VM + //一个是validator,它是vmValidator.$model, 这是为了防止IE6-8添加子属性时添加的hack + //也可以称之为safeValidate + vdom.validator = validator; + validator = platform.toJson(validator); + validator.vdom = vdom; + validator.dom = vdom.dom; - _removeData: function _removeData(elem, name) { - return internalRemoveData(elem, name, true); - } - }); + for (var name in valiDir.defaults) { + if (!validator.hasOwnProperty(name)) { + validator[name] = valiDir.defaults[name]; + } + } + validator.fields = validator.fields || []; + vdom.vmValidator = validator; + return true; + } + }, + update: function update(vdom) { - jQuery.fn.extend({ - data: function data(key, value) { - var i, - name, - data, - elem = this[0], - attrs = elem && elem.attributes; + var vmValidator = vdom.vmValidator; + var validator = vdom.validator; + var dom = vdom.dom; + dom._ms_validate_ = vmValidator; - // Special expections of .data basically thwart jQuery.access, - // so implement the relevant behavior ourselves + collectFeild(vdom.children, vmValidator.fields, vmValidator); + var type = window.netscape ? 'keypress' : 'focusin'; + avalon.bind(document, type, findValidator); + //为了方便用户手动执行验证,我们需要为原始vmValidate上添加一个onManual方法 + function onManual() { + var v = this; + v && valiDir.validateAll.call(v, v.onValidateAll); + } - // Gets all values - if (key === undefined) { - if (this.length) { - data = jQuery.data(elem); + try { + var fn = vmValidator.onManual = onManual.bind(vmValidator); + validator.onManual = fn; + } catch (e) { + avalon.warn('要想使用onManual方法,必须在validate对象预定义一个空的onManual函数'); + } + delete vdom.vmValidator; - if (elem.nodeType === 1 && !jQuery._data(elem, "parsedAttrs")) { - i = attrs.length; - while (i--) { + dom.setAttribute('novalidate', 'novalidate'); - // 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); - } - } + /* istanbul ignore if */ + if (vmValidator.validateAllInSubmit) { + avalon.bind(dom, 'submit', validateAllInSubmitFn); + } + }, + validateAll: function validateAll(callback) { + var validator = this; + var vdom = this.vdom; + var fields = validator.fields = []; + collectFeild(vdom.children, fields, validator); + var fn = typeof callback === 'function' ? callback : validator.onValidateAll; + var promises = validator.fields.filter(function (field) { + var el = field.dom; + return el && !el.disabled && validator.dom.contains(el); + }).map(function (field) { + return valiDir.validate(field, true); + }); + var uniq = {}; + return Promise.all(promises).then(function (array) { + var reasons = array.concat.apply([], array); + if (validator.deduplicateInValidateAll) { + reasons = reasons.filter(function (reason) { + var el = reason.element; + var uuid = el.uniqueID || (el.uniqueID = setTimeout('1')); + if (uniq[uuid]) { + return false; + } else { + return uniq[uuid] = true; + } + }); + } + fn.call(vdom.dom, reasons); //这里只放置未通过验证的组件 + }); + }, - return data; - } + validate: function validate(field, isValidateAll, event) { - // Sets multiple values - if ((typeof key === "undefined" ? "undefined" : _typeof(key)) === "object") { - return this.each(function () { - jQuery.data(this, key); - }); - } + var promises = []; + var value = field.value; + var elem = field.dom; + /* istanbul ignore if */ + if (typeof Promise !== 'function') { + //avalon-promise不支持phantomjs + avalon.warn('浏览器不支持原生Promise,请下载并 - - - - -
-

-
-
- - - * - */ + // Send global event + if (fireGlobals) { + globalEventContext.trigger("ajaxSend", [jqXHR, s]); + } + + // If request was aborted inside ajaxSend, stop there + if (state === 2) { + return jqXHR; + } - var none = 'none'; - function parseDisplay(elem, val) { - //用于取得此类标签的默认display值 - var doc = elem.ownerDocument; - var nodeName = elem.nodeName; - var key = '_' + nodeName; - if (!parseDisplay[key]) { - var temp = doc.body.appendChild(doc.createElement(nodeName)); - val = avalon.css(temp, 'display'); - doc.body.removeChild(temp); - if (val === none) { - val = 'block'; - } - parseDisplay[key] = val; - } - return parseDisplay[key]; - } + // Timeout + if (s.async && s.timeout > 0) { + timeoutTimer = window.setTimeout(function () { + jqXHR.abort("timeout"); + }, s.timeout); + } - avalon.parseDisplay = parseDisplay; - avalon.directive('visible', { - diff: function diff(newVal, oldVal) { - var n = !!newVal; - if (oldVal === void 0 || n !== oldVal) { - this.value = n; - return true; - } - }, - ready: true, - update: function update(vdom, show) { - var dom = vdom.dom; - if (dom && dom.nodeType === 1) { - var display = dom.style.display; - var value; - if (show) { - if (display === none) { - value = vdom.displayValue; - if (!value) { - dom.style.display = ''; - if (dom.style.cssText === '') { - dom.removeAttribute('style'); - } - } - } - if (dom.style.display === '' && avalon(dom).css('display') === none && - // fix firefox BUG,必须挂到页面上 - avalon.contains(dom.ownerDocument, dom)) { - value = parseDisplay(dom); - } - } else { + try { + state = 1; + transport.send(requestHeaders, done); + } catch (e) { - if (display !== none) { - value = none; - vdom.displayValue = display; - } - } - var cb = function cb() { - if (value !== void 0) { - dom.style.display = value; - } - }; + // Propagate exception as error if not done + if (state < 2) { + done(-1, e); - avalon.applyEffect(dom, vdom, { - hook: show ? 'onEnterDone' : 'onLeaveDone', - cb: cb - }); - } - } - }); + // Simply rethrow otherwise + } else { + throw e; + } + } + } - avalon.directive('text', { - delay: true, - init: function init() { + // Callback for when everything is done + function done(status, nativeStatusText, responses, headers) { + var isSuccess, + success, + error, + response, + modified, + statusText = nativeStatusText; - var node = this.node; - if (node.isVoidTag) { - avalon.error('自闭合元素不能使用ms-text'); - } - var child = { nodeName: '#text', nodeValue: this.getValue() }; - node.children.splice(0, node.children.length, child); - if (inBrowser) { - avalon.clearHTML(node.dom); - node.dom.appendChild(avalon.vdom(child, 'toDOM')); - } - this.node = child; - var type = 'expr'; - this.type = this.name = type; - var directive$$1 = avalon.directives[type]; - var me = this; - this.callback = function (value) { - directive$$1.update.call(me, me.node, value); - }; - } - }); + // Called once + if (state === 2) { + return; + } - avalon.directive('expr', { - update: function update(vdom, value) { - value = value == null || value === '' ? '\u200B' : value; - vdom.nodeValue = value; - //https://github.com/RubyLouvre/avalon/issues/1834 - if (vdom.dom) vdom.dom.data = value; - } - }); + // State is "done" now + state = 2; - avalon.directive('attr', { - diff: cssDiff, - update: function update(vdom, value) { - var props = vdom.props; - for (var i in value) { - if (!!value[i] === false) { - delete props[i]; - } else { - props[i] = value[i]; - } - } - var dom = vdom.dom; - if (dom && dom.nodeType === 1) { - updateAttrs(dom, value); - } - } - }); + // Clear timeout if it exists + if (timeoutTimer) { + window.clearTimeout(timeoutTimer); + } - avalon.directive('html', { + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; - update: function update(vdom, value) { - this.beforeDispose(); + // Cache response headers + responseHeadersString = headers || ""; - this.innerRender = avalon.scan('
' + value + '
', this.vm, function () { - var oldRoot = this.root; - if (vdom.children) vdom.children.length = 0; - vdom.children = oldRoot.children; - this.root = vdom; - if (vdom.dom) avalon.clearHTML(vdom.dom); - }); - }, - beforeDispose: function beforeDispose() { - if (this.innerRender) { - this.innerRender.dispose(); - } - }, - delay: true - }); + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; - avalon.directive('if', { - delay: true, - priority: 5, - init: function init() { - this.placeholder = createAnchor('if'); - var props = this.node.props; - delete props['ms-if']; - delete props[':if']; - this.fragment = avalon.vdom(this.node, 'toHTML'); - }, - diff: function diff(newVal, oldVal) { - var n = !!newVal; - if (oldVal === void 0 || n !== oldVal) { - this.value = n; - return true; - } - }, - update: function update(vdom, value) { - if (this.isShow === void 0 && value) { - continueScan(this, vdom); - return; - } - this.isShow = value; - var placeholder = this.placeholder; + // Determine if successful + isSuccess = status >= 200 && status < 300 || status === 304; - if (value) { - var p = placeholder.parentNode; - continueScan(this, vdom); - p && p.replaceChild(vdom.dom, placeholder); - } else { - //移除DOM - this.beforeDispose(); - vdom.nodeValue = 'if'; - vdom.nodeName = '#comment'; - delete vdom.children; - var dom = vdom.dom; - var p = dom && dom.parentNode; - vdom.dom = placeholder; - if (p) { - p.replaceChild(placeholder, dom); - } - } - }, - beforeDispose: function beforeDispose() { - if (this.innerRender) { - this.innerRender.dispose(); - } - } - }); + // Get response data + if (responses) { + response = ajaxHandleResponses(s, jqXHR, responses); + } - function continueScan(instance, vdom) { - var innerRender = instance.innerRender = avalon.scan(instance.fragment, instance.vm); - avalon.shadowCopy(vdom, innerRender.root); - delete vdom.nodeValue; - } + // Convert no matter what (that way responseXXX fields are always set) + response = ajaxConvert(s, response, jqXHR, isSuccess); + + // If successful, handle type chaining + if (isSuccess) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if (s.ifModified) { + modified = jqXHR.getResponseHeader("Last-Modified"); + if (modified) { + jQuery.lastModified[cacheURL] = modified; + } + modified = jqXHR.getResponseHeader("etag"); + if (modified) { + jQuery.etag[cacheURL] = modified; + } + } + + // if no content + if (status === 204 || s.type === "HEAD") { + statusText = "nocontent"; - avalon.directive('on', { - beforeInit: function beforeInit() { - this.getter = avalon.noop; - }, - init: function init() { - var vdom = this.node; - var underline = this.name.replace('ms-on-', 'e').replace('-', '_'); - var uuid = underline + '_' + this.expr.replace(/\s/g, '').replace(/[^$a-z]/ig, function (e) { - return e.charCodeAt(0); - }); - var fn = avalon.eventListeners[uuid]; - if (!fn) { - var arr = addScope(this.expr); - var body = arr[0], - filters = arr[1]; - body = makeHandle(body); + // if not modified + } else if (status === 304) { + statusText = "notmodified"; - if (filters) { - filters = filters.replace(/__value__/g, '$event'); - filters += '\nif($event.$return){\n\treturn;\n}'; - } - var ret = ['try{', '\tvar __vmodel__ = this;', '\t' + filters, '\treturn ' + body, '}catch(e){avalon.log(e, "in on dir")}'].filter(function (el) { - return (/\S/.test(el) - ); - }); - fn = new Function('$event', ret.join('\n')); - fn.uuid = uuid; - avalon.eventListeners[uuid] = fn; - } + // If we have data, let's convert it + } else { + statusText = response.state; + success = response.data; + error = response.error; + isSuccess = !error; + } + } else { - var dom = avalon.vdom(vdom, 'toDOM'); - dom._ms_context_ = this.vm; + // We extract error from statusText + // then normalize statusText and status for non-aborts + error = statusText; + if (status || !statusText) { + statusText = "error"; + if (status < 0) { + status = 0; + } + } + } - this.eventType = this.param.replace(/\-(\d)$/, ''); - delete this.param; - avalon(dom).bind(this.eventType, fn); - }, + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = (nativeStatusText || statusText) + ""; - beforeDispose: function beforeDispose() { - avalon(this.node.dom).unbind(this.eventType); - } - }); + // Success/Error + if (isSuccess) { + deferred.resolveWith(callbackContext, [success, statusText, jqXHR]); + } else { + deferred.rejectWith(callbackContext, [jqXHR, statusText, error]); + } - var rforAs = /\s+as\s+([$\w]+)/; - var rident = /^[$a-zA-Z_][$a-zA-Z0-9_]*$/; - var rinvalid = /^(null|undefined|NaN|window|this|\$index|\$id)$/; - var rargs = /[$\w_]+/g; - avalon.directive('for', { - delay: true, - priority: 3, - beforeInit: function beforeInit() { - var str = this.expr, - asName; - str = str.replace(rforAs, function (a, b) { - /* istanbul ignore if */ - if (!rident.test(b) || rinvalid.test(b)) { - avalon.error('alias ' + b + ' is invalid --- must be a valid JS identifier which is not a reserved name.'); - } else { - asName = b; - } - return ''; - }); + // Status-dependent callbacks + jqXHR.statusCode(_statusCode); + _statusCode = undefined; - var arr = str.split(' in '); - var kv = arr[0].match(rargs); - if (kv.length === 1) { - //确保avalon._each的回调有三个参数 - kv.unshift('$key'); - } - this.expr = arr[1]; - this.keyName = kv[0]; - this.valName = kv[1]; - this.signature = avalon.makeHashCode('for'); - if (asName) { - this.asName = asName; - } + if (fireGlobals) { + globalEventContext.trigger(isSuccess ? "ajaxSuccess" : "ajaxError", [jqXHR, s, isSuccess ? success : error]); + } - delete this.param; - }, - init: function init() { - var cb = this.userCb; - if (typeof cb === 'string' && cb) { - var arr = addScope(cb, 'for'); - var body = makeHandle(arr[0]); - this.userCb = new Function('$event', 'var __vmodel__ = this\nreturn ' + body); - } - this.node.forDir = this; //暴露给component/index.js中的resetParentChildren方法使用 - this.fragment = ['
', this.fragment, '
'].join(''); - this.cache = {}; - }, - diff: function diff(newVal, oldVal) { - /* istanbul ignore if */ - if (this.updating) { - return; - } - this.updating = true; - var traceIds = createFragments(this, newVal); + // Complete + completeDeferred.fireWith(callbackContext, [jqXHR, statusText]); - if (this.oldTrackIds === void 0) return true; + if (fireGlobals) { + globalEventContext.trigger("ajaxComplete", [jqXHR, s]); - if (this.oldTrackIds !== traceIds) { - this.oldTrackIds = traceIds; - return true; - } - }, - update: function update() { + // Handle the global AJAX counter + if (! --jQuery.active) { + jQuery.event.trigger("ajaxStop"); + } + } + } - if (!this.preFragments) { - this.fragments = this.fragments || []; - mountList(this); - } else { - diffList(this); - updateList(this); - } + return jqXHR; + }, - if (this.userCb) { - var me = this; - setTimeout(function () { - me.userCb.call(me.vm, { - type: 'rendered', - target: me.begin.dom, - signature: me.signature - }); - }, 0); - } - delete this.updating; - }, - beforeDispose: function beforeDispose() { - this.fragments.forEach(function (el) { - el.dispose(); - }); - } - }); + getJSON: function getJSON(url, data, callback) { + return jQuery.get(url, data, callback, "json"); + }, - function getTraceKey(item) { - var type = typeof item === 'undefined' ? 'undefined' : _typeof(item); - return item && type === 'object' ? item.$hashcode : type + ':' + item; - } + getScript: function getScript(url, callback) { + return jQuery.get(url, undefined, callback, "script"); + } + }); - //创建一组fragment的虚拟DOM - function createFragments(instance, obj) { - if (isObject(obj)) { - var array = Array.isArray(obj); - var ids = []; - var fragments = [], - i = 0; + jQuery.each(["get", "post"], function (i, method) { + jQuery[method] = function (url, data, callback, type) { - instance.isArray = array; - if (instance.fragments) { - instance.preFragments = instance.fragments; - avalon.each(obj, function (key, value) { - var k = array ? getTraceKey(value) : key; + // shift arguments if data argument was omitted + if (jQuery.isFunction(data)) { + type = type || callback; + callback = data; + data = undefined; + } - fragments.push({ - key: k, - val: value, - index: i++ - }); - ids.push(k); - }); - instance.fragments = fragments; - } else { - avalon.each(obj, function (key, value) { - if (!(key in $$skipArray)) { - var k = array ? getTraceKey(value) : key; - fragments.push(new VFragment([], k, value, i++)); - ids.push(k); - } - }); - instance.fragments = fragments; - } - return ids.join(';;'); - } else { - return NaN; - } - } + // The url can be an options object (which then must have .url) + return jQuery.ajax(jQuery.extend({ + url: url, + type: method, + dataType: type, + data: data, + success: callback + }, jQuery.isPlainObject(url) && url)); + }; + }); - function mountList(instance) { - var args = instance.fragments.map(function (fragment, index) { - FragmentDecorator(fragment, instance, index); - saveInCache(instance.cache, fragment); - return fragment; - }); - var list = instance.parentChildren; - var i = list.indexOf(instance.begin); - list.splice.apply(list, [i + 1, 0].concat(args)); - } + jQuery._evalUrl = function (url) { + return jQuery.ajax({ + url: url, + + // Make this explicit, since user can override this through ajaxSetup (#11264) + type: "GET", + dataType: "script", + cache: true, + async: false, + global: false, + "throws": true + }); + }; - function diffList(instance) { - var cache = instance.cache; - var newCache = {}; - var fuzzy = []; - var list = instance.preFragments; + jQuery.fn.extend({ + wrapAll: function wrapAll(html) { + if (jQuery.isFunction(html)) { + return this.each(function (i) { + jQuery(this).wrapAll(html.call(this, i)); + }); + } - list.forEach(function (el) { - el._dispose = true; - }); + if (this[0]) { - instance.fragments.forEach(function (c, index) { - var fragment = isInCache(cache, c.key); - //取出之前的文档碎片 - if (fragment) { - delete fragment._dispose; - fragment.oldIndex = fragment.index; - fragment.index = index; // 相当于 c.index + // The elements to wrap the target around + var wrap = jQuery(html, this[0].ownerDocument).eq(0).clone(true); - resetVM(fragment.vm, instance.keyName); - fragment.vm[instance.valName] = c.val; - fragment.vm[instance.keyName] = instance.isArray ? index : fragment.key; - saveInCache(newCache, fragment); - } else { - //如果找不到就进行模糊搜索 - fuzzy.push(c); - } - }); - fuzzy.forEach(function (c) { - var fragment = fuzzyMatchCache(cache, c.key); - if (fragment) { - //重复利用 - fragment.oldIndex = fragment.index; - fragment.key = c.key; - var val = fragment.val = c.val; - var index = fragment.index = c.index; + if (this[0].parentNode) { + wrap.insertBefore(this[0]); + } - fragment.vm[instance.valName] = val; - fragment.vm[instance.keyName] = instance.isArray ? index : fragment.key; - delete fragment._dispose; - } else { + wrap.map(function () { + var elem = this; - c = new VFragment([], c.key, c.val, c.index); - fragment = FragmentDecorator(c, instance, c.index); - list.push(fragment); - } - saveInCache(newCache, fragment); - }); + while (elem.firstChild && elem.firstChild.nodeType === 1) { + elem = elem.firstChild; + } - instance.fragments = list; - list.sort(function (a, b) { - return a.index - b.index; - }); - instance.cache = newCache; - } + return elem; + }).append(this); + } - function resetVM(vm, a, b) { - if (avalon.config.inProxyMode) { - vm.$accessors[a].value = NaN; - } else { - vm.$accessors[a].set(NaN); - } - } + return this; + }, - function updateList(instance) { - var before = instance.begin.dom; - var parent = before.parentNode; - var list = instance.fragments; - var end = instance.end.dom; - for (var i = 0, item; item = list[i]; i++) { - if (item._dispose) { - list.splice(i, 1); - i--; - item.dispose(); - continue; - } - if (item.oldIndex !== item.index) { - var f = item.toFragment(); - var isEnd = before.nextSibling === null; - parent.insertBefore(f, before.nextSibling); - if (isEnd && !parent.contains(end)) { - parent.insertBefore(end, before.nextSibling); - } - } - before = item.split; - } - var ch = instance.parentChildren; - var startIndex = ch.indexOf(instance.begin); - var endIndex = ch.indexOf(instance.end); + wrapInner: function wrapInner(html) { + if (jQuery.isFunction(html)) { + return this.each(function (i) { + jQuery(this).wrapInner(html.call(this, i)); + }); + } - list.splice.apply(ch, [startIndex + 1, endIndex - startIndex].concat(list)); - } + return this.each(function () { + var self = jQuery(this), + contents = self.contents(); - /** - * - * @param {type} fragment - * @param {type} this - * @param {type} index - * @returns { key, val, index, oldIndex, this, dom, split, vm} - */ - function FragmentDecorator(fragment, instance, index) { - var data = {}; - data[instance.keyName] = instance.isArray ? index : fragment.key; - data[instance.valName] = fragment.val; - if (instance.asName) { - data[instance.asName] = instance.value; - } - var vm = fragment.vm = platform.itemFactory(instance.vm, { - data: data - }); - if (instance.isArray) { - vm.$watch(instance.valName, function (a) { - if (instance.value && instance.value.set) { - instance.value.set(vm[instance.keyName], a); - } - }); - } else { - vm.$watch(instance.valName, function (a) { - instance.value[fragment.key] = a; - }); - } + if (contents.length) { + contents.wrapAll(html); + } else { + self.append(html); + } + }); + }, - fragment.index = index; - fragment.innerRender = avalon.scan(instance.fragment, vm, function () { - var oldRoot = this.root; - ap.push.apply(fragment.children, oldRoot.children); - this.root = fragment; - }); - return fragment; - } - // 新位置: 旧位置 - function isInCache(cache, id) { - var c = cache[id]; - if (c) { - var arr = c.arr; - /* istanbul ignore if*/ - if (arr) { - var r = arr.pop(); - if (!arr.length) { - c.arr = 0; - } - return r; - } - delete cache[id]; - return c; - } - } - //[1,1,1] number1 number1_ number1__ - function saveInCache(cache, component) { - var trackId = component.key; - if (!cache[trackId]) { - cache[trackId] = component; - } else { - var c = cache[trackId]; - var arr = c.arr || (c.arr = []); - arr.push(component); - } - } + wrap: function wrap(html) { + var isFunction = jQuery.isFunction(html); - function fuzzyMatchCache(cache) { - var key; - for (var id in cache) { - var key = id; - break; - } - if (key) { - return isInCache(cache, key); - } - } + return this.each(function (i) { + jQuery(this).wrapAll(isFunction ? html.call(this, i) : html); + }); + }, - //根据VM的属性值或表达式的值切换类名,ms-class='xxx yyy zzz:flag' - //http://www.cnblogs.com/rubylouvre/archive/2012/12/17/2818540.html - function classNames() { - var classes = []; - for (var i = 0; i < arguments.length; i++) { - var arg = arguments[i]; - var argType = typeof arg === 'undefined' ? 'undefined' : _typeof(arg); - if (argType === 'string' || argType === 'number' || arg === true) { - classes.push(arg); - } else if (Array.isArray(arg)) { - classes.push(classNames.apply(null, arg)); - } else if (argType === 'object') { - for (var key in arg) { - if (arg.hasOwnProperty(key) && arg[key]) { - classes.push(key); - } - } - } - } + unwrap: function unwrap() { + return this.parent().each(function () { + if (!jQuery.nodeName(this, "body")) { + jQuery(this).replaceWith(this.childNodes); + } + }).end(); + } + }); - return classes.join(' '); - } + function getDisplay(elem) { + return elem.style && elem.style.display || jQuery.css(elem, "display"); + } - avalon.directive('class', { - diff: function diff(newVal, oldVal) { - var type = this.type; - var vdom = this.node; - var classEvent = vdom.classEvent || {}; - if (type === 'hover') { - //在移出移入时切换类名 - classEvent.mouseenter = activateClass; - classEvent.mouseleave = abandonClass; - } else if (type === 'active') { - //在获得焦点时切换类名 - classEvent.tabIndex = vdom.props.tabindex || -1; - classEvent.mousedown = activateClass; - classEvent.mouseup = abandonClass; - classEvent.mouseleave = abandonClass; - } - vdom.classEvent = classEvent; + function filterHidden(elem) { - var className = classNames(newVal); + // Disconnected elements are considered hidden + if (!jQuery.contains(elem.ownerDocument || document, elem)) { + return true; + } + while (elem && elem.nodeType === 1) { + if (getDisplay(elem) === "none" || elem.type === "hidden") { + return true; + } + elem = elem.parentNode; + } + return false; + } - if ((typeof oldVal === 'undefined' ? 'undefined' : _typeof(oldVal)) === void 0 || oldVal !== className) { - this.value = className; + jQuery.expr.filters.hidden = function (elem) { - vdom['change-' + type] = className; - return true; - } - }, - update: function update(vdom, value) { - var dom = vdom.dom; - if (dom && dom.nodeType == 1) { + // Support: Opera <= 12.12 + // Opera reports offsetWidths and offsetHeights less than zero on some elements + return support.reliableHiddenOffsets() ? elem.offsetWidth <= 0 && elem.offsetHeight <= 0 && !elem.getClientRects().length : filterHidden(elem); + }; - var dirType = this.type; - var change = 'change-' + dirType; - var classEvent = vdom.classEvent; - if (classEvent) { - for (var i in classEvent) { - if (i === 'tabIndex') { - dom[i] = classEvent[i]; - } else { - avalon.bind(dom, i, classEvent[i]); - } - } - vdom.classEvent = {}; - } - var names = ['class', 'hover', 'active']; - names.forEach(function (type) { - if (dirType !== type) return; - if (type === 'class') { - dom && setClass(dom, value); - } else { - var oldClass = dom.getAttribute(change); - if (oldClass) { - avalon(dom).removeClass(oldClass); - } - var name = 'change-' + type; - dom.setAttribute(name, value); - } - }); - } - } - }); + jQuery.expr.filters.visible = function (elem) { + return !jQuery.expr.filters.hidden(elem); + }; - directives.active = directives.hover = directives['class']; + var r20 = /%20/g, + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, + rsubmittable = /^(?:input|select|textarea|keygen)/i; - var classMap = { - mouseenter: 'change-hover', - mouseleave: 'change-hover', - mousedown: 'change-active', - mouseup: 'change-active' - }; + function buildParams(prefix, obj, traditional, add) { + var name; - function activateClass(e) { - var elem = e.target; - avalon(elem).addClass(elem.getAttribute(classMap[e.type]) || ''); - } + if (jQuery.isArray(obj)) { - function abandonClass(e) { - var elem = e.target; - var name = classMap[e.type]; - avalon(elem).removeClass(elem.getAttribute(name) || ''); - if (name !== 'change-active') { - avalon(elem).removeClass(elem.getAttribute('change-active') || ''); - } - } + // Serialize array item. + jQuery.each(obj, function (i, v) { + if (traditional || rbracket.test(prefix)) { - function setClass(dom, neo) { - var old = dom.getAttribute('change-class'); - if (old !== neo) { - avalon(dom).removeClass(old).addClass(neo); - dom.setAttribute('change-class', neo); - } - } + // Treat each array item as a scalar. + add(prefix, v); + } else { - getLongID(activateClass); - getLongID(abandonClass); + // Item is non-scalar (array or object), encode its numeric index. + buildParams(prefix + "[" + ((typeof v === "undefined" ? "undefined" : _typeof(v)) === "object" && v != null ? i : "") + "]", v, traditional, add); + } + }); + } else if (!traditional && jQuery.type(obj) === "object") { - function lookupOption(vdom, values) { - vdom.children && vdom.children.forEach(function (el) { - if (el.nodeName === 'option') { - setOption(el, values); - } else { - lookupOption(el, values); - } - }); - } + // Serialize object item. + for (name in obj) { + buildParams(prefix + "[" + name + "]", obj[name], traditional, add); + } + } else { - function setOption(vdom, values) { - var props = vdom.props; - if (!('disabled' in props)) { - var value = getOptionValue(vdom, props); - value = String(value || '').trim(); - props.selected = values.indexOf(value) !== -1; + // Serialize scalar item. + add(prefix, obj); + } + } - if (vdom.dom) { - vdom.dom.selected = props.selected; - var v = vdom.dom.selected; //必须加上这个,防止移出节点selected失效 - } - } - } + // Serialize an array of form elements or a set of + // key/values into a query string + jQuery.param = function (a, traditional) { + var prefix, + s = [], + add = function add(key, value) { - function getOptionValue(vdom, props) { - if (props && 'value' in props) { - return props.value + ''; - } - var arr = []; - vdom.children.forEach(function (el) { - if (el.nodeName === '#text') { - arr.push(el.nodeValue); - } else if (el.nodeName === '#document-fragment') { - arr.push(getOptionValue(el)); - } - }); - return arr.join(''); - } + // If value is a function, invoke it and return its value + value = jQuery.isFunction(value) ? value() : value == null ? "" : value; + s[s.length] = encodeURIComponent(key) + "=" + encodeURIComponent(value); + }; - function getSelectedValue(vdom, arr) { - vdom.children.forEach(function (el) { - if (el.nodeName === 'option') { - if (el.props.selected === true) arr.push(getOptionValue(el, el.props)); - } else if (el.children) { - getSelectedValue(el, arr); - } - }); - return arr; - } + // Set traditional to true for jQuery <= 1.3.2 behavior. + if (traditional === undefined) { + traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional; + } - var updateDataActions = { - input: function input(prop) { - //处理单个value值处理 - var field = this; - prop = prop || 'value'; - var dom = field.dom; - var rawValue = dom[prop]; - var parsedValue = field.parseValue(rawValue); + // If an array was passed in, assume that it is an array of form elements. + if (jQuery.isArray(a) || a.jquery && !jQuery.isPlainObject(a)) { - //有时候parse后一致,vm不会改变,但input里面的值 - field.value = rawValue; - field.setValue(parsedValue); - duplexCb(field); - var pos = field.pos; - /* istanbul ignore if */ - if (dom.caret) { - field.setCaret(dom, pos); - } - //vm.aaa = '1234567890' - //处理 {{@aaa}} 这种格式化同步不一致的情况 - }, - radio: function radio() { - var field = this; - if (field.isChecked) { - var val = !field.value; - field.setValue(val); - duplexCb(field); - } else { - updateDataActions.input.call(field); - field.value = NaN; - } - }, - checkbox: function checkbox() { - var field = this; - var array = field.value; - if (!Array.isArray(array)) { - avalon.warn('ms-duplex应用于checkbox上要对应一个数组'); - array = [array]; - } - var method = field.dom.checked ? 'ensure' : 'remove'; - if (array[method]) { - var val = field.parseValue(field.dom.value); - array[method](val); - duplexCb(field); - } - this.__test__ = array; - }, - select: function select() { - var field = this; - var val = avalon(field.dom).val(); //字符串或字符串数组 - if (val + '' !== this.value + '') { - if (Array.isArray(val)) { - //转换布尔数组或其他 - val = val.map(function (v) { - return field.parseValue(v); - }); - } else { - val = field.parseValue(val); - } - field.setValue(val); - duplexCb(field); - } - }, - contenteditable: function contenteditable() { - updateDataActions.input.call(this, 'innerHTML'); - } - }; + // Serialize the form elements + jQuery.each(a, function () { + add(this.name, this.value); + }); + } else { - function duplexCb(field) { - if (field.userCb) { - field.userCb.call(field.vm, { - type: 'changed', - target: field.dom - }); - } - } + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for (prefix in a) { + buildParams(prefix, a[prefix], traditional, add); + } + } - function updateDataHandle(event) { - var elem = this; - var field = elem._ms_duplex_; - if (elem.composing) { - //防止onpropertychange引发爆栈 - return; - } - if (elem.value === field.value) { - return; - } - /* istanbul ignore if*/ - if (elem.caret) { - try { - var pos = field.getCaret(elem); - field.pos = pos; - } catch (e) {} - } - /* istanbul ignore if*/ - if (field.debounceTime > 4) { - var timestamp = new Date(); - var left = timestamp - field.time || 0; - field.time = timestamp; - /* istanbul ignore if*/ - if (left >= field.debounceTime) { - updateDataActions[field.dtype].call(field); - /* istanbul ignore else*/ - } else { - clearTimeout(field.debounceID); - field.debounceID = setTimeout(function () { - updateDataActions[field.dtype].call(field); - }, left); - } - } else if (field.isChanged) { - setTimeout(function () { - //https://github.com/RubyLouvre/avalon/issues/1908 - updateDataActions[field.dtype].call(field); - }, 4); - } else { - updateDataActions[field.dtype].call(field); - } - } + // Return the resulting serialization + return s.join("&").replace(r20, "+"); + }; - var rchangeFilter = /\|\s*change\b/; - var rdebounceFilter = /\|\s*debounce(?:\(([^)]+)\))?/; - function duplexBeforeInit() { - var expr = this.expr; - if (rchangeFilter.test(expr)) { - this.isChanged = true; - expr = expr.replace(rchangeFilter, ''); - } - var match = expr.match(rdebounceFilter); - if (match) { - expr = expr.replace(rdebounceFilter, ''); - if (!this.isChanged) { - this.debounceTime = parseInt(match[1], 10) || 300; - } - } - this.expr = expr; - } - function duplexInit() { - var expr = this.expr; - var node = this.node; - var etype = node.props.type; - this.parseValue = parseValue; - //处理数据转换器 - var parsers = this.param, - dtype; - var isChecked = false; - parsers = parsers ? parsers.split('-').map(function (a) { - if (a === 'checked') { - isChecked = true; - } - return a; - }) : []; - node.duplex = this; - if (rcheckedType.test(etype) && isChecked) { - //如果是radio, checkbox,判定用户使用了checked格式函数没有 - parsers = []; - dtype = 'radio'; - this.isChecked = isChecked; - } - this.parsers = parsers; - if (!/input|textarea|select/.test(node.nodeName)) { - if ('contenteditable' in node.props) { - dtype = 'contenteditable'; - } - } else if (!dtype) { - dtype = node.nodeName === 'select' ? 'select' : etype === 'checkbox' ? 'checkbox' : etype === 'radio' ? 'radio' : 'input'; - } - this.dtype = dtype; + jQuery.fn.extend({ + serialize: function serialize() { + return jQuery.param(this.serializeArray()); + }, + serializeArray: function serializeArray() { + return this.map(function () { - //判定是否使用了 change debounce 过滤器 - // this.isChecked = /boolean/.test(parsers) - if (dtype !== 'input' && dtype !== 'contenteditable') { - delete this.isChanged; - delete this.debounceTime; - } else if (!this.isChecked) { - this.isString = true; - } + // Can add propHook for "elements" to filter or add form elements + var elements = jQuery.prop(this, "elements"); + return elements ? jQuery.makeArray(elements) : this; + }).filter(function () { + var type = this.type; - var cb = node.props['data-duplex-changed']; - if (cb) { - var arr = addScope(cb, 'xx'); - var body = makeHandle(arr[0]); - this.userCb = new Function('$event', 'var __vmodel__ = this\nreturn ' + body); - } - } - function duplexDiff(newVal, oldVal) { - if (Array.isArray(newVal)) { - if (newVal + '' !== this.compareVal) { - this.compareVal = newVal + ''; - return true; - } - } else { - newVal = this.parseValue(newVal); - if (!this.isChecked) { - this.value = newVal += ''; - } - if (newVal !== this.compareVal) { - this.compareVal = newVal; - return true; - } - } - } + // Use .is(":disabled") so that fieldset[disabled] works + return this.name && !jQuery(this).is(":disabled") && rsubmittable.test(this.nodeName) && !rsubmitterTypes.test(type) && (this.checked || !rcheckableType.test(type)); + }).map(function (i, elem) { + var val = jQuery(this).val(); - function duplexBind(vdom, addEvent) { - var dom = vdom.dom; - this.dom = dom; - this.vdom = vdom; - this.duplexCb = updateDataHandle; - dom._ms_duplex_ = this; - //绑定事件 - addEvent(dom, this); - } + return val == null ? null : jQuery.isArray(val) ? jQuery.map(val, function (val) { + return { name: elem.name, value: val.replace(rCRLF, "\r\n") }; + }) : { name: elem.name, value: val.replace(rCRLF, "\r\n") }; + }).get(); + } + }); - var valueHijack = true; - try { - //#272 IE9-IE11, firefox - var setters = {}; - var aproto = HTMLInputElement.prototype; - var bproto = HTMLTextAreaElement.prototype; - var newSetter = function newSetter(value) { - // jshint ignore:line - setters[this.tagName].call(this, value); - var data = this._ms_duplex_; - if (!this.caret && data && data.isString) { - data.duplexCb.call(this, { type: 'setter' }); - } - }; - var inputProto = HTMLInputElement.prototype; - Object.getOwnPropertyNames(inputProto); //故意引发IE6-8等浏览器报错 - setters['INPUT'] = Object.getOwnPropertyDescriptor(aproto, 'value').set; + // Create the request object + // (This is still attached to ajaxSettings for backward compatibility) + jQuery.ajaxSettings.xhr = window.ActiveXObject !== undefined ? - Object.defineProperty(aproto, 'value', { - set: newSetter - }); - setters['TEXTAREA'] = Object.getOwnPropertyDescriptor(bproto, 'value').set; - Object.defineProperty(bproto, 'value', { - set: newSetter - }); - valueHijack = false; - } catch (e) { - //在chrome 43中 ms-duplex终于不需要使用定时器实现双向绑定了 - // http://updates.html5rocks.com/2015/04/DOM-attributes-now-on-the-prototype - // https://docs.google.com/document/d/1jwA8mtClwxI-QJuHT7872Z0pxpZz8PBkf2bGAbsUtqs/edit?pli=1 - } + // Support: IE6-IE8 + function () { - function parseValue(val) { - for (var i = 0, k; k = this.parsers[i++];) { - var fn = avalon.parsers[k]; - if (fn) { - val = fn.call(this, val); - } - } - return val; - } + // XHR cannot access local files, always use ActiveX for that case + if (this.isLocal) { + return createActiveXHR(); + } - var updateView = { - input: function input() { - //处理单个value值处理 - var vdom = this.node; - var value = this.value + ''; - vdom.dom.value = vdom.props.value = value; - }, - updateChecked: function updateChecked(vdom, checked) { - if (vdom.dom) { - vdom.dom.defaultChecked = vdom.dom.checked = checked; - } - }, - radio: function radio() { - //处理单个checked属性 - var node = this.node; - var nodeValue = node.props.value; - var checked; - if (this.isChecked) { - checked = !!this.value; - } else { - checked = this.value + '' === nodeValue; - } - node.props.checked = checked; - updateView.updateChecked(node, checked); - }, - checkbox: function checkbox() { - //处理多个checked属性 - var node = this.node; - var props = node.props; - var value = props.value + ''; - var values = [].concat(this.value); - var checked = values.some(function (el) { - return el + '' === value; - }); + // Support: IE 9-11 + // IE seems to error on cross-domain PATCH requests when ActiveX XHR + // is used. In IE 9+ always use the native XHR. + // Note: this condition won't catch Edge as it doesn't define + // document.documentMode but it also doesn't support ActiveX so it won't + // reach this code. + if (document.documentMode > 8) { + return createStandardXHR(); + } + + // Support: IE<9 + // oldIE XHR does not support non-RFC2616 methods (#13240) + // See http://msdn.microsoft.com/en-us/library/ie/ms536648(v=vs.85).aspx + // and http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9 + // Although this check for six methods instead of eight + // since IE also does not support "trace" and "connect" + return (/^(get|post|head|put|delete|options)$/i.test(this.type) && createStandardXHR() || createActiveXHR() + ); + } : - props.defaultChecked = props.checked = checked; - updateView.updateChecked(node, checked); - }, - select: function select() { - //处理子级的selected属性 - var a = Array.isArray(this.value) ? this.value.map(String) : this.value + ''; - lookupOption(this.node, a); - }, - contenteditable: function contenteditable() { - //处理单个innerHTML + // For all other browsers, use the standard XMLHttpRequest object + createStandardXHR; - var vnodes = fromString(this.value); - var fragment = createFragment(); - for (var i = 0, el; el = vnodes[i++];) { - var child = avalon.vdom(el, 'toDOM'); - fragment.appendChild(child); - } - avalon.clearHTML(this.dom).appendChild(fragment); - var list = this.node.children; - list.length = 0; - Array.prototype.push.apply(list, vnodes); + var xhrId = 0, + xhrCallbacks = {}, + xhrSupported = jQuery.ajaxSettings.xhr(); - this.duplexCb.call(this.dom); - } - }; + // Support: IE<10 + // Open requests must be manually aborted on unload (#5280) + // See https://support.microsoft.com/kb/2856746 for more info + if (window.attachEvent) { + window.attachEvent("onunload", function () { + for (var key in xhrCallbacks) { + xhrCallbacks[key](undefined, true); + } + }); + } - /* - * 通过绑定事件同步vmodel - * 总共有三种方式同步视图 - * 1. 各种事件 input, change, click, propertychange, keydown... - * 2. value属性重写 - * 3. 定时器轮询 - */ + // Determine support properties + support.cors = !!xhrSupported && "withCredentials" in xhrSupported; + xhrSupported = support.ajax = !!xhrSupported; - function updateDataEvents(dom, data) { - var events = {}; - //添加需要监听的事件 - switch (data.dtype) { - case 'radio': - case 'checkbox': - events.click = updateDataHandle; - break; - case 'select': - events.change = updateDataHandle; - break; - case 'contenteditable': - /* istanbul ignore if */ - if (data.isChanged) { - events.blur = updateDataHandle; - /* istanbul ignore else */ - } else { - /* istanbul ignore if*/ + // Create transport if the browser can provide an xhr + if (xhrSupported) { - if (avalon.modern) { - if (window$1.webkitURL) { - // http://code.metager.de/source/xref/WebKit/LayoutTests/fast/events/ - // https://bugs.webkit.org/show_bug.cgi?id=110742 - events.webkitEditableContentChanged = updateDataHandle; - } else if (window$1.MutationEvent) { - events.DOMCharacterDataModified = updateDataHandle; - } - events.input = updateDataHandle; - /* istanbul ignore else */ - } else { - events.keydown = updateModelKeyDown; - events.paste = updateModelDelay; - events.cut = updateModelDelay; - events.focus = closeComposition; - events.blur = openComposition; - } - } - break; - case 'input': - /* istanbul ignore if */ - if (data.isChanged) { - events.change = updateDataHandle; - /* istanbul ignore else */ - } else { - //http://www.cnblogs.com/rubylouvre/archive/2013/02/17/2914604.html - //http://www.matts411.com/post/internet-explorer-9-oninput/ - if (msie < 10) { - //IE6-8的propertychange有问题,第一次用JS修改值时不会触发,而且你是全部清空value也不会触发 - //IE9的propertychange不支持自动完成,退格,删除,复制,贴粘,剪切或点击右边的小X的清空操作 - events.propertychange = updateModelHack; - events.paste = updateModelDelay; - events.cut = updateModelDelay; - //IE9在第一次删除字符时不会触发oninput - events.keyup = updateModelKeyDown; - } else { - events.input = updateDataHandle; - events.compositionstart = openComposition; - //微软拼音输入法的问题需要在compositionend事件中处理 - events.compositionend = closeComposition; - //https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray - //处理低版本的标准浏览器,通过Int8Array进行区分 - if (!/\[native code\]/.test(window$1.Int8Array)) { - events.keydown = updateModelKeyDown; //safari < 5 opera < 11 - events.paste = updateModelDelay; //safari < 5 - events.cut = updateModelDelay; //safari < 5 - if (window$1.netscape) { - // Firefox <= 3.6 doesn't fire the 'input' event when text is filled in through autocomplete - events.DOMAutoComplete = updateDataHandle; - } - } - } - } - break; - } + jQuery.ajaxTransport(function (options) { - if (/password|text/.test(dom.type)) { - events.focus = openCaret; //判定是否使用光标修正功能 - events.blur = closeCaret; - data.getCaret = getCaret; - data.setCaret = setCaret; - } + // Cross domain only allowed if supported through XMLHttpRequest + if (!options.crossDomain || support.cors) { - for (var name in events) { - avalon.bind(dom, name, events[name]); - } - } + var _callback; - function updateModelHack(e) { - if (e.propertyName === 'value') { - updateDataHandle.call(this, e); - } - } + return { + send: function send(headers, complete) { + var i, + xhr = options.xhr(), + id = ++xhrId; - function updateModelDelay(e) { - var elem = this; - setTimeout(function () { - updateDataHandle.call(elem, e); - }, 0); - } + // Open the socket + xhr.open(options.type, options.url, options.async, options.username, options.password); - function openCaret() { - this.caret = true; - } - /* istanbul ignore next */ - function closeCaret() { - this.caret = false; - } - /* istanbul ignore next */ - function openComposition() { - this.composing = true; - } - /* istanbul ignore next */ - function closeComposition(e) { - this.composing = false; - updateModelDelay.call(this, e); - } - /* istanbul ignore next */ - function updateModelKeyDown(e) { - var key = e.keyCode; - // ignore - // command modifiers arrows - if (key === 91 || 15 < key && key < 19 || 37 <= key && key <= 40) return; - updateDataHandle.call(this, e); - } + // Apply custom fields if provided + if (options.xhrFields) { + for (i in options.xhrFields) { + xhr[i] = options.xhrFields[i]; + } + } - getShortID(openCaret); - getShortID(closeCaret); - getShortID(openComposition); - getShortID(closeComposition); - getShortID(updateDataHandle); - getShortID(updateModelHack); - getShortID(updateModelDelay); - getShortID(updateModelKeyDown); + // Override mime type if needed + if (options.mimeType && xhr.overrideMimeType) { + xhr.overrideMimeType(options.mimeType); + } - //IE6-8要处理光标时需要异步 - var mayBeAsync = function mayBeAsync(fn) { - setTimeout(fn, 0); - }; - /* istanbul ignore next */ - function setCaret(target, cursorPosition) { - var range$$1; - if (target.createTextRange) { - mayBeAsync(function () { - target.focus(); - range$$1 = target.createTextRange(); - range$$1.collapse(true); - range$$1.moveEnd('character', cursorPosition); - range$$1.moveStart('character', cursorPosition); - range$$1.select(); - }); - } else { - target.focus(); - if (target.selectionStart !== undefined) { - target.setSelectionRange(cursorPosition, cursorPosition); - } - } - } - /* istanbul ignore next*/ - function getCaret(target) { - var start = 0; - var normalizedValue; - var range$$1; - var textInputRange; - var len; - var endRange; + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if (!options.crossDomain && !headers["X-Requested-With"]) { + headers["X-Requested-With"] = "XMLHttpRequest"; + } + + // Set headers + for (i in headers) { + + // Support: IE<9 + // IE's ActiveXObject throws a 'Type Mismatch' exception when setting + // request header to a null-value. + // + // To keep consistent with other XHR implementations, cast the value + // to string and ignore `undefined`. + if (headers[i] !== undefined) { + xhr.setRequestHeader(i, headers[i] + ""); + } + } + + // Do send the request + // This may raise an exception which is actually + // handled in jQuery.ajax (so no try/catch here) + xhr.send(options.hasContent && options.data || null); - if (target.selectionStart + target.selectionEnd > -1) { - start = target.selectionStart; - } else { - range$$1 = document$1.selection.createRange(); + // Listener + _callback = function callback(_, isAbort) { + var status, statusText, responses; - if (range$$1 && range$$1.parentElement() === target) { - len = target.value.length; - normalizedValue = target.value.replace(/\r\n/g, '\n'); + // Was never called and is aborted or complete + if (_callback && (isAbort || xhr.readyState === 4)) { - textInputRange = target.createTextRange(); - textInputRange.moveToBookmark(range$$1.getBookmark()); + // Clean up + delete xhrCallbacks[id]; + _callback = undefined; + xhr.onreadystatechange = jQuery.noop; - endRange = target.createTextRange(); - endRange.collapse(false); + // Abort manually if needed + if (isAbort) { + if (xhr.readyState !== 4) { + xhr.abort(); + } + } else { + responses = {}; + status = xhr.status; - if (textInputRange.compareEndPoints('StartToEnd', endRange) > -1) { - start = len; - } else { - start = -textInputRange.moveStart('character', -len); - start += normalizedValue.slice(0, start).split('\n').length - 1; - } - } - } + // Support: IE<10 + // Accessing binary-data responseText throws an exception + // (#11426) + if (typeof xhr.responseText === "string") { + responses.text = xhr.responseText; + } - return start; - } + // Firefox throws an exception when accessing + // statusText for faulty cross-domain requests + try { + statusText = xhr.statusText; + } catch (e) { - avalon.directive('duplex', { - priority: 9999999, - beforeInit: duplexBeforeInit, - init: duplexInit, - diff: duplexDiff, - update: function update(vdom, value) { - if (!this.dom) { - duplexBind.call(this, vdom, updateDataEvents); - } - //如果不支持input.value的Object.defineProperty的属性支持, - //需要通过轮询同步, chrome 42及以下版本需要这个hack - pollValue.call(this, avalon.msie, valueHijack); - //更新视图 + // We normalize with Webkit giving an empty statusText + statusText = ""; + } - updateView[this.dtype].call(this); - } - }); + // Filter status for non standard behaviors - function pollValue(isIE, valueHijack$$1) { - var dom = this.dom; - if (this.isString && valueHijack$$1 && !isIE && !dom.valueHijack) { - dom.valueHijack = updateDataHandle; - var intervalID = setInterval(function () { - if (!avalon.contains(avalon.root, dom)) { - clearInterval(intervalID); - } else { - dom.valueHijack({ type: 'poll' }); - } - }, 30); - return intervalID; - } - } - avalon.__pollValue = pollValue; //export to test - /* istanbul ignore if */ - if (avalon.msie < 8) { - var oldUpdate = updateView.updateChecked; - updateView.updateChecked = function (vdom, checked) { - var dom = vdom.dom; - if (dom) { - setTimeout(function () { - oldUpdate(vdom, checked); - dom.firstCheckedIt = 1; - }, dom.firstCheckedIt ? 31 : 16); - //IE6,7 checkbox, radio是使用defaultChecked控制选中状态, - //并且要先设置defaultChecked后设置checked - //并且必须设置延迟(因为必须插入DOM树才生效) - } - }; - } + // If the request is local and we have data: assume a success + // (success with no data won't get notified, that's the best we + // can do given current implementations) + if (!status && options.isLocal && !options.crossDomain) { + status = responses.text ? 200 : 404; - avalon.directive('rules', { - diff: function diff(rules) { - if (isObject(rules)) { - var vdom = this.node; - vdom.rules = platform.toJson(rules); - return true; - } - } - }); - function isRegExp(value) { - return avalon.type(value) === 'regexp'; - } - var rmail = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/i; - var rurl = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/; - function isCorrectDate(value) { - if (typeof value === "string" && value) { - //是字符串但不能是空字符 - var arr = value.split("-"); //可以被-切成3份,并且第1个是4个字符 - if (arr.length === 3 && arr[0].length === 4) { - var year = ~~arr[0]; //全部转换为非负整数 - var month = ~~arr[1] - 1; - var date = ~~arr[2]; - var d = new Date(year, month, date); - return d.getFullYear() === year && d.getMonth() === month && d.getDate() === date; - } - } - return false; - } - //https://github.com/adform/validator.js/blob/master/validator.js - avalon.shadowCopy(avalon.validators, { - pattern: { - message: '必须匹配{{pattern}}这样的格式', - get: function get(value, field, next) { - var elem = field.dom; - var data = field.data; - if (!isRegExp(data.pattern)) { - var h5pattern = elem.getAttribute("pattern"); - data.pattern = new RegExp('^(?:' + h5pattern + ')$'); - } - next(data.pattern.test(value)); - return value; - } - }, - digits: { - message: '必须整数', - get: function get(value, field, next) { - //整数 - next(/^\-?\d+$/.test(value)); - return value; - } - }, - number: { - message: '必须数字', - get: function get(value, field, next) { - //数值 - next(!!value && isFinite(value)); // isFinite('') --> true - return value; - } - }, - norequired: { - message: '', - get: function get(value, field, next) { - next(true); - return value; - } - }, - required: { - message: '必须填写', - get: function get(value, field, next) { - next(value !== ''); - return value; - } - }, - equalto: { - message: '密码输入不一致', - get: function get(value, field, next) { - var id = String(field.data.equalto); - var other = avalon(document.getElementById(id)).val() || ""; - next(value === other); - return value; - } - }, - date: { - message: '日期格式不正确', - get: function get(value, field, next) { - var data = field.data; - if (isRegExp(data.date)) { - next(data.date.test(value)); - } else { - next(isCorrectDate(value)); - } - return value; - } - }, - url: { - message: 'URL格式不正确', - get: function get(value, field, next) { - next(rurl.test(value)); - return value; - } - }, - email: { - message: 'email格式不正确', - get: function get(value, field, next) { - next(rmail.test(value)); - return value; - } - }, - minlength: { - message: '最少输入{{minlength}}个字', - get: function get(value, field, next) { - var num = parseInt(field.data.minlength, 10); - next(value.length >= num); - return value; - } - }, - maxlength: { - message: '最多输入{{maxlength}}个字', - get: function get(value, field, next) { - var num = parseInt(field.data.maxlength, 10); - next(value.length <= num); - return value; - } - }, - min: { - message: '输入值不能小于{{min}}', - get: function get(value, field, next) { - var num = parseInt(field.data.min, 10); - next(parseFloat(value) >= num); - return value; - } - }, - max: { - message: '输入值不能大于{{max}}', - get: function get(value, field, next) { - var num = parseInt(field.data.max, 10); - next(parseFloat(value) <= num); - return value; - } - }, - chs: { - message: '必须是中文字符', - get: function get(value, field, next) { - next(/^[\u4e00-\u9fa5]+$/.test(value)); - return value; - } - } - }); + // IE - #1450: sometimes returns 1223 when it should be 204 + } else if (status === 1223) { + status = 204; + } + } + } - var valiDir = avalon.directive('validate', { - diff: function diff(validator) { - var vdom = this.node; - if (vdom.validator) { - return; - } - if (isObject(validator)) { - //注意,这个Form标签的虚拟DOM有两个验证对象 - //一个是vmValidator,它是用户VM上的那个原始子对象,也是一个VM - //一个是validator,它是vmValidator.$model, 这是为了防止IE6-8添加子属性时添加的hack - //也可以称之为safeValidate - vdom.validator = validator; - validator = platform.toJson(validator); - validator.vdom = vdom; - validator.dom = vdom.dom; + // Call complete if needed + if (responses) { + complete(status, statusText, responses, xhr.getAllResponseHeaders()); + } + }; - for (var name in valiDir.defaults) { - if (!validator.hasOwnProperty(name)) { - validator[name] = valiDir.defaults[name]; - } - } - validator.fields = validator.fields || []; - vdom.vmValidator = validator; - return true; - } - }, - update: function update(vdom) { + // Do send the request + // `xhr.send` may raise an exception, but it will be + // handled in jQuery.ajax (so no try/catch here) + if (!options.async) { - var vmValidator = vdom.vmValidator; - var validator = vdom.validator; - var dom = vdom.dom; - dom._ms_validate_ = vmValidator; + // If we're in sync mode we fire the callback + _callback(); + } else if (xhr.readyState === 4) { - collectFeild(vdom.children, vmValidator.fields, vmValidator); - var type = window.netscape ? 'keypress' : 'focusin'; - avalon.bind(document, type, findValidator); - //为了方便用户手动执行验证,我们需要为原始vmValidate上添加一个onManual方法 - function onManual() { - var v = this; - v && valiDir.validateAll.call(v, v.onValidateAll); - } + // (IE6 & IE7) if it's in cache and has been + // retrieved directly we need to fire the callback + window.setTimeout(_callback); + } else { - try { - var fn = vmValidator.onManual = onManual.bind(vmValidator); - validator.onManual = fn; - } catch (e) { - avalon.warn('要想使用onManual方法,必须在validate对象预定义一个空的onManual函数'); - } - delete vdom.vmValidator; + // Register the callback, but delay it in case `xhr.send` throws + // Add to the list of active xhr callbacks + xhr.onreadystatechange = xhrCallbacks[id] = _callback; + } + }, - dom.setAttribute('novalidate', 'novalidate'); + abort: function abort() { + if (_callback) { + _callback(undefined, true); + } + } + }; + } + }); + } - /* istanbul ignore if */ - if (vmValidator.validateAllInSubmit) { - avalon.bind(dom, 'submit', validateAllInSubmitFn); - } - }, - validateAll: function validateAll(callback) { - var validator = this; - var vdom = this.vdom; - var fields = validator.fields = []; - collectFeild(vdom.children, fields, validator); - var fn = typeof callback === 'function' ? callback : validator.onValidateAll; - var promises = validator.fields.filter(function (field) { - var el = field.dom; - return el && !el.disabled && validator.dom.contains(el); - }).map(function (field) { - return valiDir.validate(field, true); - }); - var uniq = {}; - return Promise.all(promises).then(function (array) { - var reasons = array.concat.apply([], array); - if (validator.deduplicateInValidateAll) { - reasons = reasons.filter(function (reason) { - var el = reason.element; - var uuid = el.uniqueID || (el.uniqueID = setTimeout('1')); - if (uniq[uuid]) { - return false; - } else { - return uniq[uuid] = true; - } - }); - } - fn.call(vdom.dom, reasons); //这里只放置未通过验证的组件 - }); - }, + // Functions to create xhrs + function createStandardXHR() { + try { + return new window.XMLHttpRequest(); + } catch (e) {} + } + + function createActiveXHR() { + try { + return new window.ActiveXObject("Microsoft.XMLHTTP"); + } catch (e) {} + } + + // Install script dataType + jQuery.ajaxSetup({ + accepts: { + script: "text/javascript, application/javascript, " + "application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /\b(?:java|ecma)script\b/ + }, + converters: { + "text script": function textScript(text) { + jQuery.globalEval(text); + return text; + } + } + }); + + // Handle cache's special case and global + jQuery.ajaxPrefilter("script", function (s) { + if (s.cache === undefined) { + s.cache = false; + } + if (s.crossDomain) { + s.type = "GET"; + s.global = false; + } + }); + + // Bind script tag hack transport + jQuery.ajaxTransport("script", function (s) { + + // This transport only deals with cross domain requests + if (s.crossDomain) { + + var script, + head = document.head || jQuery("head")[0] || document.documentElement; + + return { + + send: function send(_, callback) { + + script = document.createElement("script"); + + script.async = true; + + if (s.scriptCharset) { + script.charset = s.scriptCharset; + } + + script.src = s.url; + + // Attach handlers for all browsers + script.onload = script.onreadystatechange = function (_, isAbort) { + + if (isAbort || !script.readyState || /loaded|complete/.test(script.readyState)) { + + // Handle memory leak in IE + script.onload = script.onreadystatechange = null; + + // Remove the script + if (script.parentNode) { + script.parentNode.removeChild(script); + } + + // Dereference the script + script = null; + + // Callback if not abort + if (!isAbort) { + callback(200, "success"); + } + } + }; + + // Circumvent IE6 bugs with base elements (#2709 and #4378) by prepending + // Use native DOM manipulation to avoid our domManip AJAX trickery + head.insertBefore(script, head.firstChild); + }, + + abort: function abort() { + if (script) { + script.onload(undefined, true); + } + } + }; + } + }); + + var oldCallbacks = [], + rjsonp = /(=)\?(?=&|$)|\?\?/; + + // Default jsonp settings + jQuery.ajaxSetup({ + jsonp: "callback", + jsonpCallback: function jsonpCallback() { + var callback = oldCallbacks.pop() || jQuery.expando + "_" + nonce++; + this[callback] = true; + return callback; + } + }); + + // Detect, normalize options and install callbacks for jsonp requests + jQuery.ajaxPrefilter("json jsonp", function (s, originalSettings, jqXHR) { + + var callbackName, + overwritten, + responseContainer, + jsonProp = s.jsonp !== false && (rjsonp.test(s.url) ? "url" : typeof s.data === "string" && (s.contentType || "").indexOf("application/x-www-form-urlencoded") === 0 && rjsonp.test(s.data) && "data"); + + // Handle iff the expected data type is "jsonp" or we have a parameter to set + if (jsonProp || s.dataTypes[0] === "jsonp") { + + // Get callback name, remembering preexisting value associated with it + callbackName = s.jsonpCallback = jQuery.isFunction(s.jsonpCallback) ? s.jsonpCallback() : s.jsonpCallback; + + // Insert callback into url or form data + if (jsonProp) { + s[jsonProp] = s[jsonProp].replace(rjsonp, "$1" + callbackName); + } else if (s.jsonp !== false) { + s.url += (rquery.test(s.url) ? "&" : "?") + s.jsonp + "=" + callbackName; + } + + // Use data converter to retrieve json after script execution + s.converters["script json"] = function () { + if (!responseContainer) { + jQuery.error(callbackName + " was not called"); + } + return responseContainer[0]; + }; + + // force json dataType + s.dataTypes[0] = "json"; - validate: function validate(field, isValidateAll, event) { + // Install callback + overwritten = window[callbackName]; + window[callbackName] = function () { + responseContainer = arguments; + }; - var promises = []; - var value = field.value; - var elem = field.dom; - /* istanbul ignore if */ - if (typeof Promise !== 'function') { - //avalon-promise不支持phantomjs - avalon.warn('浏览器不支持原生Promise,请下载并