avalon-rails 1.4.6.0.20150915133100 → 1.4.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.46 built in 2015.9.11
8
+ avalon.mobile.shim.js 1.4.7 built in 2015.10.13
9
9
  ==================================================*/
10
10
  (function(global, factory) {
11
11
 
@@ -59,9 +59,6 @@ function createMap() {
59
59
  }
60
60
 
61
61
  var subscribers = "$" + expose
62
- var otherRequire = window.require
63
- var otherDefine = window.define
64
- var innerRequire
65
62
  var stopRepeatAssign = false
66
63
  var rword = /[^, ]+/g //切割字符串为一个个小块,以空格或豆号分开它们,结合replace实现字符串的forEach
67
64
  var rcomplexType = /^(?:object|array)$/
@@ -172,12 +169,12 @@ avalon.nextTick = new function () {// jshint ignore:line
172
169
  /*********************************************************************
173
170
  * avalon的静态方法定义区 *
174
171
  **********************************************************************/
175
- avalon.init = function(el) {
172
+ avalon.init = function (el) {
176
173
  this[0] = this.element = el
177
174
  }
178
175
  avalon.fn = avalon.prototype = avalon.init.prototype
179
176
 
180
- avalon.type = function(obj) { //取得目标的类型
177
+ avalon.type = function (obj) { //取得目标的类型
181
178
  if (obj == null) {
182
179
  return String(obj)
183
180
  }
@@ -187,25 +184,25 @@ avalon.type = function(obj) { //取得目标的类型
187
184
  typeof obj
188
185
  }
189
186
 
190
- var isFunction = function(fn) {
187
+ var isFunction = function (fn) {
191
188
  return serialize.call(fn) === "[object Function]"
192
189
  }
193
190
 
194
191
  avalon.isFunction = isFunction
195
192
 
196
- avalon.isWindow = function(obj) {
193
+ avalon.isWindow = function (obj) {
197
194
  return rwindow.test(serialize.call(obj))
198
195
  }
199
196
 
200
197
  /*判定是否是一个朴素的javascript对象(Object),不是DOM对象,不是BOM对象,不是自定义类的实例*/
201
198
 
202
- avalon.isPlainObject = function(obj) {
199
+ avalon.isPlainObject = function (obj) {
203
200
  // 简单的 typeof obj === "object"检测,会致使用isPlainObject(window)在opera下通不过
204
201
  return serialize.call(obj) === "[object Object]" && Object.getPrototypeOf(obj) === oproto
205
202
  }
206
203
 
207
204
  //与jQuery.extend方法,可用于浅拷贝,深拷贝
208
- avalon.mix = avalon.fn.mix = function() {
205
+ avalon.mix = avalon.fn.mix = function () {
209
206
  var options, name, src, copy, copyIsArray, clone,
210
207
  target = arguments[0] || {},
211
208
  i = 1,
@@ -267,15 +264,15 @@ function _number(a, len) { //用于模拟slice, splice的效果
267
264
  avalon.mix({
268
265
  rword: rword,
269
266
  subscribers: subscribers,
270
- version: 1.46,
267
+ version: 1.47,
271
268
  ui: {},
272
269
  log: log,
273
- slice: function(nodes, start, end) {
270
+ slice: function (nodes, start, end) {
274
271
  return aslice.call(nodes, start, end)
275
272
  },
276
273
  noop: noop,
277
274
  /*如果不用Error对象封装一下,str在控制台下可能会乱码*/
278
- error: function(str, e) {
275
+ error: function (str, e) {
279
276
  throw new (e || Error)(str)// jshint ignore:line
280
277
  },
281
278
  /*将一个以空格或逗号隔开的字符串或数组,转换成一个键值都为1的对象*/
@@ -290,7 +287,7 @@ avalon.mix({
290
287
  => [0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
291
288
  avalon.range(0)
292
289
  => []*/
293
- range: function(start, end, step) { // 用于生成整数数组
290
+ range: function (start, end, step) { // 用于生成整数数组
294
291
  step || (step = 1)
295
292
  if (end == null) {
296
293
  end = start || 0
@@ -307,34 +304,30 @@ avalon.mix({
307
304
  },
308
305
  eventHooks: {},
309
306
  /*绑定事件*/
310
- bind: function(el, type, fn, phase) {
307
+ bind: function (el, type, fn, phase) {
311
308
  var hooks = avalon.eventHooks
312
309
  var hook = hooks[type]
313
310
  if (typeof hook === "object") {
314
- type = hook.type
315
- if (hook.deel) {
316
- fn = hook.deel(el, type, fn, phase)
317
- }
311
+ type = hook.type || type
312
+ phase = hook.phase || !!phase
313
+ fn = hook.fn ? hook.fn(el, fn) : fn
318
314
  }
319
- if (!fn.unbind)
320
- el.addEventListener(type, fn, !!phase)
315
+ el.addEventListener(type, fn, phase)
321
316
  return fn
322
317
  },
323
318
  /*卸载事件*/
324
- unbind: function(el, type, fn, phase) {
319
+ unbind: function (el, type, fn, phase) {
325
320
  var hooks = avalon.eventHooks
326
321
  var hook = hooks[type]
327
322
  var callback = fn || noop
328
323
  if (typeof hook === "object") {
329
- type = hook.type
330
- if (hook.deel) {
331
- fn = hook.deel(el, type, fn, false)
332
- }
324
+ type = hook.type || type
325
+ phase = hook.phase || !!phase
333
326
  }
334
- el.removeEventListener(type, callback, !!phase)
327
+ el.removeEventListener(type, callback, phase)
335
328
  },
336
329
  /*读写删除元素节点的样式*/
337
- css: function(node, name, value) {
330
+ css: function (node, name, value) {
338
331
  if (node instanceof avalon) {
339
332
  node = node[0]
340
333
  }
@@ -361,7 +354,7 @@ avalon.mix({
361
354
  }
362
355
  },
363
356
  /*遍历数组与对象,回调的第一个参数为索引或键名,第二个或元素或键值*/
364
- each: function(obj, fn) {
357
+ each: function (obj, fn) {
365
358
  if (obj) { //排除null, undefined
366
359
  var i = 0
367
360
  if (isArrayLike(obj)) {
@@ -379,12 +372,12 @@ avalon.mix({
379
372
  }
380
373
  },
381
374
  //收集元素的data-{{prefix}}-*属性,并转换为对象
382
- getWidgetData: function(elem, prefix) {
375
+ getWidgetData: function (elem, prefix) {
383
376
  var raw = avalon(elem).data()
384
377
  var result = {}
385
378
  for (var i in raw) {
386
379
  if (i.indexOf(prefix) === 0) {
387
- result[i.replace(prefix, "").replace(/\w/, function(a) {
380
+ result[i.replace(prefix, "").replace(/\w/, function (a) {
388
381
  return a.toLowerCase()
389
382
  })] = raw[i]
390
383
  }
@@ -393,17 +386,17 @@ avalon.mix({
393
386
  },
394
387
  Array: {
395
388
  /*只有当前数组不存在此元素时只添加它*/
396
- ensure: function(target, item) {
389
+ ensure: function (target, item) {
397
390
  if (target.indexOf(item) === -1) {
398
391
  return target.push(item)
399
392
  }
400
393
  },
401
394
  /*移除数组中指定位置的元素,返回布尔表示成功与否*/
402
- removeAt: function(target, index) {
395
+ removeAt: function (target, index) {
403
396
  return !!target.splice(index, 1).length
404
397
  },
405
398
  /*移除数组中第一个匹配传参的那个元素,返回布尔表示成功与否*/
406
- remove: function(target, item) {
399
+ remove: function (target, item) {
407
400
  var index = target.indexOf(item)
408
401
  if (~index)
409
402
  return avalon.Array.removeAt(target, index)
@@ -635,7 +628,7 @@ if (DOC.onmousewheel === void 0) {
635
628
  chrome wheel deltaY 下100 上-100 */
636
629
  eventHooks.mousewheel = {
637
630
  type: "wheel",
638
- deel: function (elem, _, fn) {
631
+ fn: function (elem, fn) {
639
632
  return function (e) {
640
633
  e.wheelDeltaY = e.wheelDelta = e.deltaY > 0 ? -120 : 120
641
634
  e.wheelDeltaX = 0
@@ -675,11 +668,7 @@ function escapeRegExp(target) {
675
668
  }
676
669
 
677
670
  var plugins = {
678
- loader: function (builtin) {
679
- var flag = innerRequire && builtin
680
- window.require = flag ? innerRequire : otherRequire
681
- window.define = flag ? innerRequire.define : otherDefine
682
- },
671
+
683
672
  interpolate: function (array) {
684
673
  openTag = array[0]
685
674
  closeTag = array[1]
@@ -919,7 +908,7 @@ function modelFactory(source, $special, $model) {
919
908
  return name in this.$model
920
909
  })
921
910
  /* jshint ignore:end */
922
- for (var i in EventBus) {
911
+ for (i in EventBus) {
923
912
  hideProperty($vmodel, i, EventBus[i])
924
913
  }
925
914
 
@@ -1527,7 +1516,7 @@ function injectDisposeQueue(data, list) {
1527
1516
  var elem = data.element
1528
1517
  if (!data.uuid) {
1529
1518
  if (elem.nodeType !== 1) {
1530
- data.uuid = data.type + (data.pos || 0) + "-" + getUid(elem.parentNode)
1519
+ data.uuid = data.type + getUid(elem.parentNode)+ "-"+ (++disposeCount)
1531
1520
  } else {
1532
1521
  data.uuid = data.name + "-" + getUid(elem)
1533
1522
  }
@@ -2642,7 +2631,7 @@ function scanExpr(str) {
2642
2631
  }
2643
2632
  value = str.slice(start, stop)
2644
2633
  if (value) { //处理{{ }}插值表达式
2645
- tokens.push(getToken(value, start))
2634
+ tokens.push(getToken(value))
2646
2635
  }
2647
2636
  start = stop + closeTag.length
2648
2637
  } while (1)
@@ -2657,11 +2646,10 @@ function scanExpr(str) {
2657
2646
  return tokens
2658
2647
  }
2659
2648
 
2660
- function scanText(textNode, vmodels, index) {
2661
- var bindings = []
2662
- tokens = scanExpr(textNode.data)
2649
+ function scanText(textNode, vmodels) {
2650
+ var bindings = [], tokens = scanExpr(textNode.data)
2663
2651
  if (tokens.length) {
2664
- for (var i = 0; token = tokens[i++]; ) {
2652
+ for (var i = 0, token; token = tokens[i++]; ) {
2665
2653
  var node = DOC.createTextNode(token.value) //将文本转换为文本节点,并替换原来的文本节点
2666
2654
  if (token.expr) {
2667
2655
  token.value = token.value.replace(roneTime, function () {
@@ -2674,7 +2662,6 @@ function scanText(textNode, vmodels, index) {
2674
2662
  token.type = "html"
2675
2663
  return ""
2676
2664
  })// jshint ignore:line
2677
- token.pos = index * 1000 + i
2678
2665
  bindings.push(token) //收集带有插值表达式的文本
2679
2666
  }
2680
2667
  avalonFragment.appendChild(node)
@@ -3138,7 +3125,6 @@ var TimerID, ribbon = []
3138
3125
  }
3139
3126
 
3140
3127
  var watchValueInTimer = noop
3141
- var rmsinput = /text|password|hidden/
3142
3128
  new function() { // jshint ignore:line
3143
3129
  try { //#272 IE9-IE11, firefox
3144
3130
  var setters = {}
@@ -3146,7 +3132,7 @@ new function() { // jshint ignore:line
3146
3132
  var bproto = HTMLTextAreaElement.prototype
3147
3133
  function newSetter(value) { // jshint ignore:line
3148
3134
  setters[this.tagName].call(this, value)
3149
- if (rmsinput.test(this.type) && !this.msFocus && this.avalonSetter) {
3135
+ if (!this.msFocus && this.avalonSetter && this.oldValue !== value) {
3150
3136
  this.avalonSetter()
3151
3137
  }
3152
3138
  }
@@ -3200,7 +3186,7 @@ duplexBinding.INPUT = function(element, evaluator, data) {
3200
3186
  }
3201
3187
  //当model变化时,它就会改变value的值
3202
3188
  data.handler = function() {
3203
- var val = data.pipe(evaluator(), data, "set") + ""
3189
+ var val = data.pipe(evaluator(), data, "set")
3204
3190
  if (val !== element.oldValue) {
3205
3191
  element.value = element.oldValue = val
3206
3192
  }
@@ -3263,10 +3249,11 @@ duplexBinding.INPUT = function(element, evaluator, data) {
3263
3249
  bound("blur", function() {
3264
3250
  element.msFocus = false
3265
3251
  })
3266
- if (rmsinput.test($type)) {
3267
- watchValueInTimer(function() {
3252
+ if (!/^(file|button|reset|submit|checkbox|radio)$/.test(element.type)) {
3253
+ element.avalonSetter = updateVModel //#765
3254
+ watchValueInTimer(function () {
3268
3255
  if (root.contains(element)) {
3269
- if (!element.msFocus && element.oldValue !== element.value) {
3256
+ if (!element.msFocus && data.oldValue !== element.value) {
3270
3257
  updateVModel()
3271
3258
  }
3272
3259
  } else if (!element.msRetain) {
@@ -3274,11 +3261,9 @@ duplexBinding.INPUT = function(element, evaluator, data) {
3274
3261
  }
3275
3262
  })
3276
3263
  }
3277
-
3278
- element.avalonSetter = updateVModel
3279
3264
  }
3280
3265
 
3281
- element.oldValue = element.value
3266
+
3282
3267
  avalon.injectBinding(data)
3283
3268
  callback.call(element, element.value)
3284
3269
  }
@@ -3490,6 +3475,9 @@ bindingHandlers.repeat = function (data, vmodels) {
3490
3475
  }
3491
3476
  }
3492
3477
 
3478
+ data.handler = noop
3479
+ avalon.injectBinding(data)
3480
+
3493
3481
  var elem = data.element
3494
3482
  if (elem.nodeType === 1) {
3495
3483
  elem.removeAttribute(data.name)
@@ -3558,7 +3546,12 @@ bindingExecutors.repeat = function (method, pos, el) {
3558
3546
 
3559
3547
  if (data.xtype === "array") {
3560
3548
  if (old.length === neo.length) {
3561
- return
3549
+ if (old !== neo && old.length > 0) {
3550
+ bindingExecutors.repeat.call(this, 'clear', pos, el)
3551
+ }
3552
+ else {
3553
+ return
3554
+ }
3562
3555
  }
3563
3556
  method = "add"
3564
3557
  pos = 0
@@ -4066,9 +4059,9 @@ var filters = avalon.filters = {
4066
4059
  $filter: function(val) {
4067
4060
  for (var i = 1, n = arguments.length; i < n; i++) {
4068
4061
  var array = arguments[i]
4069
- var fn = avalon.filters[array.shift()]
4062
+ var fn = avalon.filters[array[0]]
4070
4063
  if (typeof fn === "function") {
4071
- var arr = [val].concat(array)
4064
+ var arr = [val].concat(array.slice(1))
4072
4065
  val = fn.apply(null, arr)
4073
4066
  }
4074
4067
  }
@@ -4384,183 +4377,555 @@ new function () {
4384
4377
  })
4385
4378
  }
4386
4379
 
4387
- new function() {// jshint ignore:line
4388
- var touchProxy = {}
4389
- var IEtouch = navigator.pointerEnabled
4390
- var IEMStouch = navigator.msPointerEnabled
4391
- var ua = navigator.userAgent
4392
- var isAndroid = ua.indexOf("Android") > 0
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是点击,长于它是长按或拖动
4400
- var me = bindingHandlers.on
4401
-
4402
- if (IEtouch) {
4403
- touchNames = ["pointerdown", "pointermove", "pointerup", "pointercancel"]
4404
- }
4405
- if (IEMStouch) {
4406
- touchNames = ["MSPointerDown", "MSPointerMove", "MSPointerUp", "MSPointerCancel"]
4407
- }
4408
- function isPrimaryTouch(event){
4409
- return (event.pointerType === 'touch' || event.pointerType === event.MSPOINTER_TYPE_TOUCH) && event.isPrimary
4410
- }
4411
-
4412
- function isPointerEventType(e, type){
4413
- return (e.type === 'pointer'+type || e.type.toLowerCase() === 'mspointer'+type)
4414
- }
4415
-
4416
- //判定滑动方向
4417
- function swipeDirection(x1, x2, y1, y2) {
4418
- return Math.abs(x1 - x2) >=
4419
- Math.abs(y1 - y2) ? (x1 - x2 > 0 ? "left" : "right") : (y1 - y2 > 0 ? "up" : "down")
4420
- }
4421
-
4422
- function fireEvent(el, name, detail) {
4423
- var event = document.createEvent("Events")
4424
- event.initEvent(name, true, true)
4425
- if (detail) {
4426
- event.detail = detail
4427
- }
4428
- el.dispatchEvent(event)
4429
- }
4430
-
4431
- function onMouse(event) {
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
4380
+ new function () { // jshint ignore:line
4381
+ var ua = navigator.userAgent.toLowerCase()
4382
+ //http://stackoverflow.com/questions/9038625/detect-if-device-is-ios
4383
+ function iOSversion() {
4384
+ //https://developer.apple.com/library/prerelease/mac/releasenotes/General/WhatsNewInSafari/Articles/Safari_9.html
4385
+ //http://mp.weixin.qq.com/s?__biz=MzA3MDQ4MzQzMg==&mid=256900619&idx=1&sn=b29f84cff0b8d7b9742e5d8b3cd8f218&scene=1&srcid=1009F9l4gh9nZ7rcQJEhmf7Q#rd
4386
+ if (/iPad|iPhone|iPod/i.test(ua) && !window.MSStream) {
4387
+ if ("backdropFilter" in document.documentElement.style) {
4388
+ return 9
4442
4389
  }
4443
- if (event.stopImmediatePropagation) {
4444
- event.stopImmediatePropagation()
4445
- } else {
4446
- event.propagationStopped = true
4447
- }
4448
- event.stopPropagation()
4449
- event.preventDefault()
4450
- }
4451
- }
4452
- function cancelLongTap() {
4453
- if (longTapTimeout) clearTimeout(longTapTimeout)
4454
- longTapTimeout = null
4455
- }
4456
- function touchstart(event) {
4457
- var _isPointerType = isPointerEventType(event, "down"),
4458
- firstTouch = _isPointerType ? event : event.touches[0],
4459
- element = "tagName" in firstTouch.target ? firstTouch.target: firstTouch.target.parentNode,
4460
- now = Date.now(),
4461
- delta = now - (touchProxy.last || now)
4462
-
4463
- if (_isPointerType && !isPrimaryTouch(event)) return
4464
- if (touchProxy.x1 || touchProxy.y1) {
4465
- touchProxy.x1 = undefined
4466
- touchProxy.y1 = undefined
4467
- }
4468
- if (delta > 0 && delta <= 250) {
4469
- touchProxy.isDoubleTap = true
4470
- }
4471
- touchProxy.x = firstTouch.pageX
4472
- touchProxy.y = firstTouch.pageY
4473
- touchProxy.mx = 0
4474
- touchProxy.my = 0
4475
- touchProxy.last = now
4476
- touchProxy.element = element
4477
-
4478
- longTapTimeout = setTimeout(function() {
4479
- longTapTimeout = null
4480
- fireEvent(element, "hold")
4481
- fireEvent(element, "longtap")
4482
- touchProxy = {}
4483
- }, clickDuration)
4484
- return true
4390
+ if (!!window.indexedDB) {
4391
+ return 8
4392
+ }
4393
+ if (!!window.SpeechSynthesisUtterance) {
4394
+ return 7
4395
+ }
4396
+ if (!!window.webkitAudioContext) {
4397
+ return 6
4398
+ }
4399
+ if (!!window.matchMedia) {
4400
+ return 5
4401
+ }
4402
+ if (!!window.history && 'pushState' in window.history) {
4403
+ return 4
4404
+ }
4405
+ return 3
4406
+ }
4407
+ return NaN
4485
4408
  }
4486
- function touchmove(event) {
4487
-
4488
- var _isPointerType = isPointerEventType(event, 'down'),
4489
- firstTouch = _isPointerType ? event : event.touches[0],
4490
- x = firstTouch.pageX,
4491
- y = firstTouch.pageY
4492
- if (_isPointerType && !isPrimaryTouch(event)) return
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
- }
4500
- cancelLongTap()
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)
4506
- }
4507
- function touchend(event) {
4508
- var _isPointerType = isPointerEventType(event, 'down'),
4509
- element = touchProxy.element
4510
-
4511
- if (_isPointerType && !isPrimaryTouch(event)) return
4512
- if (!element) return // longtap|hold触发后touchProxy为{}
4513
-
4514
- cancelLongTap()
4515
- if ((touchProxy.x1 && Math.abs(touchProxy.x1 - touchProxy.x) > dragDistance) || (touchProxy.y1 && Math.abs(touchProxy.y1 - touchProxy.y) > dragDistance)) {
4516
- //如果用户滑动的距离有点大,就认为是swipe事件
4517
- var direction = swipeDirection(touchProxy.x, touchProxy.x1, touchProxy.y, touchProxy.y1)
4518
- var details = {
4519
- direction: direction
4520
- }
4521
- fireEvent(element, "swipe", details)
4522
- fireEvent(element, "swipe" + direction, details)
4523
- touchProxy = {}
4524
- } else {
4525
- if (touchProxy.mx < dragDistance && touchProxy.my < dragDistance) {
4526
- fireEvent(element, 'tap')
4527
- if (touchProxy.isDoubleTap) {
4528
- fireEvent(element, "doubletap")
4529
- touchProxy = {}
4530
- touchProxy.element = element
4531
- } else {
4532
- touchTimeout = setTimeout(function() {
4533
- clearTimeout(touchTimeout)
4534
- touchTimeout = null
4535
- if (touchProxy.element) fireEvent(touchProxy.element, "singletap")
4536
- touchProxy = {};
4537
- touchProxy.element = element
4538
- }, 250)
4409
+
4410
+ var deviceIsAndroid = ua.indexOf('android') > 0
4411
+ var deviceIsIOS = iOSversion()
4412
+ var gestureHooks = avalon.gestureHooks = {
4413
+ pointers: {},
4414
+ start: function (event, callback) {
4415
+
4416
+ //touches是当前屏幕上所有触摸点的列表;
4417
+ //targetTouches是当前对象上所有触摸点的列表;
4418
+ //changedTouches是涉及当前事件的触摸点的列表。
4419
+ for (var i = 0; i < event.changedTouches.length; i++) {
4420
+ var touch = event.changedTouches[i]
4421
+ var pointer = {
4422
+ startTouch: mixTouchAttr({}, touch),
4423
+ startTime: Date.now(),
4424
+ status: 'tapping',
4425
+ element: event.target
4426
+ }
4427
+ gestureHooks.pointers[touch.identifier] = pointer;
4428
+ callback(pointer, touch)
4429
+
4430
+ }
4431
+ },
4432
+ move: function (event, callback) {
4433
+ for (var i = 0; i < event.changedTouches.length; i++) {
4434
+ var touch = event.changedTouches[i]
4435
+ var pointer = gestureHooks.pointers[touch.identifier]
4436
+ if (!pointer) {
4437
+ return
4438
+ }
4439
+
4440
+ if (!("lastTouch" in pointer)) {
4441
+ pointer.lastTouch = pointer.startTouch
4442
+ pointer.lastTime = pointer.startTime
4443
+ pointer.deltaX = pointer.deltaY = pointer.duration = pointer.distance = 0
4444
+ }
4445
+
4446
+ var time = Date.now() - pointer.lastTime
4447
+
4448
+ if (time > 0) {
4449
+
4450
+ var RECORD_DURATION = 70
4451
+ if (time > RECORD_DURATION) {
4452
+ time = RECORD_DURATION
4453
+ }
4454
+ if (pointer.duration + time > RECORD_DURATION) {
4455
+ pointer.duration = RECORD_DURATION - time
4456
+ }
4457
+
4458
+
4459
+ pointer.duration += time;
4460
+ pointer.lastTouch = mixTouchAttr({}, touch)
4461
+
4462
+ pointer.lastTime = Date.now()
4463
+
4464
+ pointer.deltaX = touch.clientX - pointer.startTouch.clientX
4465
+ pointer.deltaY = touch.clientY - pointer.startTouch.clientY
4466
+ var x = pointer.deltaX * pointer.deltaX
4467
+ var y = pointer.deltaY * pointer.deltaY
4468
+ pointer.distance = Math.sqrt(x + y)
4469
+ pointer.isVertical = x < y
4470
+
4471
+ callback(pointer, touch)
4472
+ }
4473
+ }
4474
+ },
4475
+ end: function (event, callback) {
4476
+ for (var i = 0; i < event.changedTouches.length; i++) {
4477
+ var touch = event.changedTouches[i],
4478
+ id = touch.identifier,
4479
+ pointer = gestureHooks.pointers[id]
4480
+
4481
+ if (!pointer)
4482
+ continue
4483
+
4484
+ callback(pointer, touch)
4485
+
4486
+ delete gestureHooks.pointers[id]
4487
+ }
4488
+ },
4489
+ fire: function (elem, type, props) {
4490
+ if (elem) {
4491
+ var event = document.createEvent('Events')
4492
+ event.initEvent(type, true, true)
4493
+ avalon.mix(event, props)
4494
+ elem.dispatchEvent(event)
4495
+ }
4496
+ },
4497
+ add: function (name, gesture) {
4498
+ function move(event) {
4499
+ gesture.touchmove(event)
4500
+ }
4501
+
4502
+ function end(event) {
4503
+ gesture.touchend(event)
4504
+
4505
+ document.removeEventListener('touchmove', move)
4506
+
4507
+ document.removeEventListener('touchend', end)
4508
+
4509
+ document.removeEventListener('touchcancel', cancel)
4510
+
4511
+ }
4512
+
4513
+ function cancel(event) {
4514
+ gesture.touchcancel(event)
4515
+
4516
+ document.removeEventListener('touchmove', move)
4517
+
4518
+ document.removeEventListener('touchend', end)
4519
+
4520
+ document.removeEventListener('touchcancel', cancel)
4521
+
4522
+ }
4523
+
4524
+ gesture.events.forEach(function (eventName) {
4525
+ avalon.eventHooks[eventName] = {
4526
+ fn: function (el, fn) {
4527
+ if (!el['touch-' + name]) {
4528
+ el['touch-' + name] = '1'
4529
+ el.addEventListener('touchstart', function (event) {
4530
+ gesture.touchstart(event)
4531
+
4532
+ document.addEventListener('touchmove', move)
4533
+
4534
+ document.addEventListener('touchend', end)
4535
+
4536
+ document.addEventListener('touchcancel', cancel)
4537
+
4538
+ })
4539
+ }
4540
+ return fn
4541
+ }
4539
4542
  }
4543
+ })
4544
+ }
4545
+ }
4546
+
4547
+
4548
+
4549
+ var touchkeys = ['screenX', 'screenY', 'clientX', 'clientY', 'pageX', 'pageY']
4550
+
4551
+ // 复制 touch 对象上的有用属性到固定对象上
4552
+ function mixTouchAttr(target, source) {
4553
+ if (source) {
4554
+ touchkeys.forEach(function (key) {
4555
+ target[key] = source[key]
4556
+ })
4557
+ }
4558
+ return target
4559
+ }
4560
+
4561
+ var supportPointer = !!navigator.pointerEnabled || !!navigator.msPointerEnabled
4562
+
4563
+ if (supportPointer) { // 支持pointer的设备可用样式来取消click事件的300毫秒延迟
4564
+ root.style.msTouchAction = root.style.touchAction = 'none'
4565
+ }
4566
+ var tapGesture = {
4567
+ events: ['tap'],
4568
+ touchBoundary: 10,
4569
+ tapDelay: 200,
4570
+ needClick: function (target) {
4571
+ //判定是否使用原生的点击事件, 否则使用sendClick方法手动触发一个人工的点击事件
4572
+ switch (target.nodeName.toLowerCase()) {
4573
+ case 'button':
4574
+ case 'select':
4575
+ case 'textarea':
4576
+ if (target.disabled) {
4577
+ return true
4578
+ }
4579
+
4580
+ break;
4581
+ case 'input':
4582
+ // IOS6 pad 上选择文件,如果不是原生的click,弹出的选择界面尺寸错误
4583
+ if ((deviceIsIOS && target.type === 'file') || target.disabled) {
4584
+ return true
4585
+ }
4586
+
4587
+ break;
4588
+ case 'label':
4589
+ case 'iframe':
4590
+ case 'video':
4591
+ return true
4592
+ }
4593
+
4594
+ return false
4595
+ },
4596
+ needFocus: function (target) {
4597
+ switch (target.nodeName.toLowerCase()) {
4598
+ case 'textarea':
4599
+ case 'select': //实测android下select也需要
4600
+ return true;
4601
+ case 'input':
4602
+ switch (target.type) {
4603
+ case 'button':
4604
+ case 'checkbox':
4605
+ case 'file':
4606
+ case 'image':
4607
+ case 'radio':
4608
+ case 'submit':
4609
+ return false
4610
+ }
4611
+ //如果是只读或disabled状态,就无须获得焦点了
4612
+ return !target.disabled && !target.readOnly
4613
+ default:
4614
+ return false
4615
+ }
4616
+ },
4617
+ focus: function (targetElement) {
4618
+ var length;
4619
+ //在iOS7下, 对一些新表单元素(如date, datetime, time, month)调用focus方法会抛错,
4620
+ //幸好的是,我们可以改用setSelectionRange获取焦点, 将光标挪到文字的最后
4621
+ var type = targetElement.type
4622
+ if (deviceIsIOS && targetElement.setSelectionRange &&
4623
+ type.indexOf('date') !== 0 && type !== 'time' && type !== 'month') {
4624
+ length = targetElement.value.length
4625
+ targetElement.setSelectionRange(length, length)
4540
4626
  } else {
4541
- touchProxy = {}
4627
+ targetElement.focus()
4542
4628
  }
4629
+ },
4630
+ findControl: function (labelElement) {
4631
+ // 获取label元素所对应的表单元素
4632
+ // 可以能过control属性, getElementById, 或用querySelector直接找其内部第一表单元素实现
4633
+ if (labelElement.control !== undefined) {
4634
+ return labelElement.control
4635
+ }
4636
+
4637
+ if (labelElement.htmlFor) {
4638
+ return document.getElementById(labelElement.htmlFor)
4639
+ }
4640
+
4641
+ return labelElement.querySelector('button, input:not([type=hidden]), keygen, meter, output, progress, select, textarea')
4642
+ },
4643
+ fixTarget: function (target) {
4644
+ if (target.nodeType === 3) {
4645
+ return target.parentNode
4646
+ }
4647
+ if (window.SVGElementInstance && (target instanceof SVGElementInstance)) {
4648
+ return target.correspondingUseElement;
4649
+ }
4650
+
4651
+ return target
4652
+ },
4653
+ updateScrollParent: function (targetElement) {
4654
+ //如果事件源元素位于某一个有滚动条的祖父元素中,那么保持其scrollParent与scrollTop值
4655
+ var scrollParent = targetElement.tapScrollParent
4656
+
4657
+ if (!scrollParent || !scrollParent.contains(targetElement)) {
4658
+ var parentElement = targetElement
4659
+ do {
4660
+ if (parentElement.scrollHeight > parentElement.offsetHeight) {
4661
+ scrollParent = parentElement
4662
+ targetElement.tapScrollParent = parentElement
4663
+ break
4664
+ }
4665
+
4666
+ parentElement = parentElement.parentElement
4667
+ } while (parentElement)
4668
+ }
4669
+
4670
+ if (scrollParent) {
4671
+ scrollParent.lastScrollTop = scrollParent.scrollTop
4672
+ }
4673
+ },
4674
+ touchHasMoved: function (event) {
4675
+ // 判定是否发生移动,其阀值是10px
4676
+ var touch = event.changedTouches[0],
4677
+ boundary = tapGesture.touchBoundary
4678
+ return Math.abs(touch.pageX - tapGesture.touchStartX) > boundary ||
4679
+ Math.abs(touch.pageY - tapGesture.touchStartY) > boundary
4680
+
4681
+ },
4682
+ findType: function (targetElement) {
4683
+ // 安卓chrome浏览器上,模拟的 click 事件不能让 select 打开,故使用 mousedown 事件
4684
+ return deviceIsAndroid && targetElement.tagName.toLowerCase() === 'select' ?
4685
+ 'mousedown' : 'click'
4686
+ },
4687
+ sendClick: function (targetElement, event) {
4688
+ // 在click之前触发tap事件
4689
+ gestureHooks.fire(targetElement, 'tap', {
4690
+ touchEvent: event
4691
+ })
4692
+ var clickEvent, touch
4693
+ //某些安卓设备必须先移除焦点,之后模拟的click事件才能让新元素获取焦点
4694
+ if (document.activeElement && document.activeElement !== targetElement) {
4695
+ document.activeElement.blur()
4696
+ }
4697
+
4698
+ touch = event.changedTouches[0]
4699
+ // 手动触发点击事件,此时必须使用document.createEvent('MouseEvents')来创建事件
4700
+ // 及使用initMouseEvent来初始化它
4701
+ clickEvent = document.createEvent('MouseEvents')
4702
+ clickEvent.initMouseEvent(tapGesture.findType(targetElement), true, true, window, 1, touch.screenX,
4703
+ touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null)
4704
+ clickEvent.touchEvent = event
4705
+ targetElement.dispatchEvent(clickEvent)
4706
+ },
4707
+ touchstart: function (event) {
4708
+ //忽略多点触摸
4709
+ if (event.targetTouches.length !== 1) {
4710
+ return true
4711
+ }
4712
+ //修正事件源对象
4713
+ var targetElement = tapGesture.fixTarget(event.target)
4714
+ var touch = event.targetTouches[0]
4715
+ if (deviceIsIOS) {
4716
+ // 判断是否是点击文字,进行选择等操作,如果是,不需要模拟click
4717
+ var selection = window.getSelection();
4718
+ if (selection.rangeCount && !selection.isCollapsed) {
4719
+ return true
4720
+ }
4721
+ var id = touch.identifier
4722
+ //当 alert 或 confirm 时,点击其他地方,会触发touch事件,identifier相同,此事件应该被忽略
4723
+ if (id && isFinite(tapGesture.lastTouchIdentifier) && tapGesture.lastTouchIdentifier === id) {
4724
+ event.preventDefault()
4725
+ return false
4726
+ }
4727
+
4728
+ tapGesture.lastTouchIdentifier = id
4729
+
4730
+ tapGesture.updateScrollParent(targetElement)
4731
+ }
4732
+ //收集触摸点的信息
4733
+ tapGesture.status = "tapping"
4734
+ tapGesture.startTime = Date.now()
4735
+ tapGesture.element = targetElement
4736
+ tapGesture.pageX = touch.pageX
4737
+ tapGesture.pageY = touch.pageY
4738
+ // 如果点击太快,阻止双击带来的放大收缩行为
4739
+ if ((tapGesture.startTime - tapGesture.lastTime) < tapGesture.tapDelay) {
4740
+ event.preventDefault()
4741
+ }
4742
+ },
4743
+ touchmove: function (event) {
4744
+ if (tapGesture.status !== "tapping") {
4745
+ return true
4746
+ }
4747
+ // 如果事件源元素发生改变,或者发生了移动,那么就取消触发点击事件
4748
+ if (tapGesture.element !== tapGesture.fixTarget(event.target) ||
4749
+ tapGesture.touchHasMoved(event)) {
4750
+ tapGesture.status = tapGesture.element = 0
4751
+ }
4752
+
4753
+ },
4754
+ touchend: function (event) {
4755
+ var targetElement = tapGesture.element
4756
+ var now = Date.now()
4757
+ //如果是touchstart与touchend相隔太久,可以认为是长按,那么就直接返回
4758
+ //或者是在touchstart, touchmove阶段,判定其不该触发点击事件,也直接返回
4759
+ if (!targetElement || now - tapGesture.startTime > tapGesture.tapDelay) {
4760
+ return true
4761
+ }
4762
+
4763
+
4764
+ tapGesture.lastTime = now
4765
+
4766
+ var startTime = tapGesture.startTime
4767
+ tapGesture.status = tapGesture.startTime = 0
4768
+
4769
+ targetTagName = targetElement.tagName.toLowerCase()
4770
+ if (targetTagName === 'label') {
4771
+ //尝试触发label上可能绑定的tap事件
4772
+ gestureHooks.fire(targetElement, 'tap', {
4773
+ touchEvent: event
4774
+ })
4775
+ var forElement = tapGesture.findControl(targetElement)
4776
+ if (forElement) {
4777
+ tapGesture.focus(targetElement)
4778
+ targetElement = forElement
4779
+ }
4780
+ } else if (tapGesture.needFocus(targetElement)) {
4781
+ // 如果元素从touchstart到touchend经历时间过长,那么不应该触发点击事
4782
+ // 或者此元素是iframe中的input元素,那么它也无法获点焦点
4783
+ if ((now - startTime) > 100 || (deviceIsIOS && window.top !== window && targetTagName === 'input')) {
4784
+ tapGesture.element = 0
4785
+ return false
4786
+ }
4787
+
4788
+ tapGesture.focus(targetElement)
4789
+ deviceIsAndroid && tapGesture.sendClick(targetElement, event)
4790
+
4791
+ return false
4792
+ }
4793
+
4794
+ if (deviceIsIOS) {
4795
+ //如果它的父容器的滚动条发生改变,那么应该识别为划动或拖动事件,不应该触发点击事件
4796
+ var scrollParent = targetElement.tapScrollParent;
4797
+ if (scrollParent && scrollParent.lastScrollTop !== scrollParent.scrollTop) {
4798
+ return true
4799
+ }
4800
+ }
4801
+ //如果这不是一个需要使用原生click的元素,则屏蔽原生事件,避免触发两次click
4802
+ if (!tapGesture.needClick(targetElement)) {
4803
+ event.preventDefault()
4804
+ // 触发一次模拟的click
4805
+ tapGesture.sendClick(targetElement, event)
4806
+ }
4807
+ },
4808
+ touchcancel: function () {
4809
+ tapGesture.startTime = tapGesture.element = 0
4543
4810
  }
4544
4811
  }
4545
- document.addEventListener('mousedown', onMouse, true)
4546
- document.addEventListener('click', onMouse, true)
4547
- document.addEventListener(touchNames[0], touchstart)
4548
- document.addEventListener(touchNames[1], touchmove)
4549
- document.addEventListener(touchNames[2], touchend)
4550
- if (touchNames[3]) {
4551
- document.addEventListener(touchNames[3], function(event) {
4552
- if (longTapTimeout) clearTimeout(longTapTimeout)
4553
- if (touchTimeout) clearTimeout(touchTimeout)
4554
- longTapTimeout = touchTimeout = null
4555
- touchProxy = {}
4556
- })
4812
+
4813
+ gestureHooks.add("tap", tapGesture)
4814
+
4815
+ var pressGesture = {
4816
+ events: ['longtap', 'doubletap'],
4817
+ cancelPress: function (pointer) {
4818
+ clearTimeout(pointer.pressingHandler)
4819
+ pointer.pressingHandler = null
4820
+ },
4821
+ touchstart: function (event) {
4822
+ gestureHooks.start(event, function (pointer, touch) {
4823
+ pointer.pressingHandler = setTimeout(function () {
4824
+ if (pointer.status === 'tapping') {
4825
+ gestureHooks.fire(event.target, 'longtap', {
4826
+ touch: touch,
4827
+ touchEvent: event
4828
+ })
4829
+ }
4830
+ pressGesture.cancelPress(pointer)
4831
+ }, 500)
4832
+ if (event.changedTouches.length !== 1) {
4833
+ pointer.status = 0
4834
+ }
4835
+ })
4836
+
4837
+ },
4838
+ touchmove: function (event) {
4839
+ gestureHooks.move(event, function (pointer) {
4840
+ if (pointer.distance > 10 && pointer.pressingHandler) {
4841
+ pressGesture.cancelPress(pointer)
4842
+ if (pointer.status === 'tapping') {
4843
+ pointer.status = 'panning'
4844
+ }
4845
+ }
4846
+ })
4847
+ },
4848
+ touchend: function (event) {
4849
+ gestureHooks.end(event, function (pointer, touch) {
4850
+ pressGesture.cancelPress(pointer)
4851
+ if (pointer.status === 'tapping') {
4852
+ pointer.lastTime = Date.now()
4853
+ if (pressGesture.lastTap && pointer.lastTime - pressGesture.lastTap.lastTime < 300) {
4854
+ gestureHooks.fire(pointer.element, 'doubletap', {
4855
+ touch: touch,
4856
+ touchEvent: event
4857
+ })
4858
+ }
4859
+
4860
+ pressGesture.lastTap = pointer
4861
+ }
4862
+ })
4863
+
4864
+ },
4865
+ touchcancel: function (event) {
4866
+ gestureHooks.end(event, function (pointer) {
4867
+ pressGesture.cancelPress(pointer)
4868
+ })
4869
+ }
4557
4870
  }
4558
- ["swipe", "swipeleft", "swiperight", "swipeup", "swipedown", "doubletap", "tap", "singletap", "dblclick", "longtap", "hold"].forEach(function(method) {
4559
- me[method + "Hook"] = me["clickHook"]
4560
- })
4871
+ gestureHooks.add('press', pressGesture)
4872
+
4873
+ var swipeGesture = {
4874
+ events: ['swipe', 'swipeleft', 'swiperight', 'swipeup', 'swipedown'],
4875
+ getAngle: function (x, y) {
4876
+ var r = Math.atan2(y, x) //radians
4877
+ var angle = Math.round(r * 180 / Math.PI) //degrees
4878
+ return angle < 0 ? 360 - Math.abs(angle) : angle
4879
+ },
4880
+ getDirection: function (x, y) {
4881
+ var angle = swipeGesture.getAngle(x, y)
4882
+ if ((angle <= 45) && (angle >= 0)) {
4883
+ return "left"
4884
+ } else if ((angle <= 360) && (angle >= 315)) {
4885
+ return "left"
4886
+ } else if ((angle >= 135) && (angle <= 225)) {
4887
+ return "right"
4888
+ } else if ((angle > 45) && (angle < 135)) {
4889
+ return "down"
4890
+ } else {
4891
+ return "up"
4892
+ }
4893
+ },
4894
+ touchstart: function (event) {
4895
+ gestureHooks.start(event, noop)
4896
+ },
4897
+ touchmove: function (event) {
4898
+ gestureHooks.move(event, noop)
4899
+ },
4900
+ touchend: function (event) {
4901
+ if (event.changedTouches.length !== 1) {
4902
+ return
4903
+ }
4904
+ gestureHooks.end(event, function (pointer, touch) {
4905
+ var isflick = (pointer.distance > 30 && pointer.distance / pointer.duration > 0.65)
4906
+ if (isflick) {
4907
+ var extra = {
4908
+ deltaX: pointer.deltaX,
4909
+ deltaY: pointer.deltaY,
4910
+ touch: touch,
4911
+ touchEvent: event,
4912
+ direction: swipeGesture.getDirection(pointer.deltaX, pointer.deltaY),
4913
+ isVertical: pointer.isVertical
4914
+ }
4915
+ var target = pointer.element
4916
+ gestureHooks.fire(target, 'swipe', extra)
4917
+ gestureHooks.fire(target, 'swipe' + extra.direction, extra)
4918
+ }
4919
+ })
4920
+ }
4921
+ }
4922
+
4923
+ swipeGesture.touchcancel = swipeGesture.touchend
4924
+ gestureHooks.add('swipe', swipeGesture)
4561
4925
 
4562
4926
  //各种摸屏事件的示意图 http://quojs.tapquo.com/ http://touch.code.baidu.com/
4563
- }// jshint ignore:line
4927
+ } // jshint ignore:line
4928
+
4564
4929
 
4565
4930
  // Register as a named AMD module, since avalon can be concatenated with other
4566
4931
  // files that may use define, but not via a proper concatenation script that