avalon-rails 1.4.6.0.20150915133100 → 1.4.7

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.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