avalon-rails 0.0.3 → 1.3.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4370 @@
1
+ /*==================================================
2
+ Copyright (c) 2013-2015 司徒正美 and other contributors
3
+ http://www.cnblogs.com/rubylouvre/
4
+ https://github.com/RubyLouvre
5
+ http://weibo.com/jslouvre/
6
+
7
+ Released under the MIT license
8
+ avalon.mobile.shim.js(去掉加载器与domReady) 1.391 build in 2015.1.31
9
+ upport IE6+ and other browsers
10
+ ==================================================*/
11
+ (function(global, factory) {
12
+
13
+ if (typeof module === "object" && typeof module.exports === "object") {
14
+ // For CommonJS and CommonJS-like environments where a proper `window`
15
+ // is present, execute the factory and get avalon.
16
+ // For environments that do not have a `window` with a `document`
17
+ // (such as Node.js), expose a factory as module.exports.
18
+ // This accentuates the need for the creation of a real `window`.
19
+ // e.g. var avalon = require("avalon")(window);
20
+ module.exports = global.document ? factory(global, true) : function(w) {
21
+ if (!w.document) {
22
+ throw new Error("Avalon requires a window with a document")
23
+ }
24
+ return factory(w)
25
+ }
26
+ } else {
27
+ factory(global)
28
+ }
29
+
30
+ // Pass this if window is not defined yet
31
+ }(typeof window !== "undefined" ? window : this, function(window, noGlobal){
32
+
33
+ /*********************************************************************
34
+ * 全局变量及方法 *
35
+ **********************************************************************/
36
+ var expose = Date.now()
37
+ //http://stackoverflow.com/questions/7290086/javascript-use-strict-and-nicks-find-global-function
38
+ var DOC = window.document
39
+ var head = DOC.head //HEAD元素
40
+ head.insertAdjacentHTML("afterBegin", '<avalon ms-skip><style id="avalonStyle">.avalonHide{ display: none!important }</style></avalon>')
41
+ var ifGroup = head.firstChild
42
+
43
+ function log() {
44
+ if (avalon.config.debug) {
45
+ // http://stackoverflow.com/questions/8785624/how-to-safely-wrap-console-log
46
+ console.log.apply(console, arguments)
47
+ }
48
+ }
49
+
50
+ var subscribers = "$" + expose
51
+ var otherRequire = window.require
52
+ var otherDefine = window.define
53
+ var stopRepeatAssign = false
54
+ var rword = /[^, ]+/g //切割字符串为一个个小块,以空格或豆号分开它们,结合replace实现字符串的forEach
55
+ var rcomplexType = /^(?:object|array)$/
56
+ var rsvg = /^\[object SVG\w*Element\]$/
57
+ var rwindow = /^\[object (?:Window|DOMWindow|global)\]$/
58
+ var oproto = Object.prototype
59
+ var ohasOwn = oproto.hasOwnProperty
60
+ var serialize = oproto.toString
61
+ var ap = Array.prototype
62
+ var aslice = ap.slice
63
+ var Registry = {} //将函数曝光到此对象上,方便访问器收集依赖
64
+ var W3C = window.dispatchEvent
65
+ var root = DOC.documentElement
66
+ var hyperspace = DOC.createDocumentFragment()
67
+ var cinerator = DOC.createElement("div")
68
+ var class2type = {}
69
+ "Boolean Number String Function Array Date RegExp Object Error".replace(rword, function(name) {
70
+ class2type["[object " + name + "]"] = name.toLowerCase()
71
+ })
72
+
73
+
74
+ function noop() {
75
+ }
76
+
77
+
78
+ function oneObject(array, val) {
79
+ if (typeof array === "string") {
80
+ array = array.match(rword) || []
81
+ }
82
+ var result = {},
83
+ value = val !== void 0 ? val : 1
84
+ for (var i = 0, n = array.length; i < n; i++) {
85
+ result[array[i]] = value
86
+ }
87
+ return result
88
+ }
89
+
90
+ function createCache(maxLength) {
91
+ var keys = []
92
+ function cache(key, value) {
93
+ if (keys.push(key) > maxLength) {
94
+ delete cache[keys.shift()]
95
+ }
96
+ return cache[key] = value;
97
+ }
98
+ return cache;
99
+ }
100
+ //生成UUID http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
101
+ var generateID = function(prefix) {
102
+ prefix = prefix || "avalon"
103
+ return (prefix + Math.random() + Math.random()).replace(/0\./g, "")
104
+ }
105
+ function IE() {
106
+ if (window.VBArray) {
107
+ var mode = document.documentMode
108
+ return mode ? mode : window.XMLHttpRequest ? 7 : 6
109
+ } else {
110
+ return 0
111
+ }
112
+ }
113
+ var IEVersion = IE()
114
+ /*********************************************************************
115
+ * avalon的静态方法定义区 *
116
+ **********************************************************************/
117
+ avalon = function(el) { //创建jQuery式的无new 实例化结构
118
+ return new avalon.init(el)
119
+ }
120
+
121
+ avalon.init = function(el) {
122
+ this[0] = this.element = el
123
+ }
124
+ avalon.fn = avalon.prototype = avalon.init.prototype
125
+
126
+ avalon.type = function(obj) { //取得目标的类型
127
+ if (obj == null) {
128
+ return String(obj)
129
+ }
130
+ // 早期的webkit内核浏览器实现了已废弃的ecma262v4标准,可以将正则字面量当作函数使用,因此typeof在判定正则时会返回function
131
+ return typeof obj === "object" || typeof obj === "function" ?
132
+ class2type[serialize.call(obj)] || "object" :
133
+ typeof obj
134
+ }
135
+
136
+ var isFunction = function(fn) {
137
+ return serialize.call(fn) == "[object Function]"
138
+ }
139
+
140
+ avalon.isFunction = isFunction
141
+
142
+ avalon.isWindow = function(obj) {
143
+ return rwindow.test(serialize.call(obj))
144
+ }
145
+
146
+ /*判定是否是一个朴素的javascript对象(Object),不是DOM对象,不是BOM对象,不是自定义类的实例*/
147
+
148
+ avalon.isPlainObject = function(obj) {
149
+ // 简单的 typeof obj === "object"检测,会致使用isPlainObject(window)在opera下通不过
150
+ return serialize.call(obj) === "[object Object]" && Object.getPrototypeOf(obj) === oproto
151
+ }
152
+
153
+ //与jQuery.extend方法,可用于浅拷贝,深拷贝
154
+ avalon.mix = avalon.fn.mix = function() {
155
+ var options, name, src, copy, copyIsArray, clone,
156
+ target = arguments[0] || {},
157
+ i = 1,
158
+ length = arguments.length,
159
+ deep = false
160
+
161
+ // 如果第一个参数为布尔,判定是否深拷贝
162
+ if (typeof target === "boolean") {
163
+ deep = target
164
+ target = arguments[1] || {}
165
+ i++
166
+ }
167
+
168
+ //确保接受方为一个复杂的数据类型
169
+ if (typeof target !== "object" && !isFunction(target)) {
170
+ target = {}
171
+ }
172
+
173
+ //如果只有一个参数,那么新成员添加于mix所在的对象上
174
+ if (i === length) {
175
+ target = this
176
+ i--
177
+ }
178
+
179
+ for (; i < length; i++) {
180
+ //只处理非空参数
181
+ if ((options = arguments[i]) != null) {
182
+ for (name in options) {
183
+ src = target[name]
184
+ copy = options[name]
185
+ // 防止环引用
186
+ if (target === copy) {
187
+ continue
188
+ }
189
+ if (deep && copy && (avalon.isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))) {
190
+
191
+ if (copyIsArray) {
192
+ copyIsArray = false
193
+ clone = src && Array.isArray(src) ? src : []
194
+
195
+ } else {
196
+ clone = src && avalon.isPlainObject(src) ? src : {}
197
+ }
198
+
199
+ target[name] = avalon.mix(deep, clone, copy)
200
+ } else if (copy !== void 0) {
201
+ target[name] = copy
202
+ }
203
+ }
204
+ }
205
+ }
206
+ return target
207
+ }
208
+
209
+ function _number(a, len) { //用于模拟slice, splice的效果
210
+ a = Math.floor(a) || 0
211
+ return a < 0 ? Math.max(len + a, 0) : Math.min(a, len);
212
+ }
213
+ avalon.mix({
214
+ rword: rword,
215
+ subscribers: subscribers,
216
+ version: 1.391,
217
+ ui: {},
218
+ log: log,
219
+ slice: function(nodes, start, end) {
220
+ return aslice.call(nodes, start, end)
221
+ },
222
+ noop: noop,
223
+ /*如果不用Error对象封装一下,str在控制台下可能会乱码*/
224
+ error: function(str, e) {
225
+ throw new (e || Error)(str)
226
+ },
227
+ /*将一个以空格或逗号隔开的字符串或数组,转换成一个键值都为1的对象*/
228
+ oneObject: oneObject,
229
+ /* avalon.range(10)
230
+ => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
231
+ avalon.range(1, 11)
232
+ => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
233
+ avalon.range(0, 30, 5)
234
+ => [0, 5, 10, 15, 20, 25]
235
+ avalon.range(0, -10, -1)
236
+ => [0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
237
+ avalon.range(0)
238
+ => []*/
239
+ range: function(start, end, step) { // 用于生成整数数组
240
+ step || (step = 1)
241
+ if (end == null) {
242
+ end = start || 0
243
+ start = 0
244
+ }
245
+ var index = -1,
246
+ length = Math.max(0, Math.ceil((end - start) / step)),
247
+ result = Array(length)
248
+ while (++index < length) {
249
+ result[index] = start
250
+ start += step
251
+ }
252
+ return result
253
+ },
254
+ eventHooks: {},
255
+ /*绑定事件*/
256
+ bind: function(el, type, fn, phase) {
257
+ var hooks = avalon.eventHooks
258
+ var hook = hooks[type]
259
+ if (typeof hook === "object") {
260
+ type = hook.type
261
+ if (hook.deel) {
262
+ fn = hook.deel(el, type, fn, true)
263
+ }
264
+ }
265
+ if (!fn.unbind)
266
+ el.addEventListener(type, fn, !!phase)
267
+ return fn
268
+ },
269
+ /*卸载事件*/
270
+ unbind: function(el, type, fn, phase) {
271
+ var hooks = avalon.eventHooks
272
+ var hook = hooks[type]
273
+ var callback = fn || noop
274
+ if (typeof hook === "object") {
275
+ type = hook.type
276
+ if (hook.deel) {
277
+ fn = hook.deel(el, type, fn, false)
278
+ }
279
+ }
280
+ el.removeEventListener(type, callback, !!phase)
281
+ },
282
+ /*读写删除元素节点的样式*/
283
+ css: function(node, name, value) {
284
+ if (node instanceof avalon) {
285
+ node = node[0]
286
+ }
287
+ var prop = /[_-]/.test(name) ? camelize(name) : name
288
+ name = avalon.cssName(prop) || prop
289
+ if (value === void 0 || typeof value === "boolean") { //获取样式
290
+ var fn = cssHooks[prop + ":get"] || cssHooks["@:get"]
291
+ if (name === "background") {
292
+ name = "backgroundColor"
293
+ }
294
+ var val = fn(node, name)
295
+ return value === true ? parseFloat(val) || 0 : val
296
+ } else if (value === "") { //请除样式
297
+ node.style[name] = ""
298
+ } else { //设置样式
299
+ if (value == null || value !== value) {
300
+ return
301
+ }
302
+ if (isFinite(value) && !avalon.cssNumber[prop]) {
303
+ value += "px"
304
+ }
305
+ fn = cssHooks[prop + ":set"] || cssHooks["@:set"]
306
+ fn(node, name, value)
307
+ }
308
+ },
309
+ /*遍历数组与对象,回调的第一个参数为索引或键名,第二个或元素或键值*/
310
+ each: function(obj, fn) {
311
+ if (obj) { //排除null, undefined
312
+ var i = 0
313
+ if (isArrayLike(obj)) {
314
+ for (var n = obj.length; i < n; i++) {
315
+ if (fn(i, obj[i]) === false)
316
+ break
317
+ }
318
+ } else {
319
+ for (i in obj) {
320
+ if (obj.hasOwnProperty(i) && fn(i, obj[i] === false)) {
321
+ break
322
+ }
323
+ }
324
+ }
325
+ }
326
+ },
327
+ //收集元素的data-{{prefix}}-*属性,并转换为对象
328
+ getWidgetData: function(elem, prefix) {
329
+ var raw = avalon(elem).data()
330
+ var result = {}
331
+ for (var i in raw) {
332
+ if (i.indexOf(prefix) === 0) {
333
+ result[i.replace(prefix, "").replace(/\w/, function(a) {
334
+ return a.toLowerCase()
335
+ })] = raw[i]
336
+ }
337
+ }
338
+ return result
339
+ },
340
+ Array: {
341
+ /*只有当前数组不存在此元素时只添加它*/
342
+ ensure: function(target, item) {
343
+ if (target.indexOf(item) === -1) {
344
+ return target.push(item)
345
+ }
346
+ },
347
+ /*移除数组中指定位置的元素,返回布尔表示成功与否*/
348
+ removeAt: function(target, index) {
349
+ return !!target.splice(index, 1).length
350
+ },
351
+ /*移除数组中第一个匹配传参的那个元素,返回布尔表示成功与否*/
352
+ remove: function(target, item) {
353
+ var index = target.indexOf(item)
354
+ if (~index)
355
+ return avalon.Array.removeAt(target, index)
356
+ return false
357
+ }
358
+ }
359
+ })
360
+
361
+ var bindingHandlers = avalon.bindingHandlers = {}
362
+ var bindingExecutors = avalon.bindingExecutors = {}
363
+
364
+ /*判定是否类数组,如节点集合,纯数组,arguments与拥有非负整数的length属性的纯JS对象*/
365
+ function isArrayLike(obj) {
366
+ if (obj && typeof obj === "object") {
367
+ var n = obj.length,
368
+ str = serialize.call(obj)
369
+ if (/(Array|List|Collection|Map|Arguments)\]$/.test(str)) {
370
+ return true
371
+ } else if (str === "[object Object]" && n === (n >>> 0)) {
372
+ return true //由于ecma262v5能修改对象属性的enumerable,因此不能用propertyIsEnumerable来判定了
373
+ }
374
+ }
375
+ return false
376
+ }
377
+ /*视浏览器情况采用最快的异步回调(在avalon.ready里,还有一个分支,用于处理IE6-9)*/
378
+ avalon.nextTick = window.setImmediate ? setImmediate.bind(window) : function(callback) {
379
+ setTimeout(callback, 0) //IE10-11 or W3C
380
+ }
381
+
382
+ /*********************************************************************
383
+ * DOM 底层补丁 *
384
+ **********************************************************************/
385
+ if (!root.contains) { //safari5+是把contains方法放在Element.prototype上而不是Node.prototype
386
+ Node.prototype.contains = function(arg) {
387
+ return !!(this.compareDocumentPosition(arg) & 16)
388
+ }
389
+ }
390
+ avalon.contains = function(root, el) {
391
+ try {
392
+ while ((el = el.parentNode))
393
+ if (el === root)
394
+ return true;
395
+ return false
396
+ } catch (e) {
397
+ return false
398
+ }
399
+ }
400
+ if (window.SVGElement) {
401
+ var svgns = "http://www.w3.org/2000/svg"
402
+ var svg = DOC.createElementNS(svgns, "svg")
403
+ svg.innerHTML = '<circle cx="50" cy="50" r="40" fill="red" />'
404
+ if (!rsvg.test(svg.firstChild)) {// #409
405
+
406
+ function enumerateNode(node, targetNode) {
407
+ if (node && node.childNodes) {
408
+ var nodes = node.childNodes
409
+ for (var i = 0, el; el = nodes[i++]; ) {
410
+ if (el.tagName) {
411
+ var svg = DOC.createElementNS(svgns,
412
+ el.tagName.toLowerCase())
413
+ // copy attrs
414
+ ap.forEach.call(el.attributes, function(attr) {
415
+ svg.setAttribute(attr.name, attr.value)
416
+ })
417
+ // 递归处理子节点
418
+ enumerateNode(el, svg)
419
+ targetNode.appendChild(svg)
420
+ }
421
+ }
422
+ }
423
+ }
424
+ Object.defineProperties(SVGElement.prototype, {
425
+ "outerHTML": {//IE9-11,firefox不支持SVG元素的innerHTML,outerHTML属性
426
+ enumerable: true,
427
+ configurable: true,
428
+ get: function() {
429
+ return new XMLSerializer().serializeToString(this)
430
+ },
431
+ set: function(html) {
432
+ var tagName = this.tagName.toLowerCase(),
433
+ par = this.parentNode,
434
+ frag = avalon.parseHTML(html)
435
+ // 操作的svg,直接插入
436
+ if (tagName === "svg") {
437
+ par.insertBefore(frag, this)
438
+ // svg节点的子节点类似
439
+ } else {
440
+ var newFrag = DOC.createDocumentFragment()
441
+ enumerateNode(frag, newFrag)
442
+ par.insertBefore(newFrag, this)
443
+ }
444
+ par.removeChild(this)
445
+ }
446
+ },
447
+ "innerHTML": {
448
+ enumerable: true,
449
+ configurable: true,
450
+ get: function() {
451
+ var s = this.outerHTML
452
+ var ropen = new RegExp("<" + this.nodeName + '\\b(?:(["\'])[^"]*?(\\1)|[^>])*>', "i")
453
+ var rclose = new RegExp("<\/" + this.nodeName + ">$", "i")
454
+ return s.replace(ropen, "").replace(rclose, "")
455
+ },
456
+ set: function(html) {
457
+ if (avalon.clearHTML) {
458
+ avalon.clearHTML(this)
459
+ var frag = avalon.parseHTML(html)
460
+ enumerateNode(frag, this)
461
+ }
462
+ }
463
+ }
464
+ })
465
+ }
466
+ }
467
+ //========================= event binding ====================
468
+ var eventHooks = avalon.eventHooks
469
+ //针对firefox, chrome修正mouseenter, mouseleave(chrome30+)
470
+ if (!("onmouseenter" in root)) {
471
+ avalon.each({
472
+ mouseenter: "mouseover",
473
+ mouseleave: "mouseout"
474
+ }, function(origType, fixType) {
475
+ eventHooks[origType] = {
476
+ type: fixType,
477
+ deel: function(elem, fn) {
478
+ return function(e) {
479
+ var t = e.relatedTarget
480
+ if (!t || (t !== elem && !(elem.compareDocumentPosition(t) & 16))) {
481
+ delete e.type
482
+ e.type = origType
483
+ return fn.call(elem, e)
484
+ }
485
+ }
486
+ }
487
+ }
488
+ })
489
+ }
490
+ //针对IE9+, w3c修正animationend
491
+ avalon.each({
492
+ AnimationEvent: "animationend",
493
+ WebKitAnimationEvent: "webkitAnimationEnd"
494
+ }, function(construct, fixType) {
495
+ if (window[construct] && !eventHooks.animationend) {
496
+ eventHooks.animationend = {
497
+ type: fixType
498
+ }
499
+ }
500
+ })
501
+
502
+ if (DOC.onmousewheel === void 0) {
503
+ /* IE6-11 chrome mousewheel wheelDetla 下 -120 上 120
504
+ firefox DOMMouseScroll detail 下3 上-3
505
+ firefox wheel detlaY 下3 上-3
506
+ IE9-11 wheel deltaY 下40 上-40
507
+ chrome wheel deltaY 下100 上-100 */
508
+ eventHooks.mousewheel = {
509
+ type: "wheel",
510
+ deel: function(elem, fn) {
511
+ return function(e) {
512
+ e.wheelDeltaY = e.wheelDelta = e.deltaY > 0 ? -120 : 120
513
+ e.wheelDeltaX = 0
514
+ Object.defineProperty(e, "type", {
515
+ value: "mousewheel"
516
+ })
517
+ fn.call(elem, e)
518
+ }
519
+ }
520
+ }
521
+ }
522
+
523
+ /*********************************************************************
524
+ * 配置系统 *
525
+ **********************************************************************/
526
+
527
+ function kernel(settings) {
528
+ for (var p in settings) {
529
+ if (!ohasOwn.call(settings, p))
530
+ continue
531
+ var val = settings[p]
532
+ if (typeof kernel.plugins[p] === "function") {
533
+ kernel.plugins[p](val)
534
+ } else if (typeof kernel[p] === "object") {
535
+ avalon.mix(kernel[p], val)
536
+ } else {
537
+ kernel[p] = val
538
+ }
539
+ }
540
+ return this
541
+ }
542
+ var openTag, closeTag, rexpr, rexprg, rbind, rregexp = /[-.*+?^${}()|[\]\/\\]/g
543
+
544
+ function escapeRegExp(target) {
545
+ //http://stevenlevithan.com/regex/xregexp/
546
+ //将字符串安全格式化为正则表达式的源码
547
+ return (target + "").replace(rregexp, "\\$&")
548
+ }
549
+ var innerRequire = noop
550
+ var plugins = {
551
+ loader: function(builtin) {
552
+ window.define = builtin ? innerRequire.define : otherDefine
553
+ window.require = builtin ? innerRequire : otherRequire
554
+ },
555
+ interpolate: function(array) {
556
+ openTag = array[0]
557
+ closeTag = array[1]
558
+ if (openTag === closeTag) {
559
+ throw new SyntaxError("openTag!==closeTag")
560
+ } else if (array + "" === "<!--,-->") {
561
+ kernel.commentInterpolate = true
562
+ } else {
563
+ var test = openTag + "test" + closeTag
564
+ cinerator.innerHTML = test
565
+ if (cinerator.innerHTML !== test && cinerator.innerHTML.indexOf("&lt;") > -1) {
566
+ throw new SyntaxError("此定界符不合法")
567
+ }
568
+ cinerator.innerHTML = ""
569
+ }
570
+ var o = escapeRegExp(openTag),
571
+ c = escapeRegExp(closeTag)
572
+ rexpr = new RegExp(o + "(.*?)" + c)
573
+ rexprg = new RegExp(o + "(.*?)" + c, "g")
574
+ rbind = new RegExp(o + ".*?" + c + "|\\sms-")
575
+ }
576
+ }
577
+
578
+ kernel.debug = true
579
+ kernel.plugins = plugins
580
+ kernel.plugins['interpolate'](["{{", "}}"])
581
+ kernel.paths = {}
582
+ kernel.shim = {}
583
+ kernel.maxRepeatSize = 100
584
+ avalon.config = kernel
585
+ /*********************************************************************
586
+ * 事件总线 *
587
+ **********************************************************************/
588
+ var EventBus = {
589
+ $watch: function(type, callback) {
590
+ if (typeof callback === "function") {
591
+ var callbacks = this.$events[type]
592
+ if (callbacks) {
593
+ callbacks.push(callback)
594
+ } else {
595
+ this.$events[type] = [callback]
596
+ }
597
+ } else { //重新开始监听此VM的第一重简单属性的变动
598
+ this.$events = this.$watch.backup
599
+ }
600
+ return this
601
+ },
602
+ $unwatch: function(type, callback) {
603
+ var n = arguments.length
604
+ if (n === 0) { //让此VM的所有$watch回调无效化
605
+ this.$watch.backup = this.$events
606
+ this.$events = {}
607
+ } else if (n === 1) {
608
+ this.$events[type] = []
609
+ } else {
610
+ var callbacks = this.$events[type] || []
611
+ var i = callbacks.length
612
+ while (~--i < 0) {
613
+ if (callbacks[i] === callback) {
614
+ return callbacks.splice(i, 1)
615
+ }
616
+ }
617
+ }
618
+ return this
619
+ },
620
+ $fire: function(type) {
621
+ var special
622
+ if (/^(\w+)!(\S+)$/.test(type)) {
623
+ special = RegExp.$1
624
+ type = RegExp.$2
625
+ }
626
+ var events = this.$events
627
+ var args = aslice.call(arguments, 1)
628
+ var detail = [type].concat(args)
629
+ if (special === "all") {
630
+ for (var i in avalon.vmodels) {
631
+ var v = avalon.vmodels[i]
632
+ if (v !== this) {
633
+ v.$fire.apply(v, detail)
634
+ }
635
+ }
636
+ } else if (special === "up" || special === "down") {
637
+ var elements = events.expr ? findNodes(events.expr) : []
638
+ if (elements.length === 0)
639
+ return
640
+ for (var i in avalon.vmodels) {
641
+ var v = avalon.vmodels[i]
642
+ if (v !== this) {
643
+ if (v.$events.expr) {
644
+ var eventNodes = findNodes(v.$events.expr)
645
+ if (eventNodes.length === 0) {
646
+ continue
647
+ }
648
+ //循环两个vmodel中的节点,查找匹配(向上匹配或者向下匹配)的节点并设置标识
649
+ Array.prototype.forEach.call(eventNodes, function(node) {
650
+ Array.prototype.forEach.call(elements, function(element) {
651
+ var ok = special === "down" ? element.contains(node) : //向下捕获
652
+ node.contains(element) //向上冒泡
653
+
654
+ if (ok) {
655
+ node._avalon = v //符合条件的加一个标识
656
+ }
657
+ });
658
+ })
659
+ }
660
+ }
661
+ }
662
+ var nodes = DOC.getElementsByTagName("*") //实现节点排序
663
+ var alls = []
664
+ Array.prototype.forEach.call(nodes, function(el) {
665
+ if (el._avalon) {
666
+ alls.push(el._avalon)
667
+ el._avalon = ""
668
+ el.removeAttribute("_avalon")
669
+ }
670
+ })
671
+ if (special === "up") {
672
+ alls.reverse()
673
+ }
674
+ for (var i = 0, el; el = alls[i++]; ) {
675
+ if (el.$fire.apply(el, detail) === false) {
676
+ break
677
+ }
678
+ }
679
+ } else {
680
+ var callbacks = events[type] || []
681
+ var all = events.$all || []
682
+ for (var i = 0, callback; callback = callbacks[i++]; ) {
683
+ if (isFunction(callback))
684
+ callback.apply(this, args)
685
+ }
686
+ for (var i = 0, callback; callback = all[i++]; ) {
687
+ if (isFunction(callback))
688
+ callback.apply(this, arguments)
689
+ }
690
+ }
691
+ }
692
+ }
693
+
694
+ var ravalon = /(\w+)\[(avalonctrl)="(\S+)"\]/
695
+ var findNodes = function(str) {
696
+ return DOC.querySelectorAll(str)
697
+ }
698
+ /*********************************************************************
699
+ * modelFactory *
700
+ **********************************************************************/
701
+ //avalon最核心的方法的两个方法之一(另一个是avalon.scan),返回一个ViewModel(VM)
702
+ var VMODELS = avalon.vmodels = {} //所有vmodel都储存在这里
703
+ avalon.define = function(id, factory) {
704
+ var $id = id.$id || id
705
+ if (!$id) {
706
+ log("warning: vm必须指定$id")
707
+ }
708
+ if (VMODELS[$id]) {
709
+ log("warning: " + $id + " 已经存在于avalon.vmodels中")
710
+ }
711
+ if (typeof id === "object") {
712
+ var model = modelFactory(id)
713
+ } else {
714
+ var scope = {
715
+ $watch: noop
716
+ }
717
+ factory(scope) //得到所有定义
718
+ model = modelFactory(scope) //偷天换日,将scope换为model
719
+ stopRepeatAssign = true
720
+ factory(model)
721
+ stopRepeatAssign = false
722
+ }
723
+ model.$id = $id
724
+ return VMODELS[$id] = model
725
+ }
726
+
727
+ //一些不需要被监听的属性
728
+ var $$skipArray = String("$id,$watch,$unwatch,$fire,$events,$model,$skipArray").match(rword)
729
+
730
+ function isObservable(name, value, $skipArray) {
731
+ if (isFunction(value) || value && value.nodeType) {
732
+ return false
733
+ }
734
+ if ($skipArray.indexOf(name) !== -1) {
735
+ return false
736
+ }
737
+ if ($$skipArray.indexOf(name) !== -1) {
738
+ return false
739
+ }
740
+ var $special = $skipArray.$special
741
+ if (name && name.charAt(0) === "$" && !$special[name]) {
742
+ return false
743
+ }
744
+ return true
745
+ }
746
+ //ms-with,ms-each, ms-repeat绑定生成的代理对象储存池
747
+ var midway = {}
748
+ function getNewValue(accessor, name, value, $vmodel) {
749
+ switch (accessor.type) {
750
+ case 0://计算属性
751
+ var getter = accessor.get
752
+ var setter = accessor.set
753
+ if (isFunction(setter)) {
754
+ var $events = $vmodel.$events
755
+ var lock = $events[name]
756
+ $events[name] = [] //清空回调,防止内部冒泡而触发多次$fire
757
+ setter.call($vmodel, value)
758
+ $events[name] = lock
759
+ }
760
+ return getter.call($vmodel) //同步$model
761
+ case 1://监控属性
762
+ return value
763
+ case 2://对象属性(包括数组与哈希)
764
+ if (value !== $vmodel.$model[name]) {
765
+ var svmodel = accessor.svmodel = objectFactory($vmodel, name, value, accessor.valueType)
766
+ value = svmodel.$model //同步$model
767
+ var fn = midway[svmodel.$id]
768
+ fn && fn() //同步视图
769
+ }
770
+ return value
771
+ }
772
+ }
773
+
774
+ var defineProperty = Object.defineProperty
775
+ var canHideOwn = true
776
+ //如果浏览器不支持ecma262v5的Object.defineProperties或者存在BUG,比如IE8
777
+ //标准浏览器使用__defineGetter__, __defineSetter__实现
778
+ try {
779
+ defineProperty({}, "_", {
780
+ value: "x"
781
+ })
782
+ var defineProperties = Object.defineProperties
783
+ } catch (e) {
784
+ canHideOwn = false
785
+ }
786
+ function modelFactory(source, $special, $model) {
787
+ if (Array.isArray(source)) {
788
+ var arr = source.concat()
789
+ source.length = 0
790
+ var collection = Collection(source)
791
+ collection.pushArray(arr)
792
+ return collection
793
+ }
794
+ if (typeof source.nodeType === "number") {
795
+ return source
796
+ }
797
+ if (source.$id && source.$events) { //fix IE6-8 createWithProxy $val: val引发的BUG
798
+ return source
799
+ }
800
+ if (!Array.isArray(source.$skipArray)) {
801
+ source.$skipArray = []
802
+ }
803
+ source.$skipArray.$special = $special || {} //强制要监听的属性
804
+ var $vmodel = {} //要返回的对象, 它在IE6-8下可能被偷龙转凤
805
+ $model = $model || {} //vmodels.$model属性
806
+ var $events = {} //vmodel.$events属性
807
+ var watchedProperties = {} //监控属性
808
+ var initCallbacks = [] //初始化才执行的函数
809
+ for (var i in source) {
810
+ (function(name, val) {
811
+ $model[name] = val
812
+ if (!isObservable(name, val, source.$skipArray)) {
813
+ return //过滤所有非监控属性
814
+ }
815
+ //总共产生三种accessor
816
+ $events[name] = []
817
+ var valueType = avalon.type(val)
818
+ var accessor = function(newValue) {
819
+ var name = accessor._name
820
+ var $vmodel = this
821
+ var $model = $vmodel.$model
822
+ var oldValue = $model[name]
823
+ var $events = $vmodel.$events
824
+
825
+ if (arguments.length) {
826
+ if (stopRepeatAssign) {
827
+ return
828
+ }
829
+ //计算属性与对象属性需要重新计算newValue
830
+ if (accessor.type !== 1) {
831
+ newValue = getNewValue(accessor, name, newValue, $vmodel)
832
+ if (!accessor.type)
833
+ return
834
+ }
835
+ if (!isEqual(oldValue, newValue)) {
836
+ $model[name] = newValue
837
+ if ($events.$digest) {
838
+ if (!accessor.pedding) {
839
+ accessor.pedding = true
840
+ setTimeout(function() {
841
+ notifySubscribers($events[name]) //同步视图
842
+ safeFire($vmodel, name, $model[name], oldValue) //触发$watch回调
843
+ accessor.pedding = false
844
+ })
845
+ }
846
+ } else {
847
+ notifySubscribers($events[name]) //同步视图
848
+ safeFire($vmodel, name, newValue, oldValue) //触发$watch回调
849
+ }
850
+ }
851
+ } else {
852
+ if (accessor.type === 0) { //type 0 计算属性 1 监控属性 2 对象属性
853
+ //计算属性不需要收集视图刷新函数,都是由其他监控属性代劳
854
+ var newValue = accessor.get.call($vmodel)
855
+ if (oldValue !== newValue) {
856
+ $model[name] = newValue
857
+ //这里不用同步视图
858
+ if ($events.$digest) {
859
+ if (!accessor.pedding) {
860
+ accessor.pedding = true
861
+ setTimeout(function() {
862
+ safeFire($vmodel, name, $model[name], oldValue) //触发$watch回调
863
+ accessor.pedding = false
864
+ })
865
+ }
866
+ } else {
867
+ safeFire($vmodel, name, newValue, oldValue) //触发$watch回调
868
+ }
869
+ }
870
+ return newValue
871
+ } else {
872
+ collectSubscribers($events[name]) //收集视图函数
873
+ return accessor.svmodel || oldValue
874
+ }
875
+ }
876
+ }
877
+ //总共产生三种accessor
878
+ if (valueType === "object" && isFunction(val.get) && Object.keys(val).length <= 2) {
879
+ //第1种为计算属性, 因变量,通过其他监控属性触发其改变
880
+ accessor.set = val.set
881
+ accessor.get = val.get
882
+ accessor.type = 0
883
+ initCallbacks.push(function() {
884
+ var data = {
885
+ evaluator: function() {
886
+ data.type = new Date - 0
887
+ data.element = null
888
+ $model[name] = accessor.get.call($vmodel)
889
+ },
890
+ element: head,
891
+ type: new Date - 0,
892
+ handler: noop,
893
+ args: []
894
+ }
895
+ Registry[expose] = data
896
+ accessor.call($vmodel)
897
+ delete Registry[expose]
898
+ })
899
+ } else if (rcomplexType.test(valueType)) {
900
+ //第2种为对象属性,产生子VM与监控数组
901
+ accessor.type = 2
902
+ accessor.valueType = valueType
903
+ initCallbacks.push(function() {
904
+ var svmodel = modelFactory(val, 0, $model[name])
905
+ accessor.svmodel = svmodel
906
+ svmodel.$events[subscribers] = $events[name]
907
+ })
908
+ } else {
909
+ accessor.type = 1
910
+ //第3种为监控属性,对应简单的数据类型,自变量
911
+ }
912
+ accessor._name = name
913
+ watchedProperties[name] = accessor
914
+ })(i, source[i])
915
+ }
916
+
917
+ $$skipArray.forEach(function(name) {
918
+ delete source[name]
919
+ delete $model[name] //这些特殊属性不应该在$model中出现
920
+ })
921
+
922
+ $vmodel = defineProperties($vmodel, descriptorFactory(watchedProperties), source) //生成一个空的ViewModel
923
+ for (var name in source) {
924
+ if (!watchedProperties[name]) {
925
+ $vmodel[name] = source[name]
926
+ }
927
+ }
928
+ //添加$id, $model, $events, $watch, $unwatch, $fire
929
+ $vmodel.$id = generateID()
930
+ $vmodel.$model = $model
931
+ $vmodel.$events = $events
932
+ for (var i in EventBus) {
933
+ var fn = EventBus[i]
934
+ if (!W3C) { //在IE6-8下,VB对象的方法里的this并不指向自身,需要用bind处理一下
935
+ fn = fn.bind($vmodel)
936
+ }
937
+ $vmodel[i] = fn
938
+ }
939
+
940
+ if (canHideOwn) {
941
+ Object.defineProperty($vmodel, "hasOwnProperty", {
942
+ value: function(name) {
943
+ return name in this.$model
944
+ },
945
+ writable: false,
946
+ enumerable: false,
947
+ configurable: true
948
+ })
949
+
950
+ } else {
951
+ $vmodel.hasOwnProperty = function(name) {
952
+ return name in $vmodel.$model
953
+ }
954
+ }
955
+ initCallbacks.forEach(function(cb) { //收集依赖
956
+ cb()
957
+ })
958
+ return $vmodel
959
+ }
960
+
961
+ //比较两个值是否相等
962
+ var isEqual = Object.is || function(v1, v2) {
963
+ if (v1 === 0 && v2 === 0) {
964
+ return 1 / v1 === 1 / v2
965
+ } else if (v1 !== v1) {
966
+ return v2 !== v2
967
+ } else {
968
+ return v1 === v2
969
+ }
970
+ }
971
+
972
+ function safeFire(a, b, c, d) {
973
+ if (a.$events) {
974
+ EventBus.$fire.call(a, b, c, d)
975
+ }
976
+ }
977
+
978
+ var descriptorFactory = W3C ? function(obj) {
979
+ var descriptors = {}
980
+ for (var i in obj) {
981
+ descriptors[i] = {
982
+ get: obj[i],
983
+ set: obj[i],
984
+ enumerable: true,
985
+ configurable: true
986
+ }
987
+ }
988
+ return descriptors
989
+ } : function(a) {
990
+ return a
991
+ }
992
+
993
+
994
+
995
+ //应用于第2种accessor
996
+ function objectFactory(parent, name, value, valueType) {
997
+ //a为原来的VM, b为新数组或新对象
998
+ var son = parent[name]
999
+ if (valueType === "array") {
1000
+ if (!Array.isArray(value) || son === value) {
1001
+ return son //fix https://github.com/RubyLouvre/avalon/issues/261
1002
+ }
1003
+ son._.$unwatch()
1004
+ son.clear()
1005
+ son._.$watch()
1006
+ son.pushArray(value.concat())
1007
+ return son
1008
+ } else {
1009
+ var iterators = parent.$events[name]
1010
+ var pool = son.$events.$withProxyPool
1011
+ if (pool) {
1012
+ recycleProxies(pool, "with")
1013
+ son.$events.$withProxyPool = null
1014
+ }
1015
+ var ret = modelFactory(value)
1016
+ ret.$events[subscribers] = iterators
1017
+ midway[ret.$id] = function(data) {
1018
+ while (data = iterators.shift()) {
1019
+ (function(el) {
1020
+ avalon.nextTick(function() {
1021
+ if (el.type) { //重新绑定
1022
+ el.rollback && el.rollback() //还原 ms-with ms-on
1023
+ bindingHandlers[el.type](el, el.vmodels)
1024
+ }
1025
+ })
1026
+ })(data)
1027
+ }
1028
+ delete midway[ret.$id]
1029
+ }
1030
+ return ret
1031
+ }
1032
+ }
1033
+ /*********************************************************************
1034
+ * 监控数组(与ms-each, ms-repeat配合使用) *
1035
+ **********************************************************************/
1036
+
1037
+ function Collection(model) {
1038
+ var array = []
1039
+ array.$id = generateID()
1040
+ array.$model = model //数据模型
1041
+ array.$events = {}
1042
+ array.$events[subscribers] = []
1043
+ array._ = modelFactory({
1044
+ length: model.length
1045
+ })
1046
+ array._.$watch("length", function(a, b) {
1047
+ array.$fire("length", a, b)
1048
+ })
1049
+ for (var i in EventBus) {
1050
+ array[i] = EventBus[i]
1051
+ }
1052
+ avalon.mix(array, CollectionPrototype)
1053
+ return array
1054
+ }
1055
+
1056
+ function mutateArray(method, pos, n, index, method2, pos2, n2) {
1057
+ var oldLen = this.length, loop = 2
1058
+ while (--loop) {
1059
+ switch (method) {
1060
+ case "add":
1061
+ var array = this.$model.slice(pos, pos + n).map(function(el) {
1062
+ if (rcomplexType.test(avalon.type(el))) {
1063
+ return el.$id ? el : modelFactory(el, 0, el)
1064
+ } else {
1065
+ return el
1066
+ }
1067
+ })
1068
+ _splice.apply(this, [pos, 0].concat(array))
1069
+ this._fire("add", pos, n)
1070
+ break
1071
+ case "del":
1072
+ var ret = this._splice(pos, n)
1073
+ this._fire("del", pos, n)
1074
+ break
1075
+ }
1076
+ if (method2) {
1077
+ method = method2
1078
+ pos = pos2
1079
+ n = n2
1080
+ loop = 2
1081
+ method2 = 0
1082
+ }
1083
+ }
1084
+ this._fire("index", index)
1085
+ if (this.length !== oldLen) {
1086
+ this._.length = this.length
1087
+ }
1088
+ return ret
1089
+ }
1090
+
1091
+ var _splice = ap.splice
1092
+ var CollectionPrototype = {
1093
+ _splice: _splice,
1094
+ _fire: function(method, a, b) {
1095
+ notifySubscribers(this.$events[subscribers], method, a, b)
1096
+ },
1097
+ size: function() { //取得数组长度,这个函数可以同步视图,length不能
1098
+ return this._.length
1099
+ },
1100
+ pushArray: function(array) {
1101
+ var m = array.length, n = this.length
1102
+ if (m) {
1103
+ ap.push.apply(this.$model, array)
1104
+ mutateArray.call(this, "add", n, m, n)
1105
+ }
1106
+ return m + n
1107
+ },
1108
+ push: function() {
1109
+ //http://jsperf.com/closure-with-arguments
1110
+ var array = []
1111
+ var i, n = arguments.length
1112
+ for (i = 0; i < n; i++) {
1113
+ array[i] = arguments[i]
1114
+ }
1115
+ return this.pushArray(arguments)
1116
+ },
1117
+ unshift: function() {
1118
+ var m = arguments.length, n = this.length
1119
+ if (m) {
1120
+ ap.unshift.apply(this.$model, arguments)
1121
+ mutateArray.call(this, "add", 0, m, 0)
1122
+ }
1123
+ return m + n //IE67的unshift不会返回长度
1124
+ },
1125
+ shift: function() {
1126
+ if (this.length) {
1127
+ var el = this.$model.shift()
1128
+ mutateArray.call(this, "del", 0, 1, 0)
1129
+ return el //返回被移除的元素
1130
+ }
1131
+ },
1132
+ pop: function() {
1133
+ var m = this.length
1134
+ if (m) {
1135
+ var el = this.$model.pop()
1136
+ mutateArray.call(this, "del", m - 1, 1, Math.max(0, m - 2))
1137
+ return el //返回被移除的元素
1138
+ }
1139
+ },
1140
+ splice: function(start) {
1141
+ var m = arguments.length, args = [], change
1142
+ var removed = _splice.apply(this.$model, arguments)
1143
+ if (removed.length) { //如果用户删掉了元素
1144
+ args.push("del", start, removed.length, 0)
1145
+ change = true
1146
+ }
1147
+ if (m > 2) { //如果用户添加了元素
1148
+ if (change) {
1149
+ args.splice(3, 1, 0, "add", start, m - 2)
1150
+ } else {
1151
+ args.push("add", start, m - 2, 0)
1152
+ }
1153
+ change = true
1154
+ }
1155
+ if (change) { //返回被移除的元素
1156
+ return mutateArray.apply(this, args)
1157
+ } else {
1158
+ return []
1159
+ }
1160
+ },
1161
+ contains: function(el) { //判定是否包含
1162
+ return this.indexOf(el) !== -1
1163
+ },
1164
+ remove: function(el) { //移除第一个等于给定值的元素
1165
+ return this.removeAt(this.indexOf(el))
1166
+ },
1167
+ removeAt: function(index) { //移除指定索引上的元素
1168
+ if (index >= 0) {
1169
+ this.$model.splice(index, 1)
1170
+ return mutateArray.call(this, "del", index, 1, 0)
1171
+ }
1172
+ return []
1173
+ },
1174
+ clear: function() {
1175
+ this.$model.length = this.length = this._.length = 0 //清空数组
1176
+ this._fire("clear", 0)
1177
+ return this
1178
+ },
1179
+ removeAll: function(all) { //移除N个元素
1180
+ if (Array.isArray(all)) {
1181
+ all.forEach(function(el) {
1182
+ this.remove(el)
1183
+ }, this)
1184
+ } else if (typeof all === "function") {
1185
+ for (var i = this.length - 1; i >= 0; i--) {
1186
+ var el = this[i]
1187
+ if (all(el, i)) {
1188
+ this.removeAt(i)
1189
+ }
1190
+ }
1191
+ } else {
1192
+ this.clear()
1193
+ }
1194
+ },
1195
+ ensure: function(el) {
1196
+ if (!this.contains(el)) { //只有不存在才push
1197
+ this.push(el)
1198
+ }
1199
+ return this
1200
+ },
1201
+ set: function(index, val) {
1202
+ if (index >= 0) {
1203
+ var valueType = avalon.type(val)
1204
+ if (val && val.$model) {
1205
+ val = val.$model
1206
+ }
1207
+ var target = this[index]
1208
+ if (valueType === "object") {
1209
+ for (var i in val) {
1210
+ if (target.hasOwnProperty(i)) {
1211
+ target[i] = val[i]
1212
+ }
1213
+ }
1214
+ } else if (valueType === "array") {
1215
+ target.clear().push.apply(target, val)
1216
+ } else if (target !== val) {
1217
+ this[index] = val
1218
+ this.$model[index] = val
1219
+ this._fire("set", index, val)
1220
+ }
1221
+ }
1222
+ return this
1223
+ }
1224
+ }
1225
+
1226
+ function sortByIndex(array, indexes) {
1227
+ var map = {};
1228
+ for (var i = 0, n = indexes.length; i < n; i++) {
1229
+ map[i] = array[i] // preserve
1230
+ var j = indexes[i]
1231
+ if (j in map) {
1232
+ array[i] = map[j]
1233
+ delete map[j]
1234
+ } else {
1235
+ array[i] = array[j]
1236
+ }
1237
+ }
1238
+ }
1239
+
1240
+ "sort,reverse".replace(rword, function(method) {
1241
+ CollectionPrototype[method] = function() {
1242
+ var newArray = this.$model//这是要排序的新数组
1243
+ var oldArray = newArray.concat() //保持原来状态的旧数组
1244
+ var mask = Math.random()
1245
+ var indexes = []
1246
+ var hasSort
1247
+ ap[method].apply(newArray, arguments) //排序
1248
+ for (var i = 0, n = oldArray.length; i < n; i++) {
1249
+ var neo = newArray[i]
1250
+ var old = oldArray[i]
1251
+ if (isEqual(neo, old)) {
1252
+ indexes.push(i)
1253
+ } else {
1254
+ var index = oldArray.indexOf(neo)
1255
+ indexes.push(index)//得到新数组的每个元素在旧数组对应的位置
1256
+ oldArray[index] = mask //屏蔽已经找过的元素
1257
+ hasSort = true
1258
+ }
1259
+ }
1260
+ if (hasSort) {
1261
+ sortByIndex(this, indexes)
1262
+ this._fire("move", indexes)
1263
+ this._fire("index", 0)
1264
+ }
1265
+ return this
1266
+ }
1267
+ })
1268
+
1269
+ /*********************************************************************
1270
+ * 依赖调度系统 *
1271
+ **********************************************************************/
1272
+ var ronduplex = /^(duplex|on)$/
1273
+
1274
+ function registerSubscriber(data) {
1275
+ Registry[expose] = data //暴光此函数,方便collectSubscribers收集
1276
+ avalon.openComputedCollect = true
1277
+ var fn = data.evaluator
1278
+ if (fn) { //如果是求值函数
1279
+ try {
1280
+ var c = ronduplex.test(data.type) ? data : fn.apply(0, data.args)
1281
+ data.handler(c, data.element, data)
1282
+ } catch (e) {
1283
+ //log("warning:exception throwed in [registerSubscriber] " + e)
1284
+ delete data.evaluator
1285
+ var node = data.element
1286
+ if (node.nodeType === 3) {
1287
+ var parent = node.parentNode
1288
+ if (kernel.commentInterpolate) {
1289
+ parent.replaceChild(DOC.createComment(data.value), node)
1290
+ } else {
1291
+ node.data = openTag + data.value + closeTag
1292
+ }
1293
+ }
1294
+ }
1295
+ }
1296
+ avalon.openComputedCollect = false
1297
+ delete Registry[expose]
1298
+ }
1299
+
1300
+ function collectSubscribers(list) { //收集依赖于这个访问器的订阅者
1301
+ var data = Registry[expose]
1302
+ if (list && data && avalon.Array.ensure(list, data) && data.element) { //只有数组不存在此元素才push进去
1303
+ addSubscribers(data, list)
1304
+ }
1305
+ }
1306
+
1307
+
1308
+ function addSubscribers(data, list) {
1309
+ data.$uuid = data.$uuid || generateID()
1310
+ list.$uuid = list.$uuid || generateID()
1311
+ var obj = {
1312
+ data: data,
1313
+ list: list,
1314
+ $$uuid: data.$uuid + list.$uuid
1315
+ }
1316
+ if (!$$subscribers[obj.$$uuid]) {
1317
+ $$subscribers[obj.$$uuid] = 1
1318
+ $$subscribers.push(obj)
1319
+ }
1320
+ }
1321
+
1322
+ function disposeData(data) {
1323
+ data.element = null
1324
+ data.rollback && data.rollback()
1325
+ for (var key in data) {
1326
+ data[key] = null
1327
+ }
1328
+ }
1329
+
1330
+ function isRemove(el) {
1331
+ try {//IE下,如果文本节点脱离DOM树,访问parentNode会报错
1332
+ if (!el.parentNode) {
1333
+ return true
1334
+ }
1335
+ } catch (e) {
1336
+ return true
1337
+ }
1338
+ return el.msRetain ? 0 : (el.nodeType === 1 ? typeof el.sourceIndex === "number" ?
1339
+ el.sourceIndex === 0 : !root.contains(el) : !avalon.contains(root, el))
1340
+ }
1341
+ var $$subscribers = avalon.$$subscribers = []
1342
+ var beginTime = new Date()
1343
+ var oldInfo = {}
1344
+ function removeSubscribers() {
1345
+ var i = $$subscribers.length
1346
+ var n = i
1347
+ var k = 0
1348
+ var obj
1349
+ var types = []
1350
+ var newInfo = {}
1351
+ var needTest = {}
1352
+ while (obj = $$subscribers[--i]) {
1353
+ var data = obj.data
1354
+ var type = data.type
1355
+ if (newInfo[type]) {
1356
+ newInfo[type]++
1357
+ } else {
1358
+ newInfo[type] = 1
1359
+ types.push(type)
1360
+ }
1361
+ }
1362
+ var diff = false
1363
+ types.forEach(function(type) {
1364
+ if (oldInfo[type] !== newInfo[type]) {
1365
+ needTest[type] = 1
1366
+ diff = true
1367
+ }
1368
+ })
1369
+ i = n
1370
+ //avalon.log("需要检测的个数 " + i)
1371
+ if (diff) {
1372
+ //avalon.log("有需要移除的元素")
1373
+ while (obj = $$subscribers[--i]) {
1374
+ var data = obj.data
1375
+ if (data.element === void 0)
1376
+ continue
1377
+ if (needTest[data.type] && isRemove(data.element)) { //如果它没有在DOM树
1378
+ k++
1379
+ $$subscribers.splice(i, 1)
1380
+ delete $$subscribers[obj.$$uuid]
1381
+ avalon.Array.remove(obj.list, data)
1382
+ //log("debug: remove " + data.type)
1383
+ disposeData(data)
1384
+ obj.data = obj.list = null
1385
+ }
1386
+ }
1387
+ }
1388
+ oldInfo = newInfo
1389
+ // avalon.log("已经移除的个数 " + k)
1390
+ beginTime = new Date()
1391
+ }
1392
+
1393
+ function notifySubscribers(list) { //通知依赖于这个访问器的订阅者更新自身
1394
+ if (list && list.length) {
1395
+ if (new Date() - beginTime > 444 && typeof list[0] === "object") {
1396
+ removeSubscribers()
1397
+ }
1398
+ var args = aslice.call(arguments, 1)
1399
+ for (var i = list.length, fn; fn = list[--i]; ) {
1400
+ var el = fn.element
1401
+ if (el && el.parentNode) {
1402
+ if (fn.$repeat) {
1403
+ fn.handler.apply(fn, args) //处理监控数组的方法
1404
+ } else if (fn.type !== "on") { //事件绑定只能由用户触发,不能由程序触发
1405
+ var fun = fn.evaluator || noop
1406
+ fn.handler(fun.apply(0, fn.args || []), el, fn)
1407
+ }
1408
+ }
1409
+ }
1410
+ }
1411
+ }
1412
+
1413
+ /************************************************************************
1414
+ * HTML处理(parseHTML, innerHTML, clearHTML) *
1415
+ **************************************************************************/
1416
+ //parseHTML的辅助变量
1417
+ var tagHooks = new function() {
1418
+ avalon.mix(this, {
1419
+ option: DOC.createElement("select"),
1420
+ thead: DOC.createElement("table"),
1421
+ td: DOC.createElement("tr"),
1422
+ area: DOC.createElement("map"),
1423
+ tr: DOC.createElement("tbody"),
1424
+ col: DOC.createElement("colgroup"),
1425
+ legend: DOC.createElement("fieldset"),
1426
+ _default: DOC.createElement("div"),
1427
+ "g": DOC.createElementNS("http://www.w3.org/2000/svg", "svg")
1428
+ })
1429
+ this.optgroup = this.option
1430
+ this.tbody = this.tfoot = this.colgroup = this.caption = this.thead
1431
+ this.th = this.td
1432
+ }
1433
+
1434
+ String("circle,defs,ellipse,image,line,path,polygon,polyline,rect,symbol,text,use").replace(rword, function(tag) {
1435
+ tagHooks[tag] = tagHooks.g //处理SVG
1436
+ })
1437
+ var rtagName = /<([\w:]+)/
1438
+ var rxhtml = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig
1439
+ var scriptTypes = oneObject(["", "text/javascript", "text/ecmascript", "application/ecmascript", "application/javascript"])
1440
+ var script = DOC.createElement("script")
1441
+
1442
+ avalon.parseHTML = function(html) {
1443
+ if (typeof html !== "string" ) {
1444
+ return DOC.createDocumentFragment()
1445
+ }
1446
+ html = html.replace(rxhtml, "<$1></$2>").trim()
1447
+ var tag = (rtagName.exec(html) || ["", ""])[1].toLowerCase(),
1448
+ //取得其标签名
1449
+ wrapper = tagHooks[tag] || tagHooks._default,
1450
+ fragment = hyperspace.cloneNode(false),
1451
+ firstChild
1452
+ wrapper.innerHTML = html
1453
+ var els = wrapper.getElementsByTagName("script")
1454
+ if (els.length) { //使用innerHTML生成的script节点不会发出请求与执行text属性
1455
+ for (var i = 0, el; el = els[i++]; ) {
1456
+ if (scriptTypes[el.type]) {
1457
+ var neo = script.cloneNode(false) //FF不能省略参数
1458
+ ap.forEach.call(el.attributes, function(attr) {
1459
+ neo.setAttribute(attr.name, attr.value)
1460
+ })
1461
+ neo.text = el.text
1462
+ el.parentNode.replaceChild(neo, el)
1463
+ }
1464
+ }
1465
+ }
1466
+
1467
+ while (firstChild = wrapper.firstChild) { // 将wrapper上的节点转移到文档碎片上!
1468
+ fragment.appendChild(firstChild)
1469
+ }
1470
+ return fragment
1471
+ }
1472
+
1473
+ avalon.innerHTML = function(node, html) {
1474
+ var a = this.parseHTML(html)
1475
+ this.clearHTML(node).appendChild(a)
1476
+ }
1477
+
1478
+ avalon.clearHTML = function(node) {
1479
+ node.textContent = ""
1480
+ while (node.firstChild) {
1481
+ node.removeChild(node.firstChild)
1482
+ }
1483
+ return node
1484
+ }
1485
+
1486
+ /*********************************************************************
1487
+ * 扫描系统 *
1488
+ **********************************************************************/
1489
+
1490
+ avalon.scan = function(elem, vmodel, group) {
1491
+ elem = elem || root
1492
+ var vmodels = vmodel ? [].concat(vmodel) : []
1493
+ scanTag(elem, vmodels)
1494
+ }
1495
+
1496
+ //http://www.w3.org/TR/html5/syntax.html#void-elements
1497
+ var stopScan = oneObject("area,base,basefont,br,col,command,embed,hr,img,input,link,meta,param,source,track,wbr,noscript,script,style,textarea".toUpperCase())
1498
+
1499
+ function checkScan(elem, callback, innerHTML) {
1500
+ var id = setTimeout(function() {
1501
+ var currHTML = elem.innerHTML
1502
+ clearTimeout(id)
1503
+ if (currHTML === innerHTML) {
1504
+ callback()
1505
+ } else {
1506
+ checkScan(elem, callback, currHTML)
1507
+ }
1508
+ })
1509
+ }
1510
+
1511
+
1512
+ function createSignalTower(elem, vmodel) {
1513
+ var id = elem.getAttribute("avalonctrl") || vmodel.$id
1514
+ elem.setAttribute("avalonctrl", id)
1515
+ vmodel.$events.expr = elem.tagName + '[avalonctrl="' + id + '"]'
1516
+ }
1517
+
1518
+ var getBindingCallback = function(elem, name, vmodels) {
1519
+ var callback = elem.getAttribute(name)
1520
+ if (callback) {
1521
+ for (var i = 0, vm; vm = vmodels[i++]; ) {
1522
+ if (vm.hasOwnProperty(callback) && typeof vm[callback] === "function") {
1523
+ return vm[callback]
1524
+ }
1525
+ }
1526
+ }
1527
+ }
1528
+
1529
+ function executeBindings(bindings, vmodels) {
1530
+ for (var i = 0, data; data = bindings[i++]; ) {
1531
+ data.vmodels = vmodels
1532
+ bindingHandlers[data.type](data, vmodels)
1533
+ if (data.evaluator && data.element && data.element.nodeType === 1) { //移除数据绑定,防止被二次解析
1534
+ //chrome使用removeAttributeNode移除不存在的特性节点时会报错 https://github.com/RubyLouvre/avalon/issues/99
1535
+ data.element.removeAttribute(data.name)
1536
+ }
1537
+ }
1538
+ bindings.length = 0
1539
+ }
1540
+
1541
+ //https://github.com/RubyLouvre/avalon/issues/636
1542
+ var mergeTextNodes = IEVersion && window.MutationObserver ? function (elem) {
1543
+ var node = elem.firstChild, text
1544
+ while (node) {
1545
+ var aaa = node.nextSibling
1546
+ if (node.nodeType === 3) {
1547
+ if (text) {
1548
+ text.nodeValue += node.nodeValue
1549
+ elem.removeChild(node)
1550
+ } else {
1551
+ text = node
1552
+ }
1553
+ } else {
1554
+ text = null
1555
+ }
1556
+ node = aaa
1557
+ }
1558
+ } : 0
1559
+
1560
+ var rmsAttr = /ms-(\w+)-?(.*)/
1561
+ var priorityMap = {
1562
+ "if": 10,
1563
+ "repeat": 90,
1564
+ "data": 100,
1565
+ "widget": 110,
1566
+ "each": 1400,
1567
+ "with": 1500,
1568
+ "duplex": 2000,
1569
+ "on": 3000
1570
+ }
1571
+
1572
+ var events = oneObject("animationend,blur,change,input,click,dblclick,focus,keydown,keypress,keyup,mousedown,mouseenter,mouseleave,mousemove,mouseout,mouseover,mouseup,scan,scroll,submit")
1573
+ var obsoleteAttrs = oneObject("value,title,alt,checked,selected,disabled,readonly,enabled")
1574
+ function bindingSorter(a, b) {
1575
+ return a.priority - b.priority
1576
+ }
1577
+
1578
+ function scanTag(elem, vmodels, node) {
1579
+ //扫描顺序 ms-skip(0) --> ms-important(1) --> ms-controller(2) --> ms-if(10) --> ms-repeat(100)
1580
+ //--> ms-if-loop(110) --> ms-attr(970) ...--> ms-each(1400)-->ms-with(1500)--〉ms-duplex(2000)垫后
1581
+ var a = elem.getAttribute("ms-skip")
1582
+ var b = elem.getAttributeNode("ms-important")
1583
+ var c = elem.getAttributeNode("ms-controller")
1584
+ if (typeof a === "string") {
1585
+ return
1586
+ } else if (node = b || c) {
1587
+ var newVmodel = avalon.vmodels[node.value]
1588
+ if (!newVmodel) {
1589
+ return
1590
+ }
1591
+ //ms-important不包含父VM,ms-controller相反
1592
+ vmodels = node === b ? [newVmodel] : [newVmodel].concat(vmodels)
1593
+ elem.removeAttribute(node.name) //removeAttributeNode不会刷新[ms-controller]样式规则
1594
+ elem.classList.remove(node.name)
1595
+ createSignalTower(elem, newVmodel)
1596
+ }
1597
+ scanAttr(elem, vmodels) //扫描特性节点
1598
+ }
1599
+ function scanNodeList(parent, vmodels) {
1600
+ var node = parent.firstChild
1601
+ while (node) {
1602
+ var nextNode = node.nextSibling
1603
+ scanNode(node, node.nodeType, vmodels)
1604
+ node = nextNode
1605
+ }
1606
+ }
1607
+
1608
+ function scanNodeArray(nodes, vmodels) {
1609
+ for (var i = 0, node; node = nodes[i++]; ) {
1610
+ scanNode(node, node.nodeType, vmodels)
1611
+ }
1612
+ }
1613
+ function scanNode(node, nodeType, vmodels) {
1614
+ if (nodeType === 1) {
1615
+ scanTag(node, vmodels) //扫描元素节点
1616
+ } else if (nodeType === 3 && rexpr.test(node.data)){
1617
+ scanText(node, vmodels) //扫描文本节点
1618
+ } else if (kernel.commentInterpolate && nodeType === 8 && !rexpr.test(node.nodeValue)) {
1619
+ scanText(node, vmodels) //扫描注释节点
1620
+ }
1621
+ }
1622
+ function scanAttr(elem, vmodels) {
1623
+ //防止setAttribute, removeAttribute时 attributes自动被同步,导致for循环出错
1624
+ var attributes = elem.hasAttributes() ? avalon.slice(elem.attributes) : []
1625
+ var bindings = [],
1626
+ msData = {},
1627
+ match
1628
+ for (var i = 0, attr; attr = attributes[i++]; ) {
1629
+ if (attr.specified) {
1630
+ if (match = attr.name.match(rmsAttr)) {
1631
+ //如果是以指定前缀命名的
1632
+ var type = match[1]
1633
+ var param = match[2] || ""
1634
+ var value = attr.value
1635
+ var name = attr.name
1636
+ msData[name] = value
1637
+ if (events[type]) {
1638
+ param = type
1639
+ type = "on"
1640
+ } else if (obsoleteAttrs[type]) {
1641
+ log("ms-" + type + "已经被废弃,请使用ms-attr-*代替")
1642
+ if (type === "enabled") {//吃掉ms-enabled绑定,用ms-disabled代替
1643
+ type = "disabled"
1644
+ value = "!(" + value + ")"
1645
+ }
1646
+ param = type
1647
+ type = "attr"
1648
+ elem.removeAttribute(name)
1649
+ name = "ms-attr-" + param
1650
+ elem.setAttribute(name, value)
1651
+ match = [name]
1652
+ msData[name] = value
1653
+ }
1654
+ if (typeof bindingHandlers[type] === "function") {
1655
+ var binding = {
1656
+ type: type,
1657
+ param: param,
1658
+ element: elem,
1659
+ name: match[0],
1660
+ value: value,
1661
+ priority: type in priorityMap ? priorityMap[type] : type.charCodeAt(0) * 10 + (Number(param) || 0)
1662
+ }
1663
+ if (type === "html" || type === "text") {
1664
+ var token = getToken(value)
1665
+ avalon.mix(binding, token)
1666
+ binding.filters = binding.filters.replace(rhasHtml, function() {
1667
+ binding.type = "html"
1668
+ binding.group = 1
1669
+ return ""
1670
+ })
1671
+ }
1672
+ if (name === "ms-if-loop") {
1673
+ binding.priority += 100
1674
+ }
1675
+ if (vmodels.length) {
1676
+ bindings.push(binding)
1677
+ if (type === "widget") {
1678
+ elem.msData = elem.msData || msData
1679
+ }
1680
+ }
1681
+ }
1682
+ }
1683
+ }
1684
+ }
1685
+ if (msData["ms-attr-checked"] && msData["ms-duplex"]) {
1686
+ log("warning!一个元素上不能同时定义ms-attr-checked与ms-duplex")
1687
+ }
1688
+ bindings.sort(bindingSorter)
1689
+ var scanNode = true
1690
+ for (var i = 0, binding; binding = bindings[i]; i++) {
1691
+ var type = binding.type
1692
+ if (rnoscanAttrBinding.test(type)) {
1693
+ return executeBindings(bindings.slice(0, i + 1), vmodels)
1694
+ } else if (scanNode) {
1695
+ scanNode = !rnoscanNodeBinding.test(type)
1696
+ }
1697
+ }
1698
+ executeBindings(bindings, vmodels)
1699
+ if (scanNode && !stopScan[elem.tagName] && rbind.test(elem.innerHTML + elem.textContent)) {
1700
+ mergeTextNodes && mergeTextNodes(elem)
1701
+ scanNodeList(elem, vmodels) //扫描子孙元素
1702
+ }
1703
+ }
1704
+
1705
+ var rnoscanAttrBinding = /^if|widget|repeat$/
1706
+ var rnoscanNodeBinding = /^each|with|html|include$/
1707
+ var rhasHtml = /\|\s*html\s*/,
1708
+ r11a = /\|\|/g,
1709
+ rlt = /&lt;/g,
1710
+ rgt = /&gt;/g
1711
+
1712
+ function getToken(value) {
1713
+ if (value.indexOf("|") > 0) {
1714
+ var index = value.replace(r11a, "\u1122\u3344").indexOf("|") //干掉所有短路或
1715
+ if (index > -1) {
1716
+ return {
1717
+ filters: value.slice(index),
1718
+ value: value.slice(0, index),
1719
+ expr: true
1720
+ }
1721
+ }
1722
+ }
1723
+ return {
1724
+ value: value,
1725
+ filters: "",
1726
+ expr: true
1727
+ }
1728
+ }
1729
+
1730
+ function scanExpr(str) {
1731
+ var tokens = [],
1732
+ value, start = 0,
1733
+ stop
1734
+ do {
1735
+ stop = str.indexOf(openTag, start)
1736
+ if (stop === -1) {
1737
+ break
1738
+ }
1739
+ value = str.slice(start, stop)
1740
+ if (value) { // {{ 左边的文本
1741
+ tokens.push({
1742
+ value: value,
1743
+ filters: "",
1744
+ expr: false
1745
+ })
1746
+ }
1747
+ start = stop + openTag.length
1748
+ stop = str.indexOf(closeTag, start)
1749
+ if (stop === -1) {
1750
+ break
1751
+ }
1752
+ value = str.slice(start, stop)
1753
+ if (value) { //处理{{ }}插值表达式
1754
+ tokens.push(getToken(value))
1755
+ }
1756
+ start = stop + closeTag.length
1757
+ } while (1)
1758
+ value = str.slice(start)
1759
+ if (value) { //}} 右边的文本
1760
+ tokens.push({
1761
+ value: value,
1762
+ expr: false,
1763
+ filters: ""
1764
+ })
1765
+ }
1766
+ return tokens
1767
+ }
1768
+
1769
+ function scanText(textNode, vmodels) {
1770
+ var bindings = []
1771
+ if (textNode.nodeType === 8) {
1772
+ var token = getToken(textNode.nodeValue)
1773
+ var tokens = [token]
1774
+ } else {
1775
+ tokens = scanExpr(textNode.data)
1776
+ }
1777
+ if (tokens.length) {
1778
+ for (var i = 0, token; token = tokens[i++]; ) {
1779
+ var node = DOC.createTextNode(token.value) //将文本转换为文本节点,并替换原来的文本节点
1780
+ if (token.expr) {
1781
+ token.type = "text"
1782
+ token.element = node
1783
+ token.filters = token.filters.replace(rhasHtml, function() {
1784
+ token.type = "html"
1785
+ token.group = 1
1786
+ return ""
1787
+ })
1788
+ bindings.push(token) //收集带有插值表达式的文本
1789
+ }
1790
+ hyperspace.appendChild(node)
1791
+ }
1792
+ textNode.parentNode.replaceChild(hyperspace, textNode)
1793
+ if (bindings.length)
1794
+ executeBindings(bindings, vmodels)
1795
+ }
1796
+ }
1797
+
1798
+
1799
+ /*********************************************************************
1800
+ * avalon的原型方法定义区 *
1801
+ **********************************************************************/
1802
+ function hyphen(target) {
1803
+ //转换为连字符线风格
1804
+ return target.replace(/([a-z\d])([A-Z]+)/g, "$1-$2").toLowerCase()
1805
+ }
1806
+ function camelize(target) {
1807
+ //转换为驼峰风格
1808
+ if (target.indexOf("-") < 0 && target.indexOf("_") < 0) {
1809
+ return target //提前判断,提高getStyle等的效率
1810
+ }
1811
+ return target.replace(/[-_][^-_]/g, function(match) {
1812
+ return match.charAt(1).toUpperCase()
1813
+ })
1814
+ }
1815
+
1816
+ "add,remove".replace(rword, function(method) {
1817
+ avalon.fn[method + "Class"] = function(cls) {
1818
+ var el = this[0]
1819
+ //https://developer.mozilla.org/zh-CN/docs/Mozilla/Firefox/Releases/26
1820
+ if (cls && typeof cls === "string" && el && el.nodeType === 1) {
1821
+ cls.replace(/\S+/g, function(c) {
1822
+ el.classList[method](c)
1823
+ })
1824
+ }
1825
+ return this
1826
+ }
1827
+ })
1828
+
1829
+ avalon.fn.mix({
1830
+ hasClass: function(cls) {
1831
+ var el = this[0] || {} //IE10+, chrome8+, firefox3.6+, safari5.1+,opera11.5+支持classList,chrome24+,firefox26+支持classList2.0
1832
+ return el.nodeType === 1 && el.classList.contains(cls)
1833
+ },
1834
+ toggleClass: function(value, stateVal) {
1835
+ var className, i = 0
1836
+ var classNames = value.split(/\s+/)
1837
+ var isBool = typeof stateVal === "boolean"
1838
+ while ((className = classNames[i++])) {
1839
+ var state = isBool ? stateVal : !this.hasClass(className)
1840
+ this[state ? "addClass" : "removeClass"](className)
1841
+ }
1842
+ return this
1843
+ },
1844
+ attr: function(name, value) {
1845
+ if (arguments.length === 2) {
1846
+ this[0].setAttribute(name, value)
1847
+ return this
1848
+ } else {
1849
+ return this[0].getAttribute(name)
1850
+ }
1851
+ },
1852
+ data: function(name, value) {
1853
+ name = "data-" + hyphen(name || "")
1854
+ switch (arguments.length) {
1855
+ case 2:
1856
+ this.attr(name, value)
1857
+ return this
1858
+ case 1:
1859
+ var val = this.attr(name)
1860
+ return parseData(val)
1861
+ case 0:
1862
+ var ret = {}
1863
+ ap.forEach.call(this[0].attributes, function(attr) {
1864
+ if (attr) {
1865
+ name = attr.name
1866
+ if (!name.indexOf("data-")) {
1867
+ name = camelize(name.slice(5))
1868
+ ret[name] = parseData(attr.value)
1869
+ }
1870
+ }
1871
+ })
1872
+ return ret
1873
+ }
1874
+ },
1875
+ removeData: function(name) {
1876
+ name = "data-" + hyphen(name)
1877
+ this[0].removeAttribute(name)
1878
+ return this
1879
+ },
1880
+ css: function(name, value) {
1881
+ if (avalon.isPlainObject(name)) {
1882
+ for (var i in name) {
1883
+ avalon.css(this, i, name[i])
1884
+ }
1885
+ } else {
1886
+ var ret = avalon.css(this, name, value)
1887
+ }
1888
+ return ret !== void 0 ? ret : this
1889
+ },
1890
+ position: function() {
1891
+ var offsetParent, offset,
1892
+ elem = this[0],
1893
+ parentOffset = {
1894
+ top: 0,
1895
+ left: 0
1896
+ };
1897
+ if (!elem) {
1898
+ return
1899
+ }
1900
+ if (this.css("position") === "fixed") {
1901
+ offset = elem.getBoundingClientRect()
1902
+ } else {
1903
+ offsetParent = this.offsetParent() //得到真正的offsetParent
1904
+ offset = this.offset() // 得到正确的offsetParent
1905
+ if (offsetParent[0].tagName !== "HTML") {
1906
+ parentOffset = offsetParent.offset()
1907
+ }
1908
+ parentOffset.top += avalon.css(offsetParent[0], "borderTopWidth", true)
1909
+ parentOffset.left += avalon.css(offsetParent[0], "borderLeftWidth", true)
1910
+ }
1911
+ return {
1912
+ top: offset.top - parentOffset.top - avalon.css(elem, "marginTop", true),
1913
+ left: offset.left - parentOffset.left - avalon.css(elem, "marginLeft", true)
1914
+ }
1915
+ },
1916
+ offsetParent: function() {
1917
+ var offsetParent = this[0].offsetParent
1918
+ while (offsetParent && avalon.css(offsetParent, "position") === "static") {
1919
+ offsetParent = offsetParent.offsetParent;
1920
+ }
1921
+ return avalon(offsetParent)
1922
+ },
1923
+ bind: function(type, fn, phase) {
1924
+ if (this[0]) { //此方法不会链
1925
+ return avalon.bind(this[0], type, fn, phase)
1926
+ }
1927
+ },
1928
+ unbind: function(type, fn, phase) {
1929
+ if (this[0]) {
1930
+ avalon.unbind(this[0], type, fn, phase)
1931
+ }
1932
+ return this
1933
+ },
1934
+ val: function(value) {
1935
+ var node = this[0]
1936
+ if (node && node.nodeType === 1) {
1937
+ var get = arguments.length === 0
1938
+ var access = get ? ":get" : ":set"
1939
+ var fn = valHooks[getValType(node) + access]
1940
+ if (fn) {
1941
+ var val = fn(node, value)
1942
+ } else if (get) {
1943
+ return (node.value || "").replace(/\r/g, "")
1944
+ } else {
1945
+ node.value = value
1946
+ }
1947
+ }
1948
+ return get ? val : this
1949
+ }
1950
+ })
1951
+
1952
+ if (root.dataset) {
1953
+ avalon.fn.data = function(name, val) {
1954
+ var dataset = this[0].dataset
1955
+ switch (arguments.length) {
1956
+ case 2:
1957
+ dataset[name] = val
1958
+ return this
1959
+ case 1:
1960
+ val = dataset[name]
1961
+ return parseData(val)
1962
+ case 0:
1963
+ var ret = {}
1964
+ for (var name in dataset) {
1965
+ ret[name] = parseData(dataset[name])
1966
+ }
1967
+ return ret
1968
+ }
1969
+ }
1970
+ }
1971
+ var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/
1972
+ avalon.parseJSON = JSON.parse
1973
+
1974
+ function parseData(data) {
1975
+ try {
1976
+ if (typeof data === "object")
1977
+ return data
1978
+ data = data === "true" ? true :
1979
+ data === "false" ? false :
1980
+ data === "null" ? null : +data + "" === data ? +data : rbrace.test(data) ? JSON.parse(data) : data
1981
+ } catch (e) {
1982
+ }
1983
+ return data
1984
+ }
1985
+ avalon.each({
1986
+ scrollLeft: "pageXOffset",
1987
+ scrollTop: "pageYOffset"
1988
+ }, function(method, prop) {
1989
+ avalon.fn[method] = function(val) {
1990
+ var node = this[0] || {}, win = getWindow(node),
1991
+ top = method === "scrollTop"
1992
+ if (!arguments.length) {
1993
+ return win ? win[prop] : node[method]
1994
+ } else {
1995
+ if (win) {
1996
+ win.scrollTo(!top ? val : avalon(win).scrollLeft(), top ? val : avalon(win).scrollTop())
1997
+ } else {
1998
+ node[method] = val
1999
+ }
2000
+ }
2001
+ }
2002
+ })
2003
+
2004
+ function getWindow(node) {
2005
+ return node.window && node.document ? node : node.nodeType === 9 ? node.defaultView : false
2006
+ }
2007
+
2008
+ //=============================css相关==================================
2009
+ var cssHooks = avalon.cssHooks = {}
2010
+ var prefixes = ["", "-webkit-", "-moz-", "-ms-"]//去掉opera-15的支持
2011
+ var cssMap = {
2012
+ "float": "cssFloat"
2013
+ }
2014
+ avalon.cssNumber = oneObject("columnCount,order,fillOpacity,fontWeight,lineHeight,opacity,orphans,widows,zIndex,zoom")
2015
+
2016
+ avalon.cssName = function(name, host, camelCase) {
2017
+ if (cssMap[name]) {
2018
+ return cssMap[name]
2019
+ }
2020
+ host = host || root.style
2021
+ for (var i = 0, n = prefixes.length; i < n; i++) {
2022
+ camelCase = camelize(prefixes[i] + name)
2023
+ if (camelCase in host) {
2024
+ return (cssMap[name] = camelCase)
2025
+ }
2026
+ }
2027
+ return null
2028
+ }
2029
+ cssHooks["@:set"] = function(node, name, value) {
2030
+ node.style[name] = value
2031
+ }
2032
+
2033
+ cssHooks["@:get"] = function(node, name) {
2034
+ if (!node || !node.style) {
2035
+ throw new Error("getComputedStyle要求传入一个节点 " + node)
2036
+ }
2037
+ var ret, computed = getComputedStyle(node, null)
2038
+ if (computed) {
2039
+ ret = name === "filter" ? computed.getPropertyValue(name) : computed[name]
2040
+ if (ret === "") {
2041
+ ret = node.style[name] //其他浏览器需要我们手动取内联样式
2042
+ }
2043
+ }
2044
+ return ret
2045
+ }
2046
+ cssHooks["opacity:get"] = function(node) {
2047
+ var ret = cssHooks["@:get"](node, "opacity")
2048
+ return ret === "" ? "1" : ret
2049
+ }
2050
+
2051
+ "top,left".replace(rword, function(name) {
2052
+ cssHooks[name + ":get"] = function(node) {
2053
+ var computed = cssHooks["@:get"](node, name)
2054
+ return /px$/.test(computed) ? computed :
2055
+ avalon(node).position()[name] + "px"
2056
+ }
2057
+ })
2058
+ var cssShow = {
2059
+ position: "absolute",
2060
+ visibility: "hidden",
2061
+ display: "block"
2062
+ }
2063
+ var rdisplayswap = /^(none|table(?!-c[ea]).+)/
2064
+
2065
+ function showHidden(node, array) {
2066
+ //http://www.cnblogs.com/rubylouvre/archive/2012/10/27/2742529.html
2067
+ if (node.offsetWidth <= 0) { //opera.offsetWidth可能小于0
2068
+ var styles = getComputedStyle(node, null)
2069
+ if (rdisplayswap.test(styles["display"])) {
2070
+ var obj = {
2071
+ node: node
2072
+ }
2073
+ for (var name in cssShow) {
2074
+ obj[name] = styles[name]
2075
+ node.style[name] = cssShow[name]
2076
+ }
2077
+ array.push(obj)
2078
+ }
2079
+ var parent = node.parentNode
2080
+ if (parent && parent.nodeType === 1) {
2081
+ showHidden(parent, array)
2082
+ }
2083
+ }
2084
+ }
2085
+
2086
+ "Width,Height".replace(rword, function(name) {//fix 481
2087
+ var method = name.toLowerCase(),
2088
+ clientProp = "client" + name,
2089
+ scrollProp = "scroll" + name,
2090
+ offsetProp = "offset" + name
2091
+ cssHooks[method + ":get"] = function(node, which, override) {
2092
+ var boxSizing = -4
2093
+ if (typeof override === "number") {
2094
+ boxSizing = override
2095
+ }
2096
+ which = name === "Width" ? ["Left", "Right"] : ["Top", "Bottom"]
2097
+ var ret = node[offsetProp] // border-box 0
2098
+ if (boxSizing === 2) { // margin-box 2
2099
+ return ret
2100
+ + avalon.css(node, "margin" + which[0], true)
2101
+ + avalon.css(node, "margin" + which[1], true)
2102
+ }
2103
+ if (boxSizing < 0) { // padding-box -2
2104
+ ret = ret
2105
+ - avalon.css(node, "border" + which[0] + "Width", true)
2106
+ - avalon.css(node, "border" + which[1] + "Width", true)
2107
+ }
2108
+ if (boxSizing === -4) { // content-box -4
2109
+ ret = ret
2110
+ - avalon.css(node, "padding" + which[0], true)
2111
+ - avalon.css(node, "padding" + which[1], true)
2112
+ }
2113
+ return ret
2114
+ }
2115
+ cssHooks[method + "&get"] = function(node) {
2116
+ var hidden = [];
2117
+ showHidden(node, hidden);
2118
+ var val = cssHooks[method + ":get"](node)
2119
+ for (var i = 0, obj; obj = hidden[i++]; ) {
2120
+ node = obj.node
2121
+ for (var n in obj) {
2122
+ if (typeof obj[n] === "string") {
2123
+ node.style[n] = obj[n]
2124
+ }
2125
+ }
2126
+ }
2127
+ return val;
2128
+ }
2129
+ avalon.fn[method] = function(value) { //会忽视其display
2130
+ var node = this[0]
2131
+ if (arguments.length === 0) {
2132
+ if (node.setTimeout) { //取得窗口尺寸,IE9后可以用node.innerWidth /innerHeight代替
2133
+ return node["inner" + name]
2134
+ }
2135
+ if (node.nodeType === 9) { //取得页面尺寸
2136
+ var doc = node.documentElement
2137
+ //FF chrome html.scrollHeight< body.scrollHeight
2138
+ //IE 标准模式 : html.scrollHeight> body.scrollHeight
2139
+ //IE 怪异模式 : html.scrollHeight 最大等于可视窗口多一点?
2140
+ return Math.max(node.body[scrollProp], doc[scrollProp], node.body[offsetProp], doc[offsetProp], doc[clientProp])
2141
+ }
2142
+ return cssHooks[method + "&get"](node)
2143
+ } else {
2144
+ return this.css(method, value)
2145
+ }
2146
+ }
2147
+ avalon.fn["inner" + name] = function() {
2148
+ return cssHooks[method + ":get"](this[0], void 0, -2)
2149
+ }
2150
+ avalon.fn["outer" + name] = function(includeMargin) {
2151
+ return cssHooks[method + ":get"](this[0], void 0, includeMargin === true ? 2 : 0)
2152
+ }
2153
+ })
2154
+ avalon.fn.offset = function() { //取得距离页面左右角的坐标
2155
+ var node = this[0], box = {
2156
+ left: 0,
2157
+ top: 0
2158
+ }
2159
+ if (!node || !node.tagName || !node.ownerDocument) {
2160
+ return box
2161
+ }
2162
+ var doc = node.ownerDocument,
2163
+ root = doc.documentElement,
2164
+ win = doc.defaultView
2165
+ if (!root.contains(node)) {
2166
+ return box
2167
+ }
2168
+ if (node.getBoundingClientRect !== void 0) {
2169
+ box = node.getBoundingClientRect()
2170
+ }
2171
+ return {
2172
+ top: box.top + win.pageYOffset - root.clientTop,
2173
+ left: box.left + win.pageXOffset - root.clientLeft
2174
+ }
2175
+ }
2176
+ //=============================val相关=======================
2177
+
2178
+ function getValType(el) {
2179
+ var ret = el.tagName.toLowerCase()
2180
+ return ret === "input" && /checkbox|radio/.test(el.type) ? "checked" : ret
2181
+ }
2182
+ var valHooks = {
2183
+ "select:get": function(node, value) {
2184
+ var option, options = node.options,
2185
+ index = node.selectedIndex,
2186
+ one = node.type === "select-one" || index < 0,
2187
+ values = one ? null : [],
2188
+ max = one ? index + 1 : options.length,
2189
+ i = index < 0 ? max : one ? index : 0
2190
+ for (; i < max; i++) {
2191
+ option = options[i]
2192
+ //旧式IE在reset后不会改变selected,需要改用i === index判定
2193
+ //我们过滤所有disabled的option元素,但在safari5下,如果设置select为disable,那么其所有孩子都disable
2194
+ //因此当一个元素为disable,需要检测其是否显式设置了disable及其父节点的disable情况
2195
+ if ((option.selected || i === index) && !option.disabled) {
2196
+ value = option.value
2197
+ if (one) {
2198
+ return value
2199
+ }
2200
+ //收集所有selected值组成数组返回
2201
+ values.push(value)
2202
+ }
2203
+ }
2204
+ return values
2205
+ },
2206
+ "select:set": function(node, values, optionSet) {
2207
+ values = [].concat(values) //强制转换为数组
2208
+ for (var i = 0, el; el = node.options[i++]; ) {
2209
+ if ((el.selected = values.indexOf(el.value) > -1)) {
2210
+ optionSet = true
2211
+ }
2212
+ }
2213
+ if (!optionSet) {
2214
+ node.selectedIndex = -1
2215
+ }
2216
+ }
2217
+ }
2218
+
2219
+ /*********************************************************************
2220
+ * 编译系统 *
2221
+ **********************************************************************/
2222
+ var quote = JSON.stringify
2223
+
2224
+ var keywords =
2225
+ // 关键字
2226
+ "break,case,catch,continue,debugger,default,delete,do,else,false" +
2227
+ ",finally,for,function,if,in,instanceof,new,null,return,switch,this" +
2228
+ ",throw,true,try,typeof,var,void,while,with"
2229
+ // 保留字
2230
+ + ",abstract,boolean,byte,char,class,const,double,enum,export,extends" +
2231
+ ",final,float,goto,implements,import,int,interface,long,native" +
2232
+ ",package,private,protected,public,short,static,super,synchronized" +
2233
+ ",throws,transient,volatile"
2234
+ // ECMA 5 - use strict
2235
+ + ",arguments,let,yield" + ",undefined"
2236
+ var rrexpstr = /\/\*[\w\W]*?\*\/|\/\/[^\n]*\n|\/\/[^\n]*$|"(?:[^"\\]|\\[\w\W])*"|'(?:[^'\\]|\\[\w\W])*'|[\s\t\n]*\.[\s\t\n]*[$\w\.]+/g
2237
+ var rsplit = /[^\w$]+/g
2238
+ var rkeywords = new RegExp(["\\b" + keywords.replace(/,/g, '\\b|\\b') + "\\b"].join('|'), 'g')
2239
+ var rnumber = /\b\d[^,]*/g
2240
+ var rcomma = /^,+|,+$/g
2241
+ var cacheVars = createCache(512)
2242
+ var getVariables = function(code) {
2243
+ var key = "," + code.trim()
2244
+ if (cacheVars[key]) {
2245
+ return cacheVars[key]
2246
+ }
2247
+ var match = code
2248
+ .replace(rrexpstr, "")
2249
+ .replace(rsplit, ",")
2250
+ .replace(rkeywords, "")
2251
+ .replace(rnumber, "")
2252
+ .replace(rcomma, "")
2253
+ .split(/^$|,+/)
2254
+ return cacheVars(key, uniqSet(match))
2255
+ }
2256
+ /*添加赋值语句*/
2257
+
2258
+ function addAssign(vars, scope, name, data) {
2259
+ var ret = [],
2260
+ prefix = " = " + name + "."
2261
+ for (var i = vars.length, prop; prop = vars[--i]; ) {
2262
+ if (scope.hasOwnProperty(prop)) {
2263
+ ret.push(prop + prefix + prop)
2264
+ data.vars.push(prop)
2265
+ if (data.type === "duplex") {
2266
+ vars.get = name + "." + prop
2267
+ }
2268
+ vars.splice(i, 1)
2269
+ }
2270
+ }
2271
+ return ret
2272
+ }
2273
+
2274
+ function uniqSet(array) {
2275
+ var ret = [],
2276
+ unique = {}
2277
+ for (var i = 0; i < array.length; i++) {
2278
+ var el = array[i]
2279
+ var id = el && typeof el.$id === "string" ? el.$id : el
2280
+ if (!unique[id]) {
2281
+ unique[id] = ret.push(el)
2282
+ }
2283
+ }
2284
+ return ret
2285
+ }
2286
+ //缓存求值函数,以便多次利用
2287
+ var cacheExprs = createCache(128)
2288
+ //取得求值函数及其传参
2289
+ var rduplex = /\w\[.*\]|\w\.\w/
2290
+ var rproxy = /(\$proxy\$[a-z]+)\d+$/
2291
+ var rthimRightParentheses = /\)\s*$/
2292
+ var rthimOtherParentheses = /\)\s*\|/g
2293
+ var rquoteFilterName = /\|\s*([$\w]+)/g
2294
+ var rpatchBracket = /"\s*\["/g
2295
+ var rthimLeftParentheses = /"\s*\(/g
2296
+ function parseFilter(val, filters) {
2297
+ filters = filters
2298
+ .replace(rthimRightParentheses, "")//处理最后的小括号
2299
+ .replace(rthimOtherParentheses, function() {//处理其他小括号
2300
+ return "],|"
2301
+ })
2302
+ .replace(rquoteFilterName, function(a, b) { //处理|及它后面的过滤器的名字
2303
+ return "[" + quote(b)
2304
+ })
2305
+ .replace(rpatchBracket, function() {
2306
+ return '"],["'
2307
+ })
2308
+ .replace(rthimLeftParentheses, function() {
2309
+ return '",'
2310
+ }) + "]"
2311
+ return "return avalon.filters.$filter(" + val + ", " + filters + ")"
2312
+ }
2313
+
2314
+ function parseExpr(code, scopes, data) {
2315
+ var dataType = data.type
2316
+ var filters = data.filters || ""
2317
+ var exprId = scopes.map(function(el) {
2318
+ return String(el.$id).replace(rproxy, "$1")
2319
+ }) + code + dataType + filters
2320
+ var vars = getVariables(code).concat(),
2321
+ assigns = [],
2322
+ names = [],
2323
+ args = [],
2324
+ prefix = ""
2325
+ //args 是一个对象数组, names 是将要生成的求值函数的参数
2326
+ scopes = uniqSet(scopes)
2327
+ data.vars = []
2328
+ for (var i = 0, sn = scopes.length; i < sn; i++) {
2329
+ if (vars.length) {
2330
+ var name = "vm" + expose + "_" + i
2331
+ names.push(name)
2332
+ args.push(scopes[i])
2333
+ assigns.push.apply(assigns, addAssign(vars, scopes[i], name, data))
2334
+ }
2335
+ }
2336
+ if (!assigns.length && dataType === "duplex") {
2337
+ return
2338
+ }
2339
+ if (dataType !== "duplex" && (code.indexOf("||") > -1 || code.indexOf("&&") > -1)) {
2340
+ //https://github.com/RubyLouvre/avalon/issues/583
2341
+ data.vars.forEach(function(v) {
2342
+ var reg = new RegExp("\\b" + v + "(?:\\.\\w+|\\[\\w+\\])+", "ig")
2343
+ code = code.replace(reg, function(_) {
2344
+ var c = _.charAt(v.length)
2345
+ var r = IEVersion ? code.slice(arguments[1] + _.length) : RegExp.rightContext
2346
+ var method = /^\s*\(/.test(r)
2347
+ if (c === "." || c === "[" || method) {//比如v为aa,我们只匹配aa.bb,aa[cc],不匹配aaa.xxx
2348
+ var name = "var" + String(Math.random()).replace(/^0\./, "")
2349
+ if (method) {//array.size()
2350
+ var array = _.split(".")
2351
+ if (array.length > 2) {
2352
+ var last = array.pop()
2353
+ assigns.push(name + " = " + array.join("."))
2354
+ return name + "." + last
2355
+ } else {
2356
+ return _
2357
+ }
2358
+ }
2359
+ assigns.push(name + " = " + _)
2360
+ return name
2361
+ } else {
2362
+ return _
2363
+ }
2364
+ })
2365
+ })
2366
+ }
2367
+ //---------------args----------------
2368
+ data.args = args
2369
+ //---------------cache----------------
2370
+ var fn = cacheExprs[exprId] //直接从缓存,免得重复生成
2371
+ if (fn) {
2372
+ data.evaluator = fn
2373
+ return
2374
+ }
2375
+ var prefix = assigns.join(", ")
2376
+ if (prefix) {
2377
+ prefix = "var " + prefix
2378
+ }
2379
+ if (/\S/.test(filters)) { //文本绑定,双工绑定才有过滤器
2380
+ if (!/text|html/.test(data.type)) {
2381
+ throw Error("ms-" + data.type + "不支持过滤器")
2382
+ }
2383
+ code = "\nvar ret" + expose + " = " + code + ";\r\n"
2384
+ code += parseFilter("ret" + expose, filters)
2385
+ } else if (dataType === "duplex") { //双工绑定
2386
+ var _body = "'use strict';\nreturn function(vvv){\n\t" +
2387
+ prefix +
2388
+ ";\n\tif(!arguments.length){\n\t\treturn " +
2389
+ code +
2390
+ "\n\t}\n\t" + (!rduplex.test(code) ? vars.get : code) +
2391
+ "= vvv;\n} "
2392
+ try {
2393
+ fn = Function.apply(noop, names.concat(_body))
2394
+ data.evaluator = cacheExprs(exprId, fn)
2395
+ } catch (e) {
2396
+ log("debug: parse error," + e.message)
2397
+ }
2398
+ return
2399
+ } else if (dataType === "on") { //事件绑定
2400
+ if (code.indexOf("(") === -1) {
2401
+ code += ".call(this, $event)"
2402
+ } else {
2403
+ code = code.replace("(", ".call(this,")
2404
+ }
2405
+ names.push("$event")
2406
+ code = "\nreturn " + code + ";" //IE全家 Function("return ")出错,需要Function("return ;")
2407
+ var lastIndex = code.lastIndexOf("\nreturn")
2408
+ var header = code.slice(0, lastIndex)
2409
+ var footer = code.slice(lastIndex)
2410
+ code = header + "\n" + footer
2411
+ } else { //其他绑定
2412
+ code = "\nreturn " + code + ";" //IE全家 Function("return ")出错,需要Function("return ;")
2413
+ }
2414
+ try {
2415
+ fn = Function.apply(noop, names.concat("'use strict';\n" + prefix + code))
2416
+ data.evaluator = cacheExprs(exprId, fn)
2417
+ } catch (e) {
2418
+ log("debug: parse error," + e.message)
2419
+ } finally {
2420
+ vars = textBuffer = names = null //释放内存
2421
+ }
2422
+ }
2423
+
2424
+
2425
+ //parseExpr的智能引用代理
2426
+
2427
+ function parseExprProxy(code, scopes, data, tokens, noregister) {
2428
+ if (Array.isArray(tokens)) {
2429
+ code = tokens.map(function(el) {
2430
+ return el.expr ? "(" + el.value + ")" : quote(el.value)
2431
+ }).join(" + ")
2432
+ }
2433
+ parseExpr(code, scopes, data)
2434
+ if (data.evaluator && !noregister) {
2435
+ data.handler = bindingExecutors[data.handlerName || data.type]
2436
+ //方便调试
2437
+ //这里非常重要,我们通过判定视图刷新函数的element是否在DOM树决定
2438
+ //将它移出订阅者列表
2439
+ registerSubscriber(data)
2440
+ }
2441
+ }
2442
+ avalon.parseExprProxy = parseExprProxy
2443
+ /*********************************************************************
2444
+ * 各种指令 *
2445
+ **********************************************************************/
2446
+ //ms-skip绑定已经在scanTag 方法中实现
2447
+ //ms-controller绑定已经在scanTag 方法中实现
2448
+ //ms-important绑定已经在scanTag 方法中实现
2449
+ var bools = "autofocus,autoplay,async,allowTransparency,checked,controls,declare,disabled,defer,defaultChecked,defaultSelected" +
2450
+ "contentEditable,isMap,loop,multiple,noHref,noResize,noShade,open,readOnly,selected"
2451
+ var boolMap = {}
2452
+ bools.replace(rword, function(name) {
2453
+ boolMap[name.toLowerCase()] = name
2454
+ })
2455
+
2456
+ var propMap = {//属性名映射
2457
+ "accept-charset": "acceptCharset",
2458
+ "char": "ch",
2459
+ "charoff": "chOff",
2460
+ "class": "className",
2461
+ "for": "htmlFor",
2462
+ "http-equiv": "httpEquiv"
2463
+ }
2464
+
2465
+ var anomaly = "accessKey,bgColor,cellPadding,cellSpacing,codeBase,codeType,colSpan," + "dateTime,defaultValue,frameBorder,longDesc,maxLength,marginWidth,marginHeight," + "rowSpan,tabIndex,useMap,vSpace,valueType,vAlign"
2466
+ anomaly.replace(rword, function(name) {
2467
+ propMap[name.toLowerCase()] = name
2468
+ })
2469
+
2470
+ var rnoscripts = /<noscript.*?>(?:[\s\S]+?)<\/noscript>/img
2471
+ var rnoscriptText = /<noscript.*?>([\s\S]+?)<\/noscript>/im
2472
+
2473
+ var getXHR = function() {
2474
+ return new (window.XMLHttpRequest || ActiveXObject)("Microsoft.XMLHTTP")
2475
+ }
2476
+
2477
+ var cacheTmpls = avalon.templateCache = {}
2478
+
2479
+ bindingHandlers.attr = function(data, vmodels) {
2480
+ var text = data.value.trim(),
2481
+ simple = true
2482
+ if (text.indexOf(openTag) > -1 && text.indexOf(closeTag) > 2) {
2483
+ simple = false
2484
+ if (rexpr.test(text) && RegExp.rightContext === "" && RegExp.leftContext === "") {
2485
+ simple = true
2486
+ text = RegExp.$1
2487
+ }
2488
+ }
2489
+ if (data.type === "include") {
2490
+ var elem = data.element
2491
+ data.includeRendered = getBindingCallback(elem, "data-include-rendered", vmodels)
2492
+ data.includeLoaded = getBindingCallback(elem, "data-include-loaded", vmodels)
2493
+ var outer = data.includeReplaced = !!avalon(elem).data("includeReplace")
2494
+ data.startInclude = DOC.createComment("ms-include")
2495
+ data.endInclude = DOC.createComment("ms-include-end")
2496
+ if (outer) {
2497
+ data.element = data.startInclude
2498
+ elem.parentNode.insertBefore(data.startInclude, elem)
2499
+ elem.parentNode.insertBefore(data.endInclude, elem.nextSibling)
2500
+ } else {
2501
+ elem.insertBefore(data.startInclude, elem.firstChild)
2502
+ elem.appendChild(data.endInclude)
2503
+ }
2504
+ }
2505
+ data.handlerName = "attr" //handleName用于处理多种绑定共用同一种bindingExecutor的情况
2506
+ parseExprProxy(text, vmodels, data, (simple ? 0 : scanExpr(data.value)))
2507
+ }
2508
+
2509
+ bindingExecutors.attr = function(val, elem, data) {
2510
+ var method = data.type,
2511
+ attrName = data.param
2512
+ if (method === "css") {
2513
+ avalon(elem).css(attrName, val)
2514
+ } else if (method === "attr") {
2515
+ // ms-attr-class="xxx" vm.xxx="aaa bbb ccc"将元素的className设置为aaa bbb ccc
2516
+ // ms-attr-class="xxx" vm.xxx=false 清空元素的所有类名
2517
+ // ms-attr-name="yyy" vm.yyy="ooo" 为元素设置name属性
2518
+ if (boolMap[attrName]) {
2519
+ var bool = boolMap[attrName]
2520
+ if (typeof elem[bool] === "boolean") {
2521
+ // IE6-11不支持动态设置fieldset的disabled属性,IE11下样式是生效了,但无法阻止用户对其底下的input元素进行设值……
2522
+ return elem[bool] = !!val
2523
+ }
2524
+ }
2525
+ var toRemove = (val === false) || (val === null) || (val === void 0)
2526
+
2527
+ if (!W3C && propMap[attrName]) { //旧式IE下需要进行名字映射
2528
+ attrName = propMap[attrName]
2529
+ }
2530
+ if (toRemove) {
2531
+ return elem.removeAttribute(attrName)
2532
+ }
2533
+ //SVG只能使用setAttribute(xxx, yyy), VML只能使用elem.xxx = yyy ,HTML的固有属性必须elem.xxx = yyy
2534
+ var isInnate = rsvg.test(elem) ? false : (DOC.namespaces && isVML(elem)) ? true : attrName in elem.cloneNode(false)
2535
+ if (isInnate) {
2536
+ elem[attrName] = val
2537
+ } else {
2538
+ elem.setAttribute(attrName, val)
2539
+ }
2540
+ } else if (method === "include" && val) {
2541
+ var vmodels = data.vmodels
2542
+ var rendered = data.includeRendered
2543
+ var loaded = data.includeLoaded
2544
+ var replace = data.includeReplaced
2545
+ var target = replace ? elem.parentNode : elem
2546
+ function scanTemplate(text) {
2547
+ if (loaded) {
2548
+ text = loaded.apply(target, [text].concat(vmodels))
2549
+ }
2550
+ if (rendered) {
2551
+ checkScan(target, function() {
2552
+ rendered.call(target)
2553
+ }, NaN)
2554
+ }
2555
+ while (true) {
2556
+ var node = data.startInclude.nextSibling
2557
+ if (node && node !== data.endInclude) {
2558
+ target.removeChild(node)
2559
+ } else {
2560
+ break
2561
+ }
2562
+ }
2563
+ var dom = avalon.parseHTML(text)
2564
+ var nodes = avalon.slice(dom.childNodes)
2565
+ target.insertBefore(dom, data.endInclude)
2566
+ scanNodeArray(nodes, vmodels)
2567
+ }
2568
+ if (data.param === "src") {
2569
+ if (cacheTmpls[val]) {
2570
+ avalon.nextTick(function() {
2571
+ scanTemplate(cacheTmpls[val])
2572
+ })
2573
+ } else {
2574
+ var xhr = getXHR()
2575
+ xhr.onreadystatechange = function() {
2576
+ if (xhr.readyState === 4) {
2577
+ var s = xhr.status
2578
+ if (s >= 200 && s < 300 || s === 304 || s === 1223) {
2579
+ scanTemplate(cacheTmpls[val] = xhr.responseText)
2580
+ }
2581
+ }
2582
+ }
2583
+ xhr.open("GET", val, true)
2584
+ if ("withCredentials" in xhr) {
2585
+ xhr.withCredentials = true
2586
+ }
2587
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest")
2588
+ xhr.send(null)
2589
+ }
2590
+ } else {
2591
+ //IE系列与够新的标准浏览器支持通过ID取得元素(firefox14+)
2592
+ //http://tjvantoll.com/2012/07/19/dom-element-references-as-global-variables/
2593
+ var el = val && val.nodeType === 1 ? val : DOC.getElementById(val)
2594
+ if (el) {
2595
+ if (el.tagName === "NOSCRIPT" && !(el.innerHTML || el.fixIE78)) { //IE7-8 innerText,innerHTML都无法取得其内容,IE6能取得其innerHTML
2596
+ var xhr = getXHR() //IE9-11与chrome的innerHTML会得到转义的内容,它们的innerText可以
2597
+ xhr.open("GET", location, false) //谢谢Nodejs 乱炖群 深圳-纯属虚构
2598
+ xhr.send(null)
2599
+ //http://bbs.csdn.net/topics/390349046?page=1#post-393492653
2600
+ var noscripts = DOC.getElementsByTagName("noscript")
2601
+ var array = (xhr.responseText || "").match(rnoscripts) || []
2602
+ var n = array.length
2603
+ for (var i = 0; i < n; i++) {
2604
+ var tag = noscripts[i]
2605
+ if (tag) { //IE6-8中noscript标签的innerHTML,innerText是只读的
2606
+ tag.style.display = "none" //http://haslayout.net/css/noscript-Ghost-Bug
2607
+ tag.fixIE78 = (array[i].match(rnoscriptText) || ["", "&nbsp;"])[1]
2608
+ }
2609
+ }
2610
+ }
2611
+ avalon.nextTick(function() {
2612
+ scanTemplate(el.fixIE78 || el.value || el.innerText || el.innerHTML)
2613
+ })
2614
+ }
2615
+ }
2616
+ } else {
2617
+ if (!root.hasAttribute && typeof val === "string" && (method === "src" || method === "href")) {
2618
+ val = val.replace(/&amp;/g, "&") //处理IE67自动转义的问题
2619
+ }
2620
+ elem[method] = val
2621
+ if (window.chrome && elem.tagName === "EMBED") {
2622
+ var parent = elem.parentNode //#525 chrome1-37下embed标签动态设置src不能发生请求
2623
+ var comment = document.createComment("ms-src")
2624
+ parent.replaceChild(comment, elem)
2625
+ parent.replaceChild(elem, comment)
2626
+ }
2627
+ }
2628
+ }
2629
+
2630
+ //这几个指令都可以使用插值表达式,如ms-src="aaa/{{b}}/{{c}}.html"
2631
+ "title,alt,src,value,css,include,href".replace(rword, function(name) {
2632
+ bindingHandlers[name] = bindingHandlers.attr
2633
+ })
2634
+ //ms-include绑定已由ms-attr绑定实现
2635
+
2636
+ //根据VM的属性值或表达式的值切换类名,ms-class="xxx yyy zzz:flag"
2637
+ //http://www.cnblogs.com/rubylouvre/archive/2012/12/17/2818540.html
2638
+ bindingHandlers["class"] = function(data, vmodels) {
2639
+ var oldStyle = data.param,
2640
+ text = data.value,
2641
+ rightExpr
2642
+ data.handlerName = "class"
2643
+ if (!oldStyle || isFinite(oldStyle)) {
2644
+ data.param = "" //去掉数字
2645
+ var noExpr = text.replace(rexprg, function(a) {
2646
+ return a.replace(/./g, "0")
2647
+ //return Math.pow(10, a.length - 1) //将插值表达式插入10的N-1次方来占位
2648
+ })
2649
+ var colonIndex = noExpr.indexOf(":") //取得第一个冒号的位置
2650
+ if (colonIndex === -1) { // 比如 ms-class="aaa bbb ccc" 的情况
2651
+ var className = text
2652
+ } else { // 比如 ms-class-1="ui-state-active:checked" 的情况
2653
+ className = text.slice(0, colonIndex)
2654
+ rightExpr = text.slice(colonIndex + 1)
2655
+ parseExpr(rightExpr, vmodels, data) //决定是添加还是删除
2656
+ if (!data.evaluator) {
2657
+ log("debug: ms-class '" + (rightExpr || "").trim() + "' 不存在于VM中")
2658
+ return false
2659
+ } else {
2660
+ data._evaluator = data.evaluator
2661
+ data._args = data.args
2662
+ }
2663
+ }
2664
+ var hasExpr = rexpr.test(className) //比如ms-class="width{{w}}"的情况
2665
+ if (!hasExpr) {
2666
+ data.immobileClass = className
2667
+ }
2668
+ parseExprProxy("", vmodels, data, (hasExpr ? scanExpr(className) : 0))
2669
+ } else {
2670
+ data.immobileClass = data.oldStyle = data.param
2671
+ parseExprProxy(text, vmodels, data)
2672
+ }
2673
+ }
2674
+
2675
+ bindingExecutors ["class"] = function(val, elem, data) {
2676
+ var $elem = avalon(elem),
2677
+ method = data.type
2678
+ if (method === "class" && data.oldStyle) { //如果是旧风格
2679
+ $elem.toggleClass(data.oldStyle, !!val)
2680
+ } else {
2681
+ //如果存在冒号就有求值函数
2682
+ data.toggleClass = data._evaluator ? !!data._evaluator.apply(elem, data._args) : true
2683
+ data.newClass = data.immobileClass || val
2684
+ if (data.oldClass && data.newClass !== data.oldClass) {
2685
+ $elem.removeClass(data.oldClass)
2686
+ }
2687
+ data.oldClass = data.newClass
2688
+ switch (method) {
2689
+ case "class":
2690
+ $elem.toggleClass(data.newClass, data.toggleClass)
2691
+ break
2692
+ case "hover":
2693
+ case "active":
2694
+ if (!data.hasBindEvent) { //确保只绑定一次
2695
+ var activate = "mouseenter" //在移出移入时切换类名
2696
+ var abandon = "mouseleave"
2697
+ if (method === "active") { //在聚焦失焦中切换类名
2698
+ elem.tabIndex = elem.tabIndex || -1
2699
+ activate = "mousedown"
2700
+ abandon = "mouseup"
2701
+ var fn0 = $elem.bind("mouseleave", function() {
2702
+ data.toggleClass && $elem.removeClass(data.newClass)
2703
+ })
2704
+ }
2705
+ var fn1 = $elem.bind(activate, function() {
2706
+ data.toggleClass && $elem.addClass(data.newClass)
2707
+ })
2708
+ var fn2 = $elem.bind(abandon, function() {
2709
+ data.toggleClass && $elem.removeClass(data.newClass)
2710
+ })
2711
+ data.rollback = function() {
2712
+ $elem.unbind("mouseleave", fn0)
2713
+ $elem.unbind(activate, fn1)
2714
+ $elem.unbind(abandon, fn2)
2715
+ }
2716
+ data.hasBindEvent = true
2717
+ }
2718
+ break;
2719
+ }
2720
+ }
2721
+ }
2722
+
2723
+ "hover,active".replace(rword, function(method) {
2724
+ bindingHandlers[method] = bindingHandlers["class"]
2725
+ })
2726
+ // bindingHandlers.data 定义在if.js
2727
+ bindingExecutors.data = function(val, elem, data) {
2728
+ var key = "data-" + data.param
2729
+ if (val && typeof val === "object") {
2730
+ elem[key] = val
2731
+ } else {
2732
+ elem.setAttribute(key, String(val))
2733
+ }
2734
+ }
2735
+
2736
+ // bindingHandlers.text 定义在if.js
2737
+ bindingExecutors.text = function(val, elem) {
2738
+ val = val == null ? "" : val //不在页面上显示undefined null
2739
+ if (elem.nodeType === 3) { //绑定在文本节点上
2740
+ try { //IE对游离于DOM树外的节点赋值会报错
2741
+ elem.data = val
2742
+ } catch (e) {
2743
+ }
2744
+ } else { //绑定在特性节点上
2745
+ elem.textContent = val
2746
+ }
2747
+ }
2748
+
2749
+ // bindingHandlers.html 定义在if.js
2750
+ bindingExecutors.html = function(val, elem, data) {
2751
+ val = val == null ? "" : val
2752
+ var isHtmlFilter = "group" in data
2753
+ var parent = isHtmlFilter ? elem.parentNode : elem
2754
+ if (!parent)
2755
+ return
2756
+ if (val.nodeType === 11) { //将val转换为文档碎片
2757
+ var fragment = val
2758
+ } else if (val.nodeType === 1 || val.item) {
2759
+ var nodes = val.nodeType === 1 ? val.childNodes : val.item ? val : []
2760
+ fragment = hyperspace.cloneNode(true)
2761
+ while (nodes[0]) {
2762
+ fragment.appendChild(nodes[0])
2763
+ }
2764
+ } else {
2765
+ fragment = avalon.parseHTML(val)
2766
+ }
2767
+ //插入占位符, 如果是过滤器,需要有节制地移除指定的数量,如果是html指令,直接清空
2768
+ var comment = DOC.createComment("ms-html")
2769
+ if (isHtmlFilter) {
2770
+ parent.insertBefore(comment, elem)
2771
+ var n = data.group, i = 1
2772
+ while (i < n) {
2773
+ var node = elem.nextSibling
2774
+ if (node) {
2775
+ parent.removeChild(node)
2776
+ i++
2777
+ }
2778
+ }
2779
+ parent.removeChild(elem)
2780
+ data.element = comment //防止被CG
2781
+ } else {
2782
+ avalon.clearHTML(parent).appendChild(comment)
2783
+ }
2784
+ if (isHtmlFilter) {
2785
+ data.group = fragment.childNodes.length || 1
2786
+ }
2787
+ var nodes = avalon.slice(fragment.childNodes)
2788
+ if (nodes[0]) {
2789
+ if (comment.parentNode)
2790
+ comment.parentNode.replaceChild(fragment, comment)
2791
+ if (isHtmlFilter) {
2792
+ data.element = nodes[0]
2793
+ }
2794
+ }
2795
+ scanNodeArray(nodes, data.vmodels)
2796
+ }
2797
+
2798
+ bindingHandlers["if"] =
2799
+ bindingHandlers.data =
2800
+ bindingHandlers.text =
2801
+ bindingHandlers.html =
2802
+ function(data, vmodels) {
2803
+ parseExprProxy(data.value, vmodels, data)
2804
+ }
2805
+
2806
+ bindingExecutors["if"] = function(val, elem, data) {
2807
+ if (val) { //插回DOM树
2808
+ if (elem.nodeType === 8) {
2809
+ elem.parentNode.replaceChild(data.template, elem)
2810
+ elem = data.element = data.template //这时可能为null
2811
+ }
2812
+ if (elem.getAttribute(data.name)) {
2813
+ elem.removeAttribute(data.name)
2814
+ scanAttr(elem, data.vmodels)
2815
+ }
2816
+ data.rollback = null
2817
+ } else { //移出DOM树,并用注释节点占据原位置
2818
+ if (elem.nodeType === 1) {
2819
+ var node = data.element = DOC.createComment("ms-if")
2820
+ elem.parentNode.replaceChild(node, elem)
2821
+ data.template = elem //元素节点
2822
+ ifGroup.appendChild(elem)
2823
+ data.rollback = function() {
2824
+ if (elem.parentNode === ifGroup) {
2825
+ ifGroup.removeChild(elem)
2826
+ }
2827
+ }
2828
+ }
2829
+ }
2830
+ }
2831
+
2832
+
2833
+ function parseDisplay(nodeName, val) {
2834
+ //用于取得此类标签的默认display值
2835
+ var key = "_" + nodeName
2836
+ if (!parseDisplay[key]) {
2837
+ var node = DOC.createElement(nodeName)
2838
+ root.appendChild(node)
2839
+ if (W3C) {
2840
+ val = getComputedStyle(node, null).display
2841
+ } else {
2842
+ val = node.currentStyle.display
2843
+ }
2844
+ root.removeChild(node)
2845
+ parseDisplay[key] = val
2846
+ }
2847
+ return parseDisplay[key]
2848
+ }
2849
+
2850
+ avalon.parseDisplay = parseDisplay
2851
+
2852
+ bindingHandlers.visible = function(data, vmodels) {
2853
+ var elem = avalon(data.element)
2854
+ var display = elem.css("display")
2855
+ if (display === "none") {
2856
+ var style = elem[0].style
2857
+ var has = /visibility/i.test(style.cssText)
2858
+ var visible = elem.css("visibility")
2859
+ style.display = ""
2860
+ style.visibility = "hidden"
2861
+ display = elem.css("display")
2862
+ if (display === "none") {
2863
+ display = parseDisplay(elem[0].nodeName)
2864
+ }
2865
+ style.visibility = has ? visible : ""
2866
+ }
2867
+ data.display = display
2868
+ parseExprProxy(data.value, vmodels, data)
2869
+ }
2870
+
2871
+ bindingExecutors.visible = function(val, elem, data) {
2872
+ elem.style.display = val ? data.display : "none"
2873
+ }
2874
+
2875
+ var rdash = /\(([^)]*)\)/
2876
+ bindingHandlers.on = function(data, vmodels) {
2877
+ var value = data.value
2878
+ data.type = "on"
2879
+ var eventType = data.param.replace(/-\d+$/, "") // ms-on-mousemove-10
2880
+ if (typeof bindingHandlers.on[eventType + "Hook"] === "function") {
2881
+ bindingHandlers.on[eventType + "Hook"](data)
2882
+ }
2883
+ if (value.indexOf("(") > 0 && value.indexOf(")") > -1) {
2884
+ var matched = (value.match(rdash) || ["", ""])[1].trim()
2885
+ if (matched === "" || matched === "$event") { // aaa() aaa($event)当成aaa处理
2886
+ value = value.replace(rdash, "")
2887
+ }
2888
+ }
2889
+ parseExprProxy(value, vmodels, data)
2890
+ }
2891
+
2892
+ bindingExecutors.on = function(callback, elem, data) {
2893
+ callback = function(e) {
2894
+ var fn = data.evaluator || noop
2895
+ return fn.apply(this, data.args.concat(e))
2896
+ }
2897
+ var eventType = data.param.replace(/-\d+$/, "") // ms-on-mousemove-10
2898
+ if (eventType === "scan") {
2899
+ callback.call(elem, {
2900
+ type: eventType
2901
+ })
2902
+ } else if (typeof data.specialBind === "function") {
2903
+ data.specialBind(elem, callback)
2904
+ } else {
2905
+ var removeFn = avalon.bind(elem, eventType, callback)
2906
+ }
2907
+ data.rollback = function() {
2908
+ if (typeof data.specialUnbind === "function") {
2909
+ data.specialUnbind()
2910
+ } else {
2911
+ avalon.unbind(elem, eventType, removeFn)
2912
+ }
2913
+ }
2914
+ }
2915
+
2916
+
2917
+ bindingHandlers.widget = function(data, vmodels) {
2918
+ var args = data.value.match(rword)
2919
+ var elem = data.element
2920
+ var widget = args[0]
2921
+ var id = args[1]
2922
+ if (!id || id === "$") {//没有定义或为$时,取组件名+随机数
2923
+ id = generateID(widget)
2924
+ }
2925
+ var optName = args[2] || widget//没有定义,取组件名
2926
+ var constructor = avalon.ui[widget]
2927
+ if (typeof constructor === "function") { //ms-widget="tabs,tabsAAA,optname"
2928
+ vmodels = elem.vmodels || vmodels
2929
+ for (var i = 0, v; v = vmodels[i++]; ) {
2930
+ if (v.hasOwnProperty(optName) && typeof v[optName] === "object") {
2931
+ var vmOptions = v[optName]
2932
+ vmOptions = vmOptions.$model || vmOptions
2933
+ break
2934
+ }
2935
+ }
2936
+ if (vmOptions) {
2937
+ var wid = vmOptions[widget + "Id"]
2938
+ if (typeof wid === "string") {
2939
+ id = wid
2940
+ }
2941
+ }
2942
+ //抽取data-tooltip-text、data-tooltip-attr属性,组成一个配置对象
2943
+ var widgetData = avalon.getWidgetData(elem, widget)
2944
+ data.value = [widget, id, optName].join(",")
2945
+ data[widget + "Id"] = id
2946
+ data.evaluator = noop
2947
+ elem.msData["ms-widget-id"] = id
2948
+ var options = data[widget + "Options"] = avalon.mix({}, constructor.defaults, vmOptions || {}, widgetData)
2949
+ elem.removeAttribute("ms-widget")
2950
+ var vmodel = constructor(elem, data, vmodels) || {} //防止组件不返回VM
2951
+ if (vmodel.$id) {
2952
+ avalon.vmodels[id] = vmodel
2953
+ createSignalTower(elem, vmodel)
2954
+ if (vmodel.hasOwnProperty("$init")) {
2955
+ vmodel.$init(function() {
2956
+ avalon.scan(elem, [vmodel].concat(vmodels))
2957
+ if (typeof options.onInit === "function") {
2958
+ options.onInit.call(elem, vmodel, options, vmodels)
2959
+ }
2960
+ })
2961
+ }
2962
+ data.rollback = function() {
2963
+ try {
2964
+ vmodel.widgetElement = null
2965
+ vmodel.$remove()
2966
+ } catch (e) {
2967
+ }
2968
+ elem.msData = {}
2969
+ delete avalon.vmodels[vmodel.$id]
2970
+ }
2971
+ addSubscribers(data, widgetList)
2972
+ if (window.chrome) {
2973
+ elem.addEventListener("DOMNodeRemovedFromDocument", function() {
2974
+ setTimeout(removeSubscribers)
2975
+ })
2976
+ }
2977
+ } else {
2978
+ avalon.scan(elem, vmodels)
2979
+ }
2980
+ } else if (vmodels.length) { //如果该组件还没有加载,那么保存当前的vmodels
2981
+ elem.vmodels = vmodels
2982
+ }
2983
+ }
2984
+ var widgetList = []
2985
+ //不存在 bindingExecutors.widget
2986
+ //双工绑定
2987
+ var duplexBinding = bindingHandlers.duplex = function(data, vmodels) {
2988
+ var elem = data.element,
2989
+ hasCast
2990
+ parseExprProxy(data.value, vmodels, data, 0, 1)
2991
+
2992
+ data.changed = getBindingCallback(elem, "data-duplex-changed", vmodels) || noop
2993
+ if (data.evaluator && data.args) {
2994
+ var params = []
2995
+ var casting = oneObject("string,number,boolean,checked")
2996
+ if (elem.type === "radio" && data.param === "") {
2997
+ data.param = "checked"
2998
+ }
2999
+ if (elem.msData) {
3000
+ elem.msData["ms-duplex"] = data.value
3001
+ }
3002
+ data.param.replace(/\w+/g, function(name) {
3003
+ if (/^(checkbox|radio)$/.test(elem.type) && /^(radio|checked)$/.test(name)) {
3004
+ if (name === "radio")
3005
+ log("ms-duplex-radio已经更名为ms-duplex-checked")
3006
+ name = "checked"
3007
+ data.isChecked = true
3008
+ }
3009
+ if (name === "bool") {
3010
+ name = "boolean"
3011
+ log("ms-duplex-bool已经更名为ms-duplex-boolean")
3012
+ } else if (name === "text") {
3013
+ name = "string"
3014
+ log("ms-duplex-text已经更名为ms-duplex-string")
3015
+ }
3016
+ if (casting[name]) {
3017
+ hasCast = true
3018
+ }
3019
+ avalon.Array.ensure(params, name)
3020
+ })
3021
+ if (!hasCast) {
3022
+ params.push("string")
3023
+ }
3024
+ data.param = params.join("-")
3025
+ data.bound = function(type, callback) {
3026
+ if (elem.addEventListener) {
3027
+ elem.addEventListener(type, callback, false)
3028
+ } else {
3029
+ elem.attachEvent("on" + type, callback)
3030
+ }
3031
+ var old = data.rollback
3032
+ data.rollback = function() {
3033
+ elem.avalonSetter = null
3034
+ avalon.unbind(elem, type, callback)
3035
+ old && old()
3036
+ }
3037
+ }
3038
+ for (var i in avalon.vmodels) {
3039
+ var v = avalon.vmodels[i]
3040
+ v.$fire("avalon-ms-duplex-init", data)
3041
+ }
3042
+ var cpipe = data.pipe || (data.pipe = pipe)
3043
+ cpipe(null, data, "init")
3044
+ var tagName = elem.tagName
3045
+ duplexBinding[tagName] && duplexBinding[tagName](elem, data.evaluator.apply(null, data.args), data)
3046
+ }
3047
+ }
3048
+ //不存在 bindingExecutors.duplex
3049
+ function fixNull(val) {
3050
+ return val == null ? "" : val
3051
+ }
3052
+ avalon.duplexHooks = {
3053
+ checked: {
3054
+ get: function(val, data) {
3055
+ return !data.element.oldValue
3056
+ }
3057
+ },
3058
+ string: {
3059
+ get: function(val) { //同步到VM
3060
+ return val
3061
+ },
3062
+ set: fixNull
3063
+ },
3064
+ "boolean": {
3065
+ get: function(val) {
3066
+ return val === "true"
3067
+ },
3068
+ set: fixNull
3069
+ },
3070
+ number: {
3071
+ get: function(val) {
3072
+ return isFinite(val) ? parseFloat(val) || 0 : val
3073
+ },
3074
+ set: fixNull
3075
+ }
3076
+ }
3077
+
3078
+ function pipe(val, data, action, e) {
3079
+ data.param.replace(/\w+/g, function(name) {
3080
+ var hook = avalon.duplexHooks[name]
3081
+ if (hook && typeof hook[action] === "function") {
3082
+ val = hook[action](val, data)
3083
+ }
3084
+ })
3085
+ return val
3086
+ }
3087
+
3088
+ var TimerID, ribbon = []
3089
+ function W3CFire(el, name, detail) {
3090
+ var event = DOC.createEvent("Events")
3091
+ event.initEvent(name, true, true)
3092
+ event.fireByAvalon = true//签名,标记事件是由avalon触发
3093
+ //event.isTrusted = false 设置这个opera会报错
3094
+ if (detail)
3095
+ event.detail = detail
3096
+ el.dispatchEvent(event)
3097
+ }
3098
+
3099
+
3100
+ avalon.tick = function(fn) {
3101
+ if (ribbon.push(fn) === 1) {
3102
+ TimerID = setInterval(ticker, 60)
3103
+ }
3104
+ }
3105
+
3106
+ function ticker() {
3107
+ for (var n = ribbon.length - 1; n >= 0; n--) {
3108
+ var el = ribbon[n]
3109
+ if (el() === false) {
3110
+ ribbon.splice(n, 1)
3111
+ }
3112
+ }
3113
+ if (!ribbon.length) {
3114
+ clearInterval(TimerID)
3115
+ }
3116
+ }
3117
+
3118
+ var watchValueInTimer = noop
3119
+ new function() {
3120
+ try {//#272 IE9-IE11, firefox
3121
+ var setters = {}
3122
+ var aproto = HTMLInputElement.prototype
3123
+ var bproto = HTMLTextAreaElement.prototype
3124
+ function newSetter(value) {
3125
+ if (avalon.contains(root, this)) {
3126
+ setters[this.tagName].call(this, value)
3127
+ if (this.avalonSetter) {
3128
+ this.avalonSetter()
3129
+ }
3130
+ }
3131
+ }
3132
+ var inputProto = HTMLInputElement.prototype
3133
+ Object.getOwnPropertyNames(inputProto) //故意引发IE6-8等浏览器报错
3134
+ setters["INPUT"] = Object.getOwnPropertyDescriptor(aproto, "value").set
3135
+ Object.defineProperty(aproto, "value", {
3136
+ set: newSetter
3137
+ })
3138
+ setters["TEXTAREA"] = Object.getOwnPropertyDescriptor(bproto, "value").set
3139
+ Object.defineProperty(bproto, "value", {
3140
+ set: newSetter
3141
+ })
3142
+ } catch (e) {
3143
+ watchValueInTimer = avalon.tick
3144
+ }
3145
+ }
3146
+
3147
+
3148
+ //处理radio, checkbox, text, textarea, password
3149
+ duplexBinding.INPUT = function(element, evaluator, data) {
3150
+ var type = element.type,
3151
+ bound = data.bound,
3152
+ $elem = avalon(element),
3153
+ composing = false
3154
+ function callback(value) {
3155
+ data.changed.call(this, value, data)
3156
+ }
3157
+ function compositionStart() {
3158
+ composing = true
3159
+ }
3160
+ function compositionEnd() {
3161
+ composing = false
3162
+ }
3163
+ //当value变化时改变model的值
3164
+ function updateVModel() {
3165
+ if (composing)//处理中文输入法在minlengh下引发的BUG
3166
+ return
3167
+ var val = element.oldValue = element.value //防止递归调用形成死循环
3168
+ var lastValue = data.pipe(val, data, "get")
3169
+ if ($elem.data("duplex-observe") !== false) {
3170
+ evaluator(lastValue)
3171
+ callback.call(element, lastValue)
3172
+ if ($elem.data("duplex-focus")) {
3173
+ avalon.nextTick(function() {
3174
+ element.focus()
3175
+ })
3176
+ }
3177
+ }
3178
+ }
3179
+ //当model变化时,它就会改变value的值
3180
+ data.handler = function() {
3181
+ var val = data.pipe(evaluator(), data, "set") + ""
3182
+ if (val !== element.oldValue) {
3183
+ element.value = val
3184
+ }
3185
+ }
3186
+ if (data.isChecked || element.type === "radio") {
3187
+ updateVModel = function() {
3188
+ if ($elem.data("duplex-observe") !== false) {
3189
+ var lastValue = data.pipe(element.value, data, "get")
3190
+ evaluator(lastValue)
3191
+ callback.call(element, lastValue)
3192
+ }
3193
+ }
3194
+ data.handler = function() {
3195
+ var val = evaluator()
3196
+ var checked = data.isChecked ? !!val : val + "" === element.value
3197
+ element.checked = element.oldValue = checked
3198
+ }
3199
+ bound("click", updateVModel)
3200
+ } else if (type === "checkbox") {
3201
+ updateVModel = function() {
3202
+ if ($elem.data("duplex-observe") !== false) {
3203
+ var method = element.checked ? "ensure" : "remove"
3204
+ var array = evaluator()
3205
+ if (!Array.isArray(array)) {
3206
+ log("ms-duplex应用于checkbox上要对应一个数组")
3207
+ array = [array]
3208
+ }
3209
+ avalon.Array[method](array, data.pipe(element.value, data, "get"))
3210
+ callback.call(element, array)
3211
+ }
3212
+ }
3213
+ data.handler = function() {
3214
+ var array = [].concat(evaluator()) //强制转换为数组
3215
+ element.checked = array.indexOf(data.pipe(element.value, data, "get")) > -1
3216
+ }
3217
+ bound("change", updateVModel)
3218
+ } else {
3219
+ var events = element.getAttribute("data-duplex-event") || element.getAttribute("data-event") || "input"
3220
+ if (element.attributes["data-event"]) {
3221
+ log("data-event指令已经废弃,请改用data-duplex-event")
3222
+ }
3223
+ events.replace(rword, function(name) {
3224
+ switch (name) {
3225
+ case "input":
3226
+ bound("input", updateVModel)
3227
+ bound("DOMAutoComplete", updateVModel)
3228
+ if (!IEVersion) {
3229
+ bound("compositionstart", compositionStart)
3230
+ bound("compositionend", compositionEnd)
3231
+ }
3232
+ break
3233
+ default:
3234
+ bound(name, updateVModel)
3235
+ break
3236
+ }
3237
+ })
3238
+ }
3239
+
3240
+ if (/text|password/.test(element.type)) {
3241
+ watchValueInTimer(function() {
3242
+ if (root.contains(element)) {
3243
+ if (element.value !== element.oldValue) {
3244
+ updateVModel()
3245
+ }
3246
+ } else if (!element.msRetain) {
3247
+ return false
3248
+ }
3249
+ })
3250
+ }
3251
+
3252
+ element.avalonSetter = updateVModel
3253
+ element.oldValue = element.value
3254
+ registerSubscriber(data)
3255
+ callback.call(element, element.value)
3256
+ }
3257
+ duplexBinding.TEXTAREA = duplexBinding.INPUT
3258
+ duplexBinding.SELECT = function(element, evaluator, data) {
3259
+ var $elem = avalon(element)
3260
+ function updateVModel() {
3261
+ if ($elem.data("duplex-observe") !== false) {
3262
+ var val = $elem.val() //字符串或字符串数组
3263
+ if (Array.isArray(val)) {
3264
+ val = val.map(function(v) {
3265
+ return data.pipe(v, data, "get")
3266
+ })
3267
+ } else {
3268
+ val = data.pipe(val, data, "get")
3269
+ }
3270
+ if (val + "" !== element.oldValue) {
3271
+ evaluator(val)
3272
+ }
3273
+ data.changed.call(element, val, data)
3274
+ }
3275
+ }
3276
+ data.handler = function() {
3277
+ var val = evaluator()
3278
+ val = val && val.$model || val
3279
+ if (Array.isArray(val)) {
3280
+ if (!element.multiple) {
3281
+ log("ms-duplex在<select multiple=true>上要求对应一个数组")
3282
+ }
3283
+ } else {
3284
+ if (element.multiple) {
3285
+ log("ms-duplex在<select multiple=false>不能对应一个数组")
3286
+ }
3287
+ }
3288
+ //必须变成字符串后才能比较
3289
+ val = Array.isArray(val) ? val.map(String) : val + ""
3290
+ if (val + "" !== element.oldValue) {
3291
+ $elem.val(val)
3292
+ element.oldValue = val + ""
3293
+ }
3294
+ }
3295
+ data.bound("change", updateVModel)
3296
+ checkScan(element, function() {
3297
+ registerSubscriber(data)
3298
+ data.changed.call(element, evaluator(), data)
3299
+ }, NaN)
3300
+ }
3301
+
3302
+
3303
+ bindingHandlers.repeat = function(data, vmodels) {
3304
+ var type = data.type
3305
+ parseExprProxy(data.value, vmodels, data, 0, 1)
3306
+ data.proxies = []
3307
+ var freturn = false
3308
+ try {
3309
+ var $repeat = data.$repeat = data.evaluator.apply(0, data.args || [])
3310
+ var xtype = avalon.type($repeat)
3311
+ if (xtype !== "object" && xtype !== "array") {
3312
+ freturn = true
3313
+ avalon.log("warning:" + data.value + "对应类型不正确")
3314
+ }
3315
+ } catch (e) {
3316
+ freturn = true
3317
+ avalon.log("warning:" + data.value + "编译出错")
3318
+ }
3319
+
3320
+ var arr = data.value.split(".") || []
3321
+ if (arr.length > 1) {
3322
+ arr.pop()
3323
+ var n = arr[0]
3324
+ for (var i = 0, v; v = vmodels[i++]; ) {
3325
+ if (v && v.hasOwnProperty(n)) {
3326
+ var events = v[n].$events || {}
3327
+ events[subscribers] = events[subscribers] || []
3328
+ events[subscribers].push(data)
3329
+ break
3330
+ }
3331
+ }
3332
+ }
3333
+ var elem = data.element
3334
+ elem.removeAttribute(data.name)
3335
+
3336
+ data.sortedCallback = getBindingCallback(elem, "data-with-sorted", vmodels)
3337
+ data.renderedCallback = getBindingCallback(elem, "data-" + type + "-rendered", vmodels)
3338
+ var signature = generateID(type)
3339
+ var comment = data.element = DOC.createComment(signature + ":end")
3340
+ data.clone = DOC.createComment(signature)
3341
+ hyperspace.appendChild(comment)
3342
+
3343
+ if (type === "each" || type === "with") {
3344
+ data.template = elem.innerHTML.trim()
3345
+ avalon.clearHTML(elem).appendChild(comment)
3346
+ } else {
3347
+ data.template = elem.outerHTML.trim()
3348
+ elem.parentNode.replaceChild(comment, elem)
3349
+ }
3350
+ data.template = avalon.parseHTML(data.template)
3351
+ data.rollback = function() {
3352
+ var elem = data.element
3353
+ if (!elem)
3354
+ return
3355
+ bindingExecutors.repeat.call(data, "clear")
3356
+ var parentNode = elem.parentNode
3357
+ var content = data.template
3358
+ var target = content.firstChild
3359
+ parentNode.replaceChild(content, elem)
3360
+ var start = data.$stamp
3361
+ start && start.parentNode && start.parentNode.removeChild(start)
3362
+ target = data.element = data.type === "repeat" ? target : parentNode
3363
+ }
3364
+ if (freturn) {
3365
+ return
3366
+ }
3367
+ data.handler = bindingExecutors.repeat
3368
+ data.$outer = {}
3369
+ var check0 = "$key"
3370
+ var check1 = "$val"
3371
+ if (Array.isArray($repeat)) {
3372
+ check0 = "$first"
3373
+ check1 = "$last"
3374
+ }
3375
+ for (var i = 0, p; p = vmodels[i++]; ) {
3376
+ if (p.hasOwnProperty(check0) && p.hasOwnProperty(check1)) {
3377
+ data.$outer = p
3378
+ break
3379
+ }
3380
+ }
3381
+ var $events = $repeat.$events
3382
+ var $list = ($events || {})[subscribers]
3383
+ if ($list && avalon.Array.ensure($list, data)) {
3384
+ addSubscribers(data, $list)
3385
+ }
3386
+ if (xtype === "object") {
3387
+ data.$with = true
3388
+ var pool = !$events ? {} : $events.$withProxyPool || ($events.$withProxyPool = {})
3389
+ data.handler("append", $repeat, pool)
3390
+ } else if ($repeat.length) {
3391
+ data.handler("add", 0, $repeat.length)
3392
+ }
3393
+ }
3394
+
3395
+ bindingExecutors.repeat = function(method, pos, el) {
3396
+ if (method) {
3397
+ var data = this
3398
+ var end = data.element
3399
+ var parent = end.parentNode
3400
+ var proxies = data.proxies
3401
+ var transation = hyperspace.cloneNode(false)
3402
+ switch (method) {
3403
+ case "add": //在pos位置后添加el数组(pos为数字,el为数组)
3404
+ var n = pos + el
3405
+ var array = data.$repeat
3406
+ var last = array.length - 1
3407
+ var fragments = []
3408
+ var start = locateNode(data, pos)
3409
+ for (var i = pos; i < n; i++) {
3410
+ var proxy = eachProxyAgent(i, data)
3411
+ proxies.splice(i, 0, proxy)
3412
+ shimController(data, transation, proxy, fragments)
3413
+ }
3414
+ parent.insertBefore(transation, start)
3415
+ for (var i = 0, fragment; fragment = fragments[i++]; ) {
3416
+ scanNodeArray(fragment.nodes, fragment.vmodels)
3417
+ fragment.nodes = fragment.vmodels = null
3418
+ }
3419
+ break
3420
+ case "del": //将pos后的el个元素删掉(pos, el都是数字)
3421
+ start = proxies[pos].$stamp
3422
+ end = locateNode(data, pos + el)
3423
+ sweepNodes(start, end)
3424
+ var removed = proxies.splice(pos, el)
3425
+ recycleProxies(removed, "each")
3426
+ break
3427
+ case "clear":
3428
+ var check = data.$stamp || proxies[0]
3429
+ if (check) {
3430
+ start = check.$stamp || check
3431
+ sweepNodes(start, end)
3432
+ }
3433
+ recycleProxies(proxies, "each")
3434
+ break
3435
+ case "move":
3436
+ start = proxies[0].$stamp
3437
+ var signature = start.nodeValue
3438
+ var rooms = []
3439
+ var room = [], node
3440
+ sweepNodes(start, end, function() {
3441
+ room.unshift(this)
3442
+ if (this.nodeValue === signature) {
3443
+ rooms.unshift(room)
3444
+ room = []
3445
+ }
3446
+ })
3447
+ sortByIndex(proxies, pos)
3448
+ sortByIndex(rooms, pos)
3449
+ while (room = rooms.shift()) {
3450
+ while (node = room.shift()) {
3451
+ transation.appendChild(node)
3452
+ }
3453
+ }
3454
+ parent.insertBefore(transation, end)
3455
+ break
3456
+ case "index": //将proxies中的第pos个起的所有元素重新索引
3457
+ var last = proxies.length - 1
3458
+ for (; el = proxies[pos]; pos++) {
3459
+ el.$index = pos
3460
+ el.$first = pos === 0
3461
+ el.$last = pos === last
3462
+ }
3463
+ return
3464
+ case "set": //将proxies中的第pos个元素的VM设置为el(pos为数字,el任意)
3465
+ var proxy = proxies[pos]
3466
+ if (proxy) {
3467
+ notifySubscribers(proxy.$events.$index)
3468
+ }
3469
+ return
3470
+ case "append": //将pos的键值对从el中取出(pos为一个普通对象,el为预先生成好的代理VM对象池)
3471
+ var pool = el
3472
+ var keys = []
3473
+ var fragments = []
3474
+ for (var key in pos) { //得到所有键名
3475
+ if (pos.hasOwnProperty(key) && key !== "hasOwnProperty") {
3476
+ keys.push(key)
3477
+ }
3478
+ }
3479
+ if (data.sortedCallback) { //如果有回调,则让它们排序
3480
+ var keys2 = data.sortedCallback.call(parent, keys)
3481
+ if (keys2 && Array.isArray(keys2) && keys2.length) {
3482
+ keys = keys2
3483
+ }
3484
+ }
3485
+ for (var i = 0, key; key = keys[i++]; ) {
3486
+ if (key !== "hasOwnProperty") {
3487
+ if (!pool[key]) {
3488
+ pool[key] = withProxyAgent(key, data)
3489
+ }
3490
+ shimController(data, transation, pool[key], fragments)
3491
+ }
3492
+ }
3493
+ var comment = data.$stamp = data.clone
3494
+ parent.insertBefore(comment, end)
3495
+ parent.insertBefore(transation, end)
3496
+ for (var i = 0, fragment; fragment = fragments[i++]; ) {
3497
+ scanNodeArray(fragment.nodes, fragment.vmodels)
3498
+ fragment.nodes = fragment.vmodels = null
3499
+ }
3500
+ break
3501
+ }
3502
+ if (method === "clear")
3503
+ method = "del"
3504
+ var callback = data.renderedCallback || noop,
3505
+ args = arguments
3506
+ checkScan(parent, function() {
3507
+ callback.apply(parent, args)
3508
+ if (parent.oldValue && parent.tagName === "SELECT") { //fix #503
3509
+ avalon(parent).val(parent.oldValue.split(","))
3510
+ }
3511
+ }, NaN)
3512
+ }
3513
+ }
3514
+
3515
+ "with,each".replace(rword, function(name) {
3516
+ bindingHandlers[name] = bindingHandlers.repeat
3517
+ })
3518
+
3519
+ function shimController(data, transation, proxy, fragments) {
3520
+ var content = data.template.cloneNode(true)
3521
+ var nodes = avalon.slice(content.childNodes)
3522
+ if (proxy.$stamp) {
3523
+ content.insertBefore(proxy.$stamp, content.firstChild)
3524
+ }
3525
+ transation.appendChild(content)
3526
+ var nv = [proxy].concat(data.vmodels)
3527
+ var fragment = {
3528
+ nodes: nodes,
3529
+ vmodels: nv
3530
+ }
3531
+ fragments.push(fragment)
3532
+ }
3533
+
3534
+ function locateNode(data, pos) {
3535
+ var proxy = data.proxies[pos]
3536
+ return proxy ? proxy.$stamp : data.element
3537
+ }
3538
+
3539
+ function sweepNodes(start, end, callback) {
3540
+ while (true) {
3541
+ var node = end.previousSibling
3542
+ if (!node)
3543
+ break
3544
+ node.parentNode.removeChild(node)
3545
+ callback && callback.call(node)
3546
+ if (node === start) {
3547
+ break
3548
+ }
3549
+ }
3550
+ }
3551
+
3552
+ // 为ms-each,ms-with, ms-repeat会创建一个代理VM,
3553
+ // 通过它们保持一个下上文,让用户能调用$index,$first,$last,$remove,$key,$val,$outer等属性与方法
3554
+ // 所有代理VM的产生,消费,收集,存放通过xxxProxyFactory,xxxProxyAgent, recycleProxies,xxxProxyPool实现
3555
+ var eachProxyPool = []
3556
+ var withProxyPool = []
3557
+ function eachProxyFactory(name) {
3558
+ var source = {
3559
+ $host: [],
3560
+ $outer: {},
3561
+ $stamp: 1,
3562
+ $index: 0,
3563
+ $first: false,
3564
+ $last: false,
3565
+ $remove: avalon.noop
3566
+ }
3567
+ source[name] = {
3568
+ get: function() {
3569
+ return this.$host[this.$index]
3570
+ },
3571
+ set: function(val) {
3572
+ this.$host.set(this.$index, val)
3573
+ }
3574
+ }
3575
+ var second = {
3576
+ $last: 1,
3577
+ $first: 1,
3578
+ $index: 1
3579
+ }
3580
+ var proxy = modelFactory(source, second)
3581
+ var e = proxy.$events
3582
+ e[name] = e.$first = e.$last = e.$index
3583
+ proxy.$id = generateID("$proxy$each")
3584
+ return proxy
3585
+ }
3586
+
3587
+ function eachProxyAgent(index, data) {
3588
+ var param = data.param || "el", proxy
3589
+ for (var i = 0, n = eachProxyPool.length; i < n; i++) {
3590
+ var candidate = eachProxyPool[i]
3591
+ if (candidate && candidate.hasOwnProperty(param)) {
3592
+ proxy = candidate
3593
+ eachProxyPool.splice(i, 1)
3594
+ }
3595
+ }
3596
+ if (!proxy) {
3597
+ proxy = eachProxyFactory(param)
3598
+ }
3599
+ var host = data.$repeat
3600
+ var last = host.length - 1
3601
+ proxy.$index = index
3602
+ proxy.$first = index === 0
3603
+ proxy.$last = index === last
3604
+ proxy.$host = host
3605
+ proxy.$outer = data.$outer
3606
+ proxy.$stamp = data.clone.cloneNode(false)
3607
+ proxy.$remove = function() {
3608
+ return host.removeAt(proxy.$index)
3609
+ }
3610
+ return proxy
3611
+ }
3612
+
3613
+ function withProxyFactory() {
3614
+ var proxy = modelFactory({
3615
+ $key: "",
3616
+ $outer: {},
3617
+ $host: {},
3618
+ $val: {
3619
+ get: function() {
3620
+ return this.$host[this.$key]
3621
+ },
3622
+ set: function(val) {
3623
+ this.$host[this.$key] = val
3624
+ }
3625
+ }
3626
+ }, {
3627
+ $val: 1
3628
+ })
3629
+ proxy.$id = generateID("$proxy$with")
3630
+ return proxy
3631
+ }
3632
+
3633
+ function withProxyAgent(key, data) {
3634
+ var proxy = withProxyPool.pop()
3635
+ if (!proxy) {
3636
+ proxy = withProxyFactory()
3637
+ }
3638
+ var host = data.$repeat
3639
+ proxy.$key = key
3640
+ proxy.$host = host
3641
+ proxy.$outer = data.$outer
3642
+ if (host.$events) {
3643
+ proxy.$events.$val = host.$events[key]
3644
+ } else {
3645
+ proxy.$events = {}
3646
+ }
3647
+ return proxy
3648
+ }
3649
+
3650
+ function recycleProxies(proxies, type) {
3651
+ var proxyPool = type === "each" ? eachProxyPool : withProxyPool
3652
+ avalon.each(proxies, function(key, proxy) {
3653
+ if (proxy.$events) {
3654
+ for (var i in proxy.$events) {
3655
+ if (Array.isArray(proxy.$events[i])) {
3656
+ proxy.$events[i].forEach(function(data) {
3657
+ if (typeof data === "object")
3658
+ disposeData(data)
3659
+ })
3660
+ proxy.$events[i].length = 0
3661
+ }
3662
+ }
3663
+ proxy.$host = proxy.$outer = {}
3664
+ if (proxyPool.unshift(proxy) > kernel.maxRepeatSize) {
3665
+ proxyPool.pop()
3666
+ }
3667
+ }
3668
+ })
3669
+ if (type === "each")
3670
+ proxies.length = 0
3671
+ }
3672
+
3673
+
3674
+
3675
+
3676
+ /*********************************************************************
3677
+ * 自带过滤器 *
3678
+ **********************************************************************/
3679
+ var rscripts = /<script[^>]*>([\S\s]*?)<\/script\s*>/gim
3680
+ var ron = /\s+(on[^=\s]+)(?:=("[^"]*"|'[^']*'|[^\s>]+))?/g
3681
+ var ropen = /<\w+\b(?:(["'])[^"]*?(\1)|[^>])*>/ig
3682
+ var rsanitize = {
3683
+ a: /\b(href)\=("javascript[^"]*"|'javascript[^']*')/ig,
3684
+ img: /\b(src)\=("javascript[^"]*"|'javascript[^']*')/ig,
3685
+ form: /\b(action)\=("javascript[^"]*"|'javascript[^']*')/ig
3686
+ }
3687
+ var rsurrogate = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g
3688
+ var rnoalphanumeric = /([^\#-~| |!])/g;
3689
+
3690
+ function numberFormat(number, decimals, dec_point, thousands_sep) {
3691
+ //form http://phpjs.org/functions/number_format/
3692
+ //number 必需,要格式化的数字
3693
+ //decimals 可选,规定多少个小数位。
3694
+ //dec_point 可选,规定用作小数点的字符串(默认为 . )。
3695
+ //thousands_sep 可选,规定用作千位分隔符的字符串(默认为 , ),如果设置了该参数,那么所有其他参数都是必需的。
3696
+ number = (number + '')
3697
+ .replace(/[^0-9+\-Ee.]/g, '')
3698
+ var n = !isFinite(+number) ? 0 : +number,
3699
+ prec = !isFinite(+decimals) ? 0 : Math.abs(decimals),
3700
+ sep = (typeof thousands_sep === 'undefined') ? ',' : thousands_sep,
3701
+ dec = (typeof dec_point === 'undefined') ? '.' : dec_point,
3702
+ s = '',
3703
+ toFixedFix = function(n, prec) {
3704
+ var k = Math.pow(10, prec)
3705
+ return '' + (Math.round(n * k) / k)
3706
+ .toFixed(prec)
3707
+ }
3708
+ // Fix for IE parseFloat(0.55).toFixed(0) = 0;
3709
+ s = (prec ? toFixedFix(n, prec) : '' + Math.round(n))
3710
+ .split('.')
3711
+ if (s[0].length > 3) {
3712
+ s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, sep)
3713
+ }
3714
+ if ((s[1] || '')
3715
+ .length < prec) {
3716
+ s[1] = s[1] || ''
3717
+ s[1] += new Array(prec - s[1].length + 1)
3718
+ .join('0')
3719
+ }
3720
+ return s.join(dec)
3721
+ }
3722
+
3723
+
3724
+ var filters = avalon.filters = {
3725
+ uppercase: function(str) {
3726
+ return str.toUpperCase()
3727
+ },
3728
+ lowercase: function(str) {
3729
+ return str.toLowerCase()
3730
+ },
3731
+ truncate: function(str, length, truncation) {
3732
+ //length,新字符串长度,truncation,新字符串的结尾的字段,返回新字符串
3733
+ length = length || 30
3734
+ truncation = truncation === void(0) ? "..." : truncation
3735
+ return str.length > length ? str.slice(0, length - truncation.length) + truncation : String(str)
3736
+ },
3737
+ $filter: function(val) {
3738
+ for (var i = 1, n = arguments.length; i < n; i++) {
3739
+ var array = arguments[i]
3740
+ var fn = avalon.filters[array.shift()]
3741
+ if (typeof fn === "function") {
3742
+ var arr = [val].concat(array)
3743
+ val = fn.apply(null, arr)
3744
+ }
3745
+ }
3746
+ return val
3747
+ },
3748
+ camelize: camelize,
3749
+ //https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
3750
+ // <a href="javasc&NewLine;ript&colon;alert('XSS')">chrome</a>
3751
+ // <a href="data:text/html;base64, PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KDEpPg==">chrome</a>
3752
+ // <a href="jav ascript:alert('XSS');">IE67chrome</a>
3753
+ // <a href="jav&#x09;ascript:alert('XSS');">IE67chrome</a>
3754
+ // <a href="jav&#x0A;ascript:alert('XSS');">IE67chrome</a>
3755
+ sanitize: function(str) {
3756
+ return str.replace(rscripts, "").replace(ropen, function(a, b) {
3757
+ var match = a.toLowerCase().match(/<(\w+)\s/)
3758
+ if (match) { //处理a标签的href属性,img标签的src属性,form标签的action属性
3759
+ var reg = rsanitize[match[1]]
3760
+ if (reg) {
3761
+ a = a.replace(reg, function(s, name, value) {
3762
+ var quote = value.charAt(0)
3763
+ return name + "=" + quote + "javascript:void(0)" + quote
3764
+ })
3765
+ }
3766
+ }
3767
+ return a.replace(ron, " ").replace(/\s+/g, " ") //移除onXXX事件
3768
+ })
3769
+ },
3770
+ escape: function(str) {
3771
+ //将字符串经过 str 转义得到适合在页面中显示的内容, 例如替换 < 为 &lt
3772
+ return String(str).
3773
+ replace(/&/g, '&amp;').
3774
+ replace(rsurrogate, function(value) {
3775
+ var hi = value.charCodeAt(0)
3776
+ var low = value.charCodeAt(1)
3777
+ return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';'
3778
+ }).
3779
+ replace(rnoalphanumeric, function(value) {
3780
+ return '&#' + value.charCodeAt(0) + ';'
3781
+ }).
3782
+ replace(/</g, '&lt;').
3783
+ replace(/>/g, '&gt;')
3784
+ },
3785
+ currency: function(amount, symbol, fractionSize) {
3786
+ return (symbol || "\uFFE5") + numberFormat(amount, isFinite(fractionSize) ? fractionSize : 2)
3787
+ },
3788
+ number: function(number, fractionSize) {
3789
+ return numberFormat(number, isFinite(fractionSize) ? fractionSize : 3)
3790
+ }
3791
+ }
3792
+ /*
3793
+ 'yyyy': 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010)
3794
+ 'yy': 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10)
3795
+ 'y': 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199)
3796
+ 'MMMM': Month in year (January-December)
3797
+ 'MMM': Month in year (Jan-Dec)
3798
+ 'MM': Month in year, padded (01-12)
3799
+ 'M': Month in year (1-12)
3800
+ 'dd': Day in month, padded (01-31)
3801
+ 'd': Day in month (1-31)
3802
+ 'EEEE': Day in Week,(Sunday-Saturday)
3803
+ 'EEE': Day in Week, (Sun-Sat)
3804
+ 'HH': Hour in day, padded (00-23)
3805
+ 'H': Hour in day (0-23)
3806
+ 'hh': Hour in am/pm, padded (01-12)
3807
+ 'h': Hour in am/pm, (1-12)
3808
+ 'mm': Minute in hour, padded (00-59)
3809
+ 'm': Minute in hour (0-59)
3810
+ 'ss': Second in minute, padded (00-59)
3811
+ 's': Second in minute (0-59)
3812
+ 'a': am/pm marker
3813
+ 'Z': 4 digit (+sign) representation of the timezone offset (-1200-+1200)
3814
+ format string can also be one of the following predefined localizable formats:
3815
+
3816
+ 'medium': equivalent to 'MMM d, y h:mm:ss a' for en_US locale (e.g. Sep 3, 2010 12:05:08 pm)
3817
+ 'short': equivalent to 'M/d/yy h:mm a' for en_US locale (e.g. 9/3/10 12:05 pm)
3818
+ 'fullDate': equivalent to 'EEEE, MMMM d,y' for en_US locale (e.g. Friday, September 3, 2010)
3819
+ 'longDate': equivalent to 'MMMM d, y' for en_US locale (e.g. September 3, 2010
3820
+ 'mediumDate': equivalent to 'MMM d, y' for en_US locale (e.g. Sep 3, 2010)
3821
+ 'shortDate': equivalent to 'M/d/yy' for en_US locale (e.g. 9/3/10)
3822
+ 'mediumTime': equivalent to 'h:mm:ss a' for en_US locale (e.g. 12:05:08 pm)
3823
+ 'shortTime': equivalent to 'h:mm a' for en_US locale (e.g. 12:05 pm)
3824
+ */
3825
+ new function() {
3826
+ function toInt(str) {
3827
+ return parseInt(str, 10) || 0
3828
+ }
3829
+
3830
+ function padNumber(num, digits, trim) {
3831
+ var neg = ""
3832
+ if (num < 0) {
3833
+ neg = '-'
3834
+ num = -num
3835
+ }
3836
+ num = "" + num
3837
+ while (num.length < digits)
3838
+ num = "0" + num
3839
+ if (trim)
3840
+ num = num.substr(num.length - digits)
3841
+ return neg + num
3842
+ }
3843
+
3844
+ function dateGetter(name, size, offset, trim) {
3845
+ return function(date) {
3846
+ var value = date["get" + name]()
3847
+ if (offset > 0 || value > -offset)
3848
+ value += offset
3849
+ if (value === 0 && offset === -12) {
3850
+ value = 12
3851
+ }
3852
+ return padNumber(value, size, trim)
3853
+ }
3854
+ }
3855
+
3856
+ function dateStrGetter(name, shortForm) {
3857
+ return function(date, formats) {
3858
+ var value = date["get" + name]()
3859
+ var get = (shortForm ? ("SHORT" + name) : name).toUpperCase()
3860
+ return formats[get][value]
3861
+ }
3862
+ }
3863
+
3864
+ function timeZoneGetter(date) {
3865
+ var zone = -1 * date.getTimezoneOffset()
3866
+ var paddedZone = (zone >= 0) ? "+" : ""
3867
+ paddedZone += padNumber(Math[zone > 0 ? "floor" : "ceil"](zone / 60), 2) + padNumber(Math.abs(zone % 60), 2)
3868
+ return paddedZone
3869
+ }
3870
+ //取得上午下午
3871
+
3872
+ function ampmGetter(date, formats) {
3873
+ return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]
3874
+ }
3875
+ var DATE_FORMATS = {
3876
+ yyyy: dateGetter("FullYear", 4),
3877
+ yy: dateGetter("FullYear", 2, 0, true),
3878
+ y: dateGetter("FullYear", 1),
3879
+ MMMM: dateStrGetter("Month"),
3880
+ MMM: dateStrGetter("Month", true),
3881
+ MM: dateGetter("Month", 2, 1),
3882
+ M: dateGetter("Month", 1, 1),
3883
+ dd: dateGetter("Date", 2),
3884
+ d: dateGetter("Date", 1),
3885
+ HH: dateGetter("Hours", 2),
3886
+ H: dateGetter("Hours", 1),
3887
+ hh: dateGetter("Hours", 2, -12),
3888
+ h: dateGetter("Hours", 1, -12),
3889
+ mm: dateGetter("Minutes", 2),
3890
+ m: dateGetter("Minutes", 1),
3891
+ ss: dateGetter("Seconds", 2),
3892
+ s: dateGetter("Seconds", 1),
3893
+ sss: dateGetter("Milliseconds", 3),
3894
+ EEEE: dateStrGetter("Day"),
3895
+ EEE: dateStrGetter("Day", true),
3896
+ a: ampmGetter,
3897
+ Z: timeZoneGetter
3898
+ }
3899
+ var rdateFormat = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/
3900
+ filters.date = function(date, format) {
3901
+ var locate = filters.date.locate,
3902
+ text = "",
3903
+ parts = [],
3904
+ fn, match
3905
+ format = format || "mediumDate"
3906
+ format = locate[format] || format
3907
+ if (typeof date === "string") {
3908
+ if (/^\d+$/.test(date)) {
3909
+ date = toInt(date)
3910
+ } else {
3911
+ var trimDate = date.trim()
3912
+ var dateArray = [0, 0, 0, 0, 0, 0, 0]
3913
+ var oDate = new Date(0)
3914
+ //取得年月日
3915
+ trimDate = trimDate.replace(/^(\d+)\D(\d+)\D(\d+)/, function(_, a, b, c) {
3916
+ var array = c.length === 4 ? [c, a, b] : [a, b, c]
3917
+ dateArray[0] = toInt(array[0]) //年
3918
+ dateArray[1] = toInt(array[1]) - 1 //月
3919
+ dateArray[2] = toInt(array[2])//日
3920
+ return ""
3921
+ })
3922
+ var dateSetter = oDate.setFullYear
3923
+ var timeSetter = oDate.setHours
3924
+ trimDate = trimDate.replace(/[T\s](\d+):(\d+):?(\d+)?\.?(\d)?/, function(_, a, b, c, d) {
3925
+ dateArray[3] = toInt(a) //小时
3926
+ dateArray[4] = toInt(b) //分钟
3927
+ dateArray[5] = toInt(c) //秒
3928
+ if (d) {
3929
+ dateArray[6] = Math.round(parseFloat("0." + d) * 1000) //毫秒
3930
+ }
3931
+ dateArray[6] = d || ""
3932
+ return ""
3933
+ })
3934
+ var tzHour = 0
3935
+ var tzMin = 0
3936
+ trimDate = trimDate.replace(/Z|([+-])(\d\d):?(\d\d)/, function(z, symbol, c, d) {
3937
+ dateSetter = oDate.setUTCFullYear
3938
+ timeSetter = oDate.setUTCHours
3939
+ if (symbol) {
3940
+ tzHour = toInt(symbol + c)
3941
+ tzMin = toInt(symbol + d)
3942
+ }
3943
+ return ""
3944
+ })
3945
+
3946
+ dateArray[3] -= tzHour
3947
+ dateArray[4] -= tzMin
3948
+ dateSetter.apply(oDate, dateArray.slice(0, 3))
3949
+ timeSetter.apply(oDate, dateArray.slice(3))
3950
+ date = oDate
3951
+ }
3952
+ }
3953
+ if (typeof date === "number") {
3954
+ date = new Date(date)
3955
+ }
3956
+ if (avalon.type(date) !== "date") {
3957
+ return
3958
+ }
3959
+ while (format) {
3960
+ match = rdateFormat.exec(format)
3961
+ if (match) {
3962
+ parts = parts.concat(match.slice(1))
3963
+ format = parts.pop()
3964
+ } else {
3965
+ parts.push(format)
3966
+ format = null
3967
+ }
3968
+ }
3969
+ parts.forEach(function(value) {
3970
+ fn = DATE_FORMATS[value]
3971
+ text += fn ? fn(date, locate) : value.replace(/(^'|'$)/g, "").replace(/''/g, "'")
3972
+ })
3973
+ return text
3974
+ }
3975
+ var locate = {
3976
+ AMPMS: {
3977
+ 0: "上午",
3978
+ 1: "下午"
3979
+ },
3980
+ DAY: {
3981
+ 0: "星期日",
3982
+ 1: "星期一",
3983
+ 2: "星期二",
3984
+ 3: "星期三",
3985
+ 4: "星期四",
3986
+ 5: "星期五",
3987
+ 6: "星期六"
3988
+ },
3989
+ MONTH: {
3990
+ 0: "1月",
3991
+ 1: "2月",
3992
+ 2: "3月",
3993
+ 3: "4月",
3994
+ 4: "5月",
3995
+ 5: "6月",
3996
+ 6: "7月",
3997
+ 7: "8月",
3998
+ 8: "9月",
3999
+ 9: "10月",
4000
+ 10: "11月",
4001
+ 11: "12月"
4002
+ },
4003
+ SHORTDAY: {
4004
+ "0": "周日",
4005
+ "1": "周一",
4006
+ "2": "周二",
4007
+ "3": "周三",
4008
+ "4": "周四",
4009
+ "5": "周五",
4010
+ "6": "周六"
4011
+ },
4012
+ fullDate: "y年M月d日EEEE",
4013
+ longDate: "y年M月d日",
4014
+ medium: "yyyy-M-d H:mm:ss",
4015
+ mediumDate: "yyyy-M-d",
4016
+ mediumTime: "H:mm:ss",
4017
+ "short": "yy-M-d ah:mm",
4018
+ shortDate: "yy-M-d",
4019
+ shortTime: "ah:mm"
4020
+ }
4021
+ locate.SHORTMONTH = locate.MONTH
4022
+ filters.date.locate = locate
4023
+ }
4024
+ new function() {
4025
+ var ua = navigator.userAgent
4026
+ var isAndroid = ua.indexOf("Android") > 0
4027
+ var isIOS = /iP(ad|hone|od)/.test(ua)
4028
+ var self = bindingHandlers.on
4029
+ var touchProxy = {}
4030
+
4031
+ var IE11touch = navigator.pointerEnabled
4032
+ var IE9_10touch = navigator.msPointerEnabled
4033
+ var w3ctouch = (function() {
4034
+ var supported = isIOS || false
4035
+ //http://stackoverflow.com/questions/5713393/creating-and-firing-touch-events-on-a-touch-enabled-browser
4036
+ try {
4037
+ var div = document.createElement("div")
4038
+ div.ontouchstart = function() {
4039
+ supported = true
4040
+ }
4041
+ var e = document.createEvent("TouchEvent")
4042
+ e.initTouchEvent("touchstart", true, true)
4043
+ div.dispatchEvent(e)
4044
+ } catch (err) {
4045
+ }
4046
+ div = div.ontouchstart = null
4047
+ return supported
4048
+ })()
4049
+ var touchSupported = !!(w3ctouch || IE11touch || IE9_10touch)
4050
+ //合成做成触屏事件所需要的各种原生事件
4051
+ var touchNames = ["mousedown", "mousemove", "mouseup", ""]
4052
+ if (w3ctouch) {
4053
+ touchNames = ["touchstart", "touchmove", "touchend", "touchcancel"]
4054
+ } else if (IE11touch) {
4055
+ touchNames = ["pointerdown", "pointermove", "pointerup", "pointercancel"]
4056
+ } else if (IE9_10touch) {
4057
+ touchNames = ["MSPointerDown", "MSPointerMove", "MSPointerUp", "MSPointerCancel"]
4058
+ }
4059
+
4060
+
4061
+ if (touchSupported) {
4062
+ //我们经由ms-click, ms-on-tap绑定事件, 其实没有直接绑在元素,全部放到一个数组中
4063
+ //当通过touchstart, touchend等事件混合计算得当前场景应该需要执行click, tap,那么
4064
+ //直接执行el["msdispatch"]方法
4065
+ "click,tap".replace(/\w+/g, function(method) {
4066
+ avalon.eventHooks[method] = {
4067
+ type: method,
4068
+ deel: function(el, type, fn, isAdd) {
4069
+ var listName = "ms" + method + "list"
4070
+ var fns = el[listName] || (el[listName] = [])
4071
+ if (!el["msdispatch"]) {
4072
+ el["msdispatch"] = function(event) {
4073
+ if (event.fireByAvalon) {
4074
+ for (var i = 0, fn; fn = fns[i++]; ) {
4075
+ fn.call(el, event)
4076
+ }
4077
+ }
4078
+ }
4079
+ el.addEventListener(type, el["msdispatch"])
4080
+ }
4081
+ if (isAdd) {
4082
+ fns.push(fn)
4083
+ } else {
4084
+ avalon.Array.remove(fns, fn)
4085
+ }
4086
+ fn.unbind = true
4087
+ return fn
4088
+ }
4089
+ }
4090
+ })
4091
+ }
4092
+
4093
+ var touchProxy = {}
4094
+ //判定滑动方向
4095
+ function swipeDirection(x1, x2, y1, y2) {
4096
+ return Math.abs(x1 - x2) >=
4097
+ Math.abs(y1 - y2) ? (x1 - x2 > 0 ? "left" : "right") : (y1 - y2 > 0 ? "up" : "down")
4098
+ }
4099
+ function getCoordinates(event) {
4100
+ var touches = event.touches && event.touches.length ? event.touches : [event];
4101
+ var e = event.changedTouches ? event.changedTouches[0] : touches[0]
4102
+ return {
4103
+ x: e.clientX,
4104
+ y: e.clientY
4105
+ }
4106
+ }
4107
+
4108
+ function touchend(event) {
4109
+ var element = touchProxy.element
4110
+ if (!element)
4111
+ return
4112
+ var e = getCoordinates(event)
4113
+ var diff = Date.now() - touchProxy.startTime //经过时间
4114
+ var totalX = Math.abs(touchProxy.x - e.x)
4115
+ var totalY = Math.abs(touchProxy.y - e.y)
4116
+
4117
+ var canDoubleClick = false
4118
+ if (touchProxy.doubleIndex === 2) {//如果已经点了两次,就可以触发dblclick 回调
4119
+ touchProxy.doubleIndex = 0
4120
+ canDoubleClick = true
4121
+ if (avalon.config.stopZoom) {
4122
+ event.preventDefault()
4123
+ }
4124
+ }
4125
+ if (totalX > 30 || totalY > 30) {
4126
+ //如果用户滑动的距离有点大,就认为是swipe事件
4127
+ var direction = swipeDirection(touchProxy.x, e.x, touchProxy.y, e.y)
4128
+ var details = {
4129
+ direction: direction
4130
+ }
4131
+ W3CFire(element, "swipe", details)
4132
+ W3CFire(element, "swipe" + direction, details)
4133
+ } else {
4134
+ //如果移动的距离太少,则认为是tap,click,hold,dblclick
4135
+ if (fastclick.canClick(element) &&
4136
+ touchProxy.mx < fastclick.dragDistance &&
4137
+ touchProxy.my < fastclick.dragDistance) {
4138
+ // 失去焦点的处理
4139
+ if (document.activeElement && document.activeElement !== element) {
4140
+ document.activeElement.blur()
4141
+ }
4142
+ //如果此元素不为表单元素,或者它没有disabled
4143
+ var forElement
4144
+ if (element.tagName.toLowerCase() === "label") {
4145
+ forElement = element.htmlFor ? document.getElementById(element.htmlFor) : null
4146
+ }
4147
+ if (forElement) {
4148
+ fastclick.focus(forElement)
4149
+ } else {
4150
+ fastclick.focus(element)
4151
+ }
4152
+ avalon.fastclick.fireEvent(element, "click", event)//触发click事件
4153
+ W3CFire(element, "tap")//触发tap事件
4154
+ if (forElement) {
4155
+ avalon.fastclick.fireEvent(forElement, "click", event)
4156
+ W3CFire(element, "tap")//触发tap事件
4157
+ }
4158
+ if (canDoubleClick) {
4159
+ if (diff < 250) {
4160
+ avalon.fastclick.fireEvent(element, "dblclick", event)//触发dblclick事件
4161
+ W3CFire(element, "doubletap")//触发doubletap事件
4162
+ }
4163
+ touchProxy.doubleIndex = 0
4164
+ }
4165
+ if (diff > fastclick.clickDuration) {
4166
+ W3CFire(element, "hold")
4167
+ W3CFire(element, "longtap")
4168
+ }
4169
+ }
4170
+ }
4171
+ avalon(element).removeClass(fastclick.activeClass)
4172
+ touchProxy.element = null
4173
+ }
4174
+ document.addEventListener(touchNames[1], function(event) {
4175
+ if (!touchProxy.element)
4176
+ return
4177
+ var e = getCoordinates(event)
4178
+ touchProxy.mx += Math.abs(touchProxy.cx - e.x)
4179
+ touchProxy.my += Math.abs(touchProxy.cy - e.y)
4180
+ if (touchProxy.tapping && (touchProxy.mx > fastclick.dragDistance || touchProxy.my > fastclick.dragDistance)) {
4181
+ touchProxy.element = null
4182
+ }
4183
+ })
4184
+ document.addEventListener(touchNames[2], touchend)
4185
+ if (touchNames[3]) {
4186
+ document.addEventListener(touchNames[3], touchend)
4187
+ }
4188
+ self["clickHook"] = function(data) {
4189
+ function touchstart(event) {
4190
+ var element = data.element
4191
+ avalon.mix(touchProxy, getCoordinates(event))
4192
+ touchProxy.cx = touchProxy.x
4193
+ touchProxy.cy = touchProxy.y
4194
+ touchProxy.mx = 0 //计算总结移动了多少距离,指在移动距离重分
4195
+ touchProxy.my = 0
4196
+ touchProxy.startTime = Date.now()
4197
+ touchProxy.event = data.param
4198
+ touchProxy.tapping = /click|tap|hold$/.test(touchProxy.event)
4199
+
4200
+ touchProxy.element = element
4201
+ //--------------处理双击事件--------------
4202
+ if (touchProxy.element !== element) {
4203
+ touchProxy.doubleIndex = 1
4204
+ touchProxy.doubleStartTime = Date.now()
4205
+ } else {
4206
+ if (!touchProxy.doubleIndex) {
4207
+ touchProxy.doubleIndex = 1
4208
+ } else {
4209
+ touchProxy.doubleIndex = 2
4210
+ }
4211
+ }
4212
+ if (touchProxy.tapping && avalon.fastclick.canClick(element)) {
4213
+ avalon(element).addClass(fastclick.activeClass)
4214
+ }
4215
+ }
4216
+ function needFixClick(type) {
4217
+ return type === "click"
4218
+ }
4219
+ if (needFixClick(data.param) ? touchSupported : true) {
4220
+ data.specialBind = function(element, callback) {
4221
+ element.addEventListener(touchNames[0], touchstart)
4222
+ data.msCallback = callback
4223
+ avalon.bind(element, data.param, callback)
4224
+ }
4225
+ data.specialUnbind = function() {
4226
+ element.removeEventListener(touchNames[0], touchstart)
4227
+ avalon.bind(data.element, data.param, data.msCallback)
4228
+ }
4229
+ }
4230
+ }
4231
+
4232
+ //fastclick只要是处理移动端点击存在300ms延迟的问题
4233
+ //这是苹果乱搞异致的,他们想在小屏幕设备上通过快速点击两次,将放大了的网页缩放至原始比例。
4234
+ var fastclick = avalon.fastclick = {
4235
+ activeClass: "ms-click-active",
4236
+ clickDuration: 750, //小于750ms是点击,长于它是长按或拖动
4237
+ dragDistance: 30, //最大移动的距离
4238
+ fireEvent: function(element, type, event) {
4239
+ var clickEvent = document.createEvent("MouseEvents")
4240
+ clickEvent.initMouseEvent(type, true, true, window, 1, event.screenX, event.screenY,
4241
+ event.clientX, event.clientY, false, false, false, false, 0, null)
4242
+ Object.defineProperty(clickEvent, "fireByAvalon", {
4243
+ value: true
4244
+ })
4245
+ element.dispatchEvent(clickEvent)
4246
+ },
4247
+ focus: function(target) {
4248
+ if (this.canFocus(target)) {
4249
+ //https://github.com/RubyLouvre/avalon/issues/254
4250
+ var value = target.value
4251
+ target.value = value
4252
+ if (isIOS && target.setSelectionRange && target.type.indexOf("date") !== 0 && target.type !== 'time') {
4253
+ // iOS 7, date datetime等控件直接对selectionStart,selectionEnd赋值会抛错
4254
+ var n = value.length
4255
+ target.setSelectionRange(n, n)
4256
+ } else {
4257
+ target.focus()
4258
+ }
4259
+ }
4260
+ },
4261
+ canClick: function(target) {
4262
+ switch (target.nodeName.toLowerCase()) {
4263
+ case "textarea":
4264
+ case "select":
4265
+ case "input":
4266
+ return !target.disabled
4267
+ default:
4268
+ return true
4269
+ }
4270
+ },
4271
+ canFocus: function(target) {
4272
+ switch (target.nodeName.toLowerCase()) {
4273
+ case "textarea":
4274
+ return true;
4275
+ case "select":
4276
+ return !isAndroid
4277
+ case "input":
4278
+ switch (target.type) {
4279
+ case "button":
4280
+ case "checkbox":
4281
+ case "file":
4282
+ case "image":
4283
+ case "radio":
4284
+ case "submit":
4285
+ return false
4286
+ }
4287
+ // No point in attempting to focus disabled inputs
4288
+ return !target.disabled && !target.readOnly
4289
+ default:
4290
+ return false
4291
+ }
4292
+ }
4293
+ };
4294
+
4295
+
4296
+ ["swipe", "swipeleft", "swiperight", "swipeup", "swipedown", "doubletap", "tap", "dblclick", "longtap", "hold"].forEach(function(method) {
4297
+ self[method + "Hook"] = self["clickHook"]
4298
+ })
4299
+
4300
+ //各种摸屏事件的示意图 http://quojs.tapquo.com/ http://touch.code.baidu.com/
4301
+ }
4302
+
4303
+ /*********************************************************************
4304
+ * END *
4305
+ **********************************************************************/
4306
+ new function() {
4307
+ avalon.config({
4308
+ loader: false
4309
+ })
4310
+ var fns = [], fn, loaded
4311
+ function flush(f) {
4312
+ loaded = 1
4313
+ while (f = fns.shift())
4314
+ f()
4315
+ }
4316
+ if (W3C) {
4317
+ avalon.bind(DOC, "DOMContentLoaded", fn = function() {
4318
+ avalon.unbind(DOC, "DOMContentLoaded", fn)
4319
+ flush()
4320
+ })
4321
+ } else {
4322
+ var id = setInterval(function() {
4323
+ if (document.readyState === "complete" && document.body) {
4324
+ clearInterval(id)
4325
+ flush()
4326
+ }
4327
+ }, 50)
4328
+ }
4329
+ avalon.ready = function(fn) {
4330
+ loaded ? fn() : fns.push(fn)
4331
+ }
4332
+ avalon.ready(function() {
4333
+ avalon.scan(DOC.body)
4334
+ })
4335
+ }
4336
+
4337
+
4338
+ // Register as a named AMD module, since avalon can be concatenated with other
4339
+ // files that may use define, but not via a proper concatenation script that
4340
+ // understands anonymous AMD modules. A named AMD is safest and most robust
4341
+ // way to register. Lowercase avalon is used because AMD module names are
4342
+ // derived from file names, and Avalon is normally delivered in a lowercase
4343
+ // file name. Do this after creating the global so that if an AMD module wants
4344
+ // to call noConflict to hide this version of avalon, it will work.
4345
+
4346
+ // Note that for maximum portability, libraries that are not avalon should
4347
+ // declare themselves as anonymous modules, and avoid setting a global if an
4348
+ // AMD loader is present. avalon is a special case. For more information, see
4349
+ // https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon
4350
+ if (typeof define === "function" && define.amd) {
4351
+ define("avalon", [], function() {
4352
+ return avalon
4353
+ })
4354
+ }
4355
+ // Map over avalon in case of overwrite
4356
+ var _avalon = window.avalon
4357
+ avalon.noConflict = function(deep) {
4358
+ if (deep && window.avalon === avalon) {
4359
+ window.avalon = avalon
4360
+ }
4361
+ return avalon
4362
+ }
4363
+ // Expose avalon and $ identifiers, even in AMD
4364
+ // and CommonJS for browser emulators
4365
+ if (noGlobal === void 0) {
4366
+ window.avalon = avalon
4367
+ }
4368
+ return avalon
4369
+
4370
+ }));