avalon-rails 1.4.1.1.20150404164109 → 1.4.6.0.20150915133100

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.
@@ -5,7 +5,7 @@
5
5
  http://weibo.com/jslouvre/
6
6
 
7
7
  Released under the MIT license
8
- avalon.mobile.shim.js 1.41 built in 2015.4.4
8
+ avalon.mobile.shim.js 1.46 built in 2015.9.11
9
9
  ==================================================*/
10
10
  (function(global, factory) {
11
11
 
@@ -75,10 +75,10 @@ var aslice = ap.slice
75
75
  var Registry = {} //将函数曝光到此对象上,方便访问器收集依赖
76
76
  var W3C = window.dispatchEvent
77
77
  var root = DOC.documentElement
78
- var hyperspace = DOC.createDocumentFragment()
78
+ var avalonFragment = DOC.createDocumentFragment()
79
79
  var cinerator = DOC.createElement("div")
80
80
  var class2type = {}
81
- "Boolean Number String Function Array Date RegExp Object Error".replace(rword, function(name) {
81
+ "Boolean Number String Function Array Date RegExp Object Error".replace(rword, function (name) {
82
82
  class2type["[object " + name + "]"] = name.toLowerCase()
83
83
  })
84
84
 
@@ -100,30 +100,35 @@ function oneObject(array, val) {
100
100
  }
101
101
 
102
102
  //生成UUID http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
103
- var generateID = function(prefix) {
103
+ var generateID = function (prefix) {
104
104
  prefix = prefix || "avalon"
105
- return (prefix + Math.random() + Math.random()).replace(/0\./g, "")
105
+ return String(Math.random() + Math.random()).replace(/\d\.\d{4}/, prefix)
106
106
  }
107
107
  function IE() {
108
108
  if (window.VBArray) {
109
109
  var mode = document.documentMode
110
110
  return mode ? mode : window.XMLHttpRequest ? 7 : 6
111
111
  } else {
112
- return 0
112
+ return NaN
113
113
  }
114
114
  }
115
115
  var IEVersion = IE()
116
116
 
117
- avalon = function(el) { //创建jQuery式的无new 实例化结构
117
+ avalon = function (el) { //创建jQuery式的无new 实例化结构
118
118
  return new avalon.init(el)
119
119
  }
120
120
 
121
+ avalon.profile = function () {
122
+ if (window.console && avalon.config.profile) {
123
+ Function.apply.call(console.log, console, arguments)
124
+ }
125
+ }
126
+
121
127
  /*视浏览器情况采用最快的异步回调*/
122
- avalon.nextTick = new function() {// jshint ignore:line
128
+ avalon.nextTick = new function () {// jshint ignore:line
123
129
  var tickImmediate = window.setImmediate
124
130
  var tickObserver = window.MutationObserver
125
- var tickPost = W3C && window.postMessage
126
- if (tickImmediate) {
131
+ if (tickImmediate) {//IE10 \11 edage
127
132
  return tickImmediate.bind(window)
128
133
  }
129
134
 
@@ -136,32 +141,32 @@ avalon.nextTick = new function() {// jshint ignore:line
136
141
  queue = queue.slice(n)
137
142
  }
138
143
 
139
- if (tickObserver) {
144
+ if (tickObserver) {// 支持MutationObserver
140
145
  var node = document.createTextNode("avalon")
141
146
  new tickObserver(callback).observe(node, {characterData: true})// jshint ignore:line
142
- return function(fn) {
147
+ return function (fn) {
143
148
  queue.push(fn)
144
149
  node.data = Math.random()
145
150
  }
146
151
  }
147
152
 
148
- if (tickPost) {
149
- window.addEventListener("message", function(e) {
150
- var source = e.source
151
- if ((source === window || source === null) && e.data === "process-tick") {
152
- e.stopPropagation()
153
- callback()
154
- }
155
- })
156
-
157
- return function(fn) {
153
+ if (window.VBArray) {
154
+ return function (fn) {
158
155
  queue.push(fn)
159
- window.postMessage('process-tick', '*')
156
+ var node = DOC.createElement("script")
157
+ node.onreadystatechange = function () {
158
+ callback() //在interactive阶段就触发
159
+ node.onreadystatechange = null
160
+ head.removeChild(node)
161
+ node = null
162
+ }
163
+ head.appendChild(node)
160
164
  }
161
165
  }
162
166
 
163
- return function(fn) {
164
- setTimeout(fn, 0)
167
+
168
+ return function (fn) {
169
+ setTimeout(fn, 4)
165
170
  }
166
171
  }// jshint ignore:line
167
172
  /*********************************************************************
@@ -262,7 +267,7 @@ function _number(a, len) { //用于模拟slice, splice的效果
262
267
  avalon.mix({
263
268
  rword: rword,
264
269
  subscribers: subscribers,
265
- version: 1.41,
270
+ version: 1.46,
266
271
  ui: {},
267
272
  log: log,
268
273
  slice: function(nodes, start, end) {
@@ -465,6 +470,7 @@ var Cache = new function() {// jshint ignore:line
465
470
  entry.newer =
466
471
  entry.older =
467
472
  this._keymap[entry.key] = void 0
473
+ delete this._keymap[entry.key] //#1029
468
474
  }
469
475
  }
470
476
  p.get = function(key) {
@@ -641,7 +647,6 @@ if (DOC.onmousewheel === void 0) {
641
647
  }
642
648
  }
643
649
  }
644
-
645
650
  /*********************************************************************
646
651
  * 配置系统 *
647
652
  **********************************************************************/
@@ -679,15 +684,15 @@ var plugins = {
679
684
  openTag = array[0]
680
685
  closeTag = array[1]
681
686
  if (openTag === closeTag) {
682
- throw new SyntaxError("openTag!==closeTag")
683
- } else if (array + "" === "<!--,-->") {
684
- kernel.commentInterpolate = true
687
+ throw new SyntaxError("openTag===closeTag")
685
688
  } else {
686
689
  var test = openTag + "test" + closeTag
687
690
  cinerator.innerHTML = test
688
691
  if (cinerator.innerHTML !== test && cinerator.innerHTML.indexOf("&lt;") > -1) {
689
692
  throw new SyntaxError("此定界符不合法")
690
693
  }
694
+ kernel.openTag = openTag
695
+ kernel.closeTag = closeTag
691
696
  cinerator.innerHTML = ""
692
697
  }
693
698
  var o = escapeRegExp(openTag),
@@ -776,8 +781,8 @@ var EventBus = {
776
781
  }
777
782
  //循环两个vmodel中的节点,查找匹配(向上匹配或者向下匹配)的节点并设置标识
778
783
  /* jshint ignore:start */
779
- Array.prototype.forEach.call(eventNodes, function (node) {
780
- Array.prototype.forEach.call(elements, function (element) {
784
+ ap.forEach.call(eventNodes, function (node) {
785
+ ap.forEach.call(elements, function (element) {
781
786
  var ok = special === "down" ? element.contains(node) : //向下捕获
782
787
  node.contains(element) //向上冒泡
783
788
  if (ok) {
@@ -791,7 +796,7 @@ var EventBus = {
791
796
  }
792
797
  var nodes = DOC.getElementsByTagName("*") //实现节点排序
793
798
  var alls = []
794
- Array.prototype.forEach.call(nodes, function (el) {
799
+ ap.forEach.call(nodes, function (el) {
795
800
  if (el._avalon) {
796
801
  alls.push(el._avalon)
797
802
  el._avalon = ""
@@ -820,12 +825,11 @@ var EventBus = {
820
825
  }
821
826
  }
822
827
  }
823
-
824
828
  /*********************************************************************
825
829
  * modelFactory *
826
830
  **********************************************************************/
827
831
  //avalon最核心的方法的两个方法之一(另一个是avalon.scan),返回一个ViewModel(VM)
828
- var VMODELS = avalon.vmodels = createMap() //所有vmodel都储存在这里
832
+ var VMODELS = avalon.vmodels = {} //所有vmodel都储存在这里
829
833
  avalon.define = function (id, factory) {
830
834
  var $id = id.$id || id
831
835
  if (!$id) {
@@ -851,194 +855,271 @@ avalon.define = function (id, factory) {
851
855
  }
852
856
 
853
857
  //一些不需要被监听的属性
854
- var $$skipArray = String("$id,$watch,$unwatch,$fire,$events,$model,$skipArray").match(rword)
855
-
856
- function isObservable(name, value, $skipArray) {
857
- if (isFunction(value) || value && value.nodeType) {
858
- return false
859
- }
860
- if ($skipArray.indexOf(name) !== -1) {
861
- return false
862
- }
863
- if ($$skipArray.indexOf(name) !== -1) {
864
- return false
865
- }
866
- var $special = $skipArray.$special
867
- if (name && name.charAt(0) === "$" && !$special[name]) {
868
- return false
869
- }
870
- return true
871
- }
872
- //ms-with,ms-each, ms-repeat绑定生成的代理对象储存池
873
- var midway = createMap()
874
- function getNewValue(accessor, name, value, $vmodel) {
875
- switch (accessor.type) {
876
- case 0://计算属性
877
- var getter = accessor.get
878
- var setter = accessor.set
879
- if (isFunction(setter)) {
880
- var $events = $vmodel.$events
881
- var lock = $events[name]
882
- $events[name] = [] //清空回调,防止内部冒泡而触发多次$fire
883
- setter.call($vmodel, value)
884
- $events[name] = lock
885
- }
886
- return getter.call($vmodel) //同步$model
887
- case 1://监控属性
888
- return value
889
- case 2://对象属性(包括数组与哈希)
890
- if (value !== $vmodel.$model[name]) {
891
- var svmodel = accessor.svmodel = objectFactory($vmodel, name, value, accessor.valueType)
892
- value = svmodel.$model //同步$model
893
- var fn = midway[svmodel.$id]
894
- fn && fn() //同步视图
895
- }
896
- return value
897
- }
898
- }
858
+ var $$skipArray = String("$id,$watch,$unwatch,$fire,$events,$model,$skipArray,$reinitialize").match(rword)
899
859
 
900
860
  function modelFactory(source, $special, $model) {
901
861
  if (Array.isArray(source)) {
902
862
  var arr = source.concat()
903
863
  source.length = 0
904
- var collection = Collection(source)// jshint ignore:line
864
+ var collection = arrayFactory(source)// jshint ignore:line
905
865
  collection.pushArray(arr)
906
866
  return collection
907
867
  }
908
- //0 null undefined || Node || VModel
868
+ //0 null undefined || Node || VModel(fix IE6-8 createWithProxy $val: val引发的BUG)
909
869
  if (!source || source.nodeType > 0 || (source.$id && source.$events)) {
910
870
  return source
911
871
  }
912
- if (!Array.isArray(source.$skipArray)) {
913
- source.$skipArray = []
914
- }
915
- source.$skipArray.$special = $special || createMap() //强制要监听的属性
872
+ var $skipArray = Array.isArray(source.$skipArray) ? source.$skipArray : []
873
+ $skipArray.$special = $special || createMap() //强制要监听的属性
916
874
  var $vmodel = {} //要返回的对象, 它在IE6-8下可能被偷龙转凤
917
875
  $model = $model || {} //vmodels.$model属性
918
876
  var $events = createMap() //vmodel.$events属性
919
- var watchedProperties = createMap() //监控属性
920
- var initCallbacks = [] //初始化才执行的函数
921
- for (var i in source) {
922
- (function (name, val) {
923
- $model[name] = val
924
- if (!isObservable(name, val, source.$skipArray)) {
925
- return //过滤所有非监控属性
926
- }
877
+ var accessors = createMap() //监控属性
878
+ var computed = []
879
+ $$skipArray.forEach(function (name) {
880
+ delete source[name]
881
+ })
882
+
883
+ var names = Object.keys(source)
884
+ /* jshint ignore:start */
885
+ names.forEach(function (name, accessor) {
886
+ var val = source[name]
887
+ $model[name] = val
888
+ if (isObservable(name, val, $skipArray)) {
927
889
  //总共产生三种accessor
928
890
  $events[name] = []
929
891
  var valueType = avalon.type(val)
930
- var accessor = function (newValue) {
931
- var name = accessor._name
932
- var $vmodel = this
933
- var $model = $vmodel.$model
934
- var oldValue = $model[name]
935
- var $events = $vmodel.$events
936
-
937
- if (arguments.length) {
938
- if (stopRepeatAssign) {
939
- return
940
- }
941
- //计算属性与对象属性需要重新计算newValue
942
- if (accessor.type !== 1) {
943
- newValue = getNewValue(accessor, name, newValue, $vmodel)
944
- if (!accessor.type)
945
- return
946
- }
947
- if (!isEqual(oldValue, newValue)) {
948
- $model[name] = newValue
949
- notifySubscribers($events[name]) //同步视图
950
- safeFire($vmodel, name, newValue, oldValue) //触发$watch回调
951
- }
952
- } else {
953
- if (accessor.type === 0) { //type 0 计算属性 1 监控属性 2 对象属性
954
- //计算属性不需要收集视图刷新函数,都是由其他监控属性代劳
955
- newValue = accessor.get.call($vmodel)
956
- if (oldValue !== newValue) {
957
- $model[name] = newValue
958
- //这里不用同步视图
959
- safeFire($vmodel, name, newValue, oldValue) //触发$watch回调
960
- }
961
- return newValue
962
- } else {
963
- collectSubscribers($events[name]) //收集视图函数
964
- return accessor.svmodel || oldValue
965
- }
966
- }
967
- }
968
892
  //总共产生三种accessor
969
893
  if (valueType === "object" && isFunction(val.get) && Object.keys(val).length <= 2) {
970
- //第1种为计算属性, 因变量,通过其他监控属性触发其改变
971
- accessor.set = val.set
972
- accessor.get = val.get
973
- accessor.type = 0
974
- initCallbacks.push(function () {
975
- var data = {
976
- evaluator: function () {
977
- data.type = Math.random(),
978
- data.element = null
979
- $model[name] = accessor.get.call($vmodel)
980
- },
981
- element: head,
982
- type: Math.random(),
983
- handler: noop,
984
- args: []
985
- }
986
- Registry[expose] = data
987
- accessor.call($vmodel)
988
- delete Registry[expose]
989
- })
894
+ accessor = makeComputedAccessor(name, val)
895
+ computed.push(accessor)
990
896
  } else if (rcomplexType.test(valueType)) {
991
- //第2种为对象属性,产生子VM与监控数组
992
- accessor.type = 2
993
- accessor.valueType = valueType
994
- initCallbacks.push(function () {
995
- var svmodel = modelFactory(val, 0, $model[name])
996
- accessor.svmodel = svmodel
997
- svmodel.$events[subscribers] = $events[name]
998
- })
897
+ // issue #940 解决$model层次依赖丢失 https://github.com/RubyLouvre/avalon/issues/940
898
+ accessor = makeComplexAccessor(name, val, valueType, $events[name], $model)
999
899
  } else {
1000
- accessor.type = 1
1001
- //第3种为监控属性,对应简单的数据类型,自变量
900
+ accessor = makeSimpleAccessor(name, val)
1002
901
  }
1003
- accessor._name = name
1004
- watchedProperties[name] = accessor
1005
- })(i, source[i])// jshint ignore:line
1006
- }
1007
-
1008
- $$skipArray.forEach(function (name) {
1009
- delete source[name]
1010
- delete $model[name] //这些特殊属性不应该在$model中出现
902
+ accessors[name] = accessor
903
+ }
1011
904
  })
1012
-
1013
- $vmodel = Object.defineProperties($vmodel, descriptorFactory(watchedProperties), source) //生成一个空的ViewModel
1014
- for (var name in source) {
1015
- if (!watchedProperties[name]) {
905
+ /* jshint ignore:end */
906
+ $vmodel = Object.defineProperties($vmodel, descriptorFactory(accessors)) //生成一个空的ViewModel
907
+ for (var i = 0; i < names.length; i++) {
908
+ var name = names[i]
909
+ if (!accessors[name]) {
1016
910
  $vmodel[name] = source[name]
1017
911
  }
1018
912
  }
1019
913
  //添加$id, $model, $events, $watch, $unwatch, $fire
1020
- $vmodel.$id = generateID()
1021
- $vmodel.$model = $model
1022
- $vmodel.$events = $events
1023
- for (i in EventBus) {
1024
- $vmodel[i] = EventBus[i]
914
+ hideProperty($vmodel, "$id", generateID())
915
+ hideProperty($vmodel, "$model", $model)
916
+ hideProperty($vmodel, "$events", $events)
917
+ /* jshint ignore:start */
918
+ hideProperty($vmodel, "hasOwnProperty", function (name) {
919
+ return name in this.$model
920
+ })
921
+ /* jshint ignore:end */
922
+ for (var i in EventBus) {
923
+ hideProperty($vmodel, i, EventBus[i])
1025
924
  }
1026
925
 
1027
- Object.defineProperty($vmodel, "hasOwnProperty", {
1028
- value: function (name) {
1029
- return name in this.$model
1030
- },
1031
- writable: false,
926
+ $vmodel.$reinitialize = function () {
927
+ computed.forEach(function (accessor) {
928
+ delete accessor._value
929
+ delete accessor.oldArgs
930
+ accessor.digest = function () {
931
+ accessor.call($vmodel)
932
+ }
933
+ dependencyDetection.begin({
934
+ callback: function (vm, dependency) {//dependency为一个accessor
935
+ var name = dependency._name
936
+ if (dependency !== accessor) {
937
+ var list = vm.$events[name]
938
+ accessor.vm = $vmodel
939
+ injectDependency(list, accessor.digest)
940
+ }
941
+ }
942
+ })
943
+ try {
944
+ accessor.get.call($vmodel)
945
+ } finally {
946
+ dependencyDetection.end()
947
+ }
948
+ })
949
+ }
950
+ $vmodel.$reinitialize()
951
+ return $vmodel
952
+ }
953
+
954
+ function hideProperty(host, name, value) {
955
+ Object.defineProperty(host, name, {
956
+ value: value,
957
+ writable: true,
1032
958
  enumerable: false,
1033
959
  configurable: true
1034
960
  })
961
+ }
1035
962
 
1036
- initCallbacks.forEach(function (cb) { //收集依赖
1037
- cb()
1038
- })
1039
- return $vmodel
963
+ function keysVM(obj) {
964
+ var arr = Object.keys(obj)
965
+ for (var i = 0; i < $$skipArray.length; i++) {
966
+ var index = arr.indexOf($$skipArray[i])
967
+ if (index !== -1) {
968
+ arr.splice(index, 1)
969
+ }
970
+ }
971
+ return arr
972
+ }
973
+ //创建一个简单访问器
974
+ function makeSimpleAccessor(name, value) {
975
+ function accessor(value) {
976
+ var oldValue = accessor._value
977
+ if (arguments.length > 0) {
978
+ if (!stopRepeatAssign && !isEqual(value, oldValue)) {
979
+ accessor.updateValue(this, value)
980
+ accessor.notify(this, value, oldValue)
981
+ }
982
+ return this
983
+ } else {
984
+ dependencyDetection.collectDependency(this, accessor)
985
+ return oldValue
986
+ }
987
+ }
988
+ accessorFactory(accessor, name)
989
+ accessor._value = value
990
+ return accessor;
991
+ }
992
+
993
+ ///创建一个计算访问器
994
+ function makeComputedAccessor(name, options) {
995
+ function accessor(value) {//计算属性
996
+ var oldValue = accessor._value
997
+ var init = ("_value" in accessor)
998
+ if (arguments.length > 0) {
999
+ if (stopRepeatAssign) {
1000
+ return this
1001
+ }
1002
+ if (typeof accessor.set === "function") {
1003
+ if (accessor.oldArgs !== value) {
1004
+ accessor.oldArgs = value
1005
+ var $events = this.$events
1006
+ var lock = $events[name]
1007
+ $events[name] = [] //清空回调,防止内部冒泡而触发多次$fire
1008
+ accessor.set.call(this, value)
1009
+ $events[name] = lock
1010
+ value = accessor.get.call(this)
1011
+ if (value !== oldValue) {
1012
+ accessor.updateValue(this, value)
1013
+ accessor.notify(this, value, oldValue) //触发$watch回调
1014
+ }
1015
+ }
1016
+ }
1017
+ return this
1018
+ } else {
1019
+ //将依赖于自己的高层访问器或视图刷新函数(以绑定对象形式)放到自己的订阅数组中
1020
+ //将自己注入到低层访问器的订阅数组中
1021
+ value = accessor.get.call(this)
1022
+ accessor.updateValue(this, value)
1023
+ if (init && oldValue !== value) {
1024
+ accessor.notify(this, value, oldValue) //触发$watch回调
1025
+ }
1026
+ return value
1027
+ }
1028
+ }
1029
+ accessor.set = options.set
1030
+ accessor.get = options.get
1031
+ accessorFactory(accessor, name)
1032
+ return accessor
1033
+ }
1034
+
1035
+
1036
+ //创建一个复杂访问器
1037
+ function makeComplexAccessor(name, initValue, valueType, list, parentModel) {
1038
+ function accessor(value) {
1039
+ var oldValue = accessor._value
1040
+ var son = accessor._vmodel
1041
+ if (arguments.length > 0) {
1042
+ if (stopRepeatAssign) {
1043
+ return this
1044
+ }
1045
+ if (valueType === "array") {
1046
+ var a = son, b = value,
1047
+ an = a.length,
1048
+ bn = b.length
1049
+ a.$lock = true
1050
+ if (an > bn) {
1051
+ a.splice(bn, an - bn)
1052
+ } else if (bn > an) {
1053
+ a.push.apply(a, b.slice(an))
1054
+ }
1055
+ var n = Math.min(an, bn)
1056
+ for (var i = 0; i < n; i++) {
1057
+ a.set(i, b[i])
1058
+ }
1059
+ delete a.$lock
1060
+ a._fire("set")
1061
+ } else if (valueType === "object") {
1062
+ var observes = this.$events[name] || []
1063
+ var newObject = avalon.mix(true, {}, value)
1064
+ for (i in son) {
1065
+ if (son.hasOwnProperty(i) && ohasOwn.call(newObject, i)) {
1066
+ son[i] = newObject[i]
1067
+ }
1068
+ }
1069
+ son = accessor._vmodel = modelFactory(value)
1070
+ son.$events[subscribers] = observes
1071
+ if (observes.length) {
1072
+ observes.forEach(function (data) {
1073
+ if(!data.type) {
1074
+ return //防止模板先加载报错
1075
+ }
1076
+ if (data.rollback) {
1077
+ data.rollback() //还原 ms-with ms-on
1078
+ }
1079
+ bindingHandlers[data.type](data, data.vmodels)
1080
+ })
1081
+ }
1082
+ }
1083
+ accessor.updateValue(this, son.$model)
1084
+ accessor.notify(this, this._value, oldValue)
1085
+ return this
1086
+ } else {
1087
+ dependencyDetection.collectDependency(this, accessor)
1088
+ return son
1089
+ }
1090
+ }
1091
+ accessorFactory(accessor, name)
1092
+ if (Array.isArray(initValue)) {
1093
+ parentModel[name] = initValue
1094
+ } else {
1095
+ parentModel[name] = parentModel[name] || {}
1096
+ }
1097
+ var son = accessor._vmodel = modelFactory(initValue, 0, parentModel[name])
1098
+ son.$events[subscribers] = list
1099
+ return accessor
1100
+ }
1101
+
1102
+ function globalUpdateValue(vmodel, value) {
1103
+ vmodel.$model[this._name] = this._value = value
1104
+ }
1105
+ function globalUpdateModelValue(vmodel, value) {
1106
+ vmodel.$model[this._name] = value
1107
+ }
1108
+ function globalNotify(vmodel, value, oldValue) {
1109
+ var name = this._name
1110
+ var array = vmodel.$events[name] //刷新值
1111
+ if (array) {
1112
+ fireDependencies(array) //同步视图
1113
+ EventBus.$fire.call(vmodel, name, value, oldValue) //触发$watch回调
1114
+ }
1040
1115
  }
1041
1116
 
1117
+ function accessorFactory(accessor, name) {
1118
+ accessor._name = name
1119
+ //同时更新_value与model
1120
+ accessor.updateValue = globalUpdateValue
1121
+ accessor.notify = globalNotify
1122
+ }
1042
1123
  //比较两个值是否相等
1043
1124
  var isEqual = Object.is || function (v1, v2) {
1044
1125
  if (v1 === 0 && v2 === 0) {
@@ -1050,14 +1131,22 @@ var isEqual = Object.is || function (v1, v2) {
1050
1131
  }
1051
1132
  }
1052
1133
 
1053
- function safeFire(a, b, c, d) {
1054
- if (a.$events) {
1055
- EventBus.$fire.call(a, b, c, d)
1134
+ function isObservable(name, value, $skipArray) {
1135
+ if (isFunction(value) || value && value.nodeType) {
1136
+ return false
1137
+ }
1138
+ if ($skipArray.indexOf(name) !== -1) {
1139
+ return false
1140
+ }
1141
+ var $special = $skipArray.$special
1142
+ if (name && name.charAt(0) === "$" && !$special[name]) {
1143
+ return false
1056
1144
  }
1145
+ return true
1057
1146
  }
1058
1147
 
1059
1148
  var descriptorFactory = function (obj) {
1060
- var descriptors = createMap()
1149
+ var descriptors = {}
1061
1150
  for (var i in obj) {
1062
1151
  descriptors[i] = {
1063
1152
  get: obj[i],
@@ -1069,50 +1158,12 @@ var descriptorFactory = function (obj) {
1069
1158
  return descriptors
1070
1159
  }
1071
1160
 
1072
- //应用于第2种accessor
1073
- function objectFactory(parent, name, value, valueType) {
1074
- //a为原来的VM, b为新数组或新对象
1075
- var son = parent[name]
1076
- if (valueType === "array") {
1077
- if (!Array.isArray(value) || son === value) {
1078
- return son //fix https://github.com/RubyLouvre/avalon/issues/261
1079
- }
1080
- son._.$unwatch()
1081
- son.clear()
1082
- son._.$watch()
1083
- son.pushArray(value.concat())
1084
- return son
1085
- } else {
1086
- var iterators = parent.$events[name]
1087
- var pool = son.$events.$withProxyPool
1088
- if (pool) {
1089
- recycleProxies(pool, "with")
1090
- son.$events.$withProxyPool = null
1091
- }
1092
- var ret = modelFactory(value)
1093
- ret.$events[subscribers] = iterators
1094
- midway[ret.$id] = function (data) {
1095
- while (data = iterators.shift()) {
1096
- (function (el) {
1097
- avalon.nextTick(function () {
1098
- var type = el.type
1099
- if (type && bindingHandlers[type]) { //#753
1100
- el.rollback && el.rollback() //还原 ms-with ms-on
1101
- bindingHandlers[type](el, el.vmodels)
1102
- }
1103
- })
1104
- })(data)// jshint ignore:line
1105
- }
1106
- delete midway[ret.$id]
1107
- }
1108
- return ret
1109
- }
1110
- }
1161
+
1111
1162
  /*********************************************************************
1112
1163
  * 监控数组(与ms-each, ms-repeat配合使用) *
1113
1164
  **********************************************************************/
1114
1165
 
1115
- function Collection(model) {
1166
+ function arrayFactory(model) {
1116
1167
  var array = []
1117
1168
  array.$id = generateID()
1118
1169
  array.$model = model //数据模型
@@ -1127,7 +1178,7 @@ function Collection(model) {
1127
1178
  for (var i in EventBus) {
1128
1179
  array[i] = EventBus[i]
1129
1180
  }
1130
- avalon.mix(array, CollectionPrototype)
1181
+ avalon.mix(array, arrayPrototype)
1131
1182
  return array
1132
1183
  }
1133
1184
 
@@ -1135,7 +1186,7 @@ function mutateArray(method, pos, n, index, method2, pos2, n2) {
1135
1186
  var oldLen = this.length, loop = 2
1136
1187
  while (--loop) {
1137
1188
  switch (method) {
1138
- case "add":
1189
+ case "add":
1139
1190
  /* jshint ignore:start */
1140
1191
  var array = this.$model.slice(pos, pos + n).map(function (el) {
1141
1192
  if (rcomplexType.test(avalon.type(el))) {
@@ -1169,10 +1220,10 @@ function mutateArray(method, pos, n, index, method2, pos2, n2) {
1169
1220
  }
1170
1221
 
1171
1222
  var _splice = ap.splice
1172
- var CollectionPrototype = {
1223
+ var arrayPrototype = {
1173
1224
  _splice: _splice,
1174
1225
  _fire: function (method, a, b) {
1175
- notifySubscribers(this.$events[subscribers], method, a, b)
1226
+ fireDependencies(this.$events[subscribers], method, a, b)
1176
1227
  },
1177
1228
  size: function () { //取得数组长度,这个函数可以同步视图,length不能
1178
1229
  return this._.length
@@ -1258,11 +1309,13 @@ var CollectionPrototype = {
1258
1309
  },
1259
1310
  removeAll: function (all) { //移除N个元素
1260
1311
  if (Array.isArray(all)) {
1261
- all.forEach(function (el) {
1262
- this.remove(el)
1263
- }, this)
1264
- } else if (typeof all === "function") {
1265
1312
  for (var i = this.length - 1; i >= 0; i--) {
1313
+ if (all.indexOf(this[i]) !== -1) {
1314
+ this.removeAt(i)
1315
+ }
1316
+ }
1317
+ } else if (typeof all === "function") {
1318
+ for ( i = this.length - 1; i >= 0; i--) {
1266
1319
  var el = this[i]
1267
1320
  if (all(el, i)) {
1268
1321
  this.removeAt(i)
@@ -1279,7 +1332,7 @@ var CollectionPrototype = {
1279
1332
  return this
1280
1333
  },
1281
1334
  set: function (index, val) {
1282
- if (index >= 0) {
1335
+ if (index < this.length && index > -1) {
1283
1336
  var valueType = avalon.type(val)
1284
1337
  if (val && val.$model) {
1285
1338
  val = val.$model
@@ -1302,6 +1355,15 @@ var CollectionPrototype = {
1302
1355
  return this
1303
1356
  }
1304
1357
  }
1358
+ //相当于原来bindingExecutors.repeat 的index分支
1359
+ function resetIndex(array, pos) {
1360
+ var last = array.length - 1
1361
+ for (var el; el = array[pos]; pos++) {
1362
+ el.$index = pos
1363
+ el.$first = pos === 0
1364
+ el.$last = pos === last
1365
+ }
1366
+ }
1305
1367
 
1306
1368
  function sortByIndex(array, indexes) {
1307
1369
  var map = {};
@@ -1318,7 +1380,7 @@ function sortByIndex(array, indexes) {
1318
1380
  }
1319
1381
 
1320
1382
  "sort,reverse".replace(rword, function (method) {
1321
- CollectionPrototype[method] = function () {
1383
+ arrayPrototype[method] = function () {
1322
1384
  var newArray = this.$model//这是要排序的新数组
1323
1385
  var oldArray = newArray.concat() //保持原来状态的旧数组
1324
1386
  var mask = Math.random()
@@ -1339,28 +1401,57 @@ function sortByIndex(array, indexes) {
1339
1401
  }
1340
1402
  if (hasSort) {
1341
1403
  sortByIndex(this, indexes)
1404
+ // sortByIndex(this.$proxy, indexes)
1342
1405
  this._fire("move", indexes)
1343
- this._fire("index", 0)
1406
+ this._fire("index", 0)
1344
1407
  }
1345
1408
  return this
1346
1409
  }
1347
1410
  })
1348
1411
 
1412
+
1349
1413
  /*********************************************************************
1350
1414
  * 依赖调度系统 *
1351
1415
  **********************************************************************/
1416
+ //检测两个对象间的依赖关系
1417
+ var dependencyDetection = (function () {
1418
+ var outerFrames = []
1419
+ var currentFrame
1420
+ return {
1421
+ begin: function (accessorObject) {
1422
+ //accessorObject为一个拥有callback的对象
1423
+ outerFrames.push(currentFrame)
1424
+ currentFrame = accessorObject
1425
+ },
1426
+ end: function () {
1427
+ currentFrame = outerFrames.pop()
1428
+ },
1429
+ collectDependency: function (vmodel, accessor) {
1430
+ if (currentFrame) {
1431
+ //被dependencyDetection.begin调用
1432
+ currentFrame.callback(vmodel, accessor);
1433
+ }
1434
+ }
1435
+ };
1436
+ })()
1437
+ //将绑定对象注入到其依赖项的订阅数组中
1352
1438
  var ronduplex = /^(duplex|on)$/
1353
-
1354
- function registerSubscriber(data) {
1355
- Registry[expose] = data //暴光此函数,方便collectSubscribers收集
1356
- avalon.openComputedCollect = true
1357
- var fn = data.evaluator
1358
- if (fn) { //如果是求值函数
1439
+ avalon.injectBinding = function (data) {
1440
+ var valueFn = data.evaluator
1441
+ if (valueFn) { //如果是求值函数
1442
+ dependencyDetection.begin({
1443
+ callback: function (vmodel, dependency) {
1444
+ injectDependency(vmodel.$events[dependency._name], data)
1445
+ }
1446
+ })
1359
1447
  try {
1360
- var c = ronduplex.test(data.type) ? data : fn.apply(0, data.args)
1361
- data.handler(c, data.element, data)
1448
+ var value = ronduplex.test(data.type) ? data : valueFn.apply(0, data.args)
1449
+ if(value === void 0){
1450
+ delete data.evaluator
1451
+ }
1452
+ data.handler(value, data.element, data)
1362
1453
  } catch (e) {
1363
- //log("warning:exception throwed in [registerSubscriber] " + e)
1454
+ log("warning:exception throwed in [avalon.injectBinding] " , e)
1364
1455
  delete data.evaluator
1365
1456
  var node = data.element
1366
1457
  if (node.nodeType === 3) {
@@ -1368,126 +1459,160 @@ function registerSubscriber(data) {
1368
1459
  if (kernel.commentInterpolate) {
1369
1460
  parent.replaceChild(DOC.createComment(data.value), node)
1370
1461
  } else {
1371
- node.data = openTag + data.value + closeTag
1462
+ node.data = openTag + (data.oneTime ? "::" : "") + data.value + closeTag
1372
1463
  }
1373
1464
  }
1465
+ } finally {
1466
+ dependencyDetection.end()
1374
1467
  }
1375
1468
  }
1376
- avalon.openComputedCollect = false
1377
- delete Registry[expose]
1378
1469
  }
1379
1470
 
1380
- function collectSubscribers(list) { //收集依赖于这个访问器的订阅者
1381
- var data = Registry[expose]
1382
- if (list && data && avalon.Array.ensure(list, data) && data.element) { //只有数组不存在此元素才push进去
1383
- addSubscribers(data, list)
1471
+ //将依赖项(比它高层的访问器或构建视图刷新函数的绑定对象)注入到订阅者数组
1472
+ function injectDependency(list, data) {
1473
+ if (data.oneTime)
1474
+ return
1475
+ if (list && avalon.Array.ensure(list, data) && data.element) {
1476
+ injectDisposeQueue(data, list)
1477
+ if (new Date() - beginTime > 444 ) {
1478
+ rejectDisposeQueue()
1479
+ }
1384
1480
  }
1385
1481
  }
1386
1482
 
1387
-
1388
- function addSubscribers(data, list) {
1389
- data.$uuid = data.$uuid || generateID()
1390
- list.$uuid = list.$uuid || generateID()
1391
- var obj = {
1392
- data: data,
1393
- list: list,
1394
- $$uuid: data.$uuid + list.$uuid
1395
- }
1396
- if (!$$subscribers[obj.$$uuid]) {
1397
- $$subscribers[obj.$$uuid] = 1
1398
- $$subscribers.push(obj)
1483
+ //通知依赖于这个访问器的订阅者更新自身
1484
+ function fireDependencies(list) {
1485
+ if (list && list.length) {
1486
+ if (new Date() - beginTime > 444 && typeof list[0] === "object") {
1487
+ rejectDisposeQueue()
1488
+ }
1489
+ var args = aslice.call(arguments, 1)
1490
+ for (var i = list.length, fn; fn = list[--i]; ) {
1491
+ var el = fn.element
1492
+ if (el && el.parentNode) {
1493
+ try {
1494
+ var valueFn = fn.evaluator
1495
+ if (fn.$repeat) {
1496
+ fn.handler.apply(fn, args) //处理监控数组的方法
1497
+ }else if("$repeat" in fn || !valueFn ){//如果没有eval,先eval
1498
+ bindingHandlers[fn.type](fn, fn.vmodels)
1499
+ } else if (fn.type !== "on") { //事件绑定只能由用户触发,不能由程序触发
1500
+ var value = valueFn.apply(0, fn.args || [])
1501
+ fn.handler(value, el, fn)
1502
+ }
1503
+ } catch (e) {
1504
+ console.log(e)
1505
+ }
1506
+ }
1507
+ }
1399
1508
  }
1400
1509
  }
1401
-
1402
- function disposeData(data) {
1403
- data.element = null
1404
- data.rollback && data.rollback()
1405
- for (var key in data) {
1406
- data[key] = null
1510
+ /*********************************************************************
1511
+ * 定时GC回收机制 *
1512
+ **********************************************************************/
1513
+ var disposeCount = 0
1514
+ var disposeQueue = avalon.$$subscribers = []
1515
+ var beginTime = new Date()
1516
+ var oldInfo = {}
1517
+ //var uuid2Node = {}
1518
+ function getUid(elem, makeID) { //IE9+,标准浏览器
1519
+ if (!elem.uuid && !makeID) {
1520
+ elem.uuid = ++disposeCount
1407
1521
  }
1522
+ return elem.uuid
1408
1523
  }
1409
1524
 
1410
- function isRemove(el) {
1411
- try {//IE下,如果文本节点脱离DOM树,访问parentNode会报错
1412
- if (!el.parentNode) {
1413
- return true
1525
+ //添加到回收列队中
1526
+ function injectDisposeQueue(data, list) {
1527
+ var elem = data.element
1528
+ if (!data.uuid) {
1529
+ if (elem.nodeType !== 1) {
1530
+ data.uuid = data.type + (data.pos || 0) + "-" + getUid(elem.parentNode)
1531
+ } else {
1532
+ data.uuid = data.name + "-" + getUid(elem)
1414
1533
  }
1415
- } catch (e) {
1416
- return true
1417
1534
  }
1418
- return el.msRetain ? 0 : (el.nodeType === 1 ? typeof el.sourceIndex === "number" ?
1419
- el.sourceIndex === 0 : !root.contains(el) : !avalon.contains(root, el))
1535
+ var lists = data.lists || (data.lists = [])
1536
+ avalon.Array.ensure(lists, list)
1537
+ list.$uuid = list.$uuid || generateID()
1538
+ if (!disposeQueue[data.uuid]) {
1539
+ disposeQueue[data.uuid] = 1
1540
+ disposeQueue.push(data)
1541
+ }
1420
1542
  }
1421
- var $$subscribers = avalon.$$subscribers = []
1422
- var beginTime = new Date()
1423
- var oldInfo = {}
1424
- function removeSubscribers() {
1425
- var i = $$subscribers.length
1543
+
1544
+ function rejectDisposeQueue(data) {
1545
+ if (avalon.optimize)
1546
+ return
1547
+ var i = disposeQueue.length
1426
1548
  var n = i
1427
- var k = 0
1428
- var obj
1429
- var types = []
1549
+ var allTypes = []
1550
+ var iffishTypes = {}
1430
1551
  var newInfo = {}
1431
- var needTest = {}
1432
- while (obj = $$subscribers[--i]) {
1433
- var data = obj.data
1552
+ //对页面上所有绑定对象进行分门别类, 只检测个数发生变化的类型
1553
+ while (data = disposeQueue[--i]) {
1434
1554
  var type = data.type
1435
1555
  if (newInfo[type]) {
1436
1556
  newInfo[type]++
1437
1557
  } else {
1438
1558
  newInfo[type] = 1
1439
- types.push(type)
1559
+ allTypes.push(type)
1440
1560
  }
1441
1561
  }
1442
1562
  var diff = false
1443
- types.forEach(function(type) {
1563
+ allTypes.forEach(function (type) {
1444
1564
  if (oldInfo[type] !== newInfo[type]) {
1445
- needTest[type] = 1
1565
+ iffishTypes[type] = 1
1446
1566
  diff = true
1447
1567
  }
1448
1568
  })
1449
1569
  i = n
1450
- //avalon.log("需要检测的个数 " + i)
1451
1570
  if (diff) {
1452
- //avalon.log("有需要移除的元素")
1453
- while (obj = $$subscribers[--i]) {
1454
- data = obj.data
1455
- if (data.element === void 0)
1571
+ while (data = disposeQueue[--i]) {
1572
+ if (data.element === null) {
1573
+ disposeQueue.splice(i, 1)
1456
1574
  continue
1457
- if (needTest[data.type] && isRemove(data.element)) { //如果它没有在DOM树
1458
- k++
1459
- $$subscribers.splice(i, 1)
1460
- delete $$subscribers[obj.$$uuid]
1461
- avalon.Array.remove(obj.list, data)
1462
- //log("debug: remove " + data.type)
1575
+ }
1576
+ if (iffishTypes[data.type] && shouldDispose(data.element)) { //如果它没有在DOM树
1577
+ disposeQueue.splice(i, 1)
1578
+ delete disposeQueue[data.uuid]
1579
+ //delete uuid2Node[data.element.uuid]
1580
+ var lists = data.lists
1581
+ for (var k = 0, list; list = lists[k++]; ) {
1582
+ avalon.Array.remove(lists, list)
1583
+ avalon.Array.remove(list, data)
1584
+ }
1463
1585
  disposeData(data)
1464
- obj.data = obj.list = null
1465
1586
  }
1466
1587
  }
1467
1588
  }
1468
1589
  oldInfo = newInfo
1469
- // avalon.log("已经移除的个数 " + k)
1470
1590
  beginTime = new Date()
1471
1591
  }
1472
1592
 
1473
- function notifySubscribers(list) { //通知依赖于这个访问器的订阅者更新自身
1474
- if (list && list.length) {
1475
- if (new Date() - beginTime > 444 && typeof list[0] === "object") {
1476
- removeSubscribers()
1477
- }
1478
- var args = aslice.call(arguments, 1)
1479
- for (var i = list.length, fn; fn = list[--i]; ) {
1480
- var el = fn.element
1481
- if (el && el.parentNode) {
1482
- if (fn.$repeat) {
1483
- fn.handler.apply(fn, args) //处理监控数组的方法
1484
- } else if (fn.type !== "on") { //事件绑定只能由用户触发,不能由程序触发
1485
- var fun = fn.evaluator || noop
1486
- fn.handler(fun.apply(0, fn.args || []), el, fn)
1487
- }
1488
- }
1593
+ function disposeData(data) {
1594
+ delete disposeQueue[data.uuid] // 先清除,不然无法回收了
1595
+ data.element = null
1596
+ data.rollback && data.rollback()
1597
+ for (var key in data) {
1598
+ data[key] = null
1599
+ }
1600
+ }
1601
+
1602
+ function shouldDispose(el) {
1603
+ try {//IE下,如果文本节点脱离DOM树,访问parentNode会报错
1604
+ var fireError = el.parentNode.nodeType
1605
+ } catch (e) {
1606
+ return true
1607
+ }
1608
+ if (el.ifRemove) {
1609
+ // 如果节点被放到ifGroup,才移除
1610
+ if (!root.contains(el.ifRemove) && (ifGroup === el.parentNode)) {
1611
+ el.parentNode && el.parentNode.removeChild(el)
1612
+ return true
1489
1613
  }
1490
1614
  }
1615
+ return el.msRetain ? 0 : (el.nodeType === 1 ? !root.contains(el) : !avalon.contains(root, el))
1491
1616
  }
1492
1617
 
1493
1618
  /************************************************************************
@@ -1520,7 +1645,7 @@ var scriptTypes = oneObject(["", "text/javascript", "text/ecmascript", "applicat
1520
1645
  var script = DOC.createElement("script")
1521
1646
  var rhtml = /<|&#?\w+;/
1522
1647
  avalon.parseHTML = function(html) {
1523
- var fragment = hyperspace.cloneNode(false)
1648
+ var fragment = avalonFragment.cloneNode(false)
1524
1649
  if (typeof html !== "string" ) {
1525
1650
  return fragment
1526
1651
  }
@@ -1568,351 +1693,30 @@ avalon.clearHTML = function(node) {
1568
1693
  }
1569
1694
 
1570
1695
  /*********************************************************************
1571
- * 扫描系统 *
1696
+ * avalon的原型方法定义区 *
1572
1697
  **********************************************************************/
1573
1698
 
1574
- avalon.scan = function(elem, vmodel, group) {
1575
- elem = elem || root
1576
- var vmodels = vmodel ? [].concat(vmodel) : []
1577
- scanTag(elem, vmodels)
1699
+ function hyphen(target) {
1700
+ //转换为连字符线风格
1701
+ return target.replace(/([a-z\d])([A-Z]+)/g, "$1-$2").toLowerCase()
1578
1702
  }
1579
1703
 
1580
- //http://www.w3.org/TR/html5/syntax.html#void-elements
1581
- var stopScan = oneObject("area,base,basefont,br,col,command,embed,hr,img,input,link,meta,param,source,track,wbr,noscript,script,style,textarea".toUpperCase())
1582
-
1583
- function checkScan(elem, callback, innerHTML) {
1584
- var id = setTimeout(function() {
1585
- var currHTML = elem.innerHTML
1586
- clearTimeout(id)
1587
- if (currHTML === innerHTML) {
1588
- callback()
1589
- } else {
1590
- checkScan(elem, callback, currHTML)
1591
- }
1704
+ function camelize(target) {
1705
+ //转换为驼峰风格
1706
+ if (target.indexOf("-") < 0 && target.indexOf("_") < 0) {
1707
+ return target //提前判断,提高getStyle等的效率
1708
+ }
1709
+ return target.replace(/[-_][^-_]/g, function(match) {
1710
+ return match.charAt(1).toUpperCase()
1592
1711
  })
1593
1712
  }
1594
1713
 
1595
-
1596
- function createSignalTower(elem, vmodel) {
1597
- var id = elem.getAttribute("avalonctrl") || vmodel.$id
1598
- elem.setAttribute("avalonctrl", id)
1599
- vmodel.$events.expr = elem.tagName + '[avalonctrl="' + id + '"]'
1600
- }
1601
-
1602
- var getBindingCallback = function(elem, name, vmodels) {
1603
- var callback = elem.getAttribute(name)
1604
- if (callback) {
1605
- for (var i = 0, vm; vm = vmodels[i++]; ) {
1606
- if (vm.hasOwnProperty(callback) && typeof vm[callback] === "function") {
1607
- return vm[callback]
1608
- }
1609
- }
1610
- }
1611
- }
1612
-
1613
- function executeBindings(bindings, vmodels) {
1614
- for (var i = 0, data; data = bindings[i++]; ) {
1615
- data.vmodels = vmodels
1616
- bindingHandlers[data.type](data, vmodels)
1617
- if (data.evaluator && data.element && data.element.nodeType === 1) { //移除数据绑定,防止被二次解析
1618
- //chrome使用removeAttributeNode移除不存在的特性节点时会报错 https://github.com/RubyLouvre/avalon/issues/99
1619
- data.element.removeAttribute(data.name)
1620
- }
1621
- }
1622
- bindings.length = 0
1623
- }
1624
-
1625
- //https://github.com/RubyLouvre/avalon/issues/636
1626
- var mergeTextNodes = IEVersion && window.MutationObserver ? function (elem) {
1627
- var node = elem.firstChild, text
1628
- while (node) {
1629
- var aaa = node.nextSibling
1630
- if (node.nodeType === 3) {
1631
- if (text) {
1632
- text.nodeValue += node.nodeValue
1633
- elem.removeChild(node)
1634
- } else {
1635
- text = node
1636
- }
1637
- } else {
1638
- text = null
1639
- }
1640
- node = aaa
1641
- }
1642
- } : 0
1643
-
1644
- var rmsAttr = /ms-(\w+)-?(.*)/
1645
- var priorityMap = {
1646
- "if": 10,
1647
- "repeat": 90,
1648
- "data": 100,
1649
- "widget": 110,
1650
- "each": 1400,
1651
- "with": 1500,
1652
- "duplex": 2000,
1653
- "on": 3000
1654
- }
1655
-
1656
- var events = oneObject("animationend,blur,change,input,click,dblclick,focus,keydown,keypress,keyup,mousedown,mouseenter,mouseleave,mousemove,mouseout,mouseover,mouseup,scan,scroll,submit")
1657
- var obsoleteAttrs = oneObject("value,title,alt,checked,selected,disabled,readonly,enabled")
1658
- function bindingSorter(a, b) {
1659
- return a.priority - b.priority
1660
- }
1661
-
1662
- function scanAttr(elem, vmodels) {
1663
- //防止setAttribute, removeAttribute时 attributes自动被同步,导致for循环出错
1664
- var attributes = elem.hasAttributes() ? avalon.slice(elem.attributes) : []
1665
- var bindings = [],
1666
- msData = createMap(),
1667
- match
1668
- for (var i = 0, attr; attr = attributes[i++]; ) {
1669
- if (attr.specified) {
1670
- if (match = attr.name.match(rmsAttr)) {
1671
- //如果是以指定前缀命名的
1672
- var type = match[1]
1673
- var param = match[2] || ""
1674
- var value = attr.value
1675
- var name = attr.name
1676
- msData[name] = value
1677
- if (events[type]) {
1678
- param = type
1679
- type = "on"
1680
- } else if (obsoleteAttrs[type]) {
1681
- log("warning!请改用ms-attr-" + type + "代替ms-" + type + "!")
1682
- if (type === "enabled") {//吃掉ms-enabled绑定,用ms-disabled代替
1683
- log("warning!ms-enabled或ms-attr-enabled已经被废弃")
1684
- type = "disabled"
1685
- value = "!(" + value + ")"
1686
- }
1687
- param = type
1688
- type = "attr"
1689
- elem.removeAttribute(name)
1690
- name = "ms-attr-" + param
1691
- elem.setAttribute(name, value)
1692
- match = [name]
1693
- msData[name] = value
1694
- }
1695
- if (typeof bindingHandlers[type] === "function") {
1696
- var binding = {
1697
- type: type,
1698
- param: param,
1699
- element: elem,
1700
- name: match[0],
1701
- value: value,
1702
- priority: type in priorityMap ? priorityMap[type] : type.charCodeAt(0) * 10 + (Number(param) || 0)
1703
- }
1704
- if (type === "html" || type === "text") {
1705
- var token = getToken(value)
1706
- avalon.mix(binding, token)
1707
- binding.filters = binding.filters.replace(rhasHtml, function () {
1708
- binding.type = "html"
1709
- binding.group = 1
1710
- return ""
1711
- })// jshint ignore:line
1712
- }
1713
- if (name === "ms-if-loop") {
1714
- binding.priority += 100
1715
- }
1716
- if (vmodels.length) {
1717
- bindings.push(binding)
1718
- if (type === "widget") {
1719
- elem.msData = elem.msData || msData
1720
- }
1721
- }
1722
- }
1723
- }
1724
- }
1725
- }
1726
- var control = elem.type
1727
- if (control && msData["ms-duplex"]) {
1728
- if (msData["ms-attr-checked"] && /radio|checkbox/.test(control)) {
1729
- log("warning!" + control + "控件不能同时定义ms-attr-checked与ms-duplex")
1730
- }
1731
- if (msData["ms-attr-value"] && /text|password/.test(control)) {
1732
- log("warning!" + control + "控件不能同时定义ms-attr-value与ms-duplex")
1733
- }
1734
- }
1735
- bindings.sort(bindingSorter)
1736
- var scanNode = true
1737
- for (i = 0; binding = bindings[i]; i++) {
1738
- type = binding.type
1739
- if (rnoscanAttrBinding.test(type)) {
1740
- return executeBindings(bindings.slice(0, i + 1), vmodels)
1741
- } else if (scanNode) {
1742
- scanNode = !rnoscanNodeBinding.test(type)
1743
- }
1744
- }
1745
- executeBindings(bindings, vmodels)
1746
- if (scanNode && !stopScan[elem.tagName] && rbind.test(elem.innerHTML + elem.textContent)) {
1747
- mergeTextNodes && mergeTextNodes(elem)
1748
- scanNodeList(elem, vmodels) //扫描子孙元素
1749
- }
1750
- }
1751
-
1752
- var rnoscanAttrBinding = /^if|widget|repeat$/
1753
- var rnoscanNodeBinding = /^each|with|html|include$/
1754
- function scanNodeList(parent, vmodels) {
1755
- var node = parent.firstChild
1756
- while (node) {
1757
- var nextNode = node.nextSibling
1758
- scanNode(node, node.nodeType, vmodels)
1759
- node = nextNode
1760
- }
1761
- }
1762
-
1763
- function scanNodeArray(nodes, vmodels) {
1764
- for (var i = 0, node; node = nodes[i++]; ) {
1765
- scanNode(node, node.nodeType, vmodels)
1766
- }
1767
- }
1768
- function scanNode(node, nodeType, vmodels) {
1769
- if (nodeType === 1) {
1770
- scanTag(node, vmodels) //扫描元素节点
1771
- } else if (nodeType === 3 && rexpr.test(node.data)){
1772
- scanText(node, vmodels) //扫描文本节点
1773
- } else if (kernel.commentInterpolate && nodeType === 8 && !rexpr.test(node.nodeValue)) {
1774
- scanText(node, vmodels) //扫描注释节点
1775
- }
1776
- }
1777
- function scanTag(elem, vmodels, node) {
1778
- //扫描顺序 ms-skip(0) --> ms-important(1) --> ms-controller(2) --> ms-if(10) --> ms-repeat(100)
1779
- //--> ms-if-loop(110) --> ms-attr(970) ...--> ms-each(1400)-->ms-with(1500)--〉ms-duplex(2000)垫后
1780
- var a = elem.getAttribute("ms-skip")
1781
- var b = elem.getAttributeNode("ms-important")
1782
- var c = elem.getAttributeNode("ms-controller")
1783
- if (typeof a === "string") {
1784
- return
1785
- } else if (node = b || c) {
1786
- var newVmodel = avalon.vmodels[node.value]
1787
- if (!newVmodel) {
1788
- return
1789
- }
1790
- //ms-important不包含父VM,ms-controller相反
1791
- vmodels = node === b ? [newVmodel] : [newVmodel].concat(vmodels)
1792
- elem.removeAttribute(node.name) //removeAttributeNode不会刷新[ms-controller]样式规则
1793
- elem.classList.remove(node.name)
1794
- createSignalTower(elem, newVmodel)
1795
- }
1796
- scanAttr(elem, vmodels) //扫描特性节点
1797
- }
1798
- var rhasHtml = /\|\s*html\s*/,
1799
- r11a = /\|\|/g,
1800
- rlt = /&lt;/g,
1801
- rgt = /&gt;/g
1802
- rstringLiteral = /(['"])(\\\1|.)+?\1/g
1803
- function getToken(value) {
1804
- if (value.indexOf("|") > 0) {
1805
- var scapegoat = value.replace( rstringLiteral, function(_){
1806
- return Array(_.length+1).join("1")// jshint ignore:line
1807
- })
1808
- var index = scapegoat.replace(r11a, "\u1122\u3344").indexOf("|") //干掉所有短路或
1809
- if (index > -1) {
1810
- return {
1811
- filters: value.slice(index),
1812
- value: value.slice(0, index),
1813
- expr: true
1814
- }
1815
- }
1816
- }
1817
- return {
1818
- value: value,
1819
- filters: "",
1820
- expr: true
1821
- }
1822
- }
1823
-
1824
- function scanExpr(str) {
1825
- var tokens = [],
1826
- value, start = 0,
1827
- stop
1828
- do {
1829
- stop = str.indexOf(openTag, start)
1830
- if (stop === -1) {
1831
- break
1832
- }
1833
- value = str.slice(start, stop)
1834
- if (value) { // {{ 左边的文本
1835
- tokens.push({
1836
- value: value,
1837
- filters: "",
1838
- expr: false
1839
- })
1840
- }
1841
- start = stop + openTag.length
1842
- stop = str.indexOf(closeTag, start)
1843
- if (stop === -1) {
1844
- break
1845
- }
1846
- value = str.slice(start, stop)
1847
- if (value) { //处理{{ }}插值表达式
1848
- tokens.push(getToken(value))
1849
- }
1850
- start = stop + closeTag.length
1851
- } while (1)
1852
- value = str.slice(start)
1853
- if (value) { //}} 右边的文本
1854
- tokens.push({
1855
- value: value,
1856
- expr: false,
1857
- filters: ""
1858
- })
1859
- }
1860
- return tokens
1861
- }
1862
-
1863
- function scanText(textNode, vmodels) {
1864
- var bindings = []
1865
- if (textNode.nodeType === 8) {
1866
- var token = getToken(textNode.nodeValue)
1867
- var tokens = [token]
1868
- } else {
1869
- tokens = scanExpr(textNode.data)
1870
- }
1871
- if (tokens.length) {
1872
- for (var i = 0; token = tokens[i++]; ) {
1873
- var node = DOC.createTextNode(token.value) //将文本转换为文本节点,并替换原来的文本节点
1874
- if (token.expr) {
1875
- token.type = "text"
1876
- token.element = node
1877
- token.filters = token.filters.replace(rhasHtml, function() {
1878
- token.type = "html"
1879
- token.group = 1
1880
- return ""
1881
- })// jshint ignore:line
1882
- bindings.push(token) //收集带有插值表达式的文本
1883
- }
1884
- hyperspace.appendChild(node)
1885
- }
1886
- textNode.parentNode.replaceChild(hyperspace, textNode)
1887
- if (bindings.length)
1888
- executeBindings(bindings, vmodels)
1889
- }
1890
- }
1891
-
1892
-
1893
- /*********************************************************************
1894
- * avalon的原型方法定义区 *
1895
- **********************************************************************/
1896
- function hyphen(target) {
1897
- //转换为连字符线风格
1898
- return target.replace(/([a-z\d])([A-Z]+)/g, "$1-$2").toLowerCase()
1899
- }
1900
- function camelize(target) {
1901
- //转换为驼峰风格
1902
- if (target.indexOf("-") < 0 && target.indexOf("_") < 0) {
1903
- return target //提前判断,提高getStyle等的效率
1904
- }
1905
- return target.replace(/[-_][^-_]/g, function (match) {
1906
- return match.charAt(1).toUpperCase()
1907
- })
1908
- }
1909
-
1910
- "add,remove".replace(rword, function (method) {
1911
- avalon.fn[method + "Class"] = function (cls) {
1714
+ "add,remove".replace(rword, function(method) {
1715
+ avalon.fn[method + "Class"] = function(cls) {
1912
1716
  var el = this[0]
1913
1717
  //https://developer.mozilla.org/zh-CN/docs/Mozilla/Firefox/Releases/26
1914
1718
  if (cls && typeof cls === "string" && el && el.nodeType === 1) {
1915
- cls.replace(/\S+/g, function (c) {
1719
+ cls.replace(/\S+/g, function(c) {
1916
1720
  el.classList[method](c)
1917
1721
  })
1918
1722
  }
@@ -1921,13 +1725,13 @@ function camelize(target) {
1921
1725
  })
1922
1726
 
1923
1727
  avalon.fn.mix({
1924
- hasClass: function (cls) {
1728
+ hasClass: function(cls) {
1925
1729
  var el = this[0] || {} //IE10+, chrome8+, firefox3.6+, safari5.1+,opera11.5+支持classList,chrome24+,firefox26+支持classList2.0
1926
1730
  return el.nodeType === 1 && el.classList.contains(cls)
1927
1731
  },
1928
- toggleClass: function (value, stateVal) {
1732
+ toggleClass: function(value, stateVal) {
1929
1733
  var className, i = 0
1930
- var classNames = value.split(/\s+/)
1734
+ var classNames = String(value).split(/\s+/)
1931
1735
  var isBool = typeof stateVal === "boolean"
1932
1736
  while ((className = classNames[i++])) {
1933
1737
  var state = isBool ? stateVal : !this.hasClass(className)
@@ -1935,7 +1739,7 @@ avalon.fn.mix({
1935
1739
  }
1936
1740
  return this
1937
1741
  },
1938
- attr: function (name, value) {
1742
+ attr: function(name, value) {
1939
1743
  if (arguments.length === 2) {
1940
1744
  this[0].setAttribute(name, value)
1941
1745
  return this
@@ -1943,7 +1747,7 @@ avalon.fn.mix({
1943
1747
  return this[0].getAttribute(name)
1944
1748
  }
1945
1749
  },
1946
- data: function (name, value) {
1750
+ data: function(name, value) {
1947
1751
  name = "data-" + hyphen(name || "")
1948
1752
  switch (arguments.length) {
1949
1753
  case 2:
@@ -1954,7 +1758,7 @@ avalon.fn.mix({
1954
1758
  return parseData(val)
1955
1759
  case 0:
1956
1760
  var ret = {}
1957
- ap.forEach.call(this[0].attributes, function (attr) {
1761
+ ap.forEach.call(this[0].attributes, function(attr) {
1958
1762
  if (attr) {
1959
1763
  name = attr.name
1960
1764
  if (!name.indexOf("data-")) {
@@ -1966,12 +1770,12 @@ avalon.fn.mix({
1966
1770
  return ret
1967
1771
  }
1968
1772
  },
1969
- removeData: function (name) {
1773
+ removeData: function(name) {
1970
1774
  name = "data-" + hyphen(name)
1971
1775
  this[0].removeAttribute(name)
1972
1776
  return this
1973
1777
  },
1974
- css: function (name, value) {
1778
+ css: function(name, value) {
1975
1779
  if (avalon.isPlainObject(name)) {
1976
1780
  for (var i in name) {
1977
1781
  avalon.css(this, i, name[i])
@@ -1981,13 +1785,13 @@ avalon.fn.mix({
1981
1785
  }
1982
1786
  return ret !== void 0 ? ret : this
1983
1787
  },
1984
- position: function () {
1788
+ position: function() {
1985
1789
  var offsetParent, offset,
1986
- elem = this[0],
1987
- parentOffset = {
1988
- top: 0,
1989
- left: 0
1990
- };
1790
+ elem = this[0],
1791
+ parentOffset = {
1792
+ top: 0,
1793
+ left: 0
1794
+ };
1991
1795
  if (!elem) {
1992
1796
  return
1993
1797
  }
@@ -2001,31 +1805,34 @@ avalon.fn.mix({
2001
1805
  }
2002
1806
  parentOffset.top += avalon.css(offsetParent[0], "borderTopWidth", true)
2003
1807
  parentOffset.left += avalon.css(offsetParent[0], "borderLeftWidth", true)
1808
+ // Subtract offsetParent scroll positions
1809
+ parentOffset.top -= offsetParent.scrollTop()
1810
+ parentOffset.left -= offsetParent.scrollLeft()
2004
1811
  }
2005
1812
  return {
2006
1813
  top: offset.top - parentOffset.top - avalon.css(elem, "marginTop", true),
2007
1814
  left: offset.left - parentOffset.left - avalon.css(elem, "marginLeft", true)
2008
1815
  }
2009
1816
  },
2010
- offsetParent: function () {
1817
+ offsetParent: function() {
2011
1818
  var offsetParent = this[0].offsetParent
2012
1819
  while (offsetParent && avalon.css(offsetParent, "position") === "static") {
2013
1820
  offsetParent = offsetParent.offsetParent;
2014
1821
  }
2015
1822
  return avalon(offsetParent || root)
2016
1823
  },
2017
- bind: function (type, fn, phase) {
1824
+ bind: function(type, fn, phase) {
2018
1825
  if (this[0]) { //此方法不会链
2019
1826
  return avalon.bind(this[0], type, fn, phase)
2020
1827
  }
2021
1828
  },
2022
- unbind: function (type, fn, phase) {
1829
+ unbind: function(type, fn, phase) {
2023
1830
  if (this[0]) {
2024
1831
  avalon.unbind(this[0], type, fn, phase)
2025
1832
  }
2026
1833
  return this
2027
1834
  },
2028
- val: function (value) {
1835
+ val: function(value) {
2029
1836
  var node = this[0]
2030
1837
  if (node && node.nodeType === 1) {
2031
1838
  var get = arguments.length === 0
@@ -2044,7 +1851,8 @@ avalon.fn.mix({
2044
1851
  })
2045
1852
 
2046
1853
  if (root.dataset) {
2047
- avalon.fn.data = function (name, val) {
1854
+ avalon.fn.data = function(name, val) {
1855
+ name = name && camelize(name)
2048
1856
  var dataset = this[0].dataset
2049
1857
  switch (arguments.length) {
2050
1858
  case 2:
@@ -2070,19 +1878,18 @@ function parseData(data) {
2070
1878
  if (typeof data === "object")
2071
1879
  return data
2072
1880
  data = data === "true" ? true :
2073
- data === "false" ? false :
2074
- data === "null" ? null : +data + "" === data ? +data : rbrace.test(data) ? JSON.parse(data) : data
2075
- } catch (e) {
2076
- }
1881
+ data === "false" ? false :
1882
+ data === "null" ? null : +data + "" === data ? +data : rbrace.test(data) ? JSON.parse(data) : data
1883
+ } catch (e) {}
2077
1884
  return data
2078
1885
  }
2079
1886
  avalon.each({
2080
1887
  scrollLeft: "pageXOffset",
2081
1888
  scrollTop: "pageYOffset"
2082
- }, function (method, prop) {
2083
- avalon.fn[method] = function (val) {
1889
+ }, function(method, prop) {
1890
+ avalon.fn[method] = function(val) {
2084
1891
  var node = this[0] || {}, win = getWindow(node),
2085
- top = method === "scrollTop"
1892
+ top = method === "scrollTop"
2086
1893
  if (!arguments.length) {
2087
1894
  return win ? win[prop] : node[method]
2088
1895
  } else {
@@ -2101,13 +1908,13 @@ function getWindow(node) {
2101
1908
 
2102
1909
  //=============================css相关==================================
2103
1910
  var cssHooks = avalon.cssHooks = createMap()
2104
- var prefixes = ["", "-webkit-", "-moz-", "-ms-"]//去掉opera-15的支持
1911
+ var prefixes = ["", "-webkit-", "-moz-", "-ms-"] //去掉opera-15的支持
2105
1912
  var cssMap = {
2106
1913
  "float": "cssFloat"
2107
1914
  }
2108
- avalon.cssNumber = oneObject("columnCount,order,fillOpacity,fontWeight,lineHeight,opacity,orphans,widows,zIndex,zoom")
1915
+ avalon.cssNumber = oneObject("animationIterationCount,columnCount,order,flex,flexGrow,flexShrink,fillOpacity,fontWeight,lineHeight,opacity,orphans,widows,zIndex,zoom")
2109
1916
 
2110
- avalon.cssName = function (name, host, camelCase) {
1917
+ avalon.cssName = function(name, host, camelCase) {
2111
1918
  if (cssMap[name]) {
2112
1919
  return cssMap[name]
2113
1920
  }
@@ -2120,33 +1927,33 @@ avalon.cssName = function (name, host, camelCase) {
2120
1927
  }
2121
1928
  return null
2122
1929
  }
2123
- cssHooks["@:set"] = function (node, name, value) {
1930
+ cssHooks["@:set"] = function(node, name, value) {
2124
1931
  node.style[name] = value
2125
1932
  }
2126
1933
 
2127
- cssHooks["@:get"] = function (node, name) {
1934
+ cssHooks["@:get"] = function(node, name) {
2128
1935
  if (!node || !node.style) {
2129
1936
  throw new Error("getComputedStyle要求传入一个节点 " + node)
2130
1937
  }
2131
1938
  var ret, computed = getComputedStyle(node)
2132
- if (computed) {
2133
- ret = name === "filter" ? computed.getPropertyValue(name) : computed[name]
2134
- if (ret === "") {
2135
- ret = node.style[name] //其他浏览器需要我们手动取内联样式
1939
+ if (computed) {
1940
+ ret = name === "filter" ? computed.getPropertyValue(name) : computed[name]
1941
+ if (ret === "") {
1942
+ ret = node.style[name] //其他浏览器需要我们手动取内联样式
1943
+ }
2136
1944
  }
2137
- }
2138
1945
  return ret
2139
1946
  }
2140
- cssHooks["opacity:get"] = function (node) {
1947
+ cssHooks["opacity:get"] = function(node) {
2141
1948
  var ret = cssHooks["@:get"](node, "opacity")
2142
1949
  return ret === "" ? "1" : ret
2143
1950
  }
2144
1951
 
2145
- "top,left".replace(rword, function (name) {
2146
- cssHooks[name + ":get"] = function (node) {
1952
+ "top,left".replace(rword, function(name) {
1953
+ cssHooks[name + ":get"] = function(node) {
2147
1954
  var computed = cssHooks["@:get"](node, name)
2148
1955
  return /px$/.test(computed) ? computed :
2149
- avalon(node).position()[name] + "px"
1956
+ avalon(node).position()[name] + "px"
2150
1957
  }
2151
1958
  })
2152
1959
  var cssShow = {
@@ -2156,125 +1963,125 @@ var cssShow = {
2156
1963
  }
2157
1964
  var rdisplayswap = /^(none|table(?!-c[ea]).+)/
2158
1965
 
2159
- function showHidden(node, array) {
2160
- //http://www.cnblogs.com/rubylouvre/archive/2012/10/27/2742529.html
2161
- if (node.offsetWidth <= 0) { //opera.offsetWidth可能小于0
2162
- var styles = getComputedStyle(node, null)
2163
- if (rdisplayswap.test(styles["display"])) {
2164
- var obj = {
2165
- node: node
1966
+ function showHidden(node, array) {
1967
+ //http://www.cnblogs.com/rubylouvre/archive/2012/10/27/2742529.html
1968
+ if (node.offsetWidth <= 0) { //opera.offsetWidth可能小于0
1969
+ var styles = getComputedStyle(node, null)
1970
+ if (rdisplayswap.test(styles["display"])) {
1971
+ var obj = {
1972
+ node: node
1973
+ }
1974
+ for (var name in cssShow) {
1975
+ obj[name] = styles[name]
1976
+ node.style[name] = cssShow[name]
1977
+ }
1978
+ array.push(obj)
2166
1979
  }
2167
- for (var name in cssShow) {
2168
- obj[name] = styles[name]
2169
- node.style[name] = cssShow[name]
1980
+ var parent = node.parentNode
1981
+ if (parent && parent.nodeType === 1) {
1982
+ showHidden(parent, array)
2170
1983
  }
2171
- array.push(obj)
2172
- }
2173
- var parent = node.parentNode
2174
- if (parent && parent.nodeType === 1) {
2175
- showHidden(parent, array)
2176
1984
  }
2177
1985
  }
2178
- }
2179
1986
 
2180
- "Width,Height".replace(rword, function (name) {//fix 481
2181
- var method = name.toLowerCase(),
1987
+ "Width,Height".replace(rword, function(name) { //fix 481
1988
+ var method = name.toLowerCase(),
2182
1989
  clientProp = "client" + name,
2183
1990
  scrollProp = "scroll" + name,
2184
1991
  offsetProp = "offset" + name
2185
- cssHooks[method + ":get"] = function (node, which, override) {
2186
- var boxSizing = -4
2187
- if (typeof override === "number") {
2188
- boxSizing = override
2189
- }
2190
- which = name === "Width" ? ["Left", "Right"] : ["Top", "Bottom"]
2191
- var ret = node[offsetProp] // border-box 0
2192
- if (boxSizing === 2) { // margin-box 2
2193
- return ret + avalon.css(node, "margin" + which[0], true) + avalon.css(node, "margin" + which[1], true)
2194
- }
2195
- if (boxSizing < 0) { // padding-box -2
2196
- ret = ret - avalon.css(node, "border" + which[0] + "Width", true) - avalon.css(node, "border" + which[1] + "Width", true)
2197
- }
2198
- if (boxSizing === -4) { // content-box -4
2199
- ret = ret - avalon.css(node, "padding" + which[0], true) - avalon.css(node, "padding" + which[1], true)
1992
+ cssHooks[method + ":get"] = function(node, which, override) {
1993
+ var boxSizing = -4
1994
+ if (typeof override === "number") {
1995
+ boxSizing = override
1996
+ }
1997
+ which = name === "Width" ? ["Left", "Right"] : ["Top", "Bottom"]
1998
+ var ret = node[offsetProp] // border-box 0
1999
+ if (boxSizing === 2) { // margin-box 2
2000
+ return ret + avalon.css(node, "margin" + which[0], true) + avalon.css(node, "margin" + which[1], true)
2001
+ }
2002
+ if (boxSizing < 0) { // padding-box -2
2003
+ ret = ret - avalon.css(node, "border" + which[0] + "Width", true) - avalon.css(node, "border" + which[1] + "Width", true)
2004
+ }
2005
+ if (boxSizing === -4) { // content-box -4
2006
+ ret = ret - avalon.css(node, "padding" + which[0], true) - avalon.css(node, "padding" + which[1], true)
2007
+ }
2008
+ return ret
2009
+ }
2010
+ cssHooks[method + "&get"] = function(node) {
2011
+ var hidden = [];
2012
+ showHidden(node, hidden);
2013
+ var val = cssHooks[method + ":get"](node)
2014
+ for (var i = 0, obj; obj = hidden[i++];) {
2015
+ node = obj.node
2016
+ for (var n in obj) {
2017
+ if (typeof obj[n] === "string") {
2018
+ node.style[n] = obj[n]
2019
+ }
2020
+ }
2021
+ }
2022
+ return val;
2200
2023
  }
2201
- return ret
2202
- }
2203
- cssHooks[method + "&get"] = function (node) {
2204
- var hidden = [];
2205
- showHidden(node, hidden);
2206
- var val = cssHooks[method + ":get"](node)
2207
- for (var i = 0, obj; obj = hidden[i++]; ) {
2208
- node = obj.node
2209
- for (var n in obj) {
2210
- if (typeof obj[n] === "string") {
2211
- node.style[n] = obj[n]
2024
+ avalon.fn[method] = function(value) { //会忽视其display
2025
+ var node = this[0]
2026
+ if (arguments.length === 0) {
2027
+ if (node.setTimeout) { //取得窗口尺寸,IE9后可以用node.innerWidth /innerHeight代替
2028
+ return node["inner" + name]
2029
+ }
2030
+ if (node.nodeType === 9) { //取得页面尺寸
2031
+ var doc = node.documentElement
2032
+ //FF chrome html.scrollHeight< body.scrollHeight
2033
+ //IE 标准模式 : html.scrollHeight> body.scrollHeight
2034
+ //IE 怪异模式 : html.scrollHeight 最大等于可视窗口多一点?
2035
+ return Math.max(node.body[scrollProp], doc[scrollProp], node.body[offsetProp], doc[offsetProp], doc[clientProp])
2212
2036
  }
2037
+ return cssHooks[method + "&get"](node)
2038
+ } else {
2039
+ return this.css(method, value)
2213
2040
  }
2214
2041
  }
2215
- return val;
2216
- }
2217
- avalon.fn[method] = function (value) { //会忽视其display
2042
+ avalon.fn["inner" + name] = function() {
2043
+ return cssHooks[method + ":get"](this[0], void 0, -2)
2044
+ }
2045
+ avalon.fn["outer" + name] = function(includeMargin) {
2046
+ return cssHooks[method + ":get"](this[0], void 0, includeMargin === true ? 2 : 0)
2047
+ }
2048
+ })
2049
+ avalon.fn.offset = function() { //取得距离页面左右角的坐标
2218
2050
  var node = this[0]
2219
- if (arguments.length === 0) {
2220
- if (node.setTimeout) { //取得窗口尺寸,IE9后可以用node.innerWidth /innerHeight代替
2221
- return node["inner" + name]
2222
- }
2223
- if (node.nodeType === 9) { //取得页面尺寸
2224
- var doc = node.documentElement
2225
- //FF chrome html.scrollHeight< body.scrollHeight
2226
- //IE 标准模式 : html.scrollHeight> body.scrollHeight
2227
- //IE 怪异模式 : html.scrollHeight 最大等于可视窗口多一点?
2228
- return Math.max(node.body[scrollProp], doc[scrollProp], node.body[offsetProp], doc[offsetProp], doc[clientProp])
2051
+ try {
2052
+ var rect = node.getBoundingClientRect()
2053
+ // Make sure element is not hidden (display: none) or disconnected
2054
+ // https://github.com/jquery/jquery/pull/2043/files#r23981494
2055
+ if (rect.width || rect.height || node.getClientRects().length) {
2056
+ var doc = node.ownerDocument
2057
+ var root = doc.documentElement
2058
+ var win = doc.defaultView
2059
+ return {
2060
+ top: rect.top + win.pageYOffset - root.clientTop,
2061
+ left: rect.left + win.pageXOffset - root.clientLeft
2062
+ }
2229
2063
  }
2230
- return cssHooks[method + "&get"](node)
2231
- } else {
2232
- return this.css(method, value)
2233
- }
2234
- }
2235
- avalon.fn["inner" + name] = function () {
2236
- return cssHooks[method + ":get"](this[0], void 0, -2)
2237
- }
2238
- avalon.fn["outer" + name] = function (includeMargin) {
2239
- return cssHooks[method + ":get"](this[0], void 0, includeMargin === true ? 2 : 0)
2240
- }
2241
- })
2242
- avalon.fn.offset = function () { //取得距离页面左右角的坐标
2243
- var node = this[0]
2244
- try {
2245
- var rect = node.getBoundingClientRect()
2246
- // Make sure element is not hidden (display: none) or disconnected
2247
- // https://github.com/jquery/jquery/pull/2043/files#r23981494
2248
- if (rect.width || rect.height || node.getClientRects().length) {
2249
- var doc = node.ownerDocument
2250
- var root = doc.documentElement
2251
- var win = doc.defaultView
2064
+ } catch (e) {
2252
2065
  return {
2253
- top: rect.top + win.pageYOffset - root.clientTop,
2254
- left: rect.left + win.pageXOffset - root.clientLeft
2066
+ left: 0,
2067
+ top: 0
2255
2068
  }
2256
2069
  }
2257
- } catch (e) {
2258
- return {
2259
- left: 0,
2260
- top: 0
2261
- }
2262
2070
  }
2263
- }
2264
- //=============================val相关=======================
2071
+ //=============================val相关=======================
2265
2072
 
2266
- function getValType(el) {
2267
- var ret = el.tagName.toLowerCase()
2268
- return ret === "input" && /checkbox|radio/.test(el.type) ? "checked" : ret
2269
- }
2073
+ function getValType(elem) {
2074
+ var ret = elem.tagName.toLowerCase()
2075
+ return ret === "input" && /checkbox|radio/.test(elem.type) ? "checked" : ret
2076
+ }
2270
2077
  var valHooks = {
2271
- "select:get": function (node, value) {
2078
+ "select:get": function(node, value) {
2272
2079
  var option, options = node.options,
2273
- index = node.selectedIndex,
2274
- one = node.type === "select-one" || index < 0,
2275
- values = one ? null : [],
2276
- max = one ? index + 1 : options.length,
2277
- i = index < 0 ? max : one ? index : 0
2080
+ index = node.selectedIndex,
2081
+ one = node.type === "select-one" || index < 0,
2082
+ values = one ? null : [],
2083
+ max = one ? index + 1 : options.length,
2084
+ i = index < 0 ? max : one ? index : 0
2278
2085
  for (; i < max; i++) {
2279
2086
  option = options[i]
2280
2087
  //旧式IE在reset后不会改变selected,需要改用i === index判定
@@ -2291,9 +2098,9 @@ var valHooks = {
2291
2098
  }
2292
2099
  return values
2293
2100
  },
2294
- "select:set": function (node, values, optionSet) {
2101
+ "select:set": function(node, values, optionSet) {
2295
2102
  values = [].concat(values) //强制转换为数组
2296
- for (var i = 0, el; el = node.options[i++]; ) {
2103
+ for (var i = 0, el; el = node.options[i++];) {
2297
2104
  if ((el.selected = values.indexOf(el.value) > -1)) {
2298
2105
  optionSet = true
2299
2106
  }
@@ -2303,7 +2110,6 @@ var valHooks = {
2303
2110
  }
2304
2111
  }
2305
2112
  }
2306
-
2307
2113
  /*********************************************************************
2308
2114
  * 编译系统 *
2309
2115
  **********************************************************************/
@@ -2323,10 +2129,10 @@ var rsplit = /[^\w$]+/g
2323
2129
  var rkeywords = new RegExp(["\\b" + keywords.replace(/,/g, '\\b|\\b') + "\\b"].join('|'), 'g')
2324
2130
  var rnumber = /\b\d[^,]*/g
2325
2131
  var rcomma = /^,+|,+$/g
2326
- var cacheVars = new Cache(512)
2132
+ var variablePool = new Cache(512)
2327
2133
  var getVariables = function (code) {
2328
2134
  var key = "," + code.trim()
2329
- var ret = cacheVars.get(key)
2135
+ var ret = variablePool.get(key)
2330
2136
  if (ret) {
2331
2137
  return ret
2332
2138
  }
@@ -2337,7 +2143,7 @@ var getVariables = function (code) {
2337
2143
  .replace(rnumber, "")
2338
2144
  .replace(rcomma, "")
2339
2145
  .split(/^$|,+/)
2340
- return cacheVars.put(key, uniqSet(match))
2146
+ return variablePool.put(key, uniqSet(match))
2341
2147
  }
2342
2148
  /*添加赋值语句*/
2343
2149
 
@@ -2370,7 +2176,7 @@ function uniqSet(array) {
2370
2176
  return ret
2371
2177
  }
2372
2178
  //缓存求值函数,以便多次利用
2373
- var cacheExprs = new Cache(128)
2179
+ var evaluatorPool = new Cache(128)
2374
2180
  //取得求值函数及其传参
2375
2181
  var rduplex = /\w\[.*\]|\w\.\w/
2376
2182
  var rproxy = /(\$proxy\$[a-z]+)\d+$/
@@ -2394,142 +2200,496 @@ function parseFilter(val, filters) {
2394
2200
  .replace(rthimLeftParentheses, function () {
2395
2201
  return '",'
2396
2202
  }) + "]"
2397
- return "return avalon.filters.$filter(" + val + ", " + filters + ")"
2203
+ return "return this.filters.$filter(" + val + ", " + filters + ")"
2204
+ }
2205
+
2206
+ function parseExpr(code, scopes, data) {
2207
+ var dataType = data.type
2208
+ var filters = data.filters || ""
2209
+ var exprId = scopes.map(function (el) {
2210
+ return String(el.$id).replace(rproxy, "$1")
2211
+ }) + code + dataType + filters
2212
+ var vars = getVariables(code).concat(),
2213
+ assigns = [],
2214
+ names = [],
2215
+ args = [],
2216
+ prefix = ""
2217
+ //args 是一个对象数组, names 是将要生成的求值函数的参数
2218
+ scopes = uniqSet(scopes)
2219
+ data.vars = []
2220
+ for (var i = 0, sn = scopes.length; i < sn; i++) {
2221
+ if (vars.length) {
2222
+ var name = "vm" + expose + "_" + i
2223
+ names.push(name)
2224
+ args.push(scopes[i])
2225
+ assigns.push.apply(assigns, addAssign(vars, scopes[i], name, data))
2226
+ }
2227
+ }
2228
+ if (!assigns.length && dataType === "duplex") {
2229
+ return
2230
+ }
2231
+ if (dataType !== "duplex" && (code.indexOf("||") > -1 || code.indexOf("&&") > -1)) {
2232
+ //https://github.com/RubyLouvre/avalon/issues/583
2233
+ data.vars.forEach(function (v) {
2234
+ var reg = new RegExp("\\b" + v + "(?:\\.\\w+|\\[\\w+\\])+", "ig")
2235
+ code = code.replace(reg, function (_, cap) {
2236
+ var c = _.charAt(v.length)
2237
+ //var r = IEVersion ? code.slice(arguments[1] + _.length) : RegExp.rightContext
2238
+ //https://github.com/RubyLouvre/avalon/issues/966
2239
+ var r = code.slice(cap + _.length)
2240
+ var method = /^\s*\(/.test(r)
2241
+ if (c === "." || c === "[" || method) {//比如v为aa,我们只匹配aa.bb,aa[cc],不匹配aaa.xxx
2242
+ var name = "var" + String(Math.random()).replace(/^0\./, "")
2243
+ if (method) {//array.size()
2244
+ var array = _.split(".")
2245
+ if (array.length > 2) {
2246
+ var last = array.pop()
2247
+ assigns.push(name + " = " + array.join("."))
2248
+ return name + "." + last
2249
+ } else {
2250
+ return _
2251
+ }
2252
+ }
2253
+ assigns.push(name + " = " + _)
2254
+ return name
2255
+ } else {
2256
+ return _
2257
+ }
2258
+ })
2259
+ })
2260
+ }
2261
+ //---------------args----------------
2262
+ data.args = args
2263
+ //---------------cache----------------
2264
+ delete data.vars
2265
+ var fn = evaluatorPool.get(exprId) //直接从缓存,免得重复生成
2266
+ if (fn) {
2267
+ data.evaluator = fn
2268
+ return
2269
+ }
2270
+ prefix = assigns.join(", ")
2271
+ if (prefix) {
2272
+ prefix = "var " + prefix
2273
+ }
2274
+ if (/\S/.test(filters)) { //文本绑定,双工绑定才有过滤器
2275
+ if (!/text|html/.test(data.type)) {
2276
+ throw Error("ms-" + data.type + "不支持过滤器")
2277
+ }
2278
+ code = "\nvar ret" + expose + " = " + code + ";\r\n"
2279
+ code += parseFilter("ret" + expose, filters)
2280
+ try {
2281
+ fn = Function.apply(noop, names.concat("'use strict';\n" + prefix + code))
2282
+ data.evaluator = evaluatorPool.put(exprId, function() {
2283
+ return fn.apply(avalon, arguments)//确保可以在编译代码中使用this获取avalon对象
2284
+ })
2285
+ } catch (e) {
2286
+ log("debug: parse error," + e.message)
2287
+ }
2288
+ vars = assigns = names = null //释放内存
2289
+ return
2290
+ } else if (dataType === "duplex") { //双工绑定
2291
+ var _body = "'use strict';\nreturn function(vvv){\n\t" +
2292
+ prefix +
2293
+ ";\n\tif(!arguments.length){\n\t\treturn " +
2294
+ code +
2295
+ "\n\t}\n\t" + (!rduplex.test(code) ? vars.get : code) +
2296
+ "= vvv;\n} "
2297
+ try {
2298
+ fn = Function.apply(noop, names.concat(_body))
2299
+ data.evaluator = evaluatorPool.put(exprId, fn)
2300
+ } catch (e) {
2301
+ log("debug: parse error," + e.message)
2302
+ }
2303
+ vars = assigns = names = null //释放内存
2304
+ return
2305
+ } else if (dataType === "on") { //事件绑定
2306
+ if (code.indexOf("(") === -1) {
2307
+ code += ".call(this, $event)"
2308
+ } else {
2309
+ code = code.replace("(", ".call(this,")
2310
+ }
2311
+ names.push("$event")
2312
+ code = "\nreturn " + code + ";" //IE全家 Function("return ")出错,需要Function("return ;")
2313
+ var lastIndex = code.lastIndexOf("\nreturn")
2314
+ var header = code.slice(0, lastIndex)
2315
+ var footer = code.slice(lastIndex)
2316
+ code = header + "\n" + footer
2317
+ } else { //其他绑定
2318
+ code = "\nreturn " + code + ";" //IE全家 Function("return ")出错,需要Function("return ;")
2319
+ }
2320
+ try {
2321
+ fn = Function.apply(noop, names.concat("'use strict';\n" + prefix + code))
2322
+ data.evaluator = evaluatorPool.put(exprId, fn)
2323
+ } catch (e) {
2324
+ log("debug: parse error," + e.message)
2325
+ }
2326
+ vars = assigns = names = null //释放内存
2327
+ }
2328
+ function stringifyExpr(code) {
2329
+ var hasExpr = rexpr.test(code) //比如ms-class="width{{w}}"的情况
2330
+ if (hasExpr) {
2331
+ var array = scanExpr(code)
2332
+ if (array.length === 1) {
2333
+ return array[0].value
2334
+ }
2335
+ return array.map(function (el) {
2336
+ return el.expr ? "(" + el.value + ")" : quote(el.value)
2337
+ }).join(" + ")
2338
+ } else {
2339
+ return code
2340
+ }
2341
+ }
2342
+ //parseExpr的智能引用代理
2343
+
2344
+ function parseExprProxy(code, scopes, data, noRegister) {
2345
+ code = code || "" //code 可能未定义
2346
+ parseExpr(code, scopes, data)
2347
+ if (data.evaluator && !noRegister) {
2348
+ data.handler = bindingExecutors[data.handlerName || data.type]
2349
+ //方便调试
2350
+ //这里非常重要,我们通过判定视图刷新函数的element是否在DOM树决定
2351
+ //将它移出订阅者列表
2352
+ avalon.injectBinding(data)
2353
+ }
2354
+ }
2355
+ avalon.parseExprProxy = parseExprProxy
2356
+ /*********************************************************************
2357
+ * 扫描系统 *
2358
+ **********************************************************************/
2359
+
2360
+ avalon.scan = function(elem, vmodel) {
2361
+ elem = elem || root
2362
+ var vmodels = vmodel ? [].concat(vmodel) : []
2363
+ scanTag(elem, vmodels)
2364
+ }
2365
+
2366
+ //http://www.w3.org/TR/html5/syntax.html#void-elements
2367
+ var stopScan = oneObject("area,base,basefont,br,col,command,embed,hr,img,input,link,meta,param,source,track,wbr,noscript,script,style,textarea".toUpperCase())
2368
+
2369
+ function checkScan(elem, callback, innerHTML) {
2370
+ var id = setTimeout(function() {
2371
+ var currHTML = elem.innerHTML
2372
+ clearTimeout(id)
2373
+ if (currHTML === innerHTML) {
2374
+ callback()
2375
+ } else {
2376
+ checkScan(elem, callback, currHTML)
2377
+ }
2378
+ })
2379
+ }
2380
+
2381
+
2382
+ function createSignalTower(elem, vmodel) {
2383
+ var id = elem.getAttribute("avalonctrl") || vmodel.$id
2384
+ elem.setAttribute("avalonctrl", id)
2385
+ vmodel.$events.expr = elem.tagName + '[avalonctrl="' + id + '"]'
2386
+ }
2387
+
2388
+ var getBindingCallback = function(elem, name, vmodels) {
2389
+ var callback = elem.getAttribute(name)
2390
+ if (callback) {
2391
+ for (var i = 0, vm; vm = vmodels[i++]; ) {
2392
+ if (vm.hasOwnProperty(callback) && typeof vm[callback] === "function") {
2393
+ return vm[callback]
2394
+ }
2395
+ }
2396
+ }
2397
+ }
2398
+
2399
+ function executeBindings(bindings, vmodels) {
2400
+ for (var i = 0, data; data = bindings[i++]; ) {
2401
+ data.vmodels = vmodels
2402
+ bindingHandlers[data.type](data, vmodels)
2403
+ if (data.evaluator && data.element && data.element.nodeType === 1) { //移除数据绑定,防止被二次解析
2404
+ //chrome使用removeAttributeNode移除不存在的特性节点时会报错 https://github.com/RubyLouvre/avalon/issues/99
2405
+ data.element.removeAttribute(data.name)
2406
+ }
2407
+ }
2408
+ bindings.length = 0
2409
+ }
2410
+
2411
+ //https://github.com/RubyLouvre/avalon/issues/636
2412
+ var mergeTextNodes = IEVersion && window.MutationObserver ? function (elem) {
2413
+ var node = elem.firstChild, text
2414
+ while (node) {
2415
+ var aaa = node.nextSibling
2416
+ if (node.nodeType === 3) {
2417
+ if (text) {
2418
+ text.nodeValue += node.nodeValue
2419
+ elem.removeChild(node)
2420
+ } else {
2421
+ text = node
2422
+ }
2423
+ } else {
2424
+ text = null
2425
+ }
2426
+ node = aaa
2427
+ }
2428
+ } : 0
2429
+ var roneTime = /^\s*::/
2430
+ var rmsAttr = /ms-(\w+)-?(.*)/
2431
+ var priorityMap = {
2432
+ "if": 10,
2433
+ "repeat": 90,
2434
+ "data": 100,
2435
+ "widget": 110,
2436
+ "each": 1400,
2437
+ "with": 1500,
2438
+ "duplex": 2000,
2439
+ "on": 3000
2440
+ }
2441
+
2442
+ var events = oneObject("animationend,blur,change,input,click,dblclick,focus,keydown,keypress,keyup,mousedown,mouseenter,mouseleave,mousemove,mouseout,mouseover,mouseup,scan,scroll,submit")
2443
+ var obsoleteAttrs = oneObject("value,title,alt,checked,selected,disabled,readonly,enabled")
2444
+ function bindingSorter(a, b) {
2445
+ return a.priority - b.priority
2446
+ }
2447
+
2448
+ function scanAttr(elem, vmodels, match) {
2449
+ var scanNode = true
2450
+ if (vmodels.length) {
2451
+ var attributes = elem.attributes
2452
+ var bindings = []
2453
+ var fixAttrs = []
2454
+ var uniq = {}
2455
+ var msData = createMap()
2456
+ for (var i = 0, attr; attr = attributes[i++]; ) {
2457
+ if (attr.specified) {
2458
+ if (match = attr.name.match(rmsAttr)) {
2459
+ //如果是以指定前缀命名的
2460
+ var type = match[1]
2461
+ var param = match[2] || ""
2462
+ var value = attr.value
2463
+ var name = attr.name
2464
+ if (uniq[name]) {//IE8下ms-repeat,ms-with BUG
2465
+ continue
2466
+ }
2467
+ uniq[name] = 1
2468
+ if (events[type]) {
2469
+ param = type
2470
+ type = "on"
2471
+ } else if (obsoleteAttrs[type]) {
2472
+ if (type === "enabled") {//吃掉ms-enabled绑定,用ms-disabled代替
2473
+ log("warning!ms-enabled或ms-attr-enabled已经被废弃")
2474
+ type = "disabled"
2475
+ value = "!(" + value + ")"
2476
+ }
2477
+ param = type
2478
+ type = "attr"
2479
+ name = "ms-" + type + "-" + param
2480
+ fixAttrs.push([attr.name, name, value])
2481
+ }
2482
+ msData[name] = value
2483
+ if (typeof bindingHandlers[type] === "function") {
2484
+ var newValue = value.replace(roneTime, "")
2485
+ var oneTime = value !== newValue
2486
+ var binding = {
2487
+ type: type,
2488
+ param: param,
2489
+ element: elem,
2490
+ name: name,
2491
+ value: newValue,
2492
+ oneTime: oneTime,
2493
+ priority: (priorityMap[type] || type.charCodeAt(0) * 10) + (Number(param.replace(/\D/g, "")) || 0)
2494
+ }
2495
+ if (type === "html" || type === "text") {
2496
+ var token = getToken(value)
2497
+ avalon.mix(binding, token)
2498
+ binding.filters = binding.filters.replace(rhasHtml, function () {
2499
+ binding.type = "html"
2500
+ binding.group = 1
2501
+ return ""
2502
+ })// jshint ignore:line
2503
+ } else if (type === "duplex") {
2504
+ var hasDuplex = name
2505
+ } else if (name === "ms-if-loop") {
2506
+ binding.priority += 100
2507
+ }
2508
+ bindings.push(binding)
2509
+ if (type === "widget") {
2510
+ elem.msData = elem.msData || msData
2511
+ }
2512
+ }
2513
+ }
2514
+ }
2515
+ }
2516
+ if (bindings.length) {
2517
+ bindings.sort(bindingSorter)
2518
+ fixAttrs.forEach(function (arr) {
2519
+ log("warning!请改用" + arr[1] + "代替" + arr[0] + "!")
2520
+ elem.removeAttribute(arr[0])
2521
+ elem.setAttribute(arr[1], arr[2])
2522
+ })
2523
+ var control = elem.type
2524
+ if (control && hasDuplex) {
2525
+ if (msData["ms-attr-value"] && elem.type === "text") {
2526
+ log("warning!" + control + "控件不能同时定义ms-attr-value与" + hasDuplex)
2527
+ }
2528
+ }
2529
+
2530
+ for (i = 0; binding = bindings[i]; i++) {
2531
+ type = binding.type
2532
+ if (rnoscanAttrBinding.test(type)) {
2533
+ return executeBindings(bindings.slice(0, i + 1), vmodels)
2534
+ } else if (scanNode) {
2535
+ scanNode = !rnoscanNodeBinding.test(type)
2536
+ }
2537
+ }
2538
+ executeBindings(bindings, vmodels)
2539
+ }
2540
+ }
2541
+ if (scanNode && !stopScan[elem.tagName] && rbind.test(elem.innerHTML + elem.textContent)) {
2542
+ mergeTextNodes && mergeTextNodes(elem)
2543
+ scanNodeList(elem, vmodels) //扫描子孙元素
2544
+ }
2545
+ }
2546
+
2547
+ var rnoscanAttrBinding = /^if|widget|repeat$/
2548
+ var rnoscanNodeBinding = /^each|with|html|include$/
2549
+ function scanNodeList(parent, vmodels) {
2550
+ var nodes = avalon.slice(parent.childNodes)
2551
+ scanNodeArray(nodes, vmodels)
2398
2552
  }
2399
2553
 
2400
- function parseExpr(code, scopes, data) {
2401
- var dataType = data.type
2402
- var filters = data.filters || ""
2403
- var exprId = scopes.map(function (el) {
2404
- return String(el.$id).replace(rproxy, "$1")
2405
- }) + code + dataType + filters
2406
- var vars = getVariables(code).concat(),
2407
- assigns = [],
2408
- names = [],
2409
- args = [],
2410
- prefix = ""
2411
- //args 是一个对象数组, names 是将要生成的求值函数的参数
2412
- scopes = uniqSet(scopes)
2413
- data.vars = []
2414
- for (var i = 0, sn = scopes.length; i < sn; i++) {
2415
- if (vars.length) {
2416
- var name = "vm" + expose + "_" + i
2417
- names.push(name)
2418
- args.push(scopes[i])
2419
- assigns.push.apply(assigns, addAssign(vars, scopes[i], name, data))
2554
+ function scanNodeArray(nodes, vmodels) {
2555
+ for (var i = 0, node; node = nodes[i++];) {
2556
+ switch (node.nodeType) {
2557
+ case 1:
2558
+ scanTag(node, vmodels) //扫描元素节点
2559
+ if (node.msCallback) {
2560
+ node.msCallback()
2561
+ node.msCallback = void 0
2562
+ }
2563
+ break
2564
+ case 3:
2565
+ if(rexpr.test(node.nodeValue)){
2566
+ scanText(node, vmodels, i) //扫描文本节点
2567
+ }
2568
+ break
2420
2569
  }
2421
2570
  }
2422
- if (!assigns.length && dataType === "duplex") {
2571
+ }
2572
+
2573
+
2574
+ function scanTag(elem, vmodels, node) {
2575
+ //扫描顺序 ms-skip(0) --> ms-important(1) --> ms-controller(2) --> ms-if(10) --> ms-repeat(100)
2576
+ //--> ms-if-loop(110) --> ms-attr(970) ...--> ms-each(1400)-->ms-with(1500)--〉ms-duplex(2000)垫后
2577
+ var a = elem.getAttribute("ms-skip")
2578
+ var b = elem.getAttributeNode("ms-important")
2579
+ var c = elem.getAttributeNode("ms-controller")
2580
+ if (typeof a === "string") {
2423
2581
  return
2582
+ } else if (node = b || c) {
2583
+ var newVmodel = avalon.vmodels[node.value]
2584
+ if (!newVmodel) {
2585
+ return
2586
+ }
2587
+ //ms-important不包含父VM,ms-controller相反
2588
+ vmodels = node === b ? [newVmodel] : [newVmodel].concat(vmodels)
2589
+ elem.removeAttribute(node.name) //removeAttributeNode不会刷新[ms-controller]样式规则
2590
+ elem.classList.remove(node.name)
2591
+ createSignalTower(elem, newVmodel)
2424
2592
  }
2425
- if (dataType !== "duplex" && (code.indexOf("||") > -1 || code.indexOf("&&") > -1)) {
2426
- //https://github.com/RubyLouvre/avalon/issues/583
2427
- data.vars.forEach(function (v) {
2428
- var reg = new RegExp("\\b" + v + "(?:\\.\\w+|\\[\\w+\\])+", "ig")
2429
- code = code.replace(reg, function (_) {
2430
- var c = _.charAt(v.length)
2431
- var r = IEVersion ? code.slice(arguments[1] + _.length) : RegExp.rightContext
2432
- var method = /^\s*\(/.test(r)
2433
- if (c === "." || c === "[" || method) {//比如v为aa,我们只匹配aa.bb,aa[cc],不匹配aaa.xxx
2434
- var name = "var" + String(Math.random()).replace(/^0\./, "")
2435
- if (method) {//array.size()
2436
- var array = _.split(".")
2437
- if (array.length > 2) {
2438
- var last = array.pop()
2439
- assigns.push(name + " = " + array.join("."))
2440
- return name + "." + last
2441
- } else {
2442
- return _
2443
- }
2444
- }
2445
- assigns.push(name + " = " + _)
2446
- return name
2447
- } else {
2448
- return _
2449
- }
2450
- })
2593
+ scanAttr(elem, vmodels) //扫描特性节点
2594
+ }
2595
+ var rhasHtml = /\|\s*html(?:\b|$)/,
2596
+ r11a = /\|\|/g,
2597
+ rlt = /&lt;/g,
2598
+ rgt = /&gt;/g,
2599
+ rstringLiteral = /(['"])(\\\1|.)+?\1/g
2600
+ function getToken(value) {
2601
+ if (value.indexOf("|") > 0) {
2602
+ var scapegoat = value.replace(rstringLiteral, function (_) {
2603
+ return Array(_.length + 1).join("1")// jshint ignore:line
2451
2604
  })
2605
+ var index = scapegoat.replace(r11a, "\u1122\u3344").indexOf("|") //干掉所有短路或
2606
+ if (index > -1) {
2607
+ return {
2608
+ filters: value.slice(index),
2609
+ value: value.slice(0, index),
2610
+ expr: true
2611
+ }
2612
+ }
2452
2613
  }
2453
- //---------------args----------------
2454
- data.args = args
2455
- //---------------cache----------------
2456
- var fn = cacheExprs.get(exprId) //直接从缓存,免得重复生成
2457
- if (fn) {
2458
- data.evaluator = fn
2459
- return
2460
- }
2461
- prefix = assigns.join(", ")
2462
- if (prefix) {
2463
- prefix = "var " + prefix
2614
+ return {
2615
+ value: value,
2616
+ filters: "",
2617
+ expr: true
2464
2618
  }
2465
- if (/\S/.test(filters)) { //文本绑定,双工绑定才有过滤器
2466
- if (!/text|html/.test(data.type)) {
2467
- throw Error("ms-" + data.type + "不支持过滤器")
2619
+ }
2620
+
2621
+ function scanExpr(str) {
2622
+ var tokens = [],
2623
+ value, start = 0,
2624
+ stop
2625
+ do {
2626
+ stop = str.indexOf(openTag, start)
2627
+ if (stop === -1) {
2628
+ break
2468
2629
  }
2469
- code = "\nvar ret" + expose + " = " + code + ";\r\n"
2470
- code += parseFilter("ret" + expose, filters)
2471
- } else if (dataType === "duplex") { //双工绑定
2472
- var _body = "'use strict';\nreturn function(vvv){\n\t" +
2473
- prefix +
2474
- ";\n\tif(!arguments.length){\n\t\treturn " +
2475
- code +
2476
- "\n\t}\n\t" + (!rduplex.test(code) ? vars.get : code) +
2477
- "= vvv;\n} "
2478
- try {
2479
- fn = Function.apply(noop, names.concat(_body))
2480
- data.evaluator = cacheExprs.put(exprId, fn)
2481
- } catch (e) {
2482
- log("debug: parse error," + e.message)
2630
+ value = str.slice(start, stop)
2631
+ if (value) { // {{ 左边的文本
2632
+ tokens.push({
2633
+ value: value,
2634
+ filters: "",
2635
+ expr: false
2636
+ })
2483
2637
  }
2484
- return
2485
- } else if (dataType === "on") { //事件绑定
2486
- if (code.indexOf("(") === -1) {
2487
- code += ".call(this, $event)"
2488
- } else {
2489
- code = code.replace("(", ".call(this,")
2638
+ start = stop + openTag.length
2639
+ stop = str.indexOf(closeTag, start)
2640
+ if (stop === -1) {
2641
+ break
2490
2642
  }
2491
- names.push("$event")
2492
- code = "\nreturn " + code + ";" //IE全家 Function("return ")出错,需要Function("return ;")
2493
- var lastIndex = code.lastIndexOf("\nreturn")
2494
- var header = code.slice(0, lastIndex)
2495
- var footer = code.slice(lastIndex)
2496
- code = header + "\n" + footer
2497
- } else { //其他绑定
2498
- code = "\nreturn " + code + ";" //IE全家 Function("return ")出错,需要Function("return ;")
2499
- }
2500
- try {
2501
- fn = Function.apply(noop, names.concat("'use strict';\n" + prefix + code))
2502
- data.evaluator = cacheExprs.put(exprId, fn)
2503
- } catch (e) {
2504
- log("debug: parse error," + e.message)
2505
- } finally {
2506
- vars = assigns = names = null //释放内存
2643
+ value = str.slice(start, stop)
2644
+ if (value) { //处理{{ }}插值表达式
2645
+ tokens.push(getToken(value, start))
2646
+ }
2647
+ start = stop + closeTag.length
2648
+ } while (1)
2649
+ value = str.slice(start)
2650
+ if (value) { //}} 右边的文本
2651
+ tokens.push({
2652
+ value: value,
2653
+ expr: false,
2654
+ filters: ""
2655
+ })
2507
2656
  }
2657
+ return tokens
2508
2658
  }
2509
2659
 
2510
-
2511
- //parseExpr的智能引用代理
2512
-
2513
- function parseExprProxy(code, scopes, data, tokens, noregister) {
2514
- if (Array.isArray(tokens)) {
2515
- code = tokens.map(function (el) {
2516
- return el.expr ? "(" + el.value + ")" : quote(el.value)
2517
- }).join(" + ")
2518
- }
2519
- parseExpr(code, scopes, data)
2520
- if (data.evaluator && !noregister) {
2521
- data.handler = bindingExecutors[data.handlerName || data.type]
2522
- //方便调试
2523
- //这里非常重要,我们通过判定视图刷新函数的element是否在DOM树决定
2524
- //将它移出订阅者列表
2525
- registerSubscriber(data)
2660
+ function scanText(textNode, vmodels, index) {
2661
+ var bindings = []
2662
+ tokens = scanExpr(textNode.data)
2663
+ if (tokens.length) {
2664
+ for (var i = 0; token = tokens[i++]; ) {
2665
+ var node = DOC.createTextNode(token.value) //将文本转换为文本节点,并替换原来的文本节点
2666
+ if (token.expr) {
2667
+ token.value = token.value.replace(roneTime, function () {
2668
+ token.oneTime = true
2669
+ return ""
2670
+ })// jshint ignore:line
2671
+ token.type = "text"
2672
+ token.element = node
2673
+ token.filters = token.filters.replace(rhasHtml, function (a, b,c) {
2674
+ token.type = "html"
2675
+ return ""
2676
+ })// jshint ignore:line
2677
+ token.pos = index * 1000 + i
2678
+ bindings.push(token) //收集带有插值表达式的文本
2679
+ }
2680
+ avalonFragment.appendChild(node)
2681
+ }
2682
+ textNode.parentNode.replaceChild(avalonFragment, textNode)
2683
+ if (bindings.length)
2684
+ executeBindings(bindings, vmodels)
2526
2685
  }
2527
2686
  }
2528
- avalon.parseExprProxy = parseExprProxy
2687
+
2529
2688
  var bools = ["autofocus,autoplay,async,allowTransparency,checked,controls",
2530
2689
  "declare,disabled,defer,defaultChecked,defaultSelected",
2531
2690
  "contentEditable,isMap,loop,multiple,noHref,noResize,noShade",
2532
- "open,readOnly,selected"].join(",")
2691
+ "open,readOnly,selected"
2692
+ ].join(",")
2533
2693
  var boolMap = {}
2534
2694
  bools.replace(rword, function (name) {
2535
2695
  boolMap[name.toLowerCase()] = name
@@ -2546,7 +2706,8 @@ var propMap = {//属性名映射
2546
2706
 
2547
2707
  var anomaly = ["accessKey,bgColor,cellPadding,cellSpacing,codeBase,codeType,colSpan",
2548
2708
  "dateTime,defaultValue,frameBorder,longDesc,maxLength,marginWidth,marginHeight",
2549
- "rowSpan,tabIndex,useMap,vSpace,valueType,vAlign"].join(",")
2709
+ "rowSpan,tabIndex,useMap,vSpace,valueType,vAlign"
2710
+ ].join(",")
2550
2711
  anomaly.replace(rword, function (name) {
2551
2712
  propMap[name.toLowerCase()] = name
2552
2713
  })
@@ -2555,21 +2716,13 @@ var rnoscripts = /<noscript.*?>(?:[\s\S]+?)<\/noscript>/img
2555
2716
  var rnoscriptText = /<noscript.*?>([\s\S]+?)<\/noscript>/im
2556
2717
 
2557
2718
  var getXHR = function () {
2558
- return new (window.XMLHttpRequest || ActiveXObject)("Microsoft.XMLHTTP")// jshint ignore:line
2719
+ return new (window.XMLHttpRequest || ActiveXObject)("Microsoft.XMLHTTP") // jshint ignore:line
2559
2720
  }
2560
2721
 
2561
- var cacheTmpls = avalon.templateCache = {}
2722
+ var templatePool = avalon.templateCache = {}
2562
2723
 
2563
2724
  bindingHandlers.attr = function (data, vmodels) {
2564
- var text = data.value.trim(),
2565
- simple = true
2566
- if (text.indexOf(openTag) > -1 && text.indexOf(closeTag) > 2) {
2567
- simple = false
2568
- if (rexpr.test(text) && RegExp.rightContext === "" && RegExp.leftContext === "") {
2569
- simple = true
2570
- text = RegExp.$1
2571
- }
2572
- }
2725
+ var value = stringifyExpr(data.value.trim())
2573
2726
  if (data.type === "include") {
2574
2727
  var elem = data.element
2575
2728
  data.includeRendered = getBindingCallback(elem, "data-include-rendered", vmodels)
@@ -2590,7 +2743,7 @@ bindingHandlers.attr = function (data, vmodels) {
2590
2743
  }
2591
2744
  }
2592
2745
  data.handlerName = "attr" //handleName用于处理多种绑定共用同一种bindingExecutor的情况
2593
- parseExprProxy(text, vmodels, data, (simple ? 0 : scanExpr(data.value)))
2746
+ parseExprProxy(value, vmodels, data)
2594
2747
  }
2595
2748
 
2596
2749
  bindingExecutors.attr = function (val, elem, data) {
@@ -2599,28 +2752,29 @@ bindingExecutors.attr = function (val, elem, data) {
2599
2752
  if (method === "css") {
2600
2753
  avalon(elem).css(attrName, val)
2601
2754
  } else if (method === "attr") {
2755
+
2602
2756
  // ms-attr-class="xxx" vm.xxx="aaa bbb ccc"将元素的className设置为aaa bbb ccc
2603
2757
  // ms-attr-class="xxx" vm.xxx=false 清空元素的所有类名
2604
2758
  // ms-attr-name="yyy" vm.yyy="ooo" 为元素设置name属性
2605
- if (boolMap[attrName]) {
2606
- var bool = boolMap[attrName]
2607
- if (typeof elem[bool] === "boolean") {
2608
- // IE6-11不支持动态设置fieldset的disabled属性,IE11下样式是生效了,但无法阻止用户对其底下的input元素进行设值……
2609
- return elem[bool] = !!val
2610
- }
2611
- }
2612
2759
  var toRemove = (val === false) || (val === null) || (val === void 0)
2613
2760
 
2614
2761
  if (!W3C && propMap[attrName]) { //旧式IE下需要进行名字映射
2615
2762
  attrName = propMap[attrName]
2616
2763
  }
2764
+ var bool = boolMap[attrName]
2765
+ if (typeof elem[bool] === "boolean") {
2766
+ elem[bool] = !!val //布尔属性必须使用el.xxx = true|false方式设值
2767
+ if (!val) { //如果为false, IE全系列下相当于setAttribute(xxx,''),会影响到样式,需要进一步处理
2768
+ toRemove = true
2769
+ }
2770
+ }
2617
2771
  if (toRemove) {
2618
2772
  return elem.removeAttribute(attrName)
2619
2773
  }
2620
2774
  //SVG只能使用setAttribute(xxx, yyy), VML只能使用elem.xxx = yyy ,HTML的固有属性必须elem.xxx = yyy
2621
2775
  var isInnate = rsvg.test(elem) ? false : (DOC.namespaces && isVML(elem)) ? true : attrName in elem.cloneNode(false)
2622
2776
  if (isInnate) {
2623
- elem[attrName] = val
2777
+ elem[attrName] = val + ""
2624
2778
  } else {
2625
2779
  elem.setAttribute(attrName, val)
2626
2780
  }
@@ -2632,7 +2786,9 @@ bindingExecutors.attr = function (val, elem, data) {
2632
2786
  var target = replace ? elem.parentNode : elem
2633
2787
  var scanTemplate = function (text) {
2634
2788
  if (loaded) {
2635
- text = loaded.apply(target, [text].concat(vmodels))
2789
+ var newText = loaded.apply(target, [text].concat(vmodels))
2790
+ if (typeof newText === "string")
2791
+ text = newText
2636
2792
  }
2637
2793
  if (rendered) {
2638
2794
  checkScan(target, function () {
@@ -2665,20 +2821,27 @@ bindingExecutors.attr = function (val, elem, data) {
2665
2821
  }
2666
2822
 
2667
2823
  if (data.param === "src") {
2668
- if (cacheTmpls[val]) {
2824
+ if (typeof templatePool[val] === "string") {
2669
2825
  avalon.nextTick(function () {
2670
- scanTemplate(cacheTmpls[val])
2826
+ scanTemplate(templatePool[val])
2671
2827
  })
2828
+ } else if (Array.isArray(templatePool[val])) { //#805 防止在循环绑定中发出许多相同的请求
2829
+ templatePool[val].push(scanTemplate)
2672
2830
  } else {
2673
2831
  var xhr = getXHR()
2674
2832
  xhr.onreadystatechange = function () {
2675
2833
  if (xhr.readyState === 4) {
2676
2834
  var s = xhr.status
2677
2835
  if (s >= 200 && s < 300 || s === 304 || s === 1223) {
2678
- scanTemplate(cacheTmpls[val] = xhr.responseText)
2836
+ var text = xhr.responseText
2837
+ for (var f = 0, fn; fn = templatePool[val][f++]; ) {
2838
+ fn(text)
2839
+ }
2840
+ templatePool[val] = text
2679
2841
  }
2680
2842
  }
2681
2843
  }
2844
+ templatePool[val] = [scanTemplate]
2682
2845
  xhr.open("GET", val, true)
2683
2846
  if ("withCredentials" in xhr) {
2684
2847
  xhr.withCredentials = true
@@ -2729,13 +2892,14 @@ bindingExecutors.attr = function (val, elem, data) {
2729
2892
  function getTemplateNodes(data, id, text) {
2730
2893
  var div = data.templateCache && data.templateCache[id]
2731
2894
  if (div) {
2732
- var dom = DOC.createDocumentFragment(), firstChild
2895
+ var dom = DOC.createDocumentFragment(),
2896
+ firstChild
2733
2897
  while (firstChild = div.firstChild) {
2734
2898
  dom.appendChild(firstChild)
2735
2899
  }
2736
2900
  return dom
2737
2901
  }
2738
- return avalon.parseHTML(text)
2902
+ return avalon.parseHTML(text)
2739
2903
  }
2740
2904
 
2741
2905
  //这几个指令都可以使用插值表达式,如ms-src="aaa/{{b}}/{{c}}.html"
@@ -2744,92 +2908,85 @@ function getTemplateNodes(data, id, text) {
2744
2908
  })
2745
2909
  //根据VM的属性值或表达式的值切换类名,ms-class="xxx yyy zzz:flag"
2746
2910
  //http://www.cnblogs.com/rubylouvre/archive/2012/12/17/2818540.html
2747
- bindingHandlers["class"] = function(data, vmodels) {
2748
- var oldStyle = data.param,
2749
- text = data.value,
2911
+ bindingHandlers["class"] = function (binding, vmodels) {
2912
+ var oldStyle = binding.param,
2913
+ text = binding.value,
2750
2914
  rightExpr
2751
- data.handlerName = "class"
2915
+ binding.handlerName = "class"
2752
2916
  if (!oldStyle || isFinite(oldStyle)) {
2753
- data.param = "" //去掉数字
2754
- var noExpr = text.replace(rexprg, function(a) {
2917
+ binding.param = "" //去掉数字
2918
+ var colonIndex = text.replace(rexprg, function (a) {
2755
2919
  return a.replace(/./g, "0")
2756
- //return Math.pow(10, a.length - 1) //将插值表达式插入10的N-1次方来占位
2757
- })
2758
- var colonIndex = noExpr.indexOf(":") //取得第一个冒号的位置
2920
+ }).indexOf(":") //取得第一个冒号的位置
2759
2921
  if (colonIndex === -1) { // 比如 ms-class="aaa bbb ccc" 的情况
2760
2922
  var className = text
2761
- } else { // 比如 ms-class-1="ui-state-active:checked" 的情况
2923
+ rightExpr = true
2924
+ } else { // 比如 ms-class-1="ui-state-active:checked" 的情况
2762
2925
  className = text.slice(0, colonIndex)
2763
2926
  rightExpr = text.slice(colonIndex + 1)
2764
- parseExpr(rightExpr, vmodels, data) //决定是添加还是删除
2765
- if (!data.evaluator) {
2766
- log("debug: ms-class '" + (rightExpr || "").trim() + "' 不存在于VM中")
2767
- return false
2768
- } else {
2769
- data._evaluator = data.evaluator
2770
- data._args = data.args
2771
- }
2772
2927
  }
2773
- var hasExpr = rexpr.test(className) //比如ms-class="width{{w}}"的情况
2774
- if (!hasExpr) {
2775
- data.immobileClass = className
2928
+ if (!rexpr.test(text)) {
2929
+ className = quote(className)
2930
+ } else {
2931
+ className = stringifyExpr(className)
2776
2932
  }
2777
- parseExprProxy("", vmodels, data, (hasExpr ? scanExpr(className) : 0))
2933
+ binding.expr = "[" + className + "," + rightExpr + "]"
2778
2934
  } else {
2779
- data.immobileClass = data.oldStyle = data.param
2780
- parseExprProxy(text, vmodels, data)
2935
+ binding.expr = '[' + quote(oldStyle) + "," + text + "]"
2936
+ binding.oldStyle = oldStyle
2937
+ }
2938
+ var method = binding.type
2939
+ if (method === "hover" || method === "active") { //确保只绑定一次
2940
+ if (!binding.hasBindEvent) {
2941
+ var elem = binding.element
2942
+ var $elem = avalon(elem)
2943
+ var activate = "mouseenter" //在移出移入时切换类名
2944
+ var abandon = "mouseleave"
2945
+ if (method === "active") { //在聚焦失焦中切换类名
2946
+ elem.tabIndex = elem.tabIndex || -1
2947
+ activate = "mousedown"
2948
+ abandon = "mouseup"
2949
+ var fn0 = $elem.bind("mouseleave", function () {
2950
+ binding.toggleClass && $elem.removeClass(binding.newClass)
2951
+ })
2952
+ }
2953
+ }
2954
+
2955
+ var fn1 = $elem.bind(activate, function () {
2956
+ binding.toggleClass && $elem.addClass(binding.newClass)
2957
+ })
2958
+ var fn2 = $elem.bind(abandon, function () {
2959
+ binding.toggleClass && $elem.removeClass(binding.newClass)
2960
+ })
2961
+ binding.rollback = function () {
2962
+ $elem.unbind("mouseleave", fn0)
2963
+ $elem.unbind(activate, fn1)
2964
+ $elem.unbind(abandon, fn2)
2965
+ }
2966
+ binding.hasBindEvent = true
2781
2967
  }
2968
+ parseExprProxy(binding.expr, vmodels, binding)
2782
2969
  }
2783
2970
 
2784
- bindingExecutors ["class"] = function(val, elem, data) {
2785
- var $elem = avalon(elem),
2786
- method = data.type
2787
- if (method === "class" && data.oldStyle) { //如果是旧风格
2788
- $elem.toggleClass(data.oldStyle, !!val)
2789
- } else {
2790
- //如果存在冒号就有求值函数
2791
- data.toggleClass = data._evaluator ? !!data._evaluator.apply(elem, data._args) : true
2792
- data.newClass = data.immobileClass || val
2793
- if (data.oldClass && data.newClass !== data.oldClass) {
2794
- $elem.removeClass(data.oldClass)
2795
- }
2796
- data.oldClass = data.newClass
2797
- switch (method) {
2798
- case "class":
2799
- $elem.toggleClass(data.newClass, data.toggleClass)
2800
- break
2801
- case "hover":
2802
- case "active":
2803
- if (!data.hasBindEvent) { //确保只绑定一次
2804
- var activate = "mouseenter" //在移出移入时切换类名
2805
- var abandon = "mouseleave"
2806
- if (method === "active") { //在聚焦失焦中切换类名
2807
- elem.tabIndex = elem.tabIndex || -1
2808
- activate = "mousedown"
2809
- abandon = "mouseup"
2810
- var fn0 = $elem.bind("mouseleave", function() {
2811
- data.toggleClass && $elem.removeClass(data.newClass)
2812
- })
2813
- }
2814
- var fn1 = $elem.bind(activate, function() {
2815
- data.toggleClass && $elem.addClass(data.newClass)
2816
- })
2817
- var fn2 = $elem.bind(abandon, function() {
2818
- data.toggleClass && $elem.removeClass(data.newClass)
2819
- })
2820
- data.rollback = function() {
2821
- $elem.unbind("mouseleave", fn0)
2822
- $elem.unbind(activate, fn1)
2823
- $elem.unbind(abandon, fn2)
2824
- }
2825
- data.hasBindEvent = true
2826
- }
2827
- break;
2971
+ bindingExecutors["class"] = function (arr, elem, binding) {
2972
+ var $elem = avalon(elem)
2973
+ binding.newClass = arr[0]
2974
+ binding.toggleClass = !!arr[1]
2975
+ if (binding.oldClass && binding.newClass !== binding.oldClass) {
2976
+ $elem.removeClass(binding.oldClass)
2977
+ }
2978
+ binding.oldClass = binding.newClass
2979
+ if (binding.type === "class") {
2980
+ if (binding.oldStyle) {
2981
+ $elem.toggleClass(binding.oldStyle, !!arr[1])
2982
+ } else {
2983
+ $elem.toggleClass(binding.newClass, binding.toggleClass)
2828
2984
  }
2829
2985
  }
2986
+
2830
2987
  }
2831
2988
 
2832
- "hover,active".replace(rword, function(method) {
2989
+ "hover,active".replace(rword, function (method) {
2833
2990
  bindingHandlers[method] = bindingHandlers["class"]
2834
2991
  })
2835
2992
  //ms-controller绑定已经在scanTag 方法中实现
@@ -2838,21 +2995,20 @@ bindingExecutors ["class"] = function(val, elem, data) {
2838
2995
 
2839
2996
  // bindingHandlers.data 定义在if.js
2840
2997
  bindingExecutors.data = function(val, elem, data) {
2841
- var key = "data-" + data.param
2842
- if (val && typeof val === "object") {
2843
- elem[key] = val
2844
- } else {
2845
- elem.setAttribute(key, String(val))
2846
- }
2998
+ var key = "data-" + data.param
2999
+ if (val && typeof val === "object") {
3000
+ elem[key] = val
3001
+ } else {
3002
+ elem.setAttribute(key, String(val))
3003
+ }
2847
3004
  }
2848
-
2849
3005
  //双工绑定
2850
- var duplexBinding = bindingHandlers.duplex = function (data, vmodels) {
3006
+ var duplexBinding = bindingHandlers.duplex = function(data, vmodels) {
2851
3007
  var elem = data.element,
2852
- hasCast
2853
- parseExprProxy(data.value, vmodels, data, 0, 1)
3008
+ hasCast
3009
+ parseExprProxy(data.value, vmodels, data, 1)
2854
3010
 
2855
- data.changed = getBindingCallback(elem, "data-duplex-changed", vmodels) || noop
3011
+ data.changed = getBindingCallback(elem, "data-duplex-changed", vmodels) || noop
2856
3012
  if (data.evaluator && data.args) {
2857
3013
  var params = []
2858
3014
  var casting = oneObject("string,number,boolean,checked")
@@ -2862,7 +3018,7 @@ var duplexBinding = bindingHandlers.duplex = function (data, vmodels) {
2862
3018
  if (elem.msData) {
2863
3019
  elem.msData["ms-duplex"] = data.value
2864
3020
  }
2865
- data.param.replace(/\w+/g, function (name) {
3021
+ data.param.replace(/\w+/g, function(name) {
2866
3022
  if (/^(checkbox|radio)$/.test(elem.type) && /^(radio|checked)$/.test(name)) {
2867
3023
  if (name === "radio")
2868
3024
  log("ms-duplex-radio已经更名为ms-duplex-checked")
@@ -2885,14 +3041,14 @@ var duplexBinding = bindingHandlers.duplex = function (data, vmodels) {
2885
3041
  params.push("string")
2886
3042
  }
2887
3043
  data.param = params.join("-")
2888
- data.bound = function (type, callback) {
3044
+ data.bound = function(type, callback) {
2889
3045
  if (elem.addEventListener) {
2890
3046
  elem.addEventListener(type, callback, false)
2891
3047
  } else {
2892
3048
  elem.attachEvent("on" + type, callback)
2893
3049
  }
2894
3050
  var old = data.rollback
2895
- data.rollback = function () {
3051
+ data.rollback = function() {
2896
3052
  elem.avalonSetter = null
2897
3053
  avalon.unbind(elem, type, callback)
2898
3054
  old && old()
@@ -2909,29 +3065,30 @@ var duplexBinding = bindingHandlers.duplex = function (data, vmodels) {
2909
3065
  }
2910
3066
  }
2911
3067
  //不存在 bindingExecutors.duplex
2912
- function fixNull(val) {
2913
- return val == null ? "" : val
2914
- }
3068
+
3069
+ function fixNull(val) {
3070
+ return val == null ? "" : val
3071
+ }
2915
3072
  avalon.duplexHooks = {
2916
3073
  checked: {
2917
- get: function (val, data) {
3074
+ get: function(val, data) {
2918
3075
  return !data.element.oldValue
2919
3076
  }
2920
3077
  },
2921
3078
  string: {
2922
- get: function (val) { //同步到VM
3079
+ get: function(val) { //同步到VM
2923
3080
  return val
2924
3081
  },
2925
3082
  set: fixNull
2926
3083
  },
2927
3084
  "boolean": {
2928
- get: function (val) {
3085
+ get: function(val) {
2929
3086
  return val === "true"
2930
3087
  },
2931
3088
  set: fixNull
2932
3089
  },
2933
3090
  number: {
2934
- get: function (val, data) {
3091
+ get: function(val, data) {
2935
3092
  var number = parseFloat(val)
2936
3093
  if (-val === -number) {
2937
3094
  return number
@@ -2951,7 +3108,7 @@ avalon.duplexHooks = {
2951
3108
  }
2952
3109
 
2953
3110
  function pipe(val, data, action, e) {
2954
- data.param.replace(/\w+/g, function (name) {
3111
+ data.param.replace(/\w+/g, function(name) {
2955
3112
  var hook = avalon.duplexHooks[name]
2956
3113
  if (hook && typeof hook[action] === "function") {
2957
3114
  val = hook[action](val, data)
@@ -2962,44 +3119,41 @@ function pipe(val, data, action, e) {
2962
3119
 
2963
3120
  var TimerID, ribbon = []
2964
3121
 
2965
- avalon.tick = function (fn) {
2966
- if (ribbon.push(fn) === 1) {
2967
- TimerID = setInterval(ticker, 60)
3122
+ avalon.tick = function(fn) {
3123
+ if (ribbon.push(fn) === 1) {
3124
+ TimerID = setInterval(ticker, 60)
3125
+ }
2968
3126
  }
2969
- }
2970
3127
 
2971
- function ticker() {
2972
- for (var n = ribbon.length - 1; n >= 0; n--) {
2973
- var el = ribbon[n]
2974
- if (el() === false) {
2975
- ribbon.splice(n, 1)
3128
+ function ticker() {
3129
+ for (var n = ribbon.length - 1; n >= 0; n--) {
3130
+ var el = ribbon[n]
3131
+ if (el() === false) {
3132
+ ribbon.splice(n, 1)
3133
+ }
3134
+ }
3135
+ if (!ribbon.length) {
3136
+ clearInterval(TimerID)
2976
3137
  }
2977
3138
  }
2978
- if (!ribbon.length) {
2979
- clearInterval(TimerID)
2980
- }
2981
- }
2982
3139
 
2983
3140
  var watchValueInTimer = noop
2984
3141
  var rmsinput = /text|password|hidden/
2985
- new function () {// jshint ignore:line
2986
- try {//#272 IE9-IE11, firefox
3142
+ new function() { // jshint ignore:line
3143
+ try { //#272 IE9-IE11, firefox
2987
3144
  var setters = {}
2988
3145
  var aproto = HTMLInputElement.prototype
2989
3146
  var bproto = HTMLTextAreaElement.prototype
2990
- function newSetter(value) {// jshint ignore:line
2991
- if (avalon.contains(root, this)) {
3147
+ function newSetter(value) { // jshint ignore:line
2992
3148
  setters[this.tagName].call(this, value)
2993
- if (!rmsinput.test(this.type))
2994
- return
2995
- if (!this.msFocus && this.avalonSetter) {
3149
+ if (rmsinput.test(this.type) && !this.msFocus && this.avalonSetter) {
2996
3150
  this.avalonSetter()
2997
3151
  }
2998
- }
2999
3152
  }
3000
3153
  var inputProto = HTMLInputElement.prototype
3001
3154
  Object.getOwnPropertyNames(inputProto) //故意引发IE6-8等浏览器报错
3002
3155
  setters["INPUT"] = Object.getOwnPropertyDescriptor(aproto, "value").set
3156
+
3003
3157
  Object.defineProperty(aproto, "value", {
3004
3158
  set: newSetter
3005
3159
  })
@@ -3008,67 +3162,66 @@ new function () {// jshint ignore:line
3008
3162
  set: newSetter
3009
3163
  })
3010
3164
  } catch (e) {
3165
+ //在chrome 43中 ms-duplex终于不需要使用定时器实现双向绑定了
3166
+ // http://updates.html5rocks.com/2015/04/DOM-attributes-now-on-the-prototype
3167
+ // https://docs.google.com/document/d/1jwA8mtClwxI-QJuHT7872Z0pxpZz8PBkf2bGAbsUtqs/edit?pli=1
3011
3168
  watchValueInTimer = avalon.tick
3012
3169
  }
3013
- }// jshint ignore:line
3014
-
3015
-
3170
+ } // jshint ignore:line
3016
3171
  //处理radio, checkbox, text, textarea, password
3017
- duplexBinding.INPUT = function (element, evaluator, data) {
3172
+ duplexBinding.INPUT = function(element, evaluator, data) {
3018
3173
  var $type = element.type,
3019
- bound = data.bound,
3020
- $elem = avalon(element),
3021
- composing = false
3022
- function callback(value) {
3023
- data.changed.call(this, value, data)
3024
- }
3025
- function compositionStart() {
3026
- composing = true
3027
- }
3028
- function compositionEnd() {
3174
+ bound = data.bound,
3175
+ $elem = avalon(element),
3029
3176
  composing = false
3030
- }
3031
- //当value变化时改变model的值
3032
3177
 
3033
- var updateVModel = function () {
3034
- if (composing)//处理中文输入法在minlengh下引发的BUG
3178
+ function callback(value) {
3179
+ data.changed.call(this, value, data)
3180
+ }
3181
+
3182
+ function compositionStart() {
3183
+ composing = true
3184
+ }
3185
+
3186
+ function compositionEnd() {
3187
+ composing = false
3188
+ }
3189
+ //当value变化时改变model的值
3190
+
3191
+ var updateVModel = function() {
3192
+ var val = element.value //防止递归调用形成死循环
3193
+ if (composing || val === element.oldValue) //处理中文输入法在minlengh下引发的BUG
3035
3194
  return
3036
- var val = element.oldValue = element.value //防止递归调用形成死循环
3037
3195
  var lastValue = data.pipe(val, data, "get")
3038
- if ($elem.data("duplex-observe") !== false) {
3196
+ if ($elem.data("duplexObserve") !== false) {
3039
3197
  evaluator(lastValue)
3040
3198
  callback.call(element, lastValue)
3041
- if ($elem.data("duplex-focus")) {
3042
- avalon.nextTick(function () {
3043
- element.focus()
3044
- })
3045
- }
3046
3199
  }
3047
3200
  }
3048
3201
  //当model变化时,它就会改变value的值
3049
- data.handler = function () {
3202
+ data.handler = function() {
3050
3203
  var val = data.pipe(evaluator(), data, "set") + ""
3051
3204
  if (val !== element.oldValue) {
3052
- element.value = val
3205
+ element.value = element.oldValue = val
3053
3206
  }
3054
3207
  }
3055
3208
  if (data.isChecked || $type === "radio") {
3056
- updateVModel = function () {
3057
- if ($elem.data("duplex-observe") !== false) {
3209
+ updateVModel = function() {
3210
+ if ($elem.data("duplexObserve") !== false) {
3058
3211
  var lastValue = data.pipe(element.value, data, "get")
3059
3212
  evaluator(lastValue)
3060
3213
  callback.call(element, lastValue)
3061
3214
  }
3062
3215
  }
3063
- data.handler = function () {
3216
+ data.handler = function() {
3064
3217
  var val = evaluator()
3065
- var checked = data.isChecked ? !!val : val + "" === element.value
3218
+ var checked = data.isChecked ? !! val : val + "" === element.value
3066
3219
  element.checked = element.oldValue = checked
3067
3220
  }
3068
3221
  bound("click", updateVModel)
3069
3222
  } else if ($type === "checkbox") {
3070
- updateVModel = function () {
3071
- if ($elem.data("duplex-observe") !== false) {
3223
+ updateVModel = function() {
3224
+ if ($elem.data("duplexObserve") !== false) {
3072
3225
  var method = element.checked ? "ensure" : "remove"
3073
3226
  var array = evaluator()
3074
3227
  if (!Array.isArray(array)) {
@@ -3079,7 +3232,7 @@ duplexBinding.INPUT = function (element, evaluator, data) {
3079
3232
  callback.call(element, array)
3080
3233
  }
3081
3234
  }
3082
- data.handler = function () {
3235
+ data.handler = function() {
3083
3236
  var array = [].concat(evaluator()) //强制转换为数组
3084
3237
  element.checked = array.indexOf(data.pipe(element.value, data, "get")) > -1
3085
3238
  }
@@ -3089,7 +3242,7 @@ duplexBinding.INPUT = function (element, evaluator, data) {
3089
3242
  if (element.attributes["data-event"]) {
3090
3243
  log("data-event指令已经废弃,请改用data-duplex-event")
3091
3244
  }
3092
- events.replace(rword, function (name) {
3245
+ events.replace(rword, function(name) {
3093
3246
  switch (name) {
3094
3247
  case "input":
3095
3248
  bound("input", updateVModel)
@@ -3104,14 +3257,14 @@ duplexBinding.INPUT = function (element, evaluator, data) {
3104
3257
  break
3105
3258
  }
3106
3259
  })
3107
- bound("focus", function () {
3260
+ bound("focus", function() {
3108
3261
  element.msFocus = true
3109
3262
  })
3110
- bound("blur", function () {
3263
+ bound("blur", function() {
3111
3264
  element.msFocus = false
3112
3265
  })
3113
3266
  if (rmsinput.test($type)) {
3114
- watchValueInTimer(function () {
3267
+ watchValueInTimer(function() {
3115
3268
  if (root.contains(element)) {
3116
3269
  if (!element.msFocus && element.oldValue !== element.value) {
3117
3270
  updateVModel()
@@ -3126,28 +3279,29 @@ duplexBinding.INPUT = function (element, evaluator, data) {
3126
3279
  }
3127
3280
 
3128
3281
  element.oldValue = element.value
3129
- registerSubscriber(data)
3282
+ avalon.injectBinding(data)
3130
3283
  callback.call(element, element.value)
3131
3284
  }
3132
3285
  duplexBinding.TEXTAREA = duplexBinding.INPUT
3133
3286
  duplexBinding.SELECT = function(element, evaluator, data) {
3134
3287
  var $elem = avalon(element)
3135
- function updateVModel() {
3136
- if ($elem.data("duplex-observe") !== false) {
3137
- var val = $elem.val() //字符串或字符串数组
3138
- if (Array.isArray(val)) {
3139
- val = val.map(function(v) {
3140
- return data.pipe(v, data, "get")
3141
- })
3142
- } else {
3143
- val = data.pipe(val, data, "get")
3144
- }
3145
- if (val + "" !== element.oldValue) {
3146
- evaluator(val)
3288
+
3289
+ function updateVModel() {
3290
+ if ($elem.data("duplexObserve") !== false) {
3291
+ var val = $elem.val() //字符串或字符串数组
3292
+ if (Array.isArray(val)) {
3293
+ val = val.map(function(v) {
3294
+ return data.pipe(v, data, "get")
3295
+ })
3296
+ } else {
3297
+ val = data.pipe(val, data, "get")
3298
+ }
3299
+ if (val + "" !== element.oldValue) {
3300
+ evaluator(val)
3301
+ }
3302
+ data.changed.call(element, val, data)
3147
3303
  }
3148
- data.changed.call(element, val, data)
3149
3304
  }
3150
- }
3151
3305
  data.handler = function() {
3152
3306
  var val = evaluator()
3153
3307
  val = val && val.$model || val
@@ -3168,74 +3322,77 @@ duplexBinding.SELECT = function(element, evaluator, data) {
3168
3322
  }
3169
3323
  }
3170
3324
  data.bound("change", updateVModel)
3171
- checkScan(element, function() {
3172
- registerSubscriber(data)
3325
+ element.msCallback = function() {
3326
+ avalon.injectBinding(data)
3173
3327
  data.changed.call(element, evaluator(), data)
3174
- }, NaN)
3328
+ }
3175
3329
  }
3176
-
3177
-
3178
3330
  // bindingHandlers.html 定义在if.js
3179
- bindingExecutors.html = function(val, elem, data) {
3180
- val = val == null ? "" : val
3181
- var isHtmlFilter = "group" in data
3331
+ bindingExecutors.html = function (val, elem, data) {
3332
+ var isHtmlFilter = elem.nodeType !== 1
3182
3333
  var parent = isHtmlFilter ? elem.parentNode : elem
3183
3334
  if (!parent)
3184
3335
  return
3185
- if (val.nodeType === 11) { //将val转换为文档碎片
3186
- var fragment = val
3336
+ val = val == null ? "" : val
3337
+ if (data.oldText !== val) {
3338
+ data.oldText = val
3339
+ } else {
3340
+ return
3341
+ }
3342
+ if (elem.nodeType === 3) {
3343
+ var signature = generateID("html")
3344
+ parent.insertBefore(DOC.createComment(signature), elem)
3345
+ data.element = DOC.createComment(signature + ":end")
3346
+ parent.replaceChild(data.element, elem)
3347
+ elem = data.element
3348
+ }
3349
+ if (typeof val !== "object") {//string, number, boolean
3350
+ var fragment = avalon.parseHTML(String(val))
3351
+ } else if (val.nodeType === 11) { //将val转换为文档碎片
3352
+ fragment = val
3187
3353
  } else if (val.nodeType === 1 || val.item) {
3188
- var nodes = val.nodeType === 1 ? val.childNodes : val.item ? val : []
3189
- fragment = hyperspace.cloneNode(true)
3354
+ var nodes = val.nodeType === 1 ? val.childNodes : val.item
3355
+ fragment = avalonFragment.cloneNode(true)
3190
3356
  while (nodes[0]) {
3191
3357
  fragment.appendChild(nodes[0])
3192
3358
  }
3193
- } else {
3194
- fragment = avalon.parseHTML(val)
3195
3359
  }
3360
+
3361
+ nodes = avalon.slice(fragment.childNodes)
3196
3362
  //插入占位符, 如果是过滤器,需要有节制地移除指定的数量,如果是html指令,直接清空
3197
- var comment = DOC.createComment("ms-html")
3198
3363
  if (isHtmlFilter) {
3199
- parent.insertBefore(comment, elem)
3200
- var n = data.group, i = 1
3201
- while (i < n) {
3202
- var node = elem.nextSibling
3203
- if (node) {
3364
+ var endValue = elem.nodeValue.slice(0, -4)
3365
+ while (true) {
3366
+ var node = elem.previousSibling
3367
+ if (!node || node.nodeType === 8 && node.nodeValue === endValue) {
3368
+ break
3369
+ } else {
3204
3370
  parent.removeChild(node)
3205
- i++
3206
3371
  }
3207
3372
  }
3208
- parent.removeChild(elem)
3209
- data.element = comment //防止被CG
3373
+ parent.insertBefore(fragment, elem)
3210
3374
  } else {
3211
- avalon.clearHTML(parent).appendChild(comment)
3212
- }
3213
- if (isHtmlFilter) {
3214
- data.group = fragment.childNodes.length || 1
3215
- }
3216
- nodes = avalon.slice(fragment.childNodes)
3217
- if (nodes[0]) {
3218
- if (comment.parentNode)
3219
- comment.parentNode.replaceChild(fragment, comment)
3220
- if (isHtmlFilter) {
3221
- data.element = nodes[0]
3222
- }
3375
+ avalon.clearHTML(elem).appendChild(fragment)
3223
3376
  }
3224
3377
  scanNodeArray(nodes, data.vmodels)
3225
3378
  }
3226
-
3227
3379
  bindingHandlers["if"] =
3228
- bindingHandlers.data =
3229
- bindingHandlers.text =
3230
- bindingHandlers.html =
3231
- function(data, vmodels) {
3232
- parseExprProxy(data.value, vmodels, data)
3233
- }
3380
+ bindingHandlers.data =
3381
+ bindingHandlers.text =
3382
+ bindingHandlers.html =
3383
+ function(data, vmodels) {
3384
+ parseExprProxy(data.value, vmodels, data)
3385
+ }
3234
3386
 
3235
3387
  bindingExecutors["if"] = function(val, elem, data) {
3388
+ try {
3389
+ if(!elem.parentNode) return
3390
+ } catch(e) {return}
3236
3391
  if (val) { //插回DOM树
3237
3392
  if (elem.nodeType === 8) {
3238
3393
  elem.parentNode.replaceChild(data.template, elem)
3394
+ elem.ifRemove = null
3395
+ // animate.enter(data.template, elem.parentNode)
3239
3396
  elem = data.element = data.template //这时可能为null
3240
3397
  }
3241
3398
  if (elem.getAttribute(data.name)) {
@@ -3247,6 +3404,8 @@ bindingExecutors["if"] = function(val, elem, data) {
3247
3404
  if (elem.nodeType === 1) {
3248
3405
  var node = data.element = DOC.createComment("ms-if")
3249
3406
  elem.parentNode.replaceChild(node, elem)
3407
+ elem.ifRemove = node
3408
+ // animate.leave(elem, node.parentNode, node)
3250
3409
  data.template = elem //元素节点
3251
3410
  ifGroup.appendChild(elem)
3252
3411
  data.rollback = function() {
@@ -3257,8 +3416,6 @@ bindingExecutors["if"] = function(val, elem, data) {
3257
3416
  }
3258
3417
  }
3259
3418
  }
3260
-
3261
-
3262
3419
  //ms-important绑定已经在scanTag 方法中实现
3263
3420
  //ms-include绑定已由ms-attr绑定实现
3264
3421
 
@@ -3302,11 +3459,9 @@ bindingExecutors.on = function(callback, elem, data) {
3302
3459
  }
3303
3460
  }
3304
3461
  }
3305
-
3306
-
3307
- bindingHandlers.repeat = function(data, vmodels) {
3462
+ bindingHandlers.repeat = function (data, vmodels) {
3308
3463
  var type = data.type
3309
- parseExprProxy(data.value, vmodels, data, 0, 1)
3464
+ parseExprProxy(data.value, vmodels, data, 1)
3310
3465
  data.proxies = []
3311
3466
  var freturn = false
3312
3467
  try {
@@ -3315,11 +3470,12 @@ bindingHandlers.repeat = function(data, vmodels) {
3315
3470
  if (xtype !== "object" && xtype !== "array") {
3316
3471
  freturn = true
3317
3472
  avalon.log("warning:" + data.value + "只能是对象或数组")
3473
+ } else {
3474
+ data.xtype = xtype
3318
3475
  }
3319
3476
  } catch (e) {
3320
3477
  freturn = true
3321
3478
  }
3322
-
3323
3479
  var arr = data.value.split(".") || []
3324
3480
  if (arr.length > 1) {
3325
3481
  arr.pop()
@@ -3333,41 +3489,43 @@ bindingHandlers.repeat = function(data, vmodels) {
3333
3489
  }
3334
3490
  }
3335
3491
  }
3492
+
3336
3493
  var elem = data.element
3337
- elem.removeAttribute(data.name)
3338
-
3339
- data.sortedCallback = getBindingCallback(elem, "data-with-sorted", vmodels)
3340
- data.renderedCallback = getBindingCallback(elem, "data-" + type + "-rendered", vmodels)
3341
- var signature = generateID(type)
3342
- var comment = data.element = DOC.createComment(signature + ":end")
3343
- data.clone = DOC.createComment(signature)
3344
- hyperspace.appendChild(comment)
3345
-
3346
- if (type === "each" || type === "with") {
3347
- data.template = elem.innerHTML.trim()
3348
- avalon.clearHTML(elem).appendChild(comment)
3349
- } else {
3350
- data.template = elem.outerHTML.trim()
3351
- elem.parentNode.replaceChild(comment, elem)
3352
- }
3353
- data.template = avalon.parseHTML(data.template)
3354
- data.rollback = function() {
3355
- var elem = data.element
3356
- if (!elem)
3357
- return
3358
- bindingExecutors.repeat.call(data, "clear")
3359
- var parentNode = elem.parentNode
3360
- var content = data.template
3361
- var target = content.firstChild
3362
- parentNode.replaceChild(content, elem)
3363
- var start = data.$stamp
3364
- start && start.parentNode && start.parentNode.removeChild(start)
3365
- target = data.element = data.type === "repeat" ? target : parentNode
3494
+ if (elem.nodeType === 1) {
3495
+ elem.removeAttribute(data.name)
3496
+ data.sortedCallback = getBindingCallback(elem, "data-with-sorted", vmodels)
3497
+ data.renderedCallback = getBindingCallback(elem, "data-" + type + "-rendered", vmodels)
3498
+ var signature = generateID(type)
3499
+ var start = DOC.createComment(signature)
3500
+ var end = DOC.createComment(signature + ":end")
3501
+ data.signature = signature
3502
+ data.template = avalonFragment.cloneNode(false)
3503
+ if (type === "repeat") {
3504
+ var parent = elem.parentNode
3505
+ parent.replaceChild(end, elem)
3506
+ parent.insertBefore(start, end)
3507
+ data.template.appendChild(elem)
3508
+ } else {
3509
+ while (elem.firstChild) {
3510
+ data.template.appendChild(elem.firstChild)
3511
+ }
3512
+ elem.appendChild(start)
3513
+ elem.appendChild(end)
3514
+ }
3515
+ data.element = end
3516
+ data.handler = bindingExecutors.repeat
3517
+ data.rollback = function () {
3518
+ var elem = data.element
3519
+ if (!elem)
3520
+ return
3521
+ data.handler("clear")
3522
+ }
3366
3523
  }
3524
+
3367
3525
  if (freturn) {
3368
3526
  return
3369
3527
  }
3370
- data.handler = bindingExecutors.repeat
3528
+
3371
3529
  data.$outer = {}
3372
3530
  var check0 = "$key"
3373
3531
  var check1 = "$val"
@@ -3375,6 +3533,7 @@ bindingHandlers.repeat = function(data, vmodels) {
3375
3533
  check0 = "$first"
3376
3534
  check1 = "$last"
3377
3535
  }
3536
+
3378
3537
  for (i = 0; v = vmodels[i++]; ) {
3379
3538
  if (v.hasOwnProperty(check0) && v.hasOwnProperty(check1)) {
3380
3539
  data.$outer = v
@@ -3383,81 +3542,101 @@ bindingHandlers.repeat = function(data, vmodels) {
3383
3542
  }
3384
3543
  var $events = $repeat.$events
3385
3544
  var $list = ($events || {})[subscribers]
3386
- if ($list && avalon.Array.ensure($list, data)) {
3387
- addSubscribers(data, $list)
3388
- }
3545
+ injectDependency($list, data)
3389
3546
  if (xtype === "object") {
3390
- data.$with = true
3391
- var pool = !$events ? {} : $events.$withProxyPool || ($events.$withProxyPool = {})
3392
- data.handler("append", $repeat, pool)
3547
+ data.handler("append")
3393
3548
  } else if ($repeat.length) {
3394
3549
  data.handler("add", 0, $repeat.length)
3395
3550
  }
3396
3551
  }
3397
3552
 
3398
- bindingExecutors.repeat = function(method, pos, el) {
3553
+ bindingExecutors.repeat = function (method, pos, el) {
3554
+ var data = this
3555
+ if (!method && data.xtype) {
3556
+ var old = data.$repeat
3557
+ var neo = data.evaluator.apply(0, data.args || [])
3558
+
3559
+ if (data.xtype === "array") {
3560
+ if (old.length === neo.length) {
3561
+ return
3562
+ }
3563
+ method = "add"
3564
+ pos = 0
3565
+ data.$repeat = neo
3566
+ el = neo.length
3567
+ } else {
3568
+ if (keysVM(old).join(";;") === keysVM(neo).join(";;")) {
3569
+ return
3570
+ }
3571
+ method = "append"
3572
+ data.$repeat = neo
3573
+ }
3574
+ }
3399
3575
  if (method) {
3400
- var data = this
3576
+ var start, fragment
3401
3577
  var end = data.element
3578
+ var comments = getComments(data)
3402
3579
  var parent = end.parentNode
3403
3580
  var proxies = data.proxies
3404
- var transation = hyperspace.cloneNode(false)
3581
+ var transation = avalonFragment.cloneNode(false)
3405
3582
  switch (method) {
3406
- case "add": //在pos位置后添加el数组(pos为数字,el为数组)
3583
+ case "add": //在pos位置后添加el数组(pos为插入位置,el为要插入的个数)
3407
3584
  var n = pos + el
3408
- var array = data.$repeat
3409
- var last = array.length - 1
3410
- var fragments = [], fragment
3411
- var start = locateNode(data, pos)
3585
+ var fragments = []
3412
3586
  for (var i = pos; i < n; i++) {
3413
3587
  var proxy = eachProxyAgent(i, data)
3414
3588
  proxies.splice(i, 0, proxy)
3415
3589
  shimController(data, transation, proxy, fragments)
3416
3590
  }
3417
- parent.insertBefore(transation, start)
3591
+ parent.insertBefore(transation, comments[pos] || end)
3418
3592
  for (i = 0; fragment = fragments[i++]; ) {
3419
3593
  scanNodeArray(fragment.nodes, fragment.vmodels)
3420
3594
  fragment.nodes = fragment.vmodels = null
3421
3595
  }
3596
+
3422
3597
  break
3423
3598
  case "del": //将pos后的el个元素删掉(pos, el都是数字)
3424
- start = proxies[pos].$stamp
3425
- end = locateNode(data, pos + el)
3426
- sweepNodes(start, end)
3599
+ sweepNodes(comments[pos], comments[pos + el] || end)
3427
3600
  var removed = proxies.splice(pos, el)
3428
3601
  recycleProxies(removed, "each")
3429
3602
  break
3430
3603
  case "clear":
3431
- var check = data.$stamp || proxies[0]
3432
- if (check) {
3433
- start = check.$stamp || check
3604
+ start = comments[0]
3605
+ if (start) {
3434
3606
  sweepNodes(start, end)
3607
+ if (data.xtype === "object") {
3608
+ parent.insertBefore(start, end)
3609
+ }else{
3610
+ recycleProxies(proxies, "each")
3611
+ }
3435
3612
  }
3436
- recycleProxies(proxies, "each")
3437
3613
  break
3438
3614
  case "move":
3439
- start = proxies[0].$stamp
3440
- var signature = start.nodeValue
3441
- var rooms = []
3442
- var room = [], node
3443
- sweepNodes(start, end, function() {
3444
- room.unshift(this)
3445
- if (this.nodeValue === signature) {
3446
- rooms.unshift(room)
3447
- room = []
3448
- }
3449
- })
3450
- sortByIndex(proxies, pos)
3451
- sortByIndex(rooms, pos)
3452
- while (room = rooms.shift()) {
3453
- while (node = room.shift()) {
3454
- transation.appendChild(node)
3615
+ start = comments[0]
3616
+ if (start) {
3617
+ var signature = start.nodeValue
3618
+ var rooms = []
3619
+ var room = [],
3620
+ node
3621
+ sweepNodes(start, end, function () {
3622
+ room.unshift(this)
3623
+ if (this.nodeValue === signature) {
3624
+ rooms.unshift(room)
3625
+ room = []
3626
+ }
3627
+ })
3628
+ sortByIndex(rooms, pos)
3629
+ sortByIndex(proxies, pos)
3630
+ while (room = rooms.shift()) {
3631
+ while (node = room.shift()) {
3632
+ transation.appendChild(node)
3633
+ }
3455
3634
  }
3635
+ parent.insertBefore(transation, end)
3456
3636
  }
3457
- parent.insertBefore(transation, end)
3458
3637
  break
3459
3638
  case "index": //将proxies中的第pos个起的所有元素重新索引
3460
- last = proxies.length - 1
3639
+ var last = proxies.length - 1
3461
3640
  for (; el = proxies[pos]; pos++) {
3462
3641
  el.$index = pos
3463
3642
  el.$first = pos === 0
@@ -3467,15 +3646,23 @@ bindingExecutors.repeat = function(method, pos, el) {
3467
3646
  case "set": //将proxies中的第pos个元素的VM设置为el(pos为数字,el任意)
3468
3647
  proxy = proxies[pos]
3469
3648
  if (proxy) {
3470
- notifySubscribers(proxy.$events.$index)
3649
+ fireDependencies(proxy.$events[data.param || "el"])
3471
3650
  }
3472
- return
3473
- case "append": //将pos的键值对从el中取出(pos为一个普通对象,el为预先生成好的代理VM对象池)
3474
- var pool = el
3651
+ break
3652
+ case "append":
3653
+ var object = data.$repeat //原来第2参数, 被循环对象
3654
+ var pool = Array.isArray(proxies) ||!proxies ? {}: proxies //代理对象组成的hash
3655
+ data.proxies = pool
3475
3656
  var keys = []
3476
3657
  fragments = []
3477
- for (var key in pos) { //得到所有键名
3478
- if (pos.hasOwnProperty(key) && key !== "hasOwnProperty") {
3658
+ for (var key in pool) {
3659
+ if (!object.hasOwnProperty(key)) {
3660
+ proxyRecycler(pool[key], withProxyPool) //去掉之前的代理VM
3661
+ delete(pool[key])
3662
+ }
3663
+ }
3664
+ for (key in object) { //得到所有键名
3665
+ if (object.hasOwnProperty(key) && key !== "hasOwnProperty") {
3479
3666
  keys.push(key)
3480
3667
  }
3481
3668
  }
@@ -3487,14 +3674,11 @@ bindingExecutors.repeat = function(method, pos, el) {
3487
3674
  }
3488
3675
  for (i = 0; key = keys[i++]; ) {
3489
3676
  if (key !== "hasOwnProperty") {
3490
- if (!pool[key]) {
3491
- pool[key] = withProxyAgent(key, data)
3492
- }
3677
+ pool[key] = withProxyAgent(pool[key], key, data)
3493
3678
  shimController(data, transation, pool[key], fragments)
3494
3679
  }
3495
3680
  }
3496
- var comment = data.$stamp = data.clone
3497
- parent.insertBefore(comment, end)
3681
+
3498
3682
  parent.insertBefore(transation, end)
3499
3683
  for (i = 0; fragment = fragments[i++]; ) {
3500
3684
  scanNodeArray(fragment.nodes, fragment.vmodels)
@@ -3502,29 +3686,26 @@ bindingExecutors.repeat = function(method, pos, el) {
3502
3686
  }
3503
3687
  break
3504
3688
  }
3689
+ if (!data.$repeat || data.$repeat.hasOwnProperty("$lock")) //IE6-8 VBScript对象会报错, 有时候data.$repeat不存在
3690
+ return
3505
3691
  if (method === "clear")
3506
3692
  method = "del"
3507
3693
  var callback = data.renderedCallback || noop,
3508
3694
  args = arguments
3509
- checkScan(parent, function() {
3510
- callback.apply(parent, args)
3511
- if (parent.oldValue && parent.tagName === "SELECT") { //fix #503
3512
- avalon(parent).val(parent.oldValue.split(","))
3513
- }
3514
- }, NaN)
3695
+ if (parent.oldValue && parent.tagName === "SELECT") { //fix #503
3696
+ avalon(parent).val(parent.oldValue.split(","))
3697
+ }
3698
+ callback.apply(parent, args)
3515
3699
  }
3516
3700
  }
3517
-
3518
- "with,each".replace(rword, function(name) {
3701
+ "with,each".replace(rword, function (name) {
3519
3702
  bindingHandlers[name] = bindingHandlers.repeat
3520
3703
  })
3521
3704
 
3522
3705
  function shimController(data, transation, proxy, fragments) {
3523
3706
  var content = data.template.cloneNode(true)
3524
3707
  var nodes = avalon.slice(content.childNodes)
3525
- if (proxy.$stamp) {
3526
- content.insertBefore(proxy.$stamp, content.firstChild)
3527
- }
3708
+ content.insertBefore(DOC.createComment(data.signature), content.firstChild)
3528
3709
  transation.appendChild(content)
3529
3710
  var nv = [proxy].concat(data.vmodels)
3530
3711
  var fragment = {
@@ -3534,11 +3715,21 @@ function shimController(data, transation, proxy, fragments) {
3534
3715
  fragments.push(fragment)
3535
3716
  }
3536
3717
 
3537
- function locateNode(data, pos) {
3538
- var proxy = data.proxies[pos]
3539
- return proxy ? proxy.$stamp : data.element
3718
+ function getComments(data) {
3719
+ var ret = []
3720
+ var nodes = data.element.parentNode.childNodes
3721
+ for (var i = 0, node; node = nodes[i++]; ) {
3722
+ if (node.nodeValue === data.signature) {
3723
+ ret.push(node)
3724
+ } else if (node.nodeValue === data.signature + ":end") {
3725
+ break
3726
+ }
3727
+ }
3728
+ return ret
3540
3729
  }
3541
3730
 
3731
+
3732
+ //移除掉start与end之间的节点(保留end)
3542
3733
  function sweepNodes(start, end, callback) {
3543
3734
  while (true) {
3544
3735
  var node = end.previousSibling
@@ -3555,24 +3746,89 @@ function sweepNodes(start, end, callback) {
3555
3746
  // 为ms-each,ms-with, ms-repeat会创建一个代理VM,
3556
3747
  // 通过它们保持一个下上文,让用户能调用$index,$first,$last,$remove,$key,$val,$outer等属性与方法
3557
3748
  // 所有代理VM的产生,消费,收集,存放通过xxxProxyFactory,xxxProxyAgent, recycleProxies,xxxProxyPool实现
3558
- var eachProxyPool = []
3559
3749
  var withProxyPool = []
3750
+ function withProxyFactory() {
3751
+ var proxy = modelFactory({
3752
+ $key: "",
3753
+ $outer: {},
3754
+ $host: {},
3755
+ $val: {
3756
+ get: function () {
3757
+ return this.$host[this.$key]
3758
+ },
3759
+ set: function (val) {
3760
+ this.$host[this.$key] = val
3761
+ }
3762
+ }
3763
+ }, {
3764
+ $val: 1
3765
+ })
3766
+ proxy.$id = generateID("$proxy$with")
3767
+ return proxy
3768
+ }
3769
+
3770
+ function withProxyAgent(proxy, key, data) {
3771
+ proxy = proxy || withProxyPool.pop()
3772
+ if (!proxy) {
3773
+ proxy = withProxyFactory()
3774
+ } else {
3775
+ proxy.$reinitialize()
3776
+ }
3777
+ var host = data.$repeat
3778
+ proxy.$key = key
3779
+
3780
+ proxy.$host = host
3781
+ proxy.$outer = data.$outer
3782
+ if (host.$events) {
3783
+ proxy.$events.$val = host.$events[key]
3784
+ } else {
3785
+ proxy.$events = {}
3786
+ }
3787
+ return proxy
3788
+ }
3789
+
3790
+
3791
+ function recycleProxies(proxies) {
3792
+ eachProxyRecycler(proxies)
3793
+ }
3794
+ function eachProxyRecycler(proxies) {
3795
+ proxies.forEach(function (proxy) {
3796
+ proxyRecycler(proxy, eachProxyPool)
3797
+ })
3798
+ proxies.length = 0
3799
+ }
3800
+
3801
+
3802
+ var eachProxyPool = []
3560
3803
  function eachProxyFactory(name) {
3561
3804
  var source = {
3562
3805
  $host: [],
3563
3806
  $outer: {},
3564
- $stamp: 1,
3565
3807
  $index: 0,
3566
3808
  $first: false,
3567
3809
  $last: false,
3568
3810
  $remove: avalon.noop
3569
3811
  }
3570
3812
  source[name] = {
3571
- get: function() {
3572
- return this.$host[this.$index]
3813
+ get: function () {
3814
+ var e = this.$events
3815
+ var array = e.$index
3816
+ e.$index = e[name] //#817 通过$index为el收集依赖
3817
+ try {
3818
+ return this.$host[this.$index]
3819
+ } finally {
3820
+ e.$index = array
3821
+ }
3573
3822
  },
3574
- set: function(val) {
3575
- this.$host.set(this.$index, val)
3823
+ set: function (val) {
3824
+ try {
3825
+ var e = this.$events
3826
+ var array = e.$index
3827
+ e.$index = []
3828
+ this.$host.set(this.$index, val)
3829
+ } finally {
3830
+ e.$index = array
3831
+ }
3576
3832
  }
3577
3833
  }
3578
3834
  var second = {
@@ -3581,14 +3837,13 @@ function eachProxyFactory(name) {
3581
3837
  $index: 1
3582
3838
  }
3583
3839
  var proxy = modelFactory(source, second)
3584
- var e = proxy.$events
3585
- e[name] = e.$first = e.$last = e.$index
3586
3840
  proxy.$id = generateID("$proxy$each")
3587
3841
  return proxy
3588
3842
  }
3589
3843
 
3590
3844
  function eachProxyAgent(index, data) {
3591
- var param = data.param || "el", proxy
3845
+ var param = data.param || "el",
3846
+ proxy
3592
3847
  for (var i = 0, n = eachProxyPool.length; i < n; i++) {
3593
3848
  var candidate = eachProxyPool[i]
3594
3849
  if (candidate && candidate.hasOwnProperty(param)) {
@@ -3606,93 +3861,45 @@ function eachProxyAgent(index, data) {
3606
3861
  proxy.$last = index === last
3607
3862
  proxy.$host = host
3608
3863
  proxy.$outer = data.$outer
3609
- proxy.$stamp = data.clone.cloneNode(false)
3610
- proxy.$remove = function() {
3864
+ proxy.$remove = function () {
3611
3865
  return host.removeAt(proxy.$index)
3612
3866
  }
3613
3867
  return proxy
3614
3868
  }
3615
3869
 
3616
- function withProxyFactory() {
3617
- var proxy = modelFactory({
3618
- $key: "",
3619
- $outer: {},
3620
- $host: {},
3621
- $val: {
3622
- get: function() {
3623
- return this.$host[this.$key]
3624
- },
3625
- set: function(val) {
3626
- this.$host[this.$key] = val
3627
- }
3628
- }
3629
- }, {
3630
- $val: 1
3631
- })
3632
- proxy.$id = generateID("$proxy$with")
3633
- return proxy
3634
- }
3635
3870
 
3636
- function withProxyAgent(key, data) {
3637
- var proxy = withProxyPool.pop()
3638
- if (!proxy) {
3639
- proxy = withProxyFactory()
3871
+ function proxyRecycler(proxy, proxyPool) {
3872
+ for (var i in proxy.$events) {
3873
+ var arr = proxy.$events[i]
3874
+ if (Array.isArray(arr)) {
3875
+ arr.forEach(function (data) {
3876
+ if (typeof data === "object")
3877
+ disposeData(data)
3878
+ })// jshint ignore:line
3879
+ arr.length = 0
3880
+ }
3640
3881
  }
3641
- var host = data.$repeat
3642
- proxy.$key = key
3643
- proxy.$host = host
3644
- proxy.$outer = data.$outer
3645
- if (host.$events) {
3646
- proxy.$events.$val = host.$events[key]
3647
- } else {
3648
- proxy.$events = {}
3882
+ proxy.$host = proxy.$outer = {}
3883
+ if (proxyPool.unshift(proxy) > kernel.maxRepeatSize) {
3884
+ proxyPool.pop()
3649
3885
  }
3650
- return proxy
3651
- }
3652
-
3653
- function recycleProxies(proxies, type) {
3654
- var proxyPool = type === "each" ? eachProxyPool : withProxyPool
3655
- avalon.each(proxies, function(key, proxy) {
3656
- if (proxy.$events) {
3657
- for (var i in proxy.$events) {
3658
- if (Array.isArray(proxy.$events[i])) {
3659
- proxy.$events[i].forEach(function(data) {
3660
- if (typeof data === "object")
3661
- disposeData(data)
3662
- })// jshint ignore:line
3663
- proxy.$events[i].length = 0
3664
- }
3665
- }
3666
- proxy.$host = proxy.$outer = {}
3667
- if (proxyPool.unshift(proxy) > kernel.maxRepeatSize) {
3668
- proxyPool.pop()
3669
- }
3670
- }
3671
- })
3672
- if (type === "each")
3673
- proxies.length = 0
3674
3886
  }
3675
3887
 
3676
-
3677
-
3678
-
3679
3888
  /*********************************************************************
3680
3889
  * 各种指令 *
3681
3890
  **********************************************************************/
3682
3891
  //ms-skip绑定已经在scanTag 方法中实现
3683
3892
  // bindingHandlers.text 定义在if.js
3684
3893
  bindingExecutors.text = function(val, elem) {
3685
- val = val == null ? "" : val //不在页面上显示undefined null
3686
- if (elem.nodeType === 3) { //绑定在文本节点上
3687
- try { //IE对游离于DOM树外的节点赋值会报错
3688
- elem.data = val
3689
- } catch (e) {
3690
- }
3691
- } else { //绑定在特性节点上
3692
- elem.textContent = val
3693
- }
3894
+ val = val == null ? "" : val //不在页面上显示undefined null
3895
+ if (elem.nodeType === 3) { //绑定在文本节点上
3896
+ try { //IE对游离于DOM树外的节点赋值会报错
3897
+ elem.data = val
3898
+ } catch (e) {}
3899
+ } else { //绑定在特性节点上
3900
+ elem.textContent = val
3901
+ }
3694
3902
  }
3695
-
3696
3903
  function parseDisplay(nodeName, val) {
3697
3904
  //用于取得此类标签的默认display值
3698
3905
  var key = "_" + nodeName
@@ -3712,42 +3919,33 @@ function parseDisplay(nodeName, val) {
3712
3919
 
3713
3920
  avalon.parseDisplay = parseDisplay
3714
3921
 
3715
- bindingHandlers.visible = function(data, vmodels) {
3716
- var elem = avalon(data.element)
3717
- var display = elem.css("display")
3718
- if (display === "none") {
3719
- var style = elem[0].style
3720
- var has = /visibility/i.test(style.cssText)
3721
- var visible = elem.css("visibility")
3722
- style.display = ""
3723
- style.visibility = "hidden"
3724
- display = elem.css("display")
3725
- if (display === "none") {
3726
- display = parseDisplay(elem[0].nodeName)
3727
- }
3728
- style.visibility = has ? visible : ""
3729
- }
3730
- data.display = display
3922
+ bindingHandlers.visible = function (data, vmodels) {
3731
3923
  parseExprProxy(data.value, vmodels, data)
3732
3924
  }
3733
3925
 
3734
- bindingExecutors.visible = function(val, elem, data) {
3735
- elem.style.display = val ? data.display : "none"
3926
+ bindingExecutors.visible = function (val, elem, binding) {
3927
+ if (val) {
3928
+ elem.style.display = binding.display || ""
3929
+ if (avalon(elem).css("display") === "none") {
3930
+ elem.style.display = binding.display = parseDisplay(elem.nodeName)
3931
+ }
3932
+ } else {
3933
+ elem.style.display = "none"
3934
+ }
3736
3935
  }
3737
-
3738
3936
  bindingHandlers.widget = function(data, vmodels) {
3739
3937
  var args = data.value.match(rword)
3740
3938
  var elem = data.element
3741
3939
  var widget = args[0]
3742
3940
  var id = args[1]
3743
- if (!id || id === "$") {//没有定义或为$时,取组件名+随机数
3941
+ if (!id || id === "$") { //没有定义或为$时,取组件名+随机数
3744
3942
  id = generateID(widget)
3745
3943
  }
3746
- var optName = args[2] || widget//没有定义,取组件名
3944
+ var optName = args[2] || widget //没有定义,取组件名
3747
3945
  var constructor = avalon.ui[widget]
3748
3946
  if (typeof constructor === "function") { //ms-widget="tabs,tabsAAA,optname"
3749
3947
  vmodels = elem.vmodels || vmodels
3750
- for (var i = 0, v; v = vmodels[i++]; ) {
3948
+ for (var i = 0, v; v = vmodels[i++];) {
3751
3949
  if (v.hasOwnProperty(optName) && typeof v[optName] === "object") {
3752
3950
  var vmOptions = v[optName]
3753
3951
  vmOptions = vmOptions.$model || vmOptions
@@ -3757,6 +3955,7 @@ bindingHandlers.widget = function(data, vmodels) {
3757
3955
  if (vmOptions) {
3758
3956
  var wid = vmOptions[widget + "Id"]
3759
3957
  if (typeof wid === "string") {
3958
+ log("warning!不再支持" + widget + "Id")
3760
3959
  id = wid
3761
3960
  }
3762
3961
  }
@@ -3772,27 +3971,26 @@ bindingHandlers.widget = function(data, vmodels) {
3772
3971
  if (vmodel.$id) {
3773
3972
  avalon.vmodels[id] = vmodel
3774
3973
  createSignalTower(elem, vmodel)
3775
- if (vmodel.hasOwnProperty("$init")) {
3974
+ try {
3776
3975
  vmodel.$init(function() {
3777
3976
  avalon.scan(elem, [vmodel].concat(vmodels))
3778
3977
  if (typeof options.onInit === "function") {
3779
3978
  options.onInit.call(elem, vmodel, options, vmodels)
3780
3979
  }
3781
3980
  })
3782
- }
3981
+ } catch (e) {}
3783
3982
  data.rollback = function() {
3784
3983
  try {
3785
- vmodel.widgetElement = null
3786
3984
  vmodel.$remove()
3787
- } catch (e) {
3788
- }
3985
+ vmodel.widgetElement = null // 放到$remove后边
3986
+ } catch (e) {}
3789
3987
  elem.msData = {}
3790
3988
  delete avalon.vmodels[vmodel.$id]
3791
3989
  }
3792
- addSubscribers(data, widgetList)
3990
+ injectDisposeQueue(data, widgetList)
3793
3991
  if (window.chrome) {
3794
3992
  elem.addEventListener("DOMNodeRemovedFromDocument", function() {
3795
- setTimeout(removeSubscribers)
3993
+ setTimeout(rejectDisposeQueue)
3796
3994
  })
3797
3995
  }
3798
3996
  } else {
@@ -3862,7 +4060,7 @@ var filters = avalon.filters = {
3862
4060
  truncate: function(str, length, truncation) {
3863
4061
  //length,新字符串长度,truncation,新字符串的结尾的字段,返回新字符串
3864
4062
  length = length || 30
3865
- truncation = truncation === void(0) ? "..." : truncation
4063
+ truncation = typeof truncation === "string" ? truncation : "..."
3866
4064
  return str.length > length ? str.slice(0, length - truncation.length) + truncation : String(str)
3867
4065
  },
3868
4066
  $filter: function(val) {
@@ -4155,71 +4353,56 @@ new function() {// jshint ignore:line
4155
4353
  /*********************************************************************
4156
4354
  * END *
4157
4355
  **********************************************************************/
4158
- new function() {
4356
+ new function () {
4159
4357
  avalon.config({
4160
4358
  loader: false
4161
4359
  })
4162
- var fns = [], fn, loaded
4360
+ var fns = [], loaded = DOC.readyState === "complete", fn
4163
4361
  function flush(f) {
4164
4362
  loaded = 1
4165
4363
  while (f = fns.shift())
4166
4364
  f()
4167
4365
  }
4168
- if (W3C) {
4169
- avalon.bind(DOC, "DOMContentLoaded", fn = function() {
4170
- avalon.unbind(DOC, "DOMContentLoaded", fn)
4366
+
4367
+ avalon.bind(DOC, "DOMContentLoaded", fn = function () {
4368
+ avalon.unbind(DOC, "DOMContentLoaded", fn)
4369
+ flush()
4370
+ })
4371
+
4372
+ var id = setInterval(function () {
4373
+ if (document.readyState === "complete" && document.body) {
4374
+ clearInterval(id)
4171
4375
  flush()
4172
- })
4173
- } else {
4174
- var id = setInterval(function() {
4175
- if (document.readyState === "complete" && document.body) {
4176
- clearInterval(id)
4177
- flush()
4178
- }
4179
- }, 50)
4180
- }
4181
- avalon.ready = function(fn) {
4376
+ }
4377
+ }, 50)
4378
+
4379
+ avalon.ready = function (fn) {
4182
4380
  loaded ? fn(avalon) : fns.push(fn)
4183
4381
  }
4184
- avalon.ready(function() {
4382
+ avalon.ready(function () {
4185
4383
  avalon.scan(DOC.body)
4186
4384
  })
4187
4385
  }
4188
4386
 
4189
4387
  new function() {// jshint ignore:line
4190
- // http://www.cnblogs.com/yexiaochai/p/3462657.html
4388
+ var touchProxy = {}
4389
+ var IEtouch = navigator.pointerEnabled
4390
+ var IEMStouch = navigator.msPointerEnabled
4191
4391
  var ua = navigator.userAgent
4192
4392
  var isAndroid = ua.indexOf("Android") > 0
4193
- var isIOS = /iP(ad|hone|od)/.test(ua)
4393
+ var isGoingtoFixTouchEndEvent = isAndroid && ua.match(/Firefox|Opera/gi)
4394
+ //合成做成触屏事件所需要的各种原生事件
4395
+ var touchNames = ["touchstart", "touchmove", "touchend", "touchcancel"]
4396
+ var touchTimeout = null
4397
+ var longTapTimeout = null
4398
+ var dragDistance = 30
4399
+ var clickDuration = 750 //小于750ms是点击,长于它是长按或拖动
4194
4400
  var me = bindingHandlers.on
4195
- var touchProxy = {}
4196
4401
 
4197
- var IE11touch = navigator.pointerEnabled
4198
- var IE9_10touch = navigator.msPointerEnabled
4199
- var w3ctouch = (function() {
4200
- var supported = isIOS || false
4201
- //http://stackoverflow.com/questions/5713393/creating-and-firing-touch-events-on-a-touch-enabled-browser
4202
- try {
4203
- var div = document.createElement("div")
4204
- div.ontouchstart = function() {
4205
- supported = true
4206
- }
4207
- var e = document.createEvent("TouchEvent")
4208
- e.initUIEvent("touchstart", true, true)
4209
- div.dispatchEvent(e)
4210
- } catch (err) {
4211
- }
4212
- div = div.ontouchstart = null
4213
- return supported
4214
- })()
4215
- var touchSupported = !!(w3ctouch || IE11touch || IE9_10touch)
4216
- //合成做成触屏事件所需要的各种原生事件
4217
- var touchNames = ["mousedown", "mousemove", "mouseup", ""]
4218
- if (w3ctouch) {
4219
- touchNames = ["touchstart", "touchmove", "touchend", "touchcancel"]
4220
- } else if (IE11touch) {
4402
+ if (IEtouch) {
4221
4403
  touchNames = ["pointerdown", "pointermove", "pointerup", "pointercancel"]
4222
- } else if (IE9_10touch) {
4404
+ }
4405
+ if (IEMStouch) {
4223
4406
  touchNames = ["MSPointerDown", "MSPointerMove", "MSPointerUp", "MSPointerCancel"]
4224
4407
  }
4225
4408
  function isPrimaryTouch(event){
@@ -4230,140 +4413,132 @@ new function() {// jshint ignore:line
4230
4413
  return (e.type === 'pointer'+type || e.type.toLowerCase() === 'mspointer'+type)
4231
4414
  }
4232
4415
 
4233
- var touchTimeout, longTapTimeout
4234
4416
  //判定滑动方向
4235
4417
  function swipeDirection(x1, x2, y1, y2) {
4236
4418
  return Math.abs(x1 - x2) >=
4237
4419
  Math.abs(y1 - y2) ? (x1 - x2 > 0 ? "left" : "right") : (y1 - y2 > 0 ? "up" : "down")
4238
4420
  }
4239
- function getCoordinates(event) {
4240
- var touches = event.touches && event.touches.length ? event.touches : [event];
4241
- var e = event.changedTouches ? event.changedTouches[0] : touches[0]
4242
- return {
4243
- x: e.clientX,
4244
- y: e.clientY
4245
- }
4246
- }
4421
+
4247
4422
  function fireEvent(el, name, detail) {
4248
4423
  var event = document.createEvent("Events")
4249
4424
  event.initEvent(name, true, true)
4250
- event.fireByAvalon = true//签名,标记事件是由avalon触发
4251
- //event.isTrusted = false 设置这个opera会报错
4252
- if (detail)
4425
+ if (detail) {
4253
4426
  event.detail = detail
4427
+ }
4254
4428
  el.dispatchEvent(event)
4255
4429
  }
4430
+
4256
4431
  function onMouse(event) {
4257
- if (event.fireByAvalon) {
4258
- return true
4259
- }
4260
- if (event.stopImmediatePropagation) {
4261
- event.stopImmediatePropagation()
4262
- } else {
4263
- event.propagationStopped = true
4432
+ var target = event.target,
4433
+ element = touchProxy.element
4434
+
4435
+ if (element && element !== target) {
4436
+ var type = target.type || '',
4437
+ targetTag = target.tagName.toLowerCase(),
4438
+ elementTag = element.tagName.toLowerCase()
4439
+ // 通过手机的“前往”提交表单时不可禁止默认行为;通过label focus input时也不可以阻止默认行为
4440
+ if ((targetTag === 'input' && elementTag === "label") || type === 'submit') {
4441
+ return false
4442
+ }
4443
+ if (event.stopImmediatePropagation) {
4444
+ event.stopImmediatePropagation()
4445
+ } else {
4446
+ event.propagationStopped = true
4447
+ }
4448
+ event.stopPropagation()
4449
+ event.preventDefault()
4264
4450
  }
4265
- event.stopPropagation()
4266
- event.preventDefault()
4267
4451
  }
4268
4452
  function cancelLongTap() {
4269
4453
  if (longTapTimeout) clearTimeout(longTapTimeout)
4270
4454
  longTapTimeout = null
4271
4455
  }
4272
4456
  function touchstart(event) {
4273
- var _isPointerType = isPointerEventType(event, 'down'),
4457
+ var _isPointerType = isPointerEventType(event, "down"),
4274
4458
  firstTouch = _isPointerType ? event : event.touches[0],
4275
- element = 'tagName' in firstTouch.target ? firstTouch.target: firstTouch.target.parentNode,
4459
+ element = "tagName" in firstTouch.target ? firstTouch.target: firstTouch.target.parentNode,
4276
4460
  now = Date.now(),
4277
4461
  delta = now - (touchProxy.last || now)
4278
4462
 
4279
4463
  if (_isPointerType && !isPrimaryTouch(event)) return
4280
-
4281
- avalon.mix(touchProxy, getCoordinates(event))
4282
- touchProxy.mx = 0
4283
- touchProxy.my = 0
4464
+ if (touchProxy.x1 || touchProxy.y1) {
4465
+ touchProxy.x1 = undefined
4466
+ touchProxy.y1 = undefined
4467
+ }
4284
4468
  if (delta > 0 && delta <= 250) {
4285
4469
  touchProxy.isDoubleTap = true
4286
4470
  }
4471
+ touchProxy.x = firstTouch.pageX
4472
+ touchProxy.y = firstTouch.pageY
4473
+ touchProxy.mx = 0
4474
+ touchProxy.my = 0
4287
4475
  touchProxy.last = now
4288
4476
  touchProxy.element = element
4289
- /*
4290
- 当触发hold和longtap事件时会触发touchcancel事件,从而阻止touchend事件的触发,继而保证在同时绑定tap和hold(longtap)事件时只触发其中一个事件
4291
- */
4292
- avalon(element).addClass(fastclick.activeClass)
4477
+
4293
4478
  longTapTimeout = setTimeout(function() {
4294
4479
  longTapTimeout = null
4295
4480
  fireEvent(element, "hold")
4296
4481
  fireEvent(element, "longtap")
4297
4482
  touchProxy = {}
4298
- avalon(element).removeClass(fastclick.activeClass)
4299
- }, fastclick.clickDuration)
4483
+ }, clickDuration)
4300
4484
  return true
4301
4485
  }
4302
4486
  function touchmove(event) {
4487
+
4303
4488
  var _isPointerType = isPointerEventType(event, 'down'),
4304
- e = getCoordinates(event)
4489
+ firstTouch = _isPointerType ? event : event.touches[0],
4490
+ x = firstTouch.pageX,
4491
+ y = firstTouch.pageY
4305
4492
  if (_isPointerType && !isPrimaryTouch(event)) return
4306
-
4493
+ /*
4494
+ android下某些浏览器触发了touchmove事件的话touchend事件不触发,禁用touchmove可以解决此bug
4495
+ http://stackoverflow.com/questions/14486804/understanding-touch-events
4496
+ */
4497
+ if (isGoingtoFixTouchEndEvent && Math.abs(touchProxy.x - x) > 10) {
4498
+ event.preventDefault()
4499
+ }
4307
4500
  cancelLongTap()
4308
- touchProxy.mx += Math.abs(touchProxy.x - e.x)
4309
- touchProxy.my += Math.abs(touchProxy.y - e.y)
4501
+
4502
+ touchProxy.x1 = x // touchend事件没有pageX、pageY始终为0,且没有clientX和clientY事件
4503
+ touchProxy.y1 = y
4504
+ touchProxy.mx += Math.abs(touchProxy.x - x)
4505
+ touchProxy.my += Math.abs(touchProxy.y - y)
4310
4506
  }
4311
4507
  function touchend(event) {
4312
- var _isPointerType = isPointerEventType(event, 'down')
4508
+ var _isPointerType = isPointerEventType(event, 'down'),
4313
4509
  element = touchProxy.element
4314
4510
 
4315
4511
  if (_isPointerType && !isPrimaryTouch(event)) return
4512
+ if (!element) return // longtap|hold触发后touchProxy为{}
4316
4513
 
4317
- if (!element) {
4318
- return
4319
- }
4320
4514
  cancelLongTap()
4321
- var e = getCoordinates(event)
4322
- var totalX = Math.abs(touchProxy.x - e.x)
4323
- var totalY = Math.abs(touchProxy.y - e.y)
4324
- if (totalX > 30 || totalY > 30) {
4515
+ if ((touchProxy.x1 && Math.abs(touchProxy.x1 - touchProxy.x) > dragDistance) || (touchProxy.y1 && Math.abs(touchProxy.y1 - touchProxy.y) > dragDistance)) {
4325
4516
  //如果用户滑动的距离有点大,就认为是swipe事件
4326
- var direction = swipeDirection(touchProxy.x, e.x, touchProxy.y, e.y)
4517
+ var direction = swipeDirection(touchProxy.x, touchProxy.x1, touchProxy.y, touchProxy.y1)
4327
4518
  var details = {
4328
4519
  direction: direction
4329
4520
  }
4330
4521
  fireEvent(element, "swipe", details)
4331
4522
  fireEvent(element, "swipe" + direction, details)
4332
- avalon(element).removeClass(fastclick.activeClass)
4333
4523
  touchProxy = {}
4334
4524
  } else {
4335
- if (fastclick.canClick(element) && touchProxy.mx < fastclick.dragDistance && touchProxy.my < fastclick.dragDistance) {
4336
- // 失去焦点的处理
4337
- if (document.activeElement && document.activeElement !== element) {
4338
- document.activeElement.blur()
4339
- }
4340
- //如果此元素不为表单元素,或者它没有disabled
4341
- var forElement
4342
- if (element.tagName.toLowerCase() === "label") {
4343
- forElement = element.htmlFor ? document.getElementById(element.htmlFor) : null
4344
- }
4345
- if (forElement) {
4346
- fastclick.focus(forElement)
4347
- } else {
4348
- fastclick.focus(element)
4349
- }
4350
- event.preventDefault()
4525
+ if (touchProxy.mx < dragDistance && touchProxy.my < dragDistance) {
4351
4526
  fireEvent(element, 'tap')
4352
- avalon.fastclick.fireEvent(element, "click", event)
4353
- avalon(element).removeClass(fastclick.activeClass)
4354
4527
  if (touchProxy.isDoubleTap) {
4355
4528
  fireEvent(element, "doubletap")
4356
- avalon.fastclick.fireEvent(element, "dblclick", event)
4357
4529
  touchProxy = {}
4358
- avalon(element).removeClass(fastclick.activeClass)
4530
+ touchProxy.element = element
4359
4531
  } else {
4360
4532
  touchTimeout = setTimeout(function() {
4361
4533
  clearTimeout(touchTimeout)
4362
4534
  touchTimeout = null
4363
- touchProxy = {}
4364
- avalon(element).removeClass(fastclick.activeClass)
4535
+ if (touchProxy.element) fireEvent(touchProxy.element, "singletap")
4536
+ touchProxy = {};
4537
+ touchProxy.element = element
4365
4538
  }, 250)
4366
4539
  }
4540
+ } else {
4541
+ touchProxy = {}
4367
4542
  }
4368
4543
  }
4369
4544
  }
@@ -4380,71 +4555,7 @@ new function() {// jshint ignore:line
4380
4555
  touchProxy = {}
4381
4556
  })
4382
4557
  }
4383
- //fastclick只要是处理移动端点击存在300ms延迟的问题
4384
- //这是苹果乱搞异致的,他们想在小屏幕设备上通过快速点击两次,将放大了的网页缩放至原始比例。
4385
- var fastclick = avalon.fastclick = {
4386
- activeClass: "ms-click-active",
4387
- clickDuration: 750, //小于750ms是点击,长于它是长按或拖动
4388
- dragDistance: 30, //最大移动的距离
4389
- fireEvent: function(element, type, event) {
4390
- var clickEvent = document.createEvent("MouseEvents")
4391
- clickEvent.initMouseEvent(type, true, true, window, 1, event.screenX, event.screenY,
4392
- event.clientX, event.clientY, false, false, false, false, 0, null)
4393
- Object.defineProperty(clickEvent, "fireByAvalon", {
4394
- value: true
4395
- })
4396
- element.dispatchEvent(clickEvent)
4397
- },
4398
- focus: function(target) {
4399
- if (this.canFocus(target)) {
4400
- //https://github.com/RubyLouvre/avalon/issues/254
4401
- var value = target.value
4402
- target.value = value
4403
- if (isIOS && target.setSelectionRange && target.type.indexOf("date") !== 0 && target.type !== 'time') {
4404
- // iOS 7, date datetime等控件直接对selectionStart,selectionEnd赋值会抛错
4405
- var n = value.length
4406
- target.setSelectionRange(n, n)
4407
- } else {
4408
- target.focus()
4409
- }
4410
- }
4411
- },
4412
- canClick: function(target) {
4413
- switch (target.nodeName.toLowerCase()) {
4414
- case "textarea":
4415
- case "select":
4416
- case "input":
4417
- return !target.disabled
4418
- default:
4419
- return true
4420
- }
4421
- },
4422
- canFocus: function(target) {
4423
- switch (target.nodeName.toLowerCase()) {
4424
- case "textarea":
4425
- return true;
4426
- case "select":
4427
- return !isAndroid
4428
- case "input":
4429
- switch (target.type) {
4430
- case "button":
4431
- case "checkbox":
4432
- case "file":
4433
- case "image":
4434
- case "radio":
4435
- case "submit":
4436
- return false
4437
- }
4438
- // No point in attempting to focus disabled inputs
4439
- return !target.disabled && !target.readOnly
4440
- default:
4441
- return false
4442
- }
4443
- }
4444
- };
4445
-
4446
-
4447
- ["swipe", "swipeleft", "swiperight", "swipeup", "swipedown", "doubletap", "tap", "dblclick", "longtap", "hold"].forEach(function(method) {
4558
+ ["swipe", "swipeleft", "swiperight", "swipeup", "swipedown", "doubletap", "tap", "singletap", "dblclick", "longtap", "hold"].forEach(function(method) {
4448
4559
  me[method + "Hook"] = me["clickHook"]
4449
4560
  })
4450
4561
 
@@ -4481,6 +4592,7 @@ new function() {// jshint ignore:line
4481
4592
  if (noGlobal === void 0) {
4482
4593
  window.avalon = avalon
4483
4594
  }
4595
+
4484
4596
  return avalon
4485
4597
 
4486
4598
  }));