Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
15747 anikendra 1
/*!
2
 * Copyright 2014 Drifty Co.
3
 * http://drifty.com/
4
 *
5
 * Ionic, v1.0.0
6
 * A powerful HTML5 mobile app framework.
7
 * http://ionicframework.com/
8
 *
9
 * By @maxlynch, @benjsperry, @adamdbradley <3
10
 *
11
 * Licensed under the MIT license. Please see LICENSE for more information.
12
 *
13
 */
14
 
15
(function() {
16
 
17
// Create global ionic obj and its namespaces
18
// build processes may have already created an ionic obj
19
window.ionic = window.ionic || {};
20
window.ionic.views = {};
21
window.ionic.version = '1.0.0';
22
 
23
(function (ionic) {
24
 
25
  ionic.DelegateService = function(methodNames) {
26
 
27
    if (methodNames.indexOf('$getByHandle') > -1) {
28
      throw new Error("Method '$getByHandle' is implicitly added to each delegate service. Do not list it as a method.");
29
    }
30
 
31
    function trueFn() { return true; }
32
 
33
    return ['$log', function($log) {
34
 
35
      /*
36
       * Creates a new object that will have all the methodNames given,
37
       * and call them on the given the controller instance matching given
38
       * handle.
39
       * The reason we don't just let $getByHandle return the controller instance
40
       * itself is that the controller instance might not exist yet.
41
       *
42
       * We want people to be able to do
43
       * `var instance = $ionicScrollDelegate.$getByHandle('foo')` on controller
44
       * instantiation, but on controller instantiation a child directive
45
       * may not have been compiled yet!
46
       *
47
       * So this is our way of solving this problem: we create an object
48
       * that will only try to fetch the controller with given handle
49
       * once the methods are actually called.
50
       */
51
      function DelegateInstance(instances, handle) {
52
        this._instances = instances;
53
        this.handle = handle;
54
      }
55
      methodNames.forEach(function(methodName) {
56
        DelegateInstance.prototype[methodName] = instanceMethodCaller(methodName);
57
      });
58
 
59
 
60
      /**
61
       * The delegate service (eg $ionicNavBarDelegate) is just an instance
62
       * with a non-defined handle, a couple extra methods for registering
63
       * and narrowing down to a specific handle.
64
       */
65
      function DelegateService() {
66
        this._instances = [];
67
      }
68
      DelegateService.prototype = DelegateInstance.prototype;
69
      DelegateService.prototype._registerInstance = function(instance, handle, filterFn) {
70
        var instances = this._instances;
71
        instance.$$delegateHandle = handle;
72
        instance.$$filterFn = filterFn || trueFn;
73
        instances.push(instance);
74
 
75
        return function deregister() {
76
          var index = instances.indexOf(instance);
77
          if (index !== -1) {
78
            instances.splice(index, 1);
79
          }
80
        };
81
      };
82
      DelegateService.prototype.$getByHandle = function(handle) {
83
        return new DelegateInstance(this._instances, handle);
84
      };
85
 
86
      return new DelegateService();
87
 
88
      function instanceMethodCaller(methodName) {
89
        return function caller() {
90
          var handle = this.handle;
91
          var args = arguments;
92
          var foundInstancesCount = 0;
93
          var returnValue;
94
 
95
          this._instances.forEach(function(instance) {
96
            if ((!handle || handle == instance.$$delegateHandle) && instance.$$filterFn(instance)) {
97
              foundInstancesCount++;
98
              var ret = instance[methodName].apply(instance, args);
99
              //Only return the value from the first call
100
              if (foundInstancesCount === 1) {
101
                returnValue = ret;
102
              }
103
            }
104
          });
105
 
106
          if (!foundInstancesCount && handle) {
107
            return $log.warn(
108
              'Delegate for handle "' + handle + '" could not find a ' +
109
              'corresponding element with delegate-handle="' + handle + '"! ' +
110
              methodName + '() was not called!\n' +
111
              'Possible cause: If you are calling ' + methodName + '() immediately, and ' +
112
              'your element with delegate-handle="' + handle + '" is a child of your ' +
113
              'controller, then your element may not be compiled yet. Put a $timeout ' +
114
              'around your call to ' + methodName + '() and try again.'
115
            );
116
          }
117
          return returnValue;
118
        };
119
      }
120
 
121
    }];
122
  };
123
 
124
})(window.ionic);
125
 
126
(function(window, document, ionic) {
127
 
128
  var readyCallbacks = [];
129
  var isDomReady = document.readyState === 'complete' || document.readyState === 'interactive';
130
 
131
  function domReady() {
132
    isDomReady = true;
133
    for (var x = 0; x < readyCallbacks.length; x++) {
134
      ionic.requestAnimationFrame(readyCallbacks[x]);
135
    }
136
    readyCallbacks = [];
137
    document.removeEventListener('DOMContentLoaded', domReady);
138
  }
139
  if (!isDomReady) {
140
    document.addEventListener('DOMContentLoaded', domReady);
141
  }
142
 
143
 
144
  // From the man himself, Mr. Paul Irish.
145
  // The requestAnimationFrame polyfill
146
  // Put it on window just to preserve its context
147
  // without having to use .call
148
  window._rAF = (function() {
149
    return window.requestAnimationFrame ||
150
           window.webkitRequestAnimationFrame ||
151
           window.mozRequestAnimationFrame ||
152
           function(callback) {
153
             window.setTimeout(callback, 16);
154
           };
155
  })();
156
 
157
  var cancelAnimationFrame = window.cancelAnimationFrame ||
158
    window.webkitCancelAnimationFrame ||
159
    window.mozCancelAnimationFrame ||
160
    window.webkitCancelRequestAnimationFrame;
161
 
162
  /**
163
  * @ngdoc utility
164
  * @name ionic.DomUtil
165
  * @module ionic
166
  */
167
  ionic.DomUtil = {
168
    //Call with proper context
169
    /**
170
     * @ngdoc method
171
     * @name ionic.DomUtil#requestAnimationFrame
172
     * @alias ionic.requestAnimationFrame
173
     * @description Calls [requestAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame), or a polyfill if not available.
174
     * @param {function} callback The function to call when the next frame
175
     * happens.
176
     */
177
    requestAnimationFrame: function(cb) {
178
      return window._rAF(cb);
179
    },
180
 
181
    cancelAnimationFrame: function(requestId) {
182
      cancelAnimationFrame(requestId);
183
    },
184
 
185
    /**
186
     * @ngdoc method
187
     * @name ionic.DomUtil#animationFrameThrottle
188
     * @alias ionic.animationFrameThrottle
189
     * @description
190
     * When given a callback, if that callback is called 100 times between
191
     * animation frames, adding Throttle will make it only run the last of
192
     * the 100 calls.
193
     *
194
     * @param {function} callback a function which will be throttled to
195
     * requestAnimationFrame
196
     * @returns {function} A function which will then call the passed in callback.
197
     * The passed in callback will receive the context the returned function is
198
     * called with.
199
     */
200
    animationFrameThrottle: function(cb) {
201
      var args, isQueued, context;
202
      return function() {
203
        args = arguments;
204
        context = this;
205
        if (!isQueued) {
206
          isQueued = true;
207
          ionic.requestAnimationFrame(function() {
208
            cb.apply(context, args);
209
            isQueued = false;
210
          });
211
        }
212
      };
213
    },
214
 
215
    contains: function(parentNode, otherNode) {
216
      var current = otherNode;
217
      while (current) {
218
        if (current === parentNode) return true;
219
        current = current.parentNode;
220
      }
221
    },
222
 
223
    /**
224
     * @ngdoc method
225
     * @name ionic.DomUtil#getPositionInParent
226
     * @description
227
     * Find an element's scroll offset within its container.
228
     * @param {DOMElement} element The element to find the offset of.
229
     * @returns {object} A position object with the following properties:
230
     *   - `{number}` `left` The left offset of the element.
231
     *   - `{number}` `top` The top offset of the element.
232
     */
233
    getPositionInParent: function(el) {
234
      return {
235
        left: el.offsetLeft,
236
        top: el.offsetTop
237
      };
238
    },
239
 
240
    /**
241
     * @ngdoc method
242
     * @name ionic.DomUtil#ready
243
     * @description
244
     * Call a function when the DOM is ready, or if it is already ready
245
     * call the function immediately.
246
     * @param {function} callback The function to be called.
247
     */
248
    ready: function(cb) {
249
      if (isDomReady) {
250
        ionic.requestAnimationFrame(cb);
251
      } else {
252
        readyCallbacks.push(cb);
253
      }
254
    },
255
 
256
    /**
257
     * @ngdoc method
258
     * @name ionic.DomUtil#getTextBounds
259
     * @description
260
     * Get a rect representing the bounds of the given textNode.
261
     * @param {DOMElement} textNode The textNode to find the bounds of.
262
     * @returns {object} An object representing the bounds of the node. Properties:
263
     *   - `{number}` `left` The left position of the textNode.
264
     *   - `{number}` `right` The right position of the textNode.
265
     *   - `{number}` `top` The top position of the textNode.
266
     *   - `{number}` `bottom` The bottom position of the textNode.
267
     *   - `{number}` `width` The width of the textNode.
268
     *   - `{number}` `height` The height of the textNode.
269
     */
270
    getTextBounds: function(textNode) {
271
      if (document.createRange) {
272
        var range = document.createRange();
273
        range.selectNodeContents(textNode);
274
        if (range.getBoundingClientRect) {
275
          var rect = range.getBoundingClientRect();
276
          if (rect) {
277
            var sx = window.scrollX;
278
            var sy = window.scrollY;
279
 
280
            return {
281
              top: rect.top + sy,
282
              left: rect.left + sx,
283
              right: rect.left + sx + rect.width,
284
              bottom: rect.top + sy + rect.height,
285
              width: rect.width,
286
              height: rect.height
287
            };
288
          }
289
        }
290
      }
291
      return null;
292
    },
293
 
294
    /**
295
     * @ngdoc method
296
     * @name ionic.DomUtil#getChildIndex
297
     * @description
298
     * Get the first index of a child node within the given element of the
299
     * specified type.
300
     * @param {DOMElement} element The element to find the index of.
301
     * @param {string} type The nodeName to match children of element against.
302
     * @returns {number} The index, or -1, of a child with nodeName matching type.
303
     */
304
    getChildIndex: function(element, type) {
305
      if (type) {
306
        var ch = element.parentNode.children;
307
        var c;
308
        for (var i = 0, k = 0, j = ch.length; i < j; i++) {
309
          c = ch[i];
310
          if (c.nodeName && c.nodeName.toLowerCase() == type) {
311
            if (c == element) {
312
              return k;
313
            }
314
            k++;
315
          }
316
        }
317
      }
318
      return Array.prototype.slice.call(element.parentNode.children).indexOf(element);
319
    },
320
 
321
    /**
322
     * @private
323
     */
324
    swapNodes: function(src, dest) {
325
      dest.parentNode.insertBefore(src, dest);
326
    },
327
 
328
    elementIsDescendant: function(el, parent, stopAt) {
329
      var current = el;
330
      do {
331
        if (current === parent) return true;
332
        current = current.parentNode;
333
      } while (current && current !== stopAt);
334
      return false;
335
    },
336
 
337
    /**
338
     * @ngdoc method
339
     * @name ionic.DomUtil#getParentWithClass
340
     * @param {DOMElement} element
341
     * @param {string} className
342
     * @returns {DOMElement} The closest parent of element matching the
343
     * className, or null.
344
     */
345
    getParentWithClass: function(e, className, depth) {
346
      depth = depth || 10;
347
      while (e.parentNode && depth--) {
348
        if (e.parentNode.classList && e.parentNode.classList.contains(className)) {
349
          return e.parentNode;
350
        }
351
        e = e.parentNode;
352
      }
353
      return null;
354
    },
355
    /**
356
     * @ngdoc method
357
     * @name ionic.DomUtil#getParentOrSelfWithClass
358
     * @param {DOMElement} element
359
     * @param {string} className
360
     * @returns {DOMElement} The closest parent or self matching the
361
     * className, or null.
362
     */
363
    getParentOrSelfWithClass: function(e, className, depth) {
364
      depth = depth || 10;
365
      while (e && depth--) {
366
        if (e.classList && e.classList.contains(className)) {
367
          return e;
368
        }
369
        e = e.parentNode;
370
      }
371
      return null;
372
    },
373
 
374
    /**
375
     * @ngdoc method
376
     * @name ionic.DomUtil#rectContains
377
     * @param {number} x
378
     * @param {number} y
379
     * @param {number} x1
380
     * @param {number} y1
381
     * @param {number} x2
382
     * @param {number} y2
383
     * @returns {boolean} Whether {x,y} fits within the rectangle defined by
384
     * {x1,y1,x2,y2}.
385
     */
386
    rectContains: function(x, y, x1, y1, x2, y2) {
387
      if (x < x1 || x > x2) return false;
388
      if (y < y1 || y > y2) return false;
389
      return true;
390
    },
391
 
392
    /**
393
     * @ngdoc method
394
     * @name ionic.DomUtil#blurAll
395
     * @description
396
     * Blurs any currently focused input element
397
     * @returns {DOMElement} The element blurred or null
398
     */
399
    blurAll: function() {
400
      if (document.activeElement && document.activeElement != document.body) {
401
        document.activeElement.blur();
402
        return document.activeElement;
403
      }
404
      return null;
405
    },
406
 
407
    cachedAttr: function(ele, key, value) {
408
      ele = ele && ele.length && ele[0] || ele;
409
      if (ele && ele.setAttribute) {
410
        var dataKey = '$attr-' + key;
411
        if (arguments.length > 2) {
412
          if (ele[dataKey] !== value) {
413
            ele.setAttribute(key, value);
414
            ele[dataKey] = value;
415
          }
416
        } else if (typeof ele[dataKey] == 'undefined') {
417
          ele[dataKey] = ele.getAttribute(key);
418
        }
419
        return ele[dataKey];
420
      }
421
    },
422
 
423
    cachedStyles: function(ele, styles) {
424
      ele = ele && ele.length && ele[0] || ele;
425
      if (ele && ele.style) {
426
        for (var prop in styles) {
427
          if (ele['$style-' + prop] !== styles[prop]) {
428
            ele.style[prop] = ele['$style-' + prop] = styles[prop];
429
          }
430
        }
431
      }
432
    }
433
 
434
  };
435
 
436
  //Shortcuts
437
  ionic.requestAnimationFrame = ionic.DomUtil.requestAnimationFrame;
438
  ionic.cancelAnimationFrame = ionic.DomUtil.cancelAnimationFrame;
439
  ionic.animationFrameThrottle = ionic.DomUtil.animationFrameThrottle;
440
 
441
})(window, document, ionic);
442
 
443
/**
444
 * ion-events.js
445
 *
446
 * Author: Max Lynch <max@drifty.com>
447
 *
448
 * Framework events handles various mobile browser events, and
449
 * detects special events like tap/swipe/etc. and emits them
450
 * as custom events that can be used in an app.
451
 *
452
 * Portions lovingly adapted from github.com/maker/ratchet and github.com/alexgibson/tap.js - thanks guys!
453
 */
454
 
455
(function(ionic) {
456
 
457
  // Custom event polyfill
458
  ionic.CustomEvent = (function() {
459
    if( typeof window.CustomEvent === 'function' ) return CustomEvent;
460
 
461
    var customEvent = function(event, params) {
462
      var evt;
463
      params = params || {
464
        bubbles: false,
465
        cancelable: false,
466
        detail: undefined
467
      };
468
      try {
469
        evt = document.createEvent("CustomEvent");
470
        evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
471
      } catch (error) {
472
        // fallback for browsers that don't support createEvent('CustomEvent')
473
        evt = document.createEvent("Event");
474
        for (var param in params) {
475
          evt[param] = params[param];
476
        }
477
        evt.initEvent(event, params.bubbles, params.cancelable);
478
      }
479
      return evt;
480
    };
481
    customEvent.prototype = window.Event.prototype;
482
    return customEvent;
483
  })();
484
 
485
 
486
  /**
487
   * @ngdoc utility
488
   * @name ionic.EventController
489
   * @module ionic
490
   */
491
  ionic.EventController = {
492
    VIRTUALIZED_EVENTS: ['tap', 'swipe', 'swiperight', 'swipeleft', 'drag', 'hold', 'release'],
493
 
494
    /**
495
     * @ngdoc method
496
     * @name ionic.EventController#trigger
497
     * @alias ionic.trigger
498
     * @param {string} eventType The event to trigger.
499
     * @param {object} data The data for the event. Hint: pass in
500
     * `{target: targetElement}`
501
     * @param {boolean=} bubbles Whether the event should bubble up the DOM.
502
     * @param {boolean=} cancelable Whether the event should be cancelable.
503
     */
504
    // Trigger a new event
505
    trigger: function(eventType, data, bubbles, cancelable) {
506
      var event = new ionic.CustomEvent(eventType, {
507
        detail: data,
508
        bubbles: !!bubbles,
509
        cancelable: !!cancelable
510
      });
511
 
512
      // Make sure to trigger the event on the given target, or dispatch it from
513
      // the window if we don't have an event target
514
      data && data.target && data.target.dispatchEvent && data.target.dispatchEvent(event) || window.dispatchEvent(event);
515
    },
516
 
517
    /**
518
     * @ngdoc method
519
     * @name ionic.EventController#on
520
     * @alias ionic.on
521
     * @description Listen to an event on an element.
522
     * @param {string} type The event to listen for.
523
     * @param {function} callback The listener to be called.
524
     * @param {DOMElement} element The element to listen for the event on.
525
     */
526
    on: function(type, callback, element) {
527
      var e = element || window;
528
 
529
      // Bind a gesture if it's a virtual event
530
      for(var i = 0, j = this.VIRTUALIZED_EVENTS.length; i < j; i++) {
531
        if(type == this.VIRTUALIZED_EVENTS[i]) {
532
          var gesture = new ionic.Gesture(element);
533
          gesture.on(type, callback);
534
          return gesture;
535
        }
536
      }
537
 
538
      // Otherwise bind a normal event
539
      e.addEventListener(type, callback);
540
    },
541
 
542
    /**
543
     * @ngdoc method
544
     * @name ionic.EventController#off
545
     * @alias ionic.off
546
     * @description Remove an event listener.
547
     * @param {string} type
548
     * @param {function} callback
549
     * @param {DOMElement} element
550
     */
551
    off: function(type, callback, element) {
552
      element.removeEventListener(type, callback);
553
    },
554
 
555
    /**
556
     * @ngdoc method
557
     * @name ionic.EventController#onGesture
558
     * @alias ionic.onGesture
559
     * @description Add an event listener for a gesture on an element.
560
     *
561
     * Available eventTypes (from [hammer.js](http://eightmedia.github.io/hammer.js/)):
562
     *
563
     * `hold`, `tap`, `doubletap`, `drag`, `dragstart`, `dragend`, `dragup`, `dragdown`, <br/>
564
     * `dragleft`, `dragright`, `swipe`, `swipeup`, `swipedown`, `swipeleft`, `swiperight`, <br/>
565
     * `transform`, `transformstart`, `transformend`, `rotate`, `pinch`, `pinchin`, `pinchout`, </br>
566
     * `touch`, `release`
567
     *
568
     * @param {string} eventType The gesture event to listen for.
569
     * @param {function(e)} callback The function to call when the gesture
570
     * happens.
571
     * @param {DOMElement} element The angular element to listen for the event on.
572
     * @param {object} options object.
573
     * @returns {ionic.Gesture} The gesture object (use this to remove the gesture later on).
574
     */
575
    onGesture: function(type, callback, element, options) {
576
      var gesture = new ionic.Gesture(element, options);
577
      gesture.on(type, callback);
578
      return gesture;
579
    },
580
 
581
    /**
582
     * @ngdoc method
583
     * @name ionic.EventController#offGesture
584
     * @alias ionic.offGesture
585
     * @description Remove an event listener for a gesture created on an element.
586
     * @param {ionic.Gesture} gesture The gesture that should be removed.
587
     * @param {string} eventType The gesture event to remove the listener for.
588
     * @param {function(e)} callback The listener to remove.
589
 
590
     */
591
    offGesture: function(gesture, type, callback) {
592
      gesture && gesture.off(type, callback);
593
    },
594
 
595
    handlePopState: function() {}
596
  };
597
 
598
 
599
  // Map some convenient top-level functions for event handling
600
  ionic.on = function() { ionic.EventController.on.apply(ionic.EventController, arguments); };
601
  ionic.off = function() { ionic.EventController.off.apply(ionic.EventController, arguments); };
602
  ionic.trigger = ionic.EventController.trigger;//function() { ionic.EventController.trigger.apply(ionic.EventController.trigger, arguments); };
603
  ionic.onGesture = function() { return ionic.EventController.onGesture.apply(ionic.EventController.onGesture, arguments); };
604
  ionic.offGesture = function() { return ionic.EventController.offGesture.apply(ionic.EventController.offGesture, arguments); };
605
 
606
})(window.ionic);
607
 
608
/* eslint camelcase:0 */
609
/**
610
  * Simple gesture controllers with some common gestures that emit
611
  * gesture events.
612
  *
613
  * Ported from github.com/EightMedia/hammer.js Gestures - thanks!
614
  */
615
(function(ionic) {
616
 
617
  /**
618
   * ionic.Gestures
619
   * use this to create instances
620
   * @param   {HTMLElement}   element
621
   * @param   {Object}        options
622
   * @returns {ionic.Gestures.Instance}
623
   * @constructor
624
   */
625
  ionic.Gesture = function(element, options) {
626
    return new ionic.Gestures.Instance(element, options || {});
627
  };
628
 
629
  ionic.Gestures = {};
630
 
631
  // default settings
632
  ionic.Gestures.defaults = {
633
    // add css to the element to prevent the browser from doing
634
    // its native behavior. this doesnt prevent the scrolling,
635
    // but cancels the contextmenu, tap highlighting etc
636
    // set to false to disable this
637
    stop_browser_behavior: 'disable-user-behavior'
638
  };
639
 
640
  // detect touchevents
641
  ionic.Gestures.HAS_POINTEREVENTS = window.navigator.pointerEnabled || window.navigator.msPointerEnabled;
642
  ionic.Gestures.HAS_TOUCHEVENTS = ('ontouchstart' in window);
643
 
644
  // dont use mouseevents on mobile devices
645
  ionic.Gestures.MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android|silk/i;
646
  ionic.Gestures.NO_MOUSEEVENTS = ionic.Gestures.HAS_TOUCHEVENTS && window.navigator.userAgent.match(ionic.Gestures.MOBILE_REGEX);
647
 
648
  // eventtypes per touchevent (start, move, end)
649
  // are filled by ionic.Gestures.event.determineEventTypes on setup
650
  ionic.Gestures.EVENT_TYPES = {};
651
 
652
  // direction defines
653
  ionic.Gestures.DIRECTION_DOWN = 'down';
654
  ionic.Gestures.DIRECTION_LEFT = 'left';
655
  ionic.Gestures.DIRECTION_UP = 'up';
656
  ionic.Gestures.DIRECTION_RIGHT = 'right';
657
 
658
  // pointer type
659
  ionic.Gestures.POINTER_MOUSE = 'mouse';
660
  ionic.Gestures.POINTER_TOUCH = 'touch';
661
  ionic.Gestures.POINTER_PEN = 'pen';
662
 
663
  // touch event defines
664
  ionic.Gestures.EVENT_START = 'start';
665
  ionic.Gestures.EVENT_MOVE = 'move';
666
  ionic.Gestures.EVENT_END = 'end';
667
 
668
  // hammer document where the base events are added at
669
  ionic.Gestures.DOCUMENT = window.document;
670
 
671
  // plugins namespace
672
  ionic.Gestures.plugins = {};
673
 
674
  // if the window events are set...
675
  ionic.Gestures.READY = false;
676
 
677
  /**
678
   * setup events to detect gestures on the document
679
   */
680
  function setup() {
681
    if(ionic.Gestures.READY) {
682
      return;
683
    }
684
 
685
    // find what eventtypes we add listeners to
686
    ionic.Gestures.event.determineEventTypes();
687
 
688
    // Register all gestures inside ionic.Gestures.gestures
689
    for(var name in ionic.Gestures.gestures) {
690
      if(ionic.Gestures.gestures.hasOwnProperty(name)) {
691
        ionic.Gestures.detection.register(ionic.Gestures.gestures[name]);
692
      }
693
    }
694
 
695
    // Add touch events on the document
696
    ionic.Gestures.event.onTouch(ionic.Gestures.DOCUMENT, ionic.Gestures.EVENT_MOVE, ionic.Gestures.detection.detect);
697
    ionic.Gestures.event.onTouch(ionic.Gestures.DOCUMENT, ionic.Gestures.EVENT_END, ionic.Gestures.detection.detect);
698
 
699
    // ionic.Gestures is ready...!
700
    ionic.Gestures.READY = true;
701
  }
702
 
703
  /**
704
   * create new hammer instance
705
   * all methods should return the instance itself, so it is chainable.
706
   * @param   {HTMLElement}       element
707
   * @param   {Object}            [options={}]
708
   * @returns {ionic.Gestures.Instance}
709
   * @name Gesture.Instance
710
   * @constructor
711
   */
712
  ionic.Gestures.Instance = function(element, options) {
713
    var self = this;
714
 
715
    // A null element was passed into the instance, which means
716
    // whatever lookup was done to find this element failed to find it
717
    // so we can't listen for events on it.
718
    if(element === null) {
719
      void 0;
720
      return this;
721
    }
722
 
723
    // setup ionic.GesturesJS window events and register all gestures
724
    // this also sets up the default options
725
    setup();
726
 
727
    this.element = element;
728
 
729
    // start/stop detection option
730
    this.enabled = true;
731
 
732
    // merge options
733
    this.options = ionic.Gestures.utils.extend(
734
        ionic.Gestures.utils.extend({}, ionic.Gestures.defaults),
735
        options || {});
736
 
737
    // add some css to the element to prevent the browser from doing its native behavoir
738
    if(this.options.stop_browser_behavior) {
739
      ionic.Gestures.utils.stopDefaultBrowserBehavior(this.element, this.options.stop_browser_behavior);
740
    }
741
 
742
    // start detection on touchstart
743
    ionic.Gestures.event.onTouch(element, ionic.Gestures.EVENT_START, function(ev) {
744
      if(self.enabled) {
745
        ionic.Gestures.detection.startDetect(self, ev);
746
      }
747
    });
748
 
749
    // return instance
750
    return this;
751
  };
752
 
753
 
754
  ionic.Gestures.Instance.prototype = {
755
    /**
756
     * bind events to the instance
757
     * @param   {String}      gesture
758
     * @param   {Function}    handler
759
     * @returns {ionic.Gestures.Instance}
760
     */
761
    on: function onEvent(gesture, handler){
762
      var gestures = gesture.split(' ');
763
      for(var t = 0; t < gestures.length; t++) {
764
        this.element.addEventListener(gestures[t], handler, false);
765
      }
766
      return this;
767
    },
768
 
769
 
770
    /**
771
     * unbind events to the instance
772
     * @param   {String}      gesture
773
     * @param   {Function}    handler
774
     * @returns {ionic.Gestures.Instance}
775
     */
776
    off: function offEvent(gesture, handler){
777
      var gestures = gesture.split(' ');
778
      for(var t = 0; t < gestures.length; t++) {
779
        this.element.removeEventListener(gestures[t], handler, false);
780
      }
781
      return this;
782
    },
783
 
784
 
785
    /**
786
     * trigger gesture event
787
     * @param   {String}      gesture
788
     * @param   {Object}      eventData
789
     * @returns {ionic.Gestures.Instance}
790
     */
791
    trigger: function triggerEvent(gesture, eventData){
792
      // create DOM event
793
      var event = ionic.Gestures.DOCUMENT.createEvent('Event');
794
      event.initEvent(gesture, true, true);
795
      event.gesture = eventData;
796
 
797
      // trigger on the target if it is in the instance element,
798
      // this is for event delegation tricks
799
      var element = this.element;
800
      if(ionic.Gestures.utils.hasParent(eventData.target, element)) {
801
        element = eventData.target;
802
      }
803
 
804
      element.dispatchEvent(event);
805
      return this;
806
    },
807
 
808
 
809
    /**
810
     * enable of disable hammer.js detection
811
     * @param   {Boolean}   state
812
     * @returns {ionic.Gestures.Instance}
813
     */
814
    enable: function enable(state) {
815
      this.enabled = state;
816
      return this;
817
    }
818
  };
819
 
820
  /**
821
   * this holds the last move event,
822
   * used to fix empty touchend issue
823
   * see the onTouch event for an explanation
824
   * type {Object}
825
   */
826
  var last_move_event = null;
827
 
828
 
829
  /**
830
   * when the mouse is hold down, this is true
831
   * type {Boolean}
832
   */
833
  var enable_detect = false;
834
 
835
 
836
  /**
837
   * when touch events have been fired, this is true
838
   * type {Boolean}
839
   */
840
  var touch_triggered = false;
841
 
842
 
843
  ionic.Gestures.event = {
844
    /**
845
     * simple addEventListener
846
     * @param   {HTMLElement}   element
847
     * @param   {String}        type
848
     * @param   {Function}      handler
849
     */
850
    bindDom: function(element, type, handler) {
851
      var types = type.split(' ');
852
      for(var t = 0; t < types.length; t++) {
853
        element.addEventListener(types[t], handler, false);
854
      }
855
    },
856
 
857
 
858
    /**
859
     * touch events with mouse fallback
860
     * @param   {HTMLElement}   element
861
     * @param   {String}        eventType        like ionic.Gestures.EVENT_MOVE
862
     * @param   {Function}      handler
863
     */
864
    onTouch: function onTouch(element, eventType, handler) {
865
      var self = this;
866
 
867
      this.bindDom(element, ionic.Gestures.EVENT_TYPES[eventType], function bindDomOnTouch(ev) {
868
        var sourceEventType = ev.type.toLowerCase();
869
 
870
        // onmouseup, but when touchend has been fired we do nothing.
871
        // this is for touchdevices which also fire a mouseup on touchend
872
        if(sourceEventType.match(/mouse/) && touch_triggered) {
873
          return;
874
        }
875
 
876
        // mousebutton must be down or a touch event
877
        else if( sourceEventType.match(/touch/) ||   // touch events are always on screen
878
          sourceEventType.match(/pointerdown/) || // pointerevents touch
879
          (sourceEventType.match(/mouse/) && ev.which === 1)   // mouse is pressed
880
          ){
881
            enable_detect = true;
882
          }
883
 
884
        // mouse isn't pressed
885
        else if(sourceEventType.match(/mouse/) && ev.which !== 1) {
886
          enable_detect = false;
887
        }
888
 
889
 
890
        // we are in a touch event, set the touch triggered bool to true,
891
        // this for the conflicts that may occur on ios and android
892
        if(sourceEventType.match(/touch|pointer/)) {
893
          touch_triggered = true;
894
        }
895
 
896
        // count the total touches on the screen
897
        var count_touches = 0;
898
 
899
        // when touch has been triggered in this detection session
900
        // and we are now handling a mouse event, we stop that to prevent conflicts
901
        if(enable_detect) {
902
          // update pointerevent
903
          if(ionic.Gestures.HAS_POINTEREVENTS && eventType != ionic.Gestures.EVENT_END) {
904
            count_touches = ionic.Gestures.PointerEvent.updatePointer(eventType, ev);
905
          }
906
          // touch
907
          else if(sourceEventType.match(/touch/)) {
908
            count_touches = ev.touches.length;
909
          }
910
          // mouse
911
          else if(!touch_triggered) {
912
            count_touches = sourceEventType.match(/up/) ? 0 : 1;
913
          }
914
 
915
          // if we are in a end event, but when we remove one touch and
916
          // we still have enough, set eventType to move
917
          if(count_touches > 0 && eventType == ionic.Gestures.EVENT_END) {
918
            eventType = ionic.Gestures.EVENT_MOVE;
919
          }
920
          // no touches, force the end event
921
          else if(!count_touches) {
922
            eventType = ionic.Gestures.EVENT_END;
923
          }
924
 
925
          // store the last move event
926
          if(count_touches || last_move_event === null) {
927
            last_move_event = ev;
928
          }
929
 
930
          // trigger the handler
931
          handler.call(ionic.Gestures.detection, self.collectEventData(element, eventType, self.getTouchList(last_move_event, eventType), ev));
932
 
933
          // remove pointerevent from list
934
          if(ionic.Gestures.HAS_POINTEREVENTS && eventType == ionic.Gestures.EVENT_END) {
935
            count_touches = ionic.Gestures.PointerEvent.updatePointer(eventType, ev);
936
          }
937
        }
938
 
939
        //debug(sourceEventType +" "+ eventType);
940
 
941
        // on the end we reset everything
942
        if(!count_touches) {
943
          last_move_event = null;
944
          enable_detect = false;
945
          touch_triggered = false;
946
          ionic.Gestures.PointerEvent.reset();
947
        }
948
      });
949
    },
950
 
951
 
952
    /**
953
     * we have different events for each device/browser
954
     * determine what we need and set them in the ionic.Gestures.EVENT_TYPES constant
955
     */
956
    determineEventTypes: function determineEventTypes() {
957
      // determine the eventtype we want to set
958
      var types;
959
 
960
      // pointerEvents magic
961
      if(ionic.Gestures.HAS_POINTEREVENTS) {
962
        types = ionic.Gestures.PointerEvent.getEvents();
963
      }
964
      // on Android, iOS, blackberry, windows mobile we dont want any mouseevents
965
      else if(ionic.Gestures.NO_MOUSEEVENTS) {
966
        types = [
967
          'touchstart',
968
          'touchmove',
969
          'touchend touchcancel'];
970
      }
971
      // for non pointer events browsers and mixed browsers,
972
      // like chrome on windows8 touch laptop
973
      else {
974
        types = [
975
          'touchstart mousedown',
976
          'touchmove mousemove',
977
          'touchend touchcancel mouseup'];
978
      }
979
 
980
      ionic.Gestures.EVENT_TYPES[ionic.Gestures.EVENT_START] = types[0];
981
      ionic.Gestures.EVENT_TYPES[ionic.Gestures.EVENT_MOVE] = types[1];
982
      ionic.Gestures.EVENT_TYPES[ionic.Gestures.EVENT_END] = types[2];
983
    },
984
 
985
 
986
    /**
987
     * create touchlist depending on the event
988
     * @param   {Object}    ev
989
     * @param   {String}    eventType   used by the fakemultitouch plugin
990
     */
991
    getTouchList: function getTouchList(ev/*, eventType*/) {
992
      // get the fake pointerEvent touchlist
993
      if(ionic.Gestures.HAS_POINTEREVENTS) {
994
        return ionic.Gestures.PointerEvent.getTouchList();
995
      }
996
      // get the touchlist
997
      else if(ev.touches) {
998
        return ev.touches;
999
      }
1000
      // make fake touchlist from mouse position
1001
      else {
1002
        ev.identifier = 1;
1003
        return [ev];
1004
      }
1005
    },
1006
 
1007
 
1008
    /**
1009
     * collect event data for ionic.Gestures js
1010
     * @param   {HTMLElement}   element
1011
     * @param   {String}        eventType        like ionic.Gestures.EVENT_MOVE
1012
     * @param   {Object}        eventData
1013
     */
1014
    collectEventData: function collectEventData(element, eventType, touches, ev) {
1015
 
1016
      // find out pointerType
1017
      var pointerType = ionic.Gestures.POINTER_TOUCH;
1018
      if(ev.type.match(/mouse/) || ionic.Gestures.PointerEvent.matchType(ionic.Gestures.POINTER_MOUSE, ev)) {
1019
        pointerType = ionic.Gestures.POINTER_MOUSE;
1020
      }
1021
 
1022
      return {
1023
        center: ionic.Gestures.utils.getCenter(touches),
1024
        timeStamp: new Date().getTime(),
1025
        target: ev.target,
1026
        touches: touches,
1027
        eventType: eventType,
1028
        pointerType: pointerType,
1029
        srcEvent: ev,
1030
 
1031
        /**
1032
         * prevent the browser default actions
1033
         * mostly used to disable scrolling of the browser
1034
         */
1035
        preventDefault: function() {
1036
          if(this.srcEvent.preventManipulation) {
1037
            this.srcEvent.preventManipulation();
1038
          }
1039
 
1040
          if(this.srcEvent.preventDefault) {
1041
            // this.srcEvent.preventDefault();
1042
          }
1043
        },
1044
 
1045
        /**
1046
         * stop bubbling the event up to its parents
1047
         */
1048
        stopPropagation: function() {
1049
          this.srcEvent.stopPropagation();
1050
        },
1051
 
1052
        /**
1053
         * immediately stop gesture detection
1054
         * might be useful after a swipe was detected
1055
         * @return {*}
1056
         */
1057
        stopDetect: function() {
1058
          return ionic.Gestures.detection.stopDetect();
1059
        }
1060
      };
1061
    }
1062
  };
1063
 
1064
  ionic.Gestures.PointerEvent = {
1065
    /**
1066
     * holds all pointers
1067
     * type {Object}
1068
     */
1069
    pointers: {},
1070
 
1071
    /**
1072
     * get a list of pointers
1073
     * @returns {Array}     touchlist
1074
     */
1075
    getTouchList: function() {
1076
      var self = this;
1077
      var touchlist = [];
1078
 
1079
      // we can use forEach since pointerEvents only is in IE10
1080
      Object.keys(self.pointers).sort().forEach(function(id) {
1081
        touchlist.push(self.pointers[id]);
1082
      });
1083
      return touchlist;
1084
    },
1085
 
1086
    /**
1087
     * update the position of a pointer
1088
     * @param   {String}   type             ionic.Gestures.EVENT_END
1089
     * @param   {Object}   pointerEvent
1090
     */
1091
    updatePointer: function(type, pointerEvent) {
1092
      if(type == ionic.Gestures.EVENT_END) {
1093
        this.pointers = {};
1094
      }
1095
      else {
1096
        pointerEvent.identifier = pointerEvent.pointerId;
1097
        this.pointers[pointerEvent.pointerId] = pointerEvent;
1098
      }
1099
 
1100
      return Object.keys(this.pointers).length;
1101
    },
1102
 
1103
    /**
1104
     * check if ev matches pointertype
1105
     * @param   {String}        pointerType     ionic.Gestures.POINTER_MOUSE
1106
     * @param   {PointerEvent}  ev
1107
     */
1108
    matchType: function(pointerType, ev) {
1109
      if(!ev.pointerType) {
1110
        return false;
1111
      }
1112
 
1113
      var types = {};
1114
      types[ionic.Gestures.POINTER_MOUSE] = (ev.pointerType == ev.MSPOINTER_TYPE_MOUSE || ev.pointerType == ionic.Gestures.POINTER_MOUSE);
1115
      types[ionic.Gestures.POINTER_TOUCH] = (ev.pointerType == ev.MSPOINTER_TYPE_TOUCH || ev.pointerType == ionic.Gestures.POINTER_TOUCH);
1116
      types[ionic.Gestures.POINTER_PEN] = (ev.pointerType == ev.MSPOINTER_TYPE_PEN || ev.pointerType == ionic.Gestures.POINTER_PEN);
1117
      return types[pointerType];
1118
    },
1119
 
1120
 
1121
    /**
1122
     * get events
1123
     */
1124
    getEvents: function() {
1125
      return [
1126
        'pointerdown MSPointerDown',
1127
      'pointermove MSPointerMove',
1128
      'pointerup pointercancel MSPointerUp MSPointerCancel'
1129
        ];
1130
    },
1131
 
1132
    /**
1133
     * reset the list
1134
     */
1135
    reset: function() {
1136
      this.pointers = {};
1137
    }
1138
  };
1139
 
1140
 
1141
  ionic.Gestures.utils = {
1142
    /**
1143
     * extend method,
1144
     * also used for cloning when dest is an empty object
1145
     * @param   {Object}    dest
1146
     * @param   {Object}    src
1147
     * @param	{Boolean}	merge		do a merge
1148
     * @returns {Object}    dest
1149
     */
1150
    extend: function extend(dest, src, merge) {
1151
      for (var key in src) {
1152
        if(dest[key] !== undefined && merge) {
1153
          continue;
1154
        }
1155
        dest[key] = src[key];
1156
      }
1157
      return dest;
1158
    },
1159
 
1160
 
1161
    /**
1162
     * find if a node is in the given parent
1163
     * used for event delegation tricks
1164
     * @param   {HTMLElement}   node
1165
     * @param   {HTMLElement}   parent
1166
     * @returns {boolean}       has_parent
1167
     */
1168
    hasParent: function(node, parent) {
1169
      while(node){
1170
        if(node == parent) {
1171
          return true;
1172
        }
1173
        node = node.parentNode;
1174
      }
1175
      return false;
1176
    },
1177
 
1178
 
1179
    /**
1180
     * get the center of all the touches
1181
     * @param   {Array}     touches
1182
     * @returns {Object}    center
1183
     */
1184
    getCenter: function getCenter(touches) {
1185
      var valuesX = [], valuesY = [];
1186
 
1187
      for(var t = 0, len = touches.length; t < len; t++) {
1188
        valuesX.push(touches[t].pageX);
1189
        valuesY.push(touches[t].pageY);
1190
      }
1191
 
1192
      return {
1193
        pageX: ((Math.min.apply(Math, valuesX) + Math.max.apply(Math, valuesX)) / 2),
1194
          pageY: ((Math.min.apply(Math, valuesY) + Math.max.apply(Math, valuesY)) / 2)
1195
      };
1196
    },
1197
 
1198
 
1199
    /**
1200
     * calculate the velocity between two points
1201
     * @param   {Number}    delta_time
1202
     * @param   {Number}    delta_x
1203
     * @param   {Number}    delta_y
1204
     * @returns {Object}    velocity
1205
     */
1206
    getVelocity: function getVelocity(delta_time, delta_x, delta_y) {
1207
      return {
1208
        x: Math.abs(delta_x / delta_time) || 0,
1209
        y: Math.abs(delta_y / delta_time) || 0
1210
      };
1211
    },
1212
 
1213
 
1214
    /**
1215
     * calculate the angle between two coordinates
1216
     * @param   {Touch}     touch1
1217
     * @param   {Touch}     touch2
1218
     * @returns {Number}    angle
1219
     */
1220
    getAngle: function getAngle(touch1, touch2) {
1221
      var y = touch2.pageY - touch1.pageY,
1222
      x = touch2.pageX - touch1.pageX;
1223
      return Math.atan2(y, x) * 180 / Math.PI;
1224
    },
1225
 
1226
 
1227
    /**
1228
     * angle to direction define
1229
     * @param   {Touch}     touch1
1230
     * @param   {Touch}     touch2
1231
     * @returns {String}    direction constant, like ionic.Gestures.DIRECTION_LEFT
1232
     */
1233
    getDirection: function getDirection(touch1, touch2) {
1234
      var x = Math.abs(touch1.pageX - touch2.pageX),
1235
      y = Math.abs(touch1.pageY - touch2.pageY);
1236
 
1237
      if(x >= y) {
1238
        return touch1.pageX - touch2.pageX > 0 ? ionic.Gestures.DIRECTION_LEFT : ionic.Gestures.DIRECTION_RIGHT;
1239
      }
1240
      else {
1241
        return touch1.pageY - touch2.pageY > 0 ? ionic.Gestures.DIRECTION_UP : ionic.Gestures.DIRECTION_DOWN;
1242
      }
1243
    },
1244
 
1245
 
1246
    /**
1247
     * calculate the distance between two touches
1248
     * @param   {Touch}     touch1
1249
     * @param   {Touch}     touch2
1250
     * @returns {Number}    distance
1251
     */
1252
    getDistance: function getDistance(touch1, touch2) {
1253
      var x = touch2.pageX - touch1.pageX,
1254
      y = touch2.pageY - touch1.pageY;
1255
      return Math.sqrt((x * x) + (y * y));
1256
    },
1257
 
1258
 
1259
    /**
1260
     * calculate the scale factor between two touchLists (fingers)
1261
     * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
1262
     * @param   {Array}     start
1263
     * @param   {Array}     end
1264
     * @returns {Number}    scale
1265
     */
1266
    getScale: function getScale(start, end) {
1267
      // need two fingers...
1268
      if(start.length >= 2 && end.length >= 2) {
1269
        return this.getDistance(end[0], end[1]) /
1270
          this.getDistance(start[0], start[1]);
1271
      }
1272
      return 1;
1273
    },
1274
 
1275
 
1276
    /**
1277
     * calculate the rotation degrees between two touchLists (fingers)
1278
     * @param   {Array}     start
1279
     * @param   {Array}     end
1280
     * @returns {Number}    rotation
1281
     */
1282
    getRotation: function getRotation(start, end) {
1283
      // need two fingers
1284
      if(start.length >= 2 && end.length >= 2) {
1285
        return this.getAngle(end[1], end[0]) -
1286
          this.getAngle(start[1], start[0]);
1287
      }
1288
      return 0;
1289
    },
1290
 
1291
 
1292
    /**
1293
     * boolean if the direction is vertical
1294
     * @param    {String}    direction
1295
     * @returns  {Boolean}   is_vertical
1296
     */
1297
    isVertical: function isVertical(direction) {
1298
      return (direction == ionic.Gestures.DIRECTION_UP || direction == ionic.Gestures.DIRECTION_DOWN);
1299
    },
1300
 
1301
 
1302
    /**
1303
     * stop browser default behavior with css class
1304
     * @param   {HtmlElement}   element
1305
     * @param   {Object}        css_class
1306
     */
1307
    stopDefaultBrowserBehavior: function stopDefaultBrowserBehavior(element, css_class) {
1308
      // changed from making many style changes to just adding a preset classname
1309
      // less DOM manipulations, less code, and easier to control in the CSS side of things
1310
      // hammer.js doesn't come with CSS, but ionic does, which is why we prefer this method
1311
      if(element && element.classList) {
1312
        element.classList.add(css_class);
1313
        element.onselectstart = function() {
1314
          return false;
1315
        };
1316
      }
1317
    }
1318
  };
1319
 
1320
 
1321
  ionic.Gestures.detection = {
1322
    // contains all registred ionic.Gestures.gestures in the correct order
1323
    gestures: [],
1324
 
1325
    // data of the current ionic.Gestures.gesture detection session
1326
    current: null,
1327
 
1328
    // the previous ionic.Gestures.gesture session data
1329
    // is a full clone of the previous gesture.current object
1330
    previous: null,
1331
 
1332
    // when this becomes true, no gestures are fired
1333
    stopped: false,
1334
 
1335
 
1336
    /**
1337
     * start ionic.Gestures.gesture detection
1338
     * @param   {ionic.Gestures.Instance}   inst
1339
     * @param   {Object}            eventData
1340
     */
1341
    startDetect: function startDetect(inst, eventData) {
1342
      // already busy with a ionic.Gestures.gesture detection on an element
1343
      if(this.current) {
1344
        return;
1345
      }
1346
 
1347
      this.stopped = false;
1348
 
1349
      this.current = {
1350
        inst: inst, // reference to ionic.GesturesInstance we're working for
1351
        startEvent: ionic.Gestures.utils.extend({}, eventData), // start eventData for distances, timing etc
1352
        lastEvent: false, // last eventData
1353
        name: '' // current gesture we're in/detected, can be 'tap', 'hold' etc
1354
      };
1355
 
1356
      this.detect(eventData);
1357
    },
1358
 
1359
 
1360
    /**
1361
     * ionic.Gestures.gesture detection
1362
     * @param   {Object}    eventData
1363
     */
1364
    detect: function detect(eventData) {
1365
      if(!this.current || this.stopped) {
1366
        return null;
1367
      }
1368
 
1369
      // extend event data with calculations about scale, distance etc
1370
      eventData = this.extendEventData(eventData);
1371
 
1372
      // instance options
1373
      var inst_options = this.current.inst.options;
1374
 
1375
      // call ionic.Gestures.gesture handlers
1376
      for(var g = 0, len = this.gestures.length; g < len; g++) {
1377
        var gesture = this.gestures[g];
1378
 
1379
        // only when the instance options have enabled this gesture
1380
        if(!this.stopped && inst_options[gesture.name] !== false) {
1381
          // if a handler returns false, we stop with the detection
1382
          if(gesture.handler.call(gesture, eventData, this.current.inst) === false) {
1383
            this.stopDetect();
1384
            break;
1385
          }
1386
        }
1387
      }
1388
 
1389
      // store as previous event event
1390
      if(this.current) {
1391
        this.current.lastEvent = eventData;
1392
      }
1393
 
1394
      // endevent, but not the last touch, so dont stop
1395
      if(eventData.eventType == ionic.Gestures.EVENT_END && !eventData.touches.length - 1) {
1396
        this.stopDetect();
1397
      }
1398
 
1399
      return eventData;
1400
    },
1401
 
1402
 
1403
    /**
1404
     * clear the ionic.Gestures.gesture vars
1405
     * this is called on endDetect, but can also be used when a final ionic.Gestures.gesture has been detected
1406
     * to stop other ionic.Gestures.gestures from being fired
1407
     */
1408
    stopDetect: function stopDetect() {
1409
      // clone current data to the store as the previous gesture
1410
      // used for the double tap gesture, since this is an other gesture detect session
1411
      this.previous = ionic.Gestures.utils.extend({}, this.current);
1412
 
1413
      // reset the current
1414
      this.current = null;
1415
 
1416
      // stopped!
1417
      this.stopped = true;
1418
    },
1419
 
1420
 
1421
    /**
1422
     * extend eventData for ionic.Gestures.gestures
1423
     * @param   {Object}   ev
1424
     * @returns {Object}   ev
1425
     */
1426
    extendEventData: function extendEventData(ev) {
1427
      var startEv = this.current.startEvent;
1428
 
1429
      // if the touches change, set the new touches over the startEvent touches
1430
      // this because touchevents don't have all the touches on touchstart, or the
1431
      // user must place his fingers at the EXACT same time on the screen, which is not realistic
1432
      // but, sometimes it happens that both fingers are touching at the EXACT same time
1433
      if(startEv && (ev.touches.length != startEv.touches.length || ev.touches === startEv.touches)) {
1434
        // extend 1 level deep to get the touchlist with the touch objects
1435
        startEv.touches = [];
1436
        for(var i = 0, len = ev.touches.length; i < len; i++) {
1437
          startEv.touches.push(ionic.Gestures.utils.extend({}, ev.touches[i]));
1438
        }
1439
      }
1440
 
1441
      var delta_time = ev.timeStamp - startEv.timeStamp,
1442
          delta_x = ev.center.pageX - startEv.center.pageX,
1443
          delta_y = ev.center.pageY - startEv.center.pageY,
1444
          velocity = ionic.Gestures.utils.getVelocity(delta_time, delta_x, delta_y);
1445
 
1446
      ionic.Gestures.utils.extend(ev, {
1447
        deltaTime: delta_time,
1448
        deltaX: delta_x,
1449
        deltaY: delta_y,
1450
 
1451
        velocityX: velocity.x,
1452
        velocityY: velocity.y,
1453
 
1454
        distance: ionic.Gestures.utils.getDistance(startEv.center, ev.center),
1455
        angle: ionic.Gestures.utils.getAngle(startEv.center, ev.center),
1456
        direction: ionic.Gestures.utils.getDirection(startEv.center, ev.center),
1457
 
1458
        scale: ionic.Gestures.utils.getScale(startEv.touches, ev.touches),
1459
        rotation: ionic.Gestures.utils.getRotation(startEv.touches, ev.touches),
1460
 
1461
        startEvent: startEv
1462
      });
1463
 
1464
      return ev;
1465
    },
1466
 
1467
 
1468
    /**
1469
     * register new gesture
1470
     * @param   {Object}    gesture object, see gestures.js for documentation
1471
     * @returns {Array}     gestures
1472
     */
1473
    register: function register(gesture) {
1474
      // add an enable gesture options if there is no given
1475
      var options = gesture.defaults || {};
1476
      if(options[gesture.name] === undefined) {
1477
        options[gesture.name] = true;
1478
      }
1479
 
1480
      // extend ionic.Gestures default options with the ionic.Gestures.gesture options
1481
      ionic.Gestures.utils.extend(ionic.Gestures.defaults, options, true);
1482
 
1483
      // set its index
1484
      gesture.index = gesture.index || 1000;
1485
 
1486
      // add ionic.Gestures.gesture to the list
1487
      this.gestures.push(gesture);
1488
 
1489
      // sort the list by index
1490
      this.gestures.sort(function(a, b) {
1491
        if (a.index < b.index) {
1492
          return -1;
1493
        }
1494
        if (a.index > b.index) {
1495
          return 1;
1496
        }
1497
        return 0;
1498
      });
1499
 
1500
      return this.gestures;
1501
    }
1502
  };
1503
 
1504
 
1505
  ionic.Gestures.gestures = ionic.Gestures.gestures || {};
1506
 
1507
  /**
1508
   * Custom gestures
1509
   * ==============================
1510
   *
1511
   * Gesture object
1512
   * --------------------
1513
   * The object structure of a gesture:
1514
   *
1515
   * { name: 'mygesture',
1516
   *   index: 1337,
1517
   *   defaults: {
1518
   *     mygesture_option: true
1519
   *   }
1520
   *   handler: function(type, ev, inst) {
1521
   *     // trigger gesture event
1522
   *     inst.trigger(this.name, ev);
1523
   *   }
1524
   * }
1525
 
1526
   * @param   {String}    name
1527
   * this should be the name of the gesture, lowercase
1528
   * it is also being used to disable/enable the gesture per instance config.
1529
   *
1530
   * @param   {Number}    [index=1000]
1531
   * the index of the gesture, where it is going to be in the stack of gestures detection
1532
   * like when you build an gesture that depends on the drag gesture, it is a good
1533
   * idea to place it after the index of the drag gesture.
1534
   *
1535
   * @param   {Object}    [defaults={}]
1536
   * the default settings of the gesture. these are added to the instance settings,
1537
   * and can be overruled per instance. you can also add the name of the gesture,
1538
   * but this is also added by default (and set to true).
1539
   *
1540
   * @param   {Function}  handler
1541
   * this handles the gesture detection of your custom gesture and receives the
1542
   * following arguments:
1543
   *
1544
   *      @param  {Object}    eventData
1545
   *      event data containing the following properties:
1546
   *          timeStamp   {Number}        time the event occurred
1547
   *          target      {HTMLElement}   target element
1548
   *          touches     {Array}         touches (fingers, pointers, mouse) on the screen
1549
   *          pointerType {String}        kind of pointer that was used. matches ionic.Gestures.POINTER_MOUSE|TOUCH
1550
   *          center      {Object}        center position of the touches. contains pageX and pageY
1551
   *          deltaTime   {Number}        the total time of the touches in the screen
1552
   *          deltaX      {Number}        the delta on x axis we haved moved
1553
   *          deltaY      {Number}        the delta on y axis we haved moved
1554
   *          velocityX   {Number}        the velocity on the x
1555
   *          velocityY   {Number}        the velocity on y
1556
   *          angle       {Number}        the angle we are moving
1557
   *          direction   {String}        the direction we are moving. matches ionic.Gestures.DIRECTION_UP|DOWN|LEFT|RIGHT
1558
   *          distance    {Number}        the distance we haved moved
1559
   *          scale       {Number}        scaling of the touches, needs 2 touches
1560
   *          rotation    {Number}        rotation of the touches, needs 2 touches *
1561
   *          eventType   {String}        matches ionic.Gestures.EVENT_START|MOVE|END
1562
   *          srcEvent    {Object}        the source event, like TouchStart or MouseDown *
1563
   *          startEvent  {Object}        contains the same properties as above,
1564
   *                                      but from the first touch. this is used to calculate
1565
   *                                      distances, deltaTime, scaling etc
1566
   *
1567
   *      @param  {ionic.Gestures.Instance}    inst
1568
   *      the instance we are doing the detection for. you can get the options from
1569
   *      the inst.options object and trigger the gesture event by calling inst.trigger
1570
   *
1571
   *
1572
   * Handle gestures
1573
   * --------------------
1574
   * inside the handler you can get/set ionic.Gestures.detectionic.current. This is the current
1575
   * detection sessionic. It has the following properties
1576
   *      @param  {String}    name
1577
   *      contains the name of the gesture we have detected. it has not a real function,
1578
   *      only to check in other gestures if something is detected.
1579
   *      like in the drag gesture we set it to 'drag' and in the swipe gesture we can
1580
   *      check if the current gesture is 'drag' by accessing ionic.Gestures.detectionic.current.name
1581
   *
1582
   *      readonly
1583
   *      @param  {ionic.Gestures.Instance}    inst
1584
   *      the instance we do the detection for
1585
   *
1586
   *      readonly
1587
   *      @param  {Object}    startEvent
1588
   *      contains the properties of the first gesture detection in this sessionic.
1589
   *      Used for calculations about timing, distance, etc.
1590
   *
1591
   *      readonly
1592
   *      @param  {Object}    lastEvent
1593
   *      contains all the properties of the last gesture detect in this sessionic.
1594
   *
1595
   * after the gesture detection session has been completed (user has released the screen)
1596
   * the ionic.Gestures.detectionic.current object is copied into ionic.Gestures.detectionic.previous,
1597
   * this is usefull for gestures like doubletap, where you need to know if the
1598
   * previous gesture was a tap
1599
   *
1600
   * options that have been set by the instance can be received by calling inst.options
1601
   *
1602
   * You can trigger a gesture event by calling inst.trigger("mygesture", event).
1603
   * The first param is the name of your gesture, the second the event argument
1604
   *
1605
   *
1606
   * Register gestures
1607
   * --------------------
1608
   * When an gesture is added to the ionic.Gestures.gestures object, it is auto registered
1609
   * at the setup of the first ionic.Gestures instance. You can also call ionic.Gestures.detectionic.register
1610
   * manually and pass your gesture object as a param
1611
   *
1612
   */
1613
 
1614
  /**
1615
   * Hold
1616
   * Touch stays at the same place for x time
1617
   * events  hold
1618
   */
1619
  ionic.Gestures.gestures.Hold = {
1620
    name: 'hold',
1621
    index: 10,
1622
    defaults: {
1623
      hold_timeout: 500,
1624
      hold_threshold: 1
1625
    },
1626
    timer: null,
1627
    handler: function holdGesture(ev, inst) {
1628
      switch(ev.eventType) {
1629
        case ionic.Gestures.EVENT_START:
1630
          // clear any running timers
1631
          clearTimeout(this.timer);
1632
 
1633
          // set the gesture so we can check in the timeout if it still is
1634
          ionic.Gestures.detection.current.name = this.name;
1635
 
1636
          // set timer and if after the timeout it still is hold,
1637
          // we trigger the hold event
1638
          this.timer = setTimeout(function() {
1639
            if(ionic.Gestures.detection.current.name == 'hold') {
1640
              ionic.tap.cancelClick();
1641
              inst.trigger('hold', ev);
1642
            }
1643
          }, inst.options.hold_timeout);
1644
          break;
1645
 
1646
          // when you move or end we clear the timer
1647
        case ionic.Gestures.EVENT_MOVE:
1648
          if(ev.distance > inst.options.hold_threshold) {
1649
            clearTimeout(this.timer);
1650
          }
1651
          break;
1652
 
1653
        case ionic.Gestures.EVENT_END:
1654
          clearTimeout(this.timer);
1655
          break;
1656
      }
1657
    }
1658
  };
1659
 
1660
 
1661
  /**
1662
   * Tap/DoubleTap
1663
   * Quick touch at a place or double at the same place
1664
   * events  tap, doubletap
1665
   */
1666
  ionic.Gestures.gestures.Tap = {
1667
    name: 'tap',
1668
    index: 100,
1669
    defaults: {
1670
      tap_max_touchtime: 250,
1671
      tap_max_distance: 10,
1672
      tap_always: true,
1673
      doubletap_distance: 20,
1674
      doubletap_interval: 300
1675
    },
1676
    handler: function tapGesture(ev, inst) {
1677
      if(ev.eventType == ionic.Gestures.EVENT_END && ev.srcEvent.type != 'touchcancel') {
1678
        // previous gesture, for the double tap since these are two different gesture detections
1679
        var prev = ionic.Gestures.detection.previous,
1680
        did_doubletap = false;
1681
 
1682
        // when the touchtime is higher then the max touch time
1683
        // or when the moving distance is too much
1684
        if(ev.deltaTime > inst.options.tap_max_touchtime ||
1685
            ev.distance > inst.options.tap_max_distance) {
1686
              return;
1687
            }
1688
 
1689
        // check if double tap
1690
        if(prev && prev.name == 'tap' &&
1691
            (ev.timeStamp - prev.lastEvent.timeStamp) < inst.options.doubletap_interval &&
1692
            ev.distance < inst.options.doubletap_distance) {
1693
              inst.trigger('doubletap', ev);
1694
              did_doubletap = true;
1695
            }
1696
 
1697
        // do a single tap
1698
        if(!did_doubletap || inst.options.tap_always) {
1699
          ionic.Gestures.detection.current.name = 'tap';
1700
          inst.trigger('tap', ev);
1701
        }
1702
      }
1703
    }
1704
  };
1705
 
1706
 
1707
  /**
1708
   * Swipe
1709
   * triggers swipe events when the end velocity is above the threshold
1710
   * events  swipe, swipeleft, swiperight, swipeup, swipedown
1711
   */
1712
  ionic.Gestures.gestures.Swipe = {
1713
    name: 'swipe',
1714
    index: 40,
1715
    defaults: {
1716
      // set 0 for unlimited, but this can conflict with transform
1717
      swipe_max_touches: 1,
1718
      swipe_velocity: 0.4
1719
    },
1720
    handler: function swipeGesture(ev, inst) {
1721
      if(ev.eventType == ionic.Gestures.EVENT_END) {
1722
        // max touches
1723
        if(inst.options.swipe_max_touches > 0 &&
1724
            ev.touches.length > inst.options.swipe_max_touches) {
1725
              return;
1726
            }
1727
 
1728
        // when the distance we moved is too small we skip this gesture
1729
        // or we can be already in dragging
1730
        if(ev.velocityX > inst.options.swipe_velocity ||
1731
            ev.velocityY > inst.options.swipe_velocity) {
1732
              // trigger swipe events
1733
              inst.trigger(this.name, ev);
1734
              inst.trigger(this.name + ev.direction, ev);
1735
            }
1736
      }
1737
    }
1738
  };
1739
 
1740
 
1741
  /**
1742
   * Drag
1743
   * Move with x fingers (default 1) around on the page. Blocking the scrolling when
1744
   * moving left and right is a good practice. When all the drag events are blocking
1745
   * you disable scrolling on that area.
1746
   * events  drag, drapleft, dragright, dragup, dragdown
1747
   */
1748
  ionic.Gestures.gestures.Drag = {
1749
    name: 'drag',
1750
    index: 50,
1751
    defaults: {
1752
      drag_min_distance: 10,
1753
      // Set correct_for_drag_min_distance to true to make the starting point of the drag
1754
      // be calculated from where the drag was triggered, not from where the touch started.
1755
      // Useful to avoid a jerk-starting drag, which can make fine-adjustments
1756
      // through dragging difficult, and be visually unappealing.
1757
      correct_for_drag_min_distance: true,
1758
      // set 0 for unlimited, but this can conflict with transform
1759
      drag_max_touches: 1,
1760
      // prevent default browser behavior when dragging occurs
1761
      // be careful with it, it makes the element a blocking element
1762
      // when you are using the drag gesture, it is a good practice to set this true
1763
      drag_block_horizontal: true,
1764
      drag_block_vertical: true,
1765
      // drag_lock_to_axis keeps the drag gesture on the axis that it started on,
1766
      // It disallows vertical directions if the initial direction was horizontal, and vice versa.
1767
      drag_lock_to_axis: false,
1768
      // drag lock only kicks in when distance > drag_lock_min_distance
1769
      // This way, locking occurs only when the distance has become large enough to reliably determine the direction
1770
      drag_lock_min_distance: 25,
1771
      // prevent default if the gesture is going the given direction
1772
      prevent_default_directions: []
1773
    },
1774
    triggered: false,
1775
    handler: function dragGesture(ev, inst) {
1776
      if (ev.srcEvent.type == 'touchstart' || ev.srcEvent.type == 'touchend') {
1777
        this.preventedFirstMove = false;
1778
 
1779
      } else if (!this.preventedFirstMove && ev.srcEvent.type == 'touchmove') {
1780
        if (inst.options.prevent_default_directions.indexOf(ev.direction) != -1) {
1781
          ev.srcEvent.preventDefault();
1782
        }
1783
        this.preventedFirstMove = true;
1784
      }
1785
 
1786
      // current gesture isnt drag, but dragged is true
1787
      // this means an other gesture is busy. now call dragend
1788
      if(ionic.Gestures.detection.current.name != this.name && this.triggered) {
1789
        inst.trigger(this.name + 'end', ev);
1790
        this.triggered = false;
1791
        return;
1792
      }
1793
 
1794
      // max touches
1795
      if(inst.options.drag_max_touches > 0 &&
1796
          ev.touches.length > inst.options.drag_max_touches) {
1797
            return;
1798
          }
1799
 
1800
      switch(ev.eventType) {
1801
        case ionic.Gestures.EVENT_START:
1802
          this.triggered = false;
1803
          break;
1804
 
1805
        case ionic.Gestures.EVENT_MOVE:
1806
          // when the distance we moved is too small we skip this gesture
1807
          // or we can be already in dragging
1808
          if(ev.distance < inst.options.drag_min_distance &&
1809
              ionic.Gestures.detection.current.name != this.name) {
1810
                return;
1811
              }
1812
 
1813
          // we are dragging!
1814
          if(ionic.Gestures.detection.current.name != this.name) {
1815
            ionic.Gestures.detection.current.name = this.name;
1816
            if (inst.options.correct_for_drag_min_distance) {
1817
              // When a drag is triggered, set the event center to drag_min_distance pixels from the original event center.
1818
              // Without this correction, the dragged distance would jumpstart at drag_min_distance pixels instead of at 0.
1819
              // It might be useful to save the original start point somewhere
1820
              var factor = Math.abs(inst.options.drag_min_distance / ev.distance);
1821
              ionic.Gestures.detection.current.startEvent.center.pageX += ev.deltaX * factor;
1822
              ionic.Gestures.detection.current.startEvent.center.pageY += ev.deltaY * factor;
1823
 
1824
              // recalculate event data using new start point
1825
              ev = ionic.Gestures.detection.extendEventData(ev);
1826
            }
1827
          }
1828
 
1829
          // lock drag to axis?
1830
          if(ionic.Gestures.detection.current.lastEvent.drag_locked_to_axis || (inst.options.drag_lock_to_axis && inst.options.drag_lock_min_distance <= ev.distance)) {
1831
            ev.drag_locked_to_axis = true;
1832
          }
1833
          var last_direction = ionic.Gestures.detection.current.lastEvent.direction;
1834
          if(ev.drag_locked_to_axis && last_direction !== ev.direction) {
1835
            // keep direction on the axis that the drag gesture started on
1836
            if(ionic.Gestures.utils.isVertical(last_direction)) {
1837
              ev.direction = (ev.deltaY < 0) ? ionic.Gestures.DIRECTION_UP : ionic.Gestures.DIRECTION_DOWN;
1838
            }
1839
            else {
1840
              ev.direction = (ev.deltaX < 0) ? ionic.Gestures.DIRECTION_LEFT : ionic.Gestures.DIRECTION_RIGHT;
1841
            }
1842
          }
1843
 
1844
          // first time, trigger dragstart event
1845
          if(!this.triggered) {
1846
            inst.trigger(this.name + 'start', ev);
1847
            this.triggered = true;
1848
          }
1849
 
1850
          // trigger normal event
1851
          inst.trigger(this.name, ev);
1852
 
1853
          // direction event, like dragdown
1854
          inst.trigger(this.name + ev.direction, ev);
1855
 
1856
          // block the browser events
1857
          if( (inst.options.drag_block_vertical && ionic.Gestures.utils.isVertical(ev.direction)) ||
1858
              (inst.options.drag_block_horizontal && !ionic.Gestures.utils.isVertical(ev.direction))) {
1859
                ev.preventDefault();
1860
              }
1861
          break;
1862
 
1863
        case ionic.Gestures.EVENT_END:
1864
          // trigger dragend
1865
          if(this.triggered) {
1866
            inst.trigger(this.name + 'end', ev);
1867
          }
1868
 
1869
          this.triggered = false;
1870
          break;
1871
      }
1872
    }
1873
  };
1874
 
1875
 
1876
  /**
1877
   * Transform
1878
   * User want to scale or rotate with 2 fingers
1879
   * events  transform, pinch, pinchin, pinchout, rotate
1880
   */
1881
  ionic.Gestures.gestures.Transform = {
1882
    name: 'transform',
1883
    index: 45,
1884
    defaults: {
1885
      // factor, no scale is 1, zoomin is to 0 and zoomout until higher then 1
1886
      transform_min_scale: 0.01,
1887
      // rotation in degrees
1888
      transform_min_rotation: 1,
1889
      // prevent default browser behavior when two touches are on the screen
1890
      // but it makes the element a blocking element
1891
      // when you are using the transform gesture, it is a good practice to set this true
1892
      transform_always_block: false
1893
    },
1894
    triggered: false,
1895
    handler: function transformGesture(ev, inst) {
1896
      // current gesture isnt drag, but dragged is true
1897
      // this means an other gesture is busy. now call dragend
1898
      if(ionic.Gestures.detection.current.name != this.name && this.triggered) {
1899
        inst.trigger(this.name + 'end', ev);
1900
        this.triggered = false;
1901
        return;
1902
      }
1903
 
1904
      // atleast multitouch
1905
      if(ev.touches.length < 2) {
1906
        return;
1907
      }
1908
 
1909
      // prevent default when two fingers are on the screen
1910
      if(inst.options.transform_always_block) {
1911
        ev.preventDefault();
1912
      }
1913
 
1914
      switch(ev.eventType) {
1915
        case ionic.Gestures.EVENT_START:
1916
          this.triggered = false;
1917
          break;
1918
 
1919
        case ionic.Gestures.EVENT_MOVE:
1920
          var scale_threshold = Math.abs(1 - ev.scale);
1921
          var rotation_threshold = Math.abs(ev.rotation);
1922
 
1923
          // when the distance we moved is too small we skip this gesture
1924
          // or we can be already in dragging
1925
          if(scale_threshold < inst.options.transform_min_scale &&
1926
              rotation_threshold < inst.options.transform_min_rotation) {
1927
                return;
1928
              }
1929
 
1930
          // we are transforming!
1931
          ionic.Gestures.detection.current.name = this.name;
1932
 
1933
          // first time, trigger dragstart event
1934
          if(!this.triggered) {
1935
            inst.trigger(this.name + 'start', ev);
1936
            this.triggered = true;
1937
          }
1938
 
1939
          inst.trigger(this.name, ev); // basic transform event
1940
 
1941
          // trigger rotate event
1942
          if(rotation_threshold > inst.options.transform_min_rotation) {
1943
            inst.trigger('rotate', ev);
1944
          }
1945
 
1946
          // trigger pinch event
1947
          if(scale_threshold > inst.options.transform_min_scale) {
1948
            inst.trigger('pinch', ev);
1949
            inst.trigger('pinch' + ((ev.scale < 1) ? 'in' : 'out'), ev);
1950
          }
1951
          break;
1952
 
1953
        case ionic.Gestures.EVENT_END:
1954
          // trigger dragend
1955
          if(this.triggered) {
1956
            inst.trigger(this.name + 'end', ev);
1957
          }
1958
 
1959
          this.triggered = false;
1960
          break;
1961
      }
1962
    }
1963
  };
1964
 
1965
 
1966
  /**
1967
   * Touch
1968
   * Called as first, tells the user has touched the screen
1969
   * events  touch
1970
   */
1971
  ionic.Gestures.gestures.Touch = {
1972
    name: 'touch',
1973
    index: -Infinity,
1974
    defaults: {
1975
      // call preventDefault at touchstart, and makes the element blocking by
1976
      // disabling the scrolling of the page, but it improves gestures like
1977
      // transforming and dragging.
1978
      // be careful with using this, it can be very annoying for users to be stuck
1979
      // on the page
1980
      prevent_default: false,
1981
 
1982
      // disable mouse events, so only touch (or pen!) input triggers events
1983
      prevent_mouseevents: false
1984
    },
1985
    handler: function touchGesture(ev, inst) {
1986
      if(inst.options.prevent_mouseevents && ev.pointerType == ionic.Gestures.POINTER_MOUSE) {
1987
        ev.stopDetect();
1988
        return;
1989
      }
1990
 
1991
      if(inst.options.prevent_default) {
1992
        ev.preventDefault();
1993
      }
1994
 
1995
      if(ev.eventType == ionic.Gestures.EVENT_START) {
1996
        inst.trigger(this.name, ev);
1997
      }
1998
    }
1999
  };
2000
 
2001
 
2002
  /**
2003
   * Release
2004
   * Called as last, tells the user has released the screen
2005
   * events  release
2006
   */
2007
  ionic.Gestures.gestures.Release = {
2008
    name: 'release',
2009
    index: Infinity,
2010
    handler: function releaseGesture(ev, inst) {
2011
      if(ev.eventType == ionic.Gestures.EVENT_END) {
2012
        inst.trigger(this.name, ev);
2013
      }
2014
    }
2015
  };
2016
})(window.ionic);
2017
 
2018
(function(window, document, ionic) {
2019
 
2020
  function getParameterByName(name) {
2021
    name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
2022
    var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
2023
    results = regex.exec(location.search);
2024
    return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
2025
  }
2026
 
2027
  var IOS = 'ios';
2028
  var ANDROID = 'android';
2029
  var WINDOWS_PHONE = 'windowsphone';
2030
  var requestAnimationFrame = ionic.requestAnimationFrame;
2031
 
2032
  /**
2033
   * @ngdoc utility
2034
   * @name ionic.Platform
2035
   * @module ionic
2036
   * @description
2037
   * A set of utility methods that can be used to retrieve the device ready state and
2038
   * various other information such as what kind of platform the app is currently installed on.
2039
   *
2040
   * @usage
2041
   * ```js
2042
   * angular.module('PlatformApp', ['ionic'])
2043
   * .controller('PlatformCtrl', function($scope) {
2044
   *
2045
   *   ionic.Platform.ready(function(){
2046
   *     // will execute when device is ready, or immediately if the device is already ready.
2047
   *   });
2048
   *
2049
   *   var deviceInformation = ionic.Platform.device();
2050
   *
2051
   *   var isWebView = ionic.Platform.isWebView();
2052
   *   var isIPad = ionic.Platform.isIPad();
2053
   *   var isIOS = ionic.Platform.isIOS();
2054
   *   var isAndroid = ionic.Platform.isAndroid();
2055
   *   var isWindowsPhone = ionic.Platform.isWindowsPhone();
2056
   *
2057
   *   var currentPlatform = ionic.Platform.platform();
2058
   *   var currentPlatformVersion = ionic.Platform.version();
2059
   *
2060
   *   ionic.Platform.exitApp(); // stops the app
2061
   * });
2062
   * ```
2063
   */
2064
  var self = ionic.Platform = {
2065
 
2066
    // Put navigator on platform so it can be mocked and set
2067
    // the browser does not allow window.navigator to be set
2068
    navigator: window.navigator,
2069
 
2070
    /**
2071
     * @ngdoc property
2072
     * @name ionic.Platform#isReady
2073
     * @returns {boolean} Whether the device is ready.
2074
     */
2075
    isReady: false,
2076
    /**
2077
     * @ngdoc property
2078
     * @name ionic.Platform#isFullScreen
2079
     * @returns {boolean} Whether the device is fullscreen.
2080
     */
2081
    isFullScreen: false,
2082
    /**
2083
     * @ngdoc property
2084
     * @name ionic.Platform#platforms
2085
     * @returns {Array(string)} An array of all platforms found.
2086
     */
2087
    platforms: null,
2088
    /**
2089
     * @ngdoc property
2090
     * @name ionic.Platform#grade
2091
     * @returns {string} What grade the current platform is.
2092
     */
2093
    grade: null,
2094
    ua: navigator.userAgent,
2095
 
2096
    /**
2097
     * @ngdoc method
2098
     * @name ionic.Platform#ready
2099
     * @description
2100
     * Trigger a callback once the device is ready, or immediately
2101
     * if the device is already ready. This method can be run from
2102
     * anywhere and does not need to be wrapped by any additonal methods.
2103
     * When the app is within a WebView (Cordova), it'll fire
2104
     * the callback once the device is ready. If the app is within
2105
     * a web browser, it'll fire the callback after `window.load`.
2106
     * Please remember that Cordova features (Camera, FileSystem, etc) still
2107
     * will not work in a web browser.
2108
     * @param {function} callback The function to call.
2109
     */
2110
    ready: function(cb) {
2111
      // run through tasks to complete now that the device is ready
2112
      if (self.isReady) {
2113
        cb();
2114
      } else {
2115
        // the platform isn't ready yet, add it to this array
2116
        // which will be called once the platform is ready
2117
        readyCallbacks.push(cb);
2118
      }
2119
    },
2120
 
2121
    /**
2122
     * @private
2123
     */
2124
    detect: function() {
2125
      self._checkPlatforms();
2126
 
2127
      requestAnimationFrame(function() {
2128
        // only add to the body class if we got platform info
2129
        for (var i = 0; i < self.platforms.length; i++) {
2130
          document.body.classList.add('platform-' + self.platforms[i]);
2131
        }
2132
      });
2133
    },
2134
 
2135
    /**
2136
     * @ngdoc method
2137
     * @name ionic.Platform#setGrade
2138
     * @description Set the grade of the device: 'a', 'b', or 'c'. 'a' is the best
2139
     * (most css features enabled), 'c' is the worst.  By default, sets the grade
2140
     * depending on the current device.
2141
     * @param {string} grade The new grade to set.
2142
     */
2143
    setGrade: function(grade) {
2144
      var oldGrade = self.grade;
2145
      self.grade = grade;
2146
      requestAnimationFrame(function() {
2147
        if (oldGrade) {
2148
          document.body.classList.remove('grade-' + oldGrade);
2149
        }
2150
        document.body.classList.add('grade-' + grade);
2151
      });
2152
    },
2153
 
2154
    /**
2155
     * @ngdoc method
2156
     * @name ionic.Platform#device
2157
     * @description Return the current device (given by cordova).
2158
     * @returns {object} The device object.
2159
     */
2160
    device: function() {
2161
      return window.device || {};
2162
    },
2163
 
2164
    _checkPlatforms: function() {
2165
      self.platforms = [];
2166
      var grade = 'a';
2167
 
2168
      if (self.isWebView()) {
2169
        self.platforms.push('webview');
2170
        if (!(!window.cordova && !window.PhoneGap && !window.phonegap)) {
2171
          self.platforms.push('cordova');
2172
        } else if (window.forge) {
2173
          self.platforms.push('trigger');
2174
        }
2175
      } else {
2176
        self.platforms.push('browser');
2177
      }
2178
      if (self.isIPad()) self.platforms.push('ipad');
2179
 
2180
      var platform = self.platform();
2181
      if (platform) {
2182
        self.platforms.push(platform);
2183
 
2184
        var version = self.version();
2185
        if (version) {
2186
          var v = version.toString();
2187
          if (v.indexOf('.') > 0) {
2188
            v = v.replace('.', '_');
2189
          } else {
2190
            v += '_0';
2191
          }
2192
          self.platforms.push(platform + v.split('_')[0]);
2193
          self.platforms.push(platform + v);
2194
 
2195
          if (self.isAndroid() && version < 4.4) {
2196
            grade = (version < 4 ? 'c' : 'b');
2197
          } else if (self.isWindowsPhone()) {
2198
            grade = 'b';
2199
          }
2200
        }
2201
      }
2202
 
2203
      self.setGrade(grade);
2204
    },
2205
 
2206
    /**
2207
     * @ngdoc method
2208
     * @name ionic.Platform#isWebView
2209
     * @returns {boolean} Check if we are running within a WebView (such as Cordova).
2210
     */
2211
    isWebView: function() {
2212
      return !(!window.cordova && !window.PhoneGap && !window.phonegap && !window.forge);
2213
    },
2214
    /**
2215
     * @ngdoc method
2216
     * @name ionic.Platform#isIPad
2217
     * @returns {boolean} Whether we are running on iPad.
2218
     */
2219
    isIPad: function() {
2220
      if (/iPad/i.test(self.navigator.platform)) {
2221
        return true;
2222
      }
2223
      return /iPad/i.test(self.ua);
2224
    },
2225
    /**
2226
     * @ngdoc method
2227
     * @name ionic.Platform#isIOS
2228
     * @returns {boolean} Whether we are running on iOS.
2229
     */
2230
    isIOS: function() {
2231
      return self.is(IOS);
2232
    },
2233
    /**
2234
     * @ngdoc method
2235
     * @name ionic.Platform#isAndroid
2236
     * @returns {boolean} Whether we are running on Android.
2237
     */
2238
    isAndroid: function() {
2239
      return self.is(ANDROID);
2240
    },
2241
    /**
2242
     * @ngdoc method
2243
     * @name ionic.Platform#isWindowsPhone
2244
     * @returns {boolean} Whether we are running on Windows Phone.
2245
     */
2246
    isWindowsPhone: function() {
2247
      return self.is(WINDOWS_PHONE);
2248
    },
2249
 
2250
    /**
2251
     * @ngdoc method
2252
     * @name ionic.Platform#platform
2253
     * @returns {string} The name of the current platform.
2254
     */
2255
    platform: function() {
2256
      // singleton to get the platform name
2257
      if (platformName === null) self.setPlatform(self.device().platform);
2258
      return platformName;
2259
    },
2260
 
2261
    /**
2262
     * @private
2263
     */
2264
    setPlatform: function(n) {
2265
      if (typeof n != 'undefined' && n !== null && n.length) {
2266
        platformName = n.toLowerCase();
2267
      } else if (getParameterByName('ionicplatform')) {
2268
        platformName = getParameterByName('ionicplatform');
2269
      } else if (self.ua.indexOf('Android') > 0) {
2270
        platformName = ANDROID;
2271
      } else if (/iPhone|iPad|iPod/.test(self.ua)) {
2272
        platformName = IOS;
2273
      } else if (self.ua.indexOf('Windows Phone') > -1) {
2274
        platformName = WINDOWS_PHONE;
2275
      } else {
2276
        platformName = self.navigator.platform && navigator.platform.toLowerCase().split(' ')[0] || '';
2277
      }
2278
    },
2279
 
2280
    /**
2281
     * @ngdoc method
2282
     * @name ionic.Platform#version
2283
     * @returns {number} The version of the current device platform.
2284
     */
2285
    version: function() {
2286
      // singleton to get the platform version
2287
      if (platformVersion === null) self.setVersion(self.device().version);
2288
      return platformVersion;
2289
    },
2290
 
2291
    /**
2292
     * @private
2293
     */
2294
    setVersion: function(v) {
2295
      if (typeof v != 'undefined' && v !== null) {
2296
        v = v.split('.');
2297
        v = parseFloat(v[0] + '.' + (v.length > 1 ? v[1] : 0));
2298
        if (!isNaN(v)) {
2299
          platformVersion = v;
2300
          return;
2301
        }
2302
      }
2303
 
2304
      platformVersion = 0;
2305
 
2306
      // fallback to user-agent checking
2307
      var pName = self.platform();
2308
      var versionMatch = {
2309
        'android': /Android (\d+).(\d+)?/,
2310
        'ios': /OS (\d+)_(\d+)?/,
2311
        'windowsphone': /Windows Phone (\d+).(\d+)?/
2312
      };
2313
      if (versionMatch[pName]) {
2314
        v = self.ua.match(versionMatch[pName]);
2315
        if (v && v.length > 2) {
2316
          platformVersion = parseFloat(v[1] + '.' + v[2]);
2317
        }
2318
      }
2319
    },
2320
 
2321
    // Check if the platform is the one detected by cordova
2322
    is: function(type) {
2323
      type = type.toLowerCase();
2324
      // check if it has an array of platforms
2325
      if (self.platforms) {
2326
        for (var x = 0; x < self.platforms.length; x++) {
2327
          if (self.platforms[x] === type) return true;
2328
        }
2329
      }
2330
      // exact match
2331
      var pName = self.platform();
2332
      if (pName) {
2333
        return pName === type.toLowerCase();
2334
      }
2335
 
2336
      // A quick hack for to check userAgent
2337
      return self.ua.toLowerCase().indexOf(type) >= 0;
2338
    },
2339
 
2340
    /**
2341
     * @ngdoc method
2342
     * @name ionic.Platform#exitApp
2343
     * @description Exit the app.
2344
     */
2345
    exitApp: function() {
2346
      self.ready(function() {
2347
        navigator.app && navigator.app.exitApp && navigator.app.exitApp();
2348
      });
2349
    },
2350
 
2351
    /**
2352
     * @ngdoc method
2353
     * @name ionic.Platform#showStatusBar
2354
     * @description Shows or hides the device status bar (in Cordova). Requires `cordova plugin add org.apache.cordova.statusbar`
2355
     * @param {boolean} shouldShow Whether or not to show the status bar.
2356
     */
2357
    showStatusBar: function(val) {
2358
      // Only useful when run within cordova
2359
      self._showStatusBar = val;
2360
      self.ready(function() {
2361
        // run this only when or if the platform (cordova) is ready
2362
        requestAnimationFrame(function() {
2363
          if (self._showStatusBar) {
2364
            // they do not want it to be full screen
2365
            window.StatusBar && window.StatusBar.show();
2366
            document.body.classList.remove('status-bar-hide');
2367
          } else {
2368
            // it should be full screen
2369
            window.StatusBar && window.StatusBar.hide();
2370
            document.body.classList.add('status-bar-hide');
2371
          }
2372
        });
2373
      });
2374
    },
2375
 
2376
    /**
2377
     * @ngdoc method
2378
     * @name ionic.Platform#fullScreen
2379
     * @description
2380
     * Sets whether the app is fullscreen or not (in Cordova).
2381
     * @param {boolean=} showFullScreen Whether or not to set the app to fullscreen. Defaults to true. Requires `cordova plugin add org.apache.cordova.statusbar`
2382
     * @param {boolean=} showStatusBar Whether or not to show the device's status bar. Defaults to false.
2383
     */
2384
    fullScreen: function(showFullScreen, showStatusBar) {
2385
      // showFullScreen: default is true if no param provided
2386
      self.isFullScreen = (showFullScreen !== false);
2387
 
2388
      // add/remove the fullscreen classname to the body
2389
      ionic.DomUtil.ready(function() {
2390
        // run this only when or if the DOM is ready
2391
        requestAnimationFrame(function() {
2392
          if (self.isFullScreen) {
2393
            document.body.classList.add('fullscreen');
2394
          } else {
2395
            document.body.classList.remove('fullscreen');
2396
          }
2397
        });
2398
        // showStatusBar: default is false if no param provided
2399
        self.showStatusBar((showStatusBar === true));
2400
      });
2401
    }
2402
 
2403
  };
2404
 
2405
  var platformName = null, // just the name, like iOS or Android
2406
  platformVersion = null, // a float of the major and minor, like 7.1
2407
  readyCallbacks = [],
2408
  windowLoadListenderAttached;
2409
 
2410
  // setup listeners to know when the device is ready to go
2411
  function onWindowLoad() {
2412
    if (self.isWebView()) {
2413
      // the window and scripts are fully loaded, and a cordova/phonegap
2414
      // object exists then let's listen for the deviceready
2415
      document.addEventListener("deviceready", onPlatformReady, false);
2416
    } else {
2417
      // the window and scripts are fully loaded, but the window object doesn't have the
2418
      // cordova/phonegap object, so its just a browser, not a webview wrapped w/ cordova
2419
      onPlatformReady();
2420
    }
2421
    if (windowLoadListenderAttached) {
2422
      window.removeEventListener("load", onWindowLoad, false);
2423
    }
2424
  }
2425
  if (document.readyState === 'complete') {
2426
    onWindowLoad();
2427
  } else {
2428
    windowLoadListenderAttached = true;
2429
    window.addEventListener("load", onWindowLoad, false);
2430
  }
2431
 
2432
  function onPlatformReady() {
2433
    // the device is all set to go, init our own stuff then fire off our event
2434
    self.isReady = true;
2435
    self.detect();
2436
    for (var x = 0; x < readyCallbacks.length; x++) {
2437
      // fire off all the callbacks that were added before the platform was ready
2438
      readyCallbacks[x]();
2439
    }
2440
    readyCallbacks = [];
2441
    ionic.trigger('platformready', { target: document });
2442
 
2443
    requestAnimationFrame(function() {
2444
      document.body.classList.add('platform-ready');
2445
    });
2446
  }
2447
 
2448
})(this, document, ionic);
2449
 
2450
(function(document, ionic) {
2451
  'use strict';
2452
 
2453
  // Ionic CSS polyfills
2454
  ionic.CSS = {};
2455
 
2456
  (function() {
2457
 
2458
    // transform
2459
    var i, keys = ['webkitTransform', 'transform', '-webkit-transform', 'webkit-transform',
2460
                   '-moz-transform', 'moz-transform', 'MozTransform', 'mozTransform', 'msTransform'];
2461
 
2462
    for (i = 0; i < keys.length; i++) {
2463
      if (document.documentElement.style[keys[i]] !== undefined) {
2464
        ionic.CSS.TRANSFORM = keys[i];
2465
        break;
2466
      }
2467
    }
2468
 
2469
    // transition
2470
    keys = ['webkitTransition', 'mozTransition', 'msTransition', 'transition'];
2471
    for (i = 0; i < keys.length; i++) {
2472
      if (document.documentElement.style[keys[i]] !== undefined) {
2473
        ionic.CSS.TRANSITION = keys[i];
2474
        break;
2475
      }
2476
    }
2477
 
2478
    // The only prefix we care about is webkit for transitions.
2479
    var isWebkit = ionic.CSS.TRANSITION.indexOf('webkit') > -1;
2480
 
2481
    // transition duration
2482
    ionic.CSS.TRANSITION_DURATION = (isWebkit ? '-webkit-' : '') + 'transition-duration';
2483
 
2484
    // To be sure transitionend works everywhere, include *both* the webkit and non-webkit events
2485
    ionic.CSS.TRANSITIONEND = (isWebkit ? 'webkitTransitionEnd ' : '') + 'transitionend';
2486
  })();
2487
 
2488
  // classList polyfill for them older Androids
2489
  // https://gist.github.com/devongovett/1381839
2490
  if (!("classList" in document.documentElement) && Object.defineProperty && typeof HTMLElement !== 'undefined') {
2491
    Object.defineProperty(HTMLElement.prototype, 'classList', {
2492
      get: function() {
2493
        var self = this;
2494
        function update(fn) {
2495
          return function() {
2496
            var x, classes = self.className.split(/\s+/);
2497
 
2498
            for (x = 0; x < arguments.length; x++) {
2499
              fn(classes, classes.indexOf(arguments[x]), arguments[x]);
2500
            }
2501
 
2502
            self.className = classes.join(" ");
2503
          };
2504
        }
2505
 
2506
        return {
2507
          add: update(function(classes, index, value) {
2508
            ~index || classes.push(value);
2509
          }),
2510
 
2511
          remove: update(function(classes, index) {
2512
            ~index && classes.splice(index, 1);
2513
          }),
2514
 
2515
          toggle: update(function(classes, index, value) {
2516
            ~index ? classes.splice(index, 1) : classes.push(value);
2517
          }),
2518
 
2519
          contains: function(value) {
2520
            return !!~self.className.split(/\s+/).indexOf(value);
2521
          },
2522
 
2523
          item: function(i) {
2524
            return self.className.split(/\s+/)[i] || null;
2525
          }
2526
        };
2527
 
2528
      }
2529
    });
2530
  }
2531
 
2532
})(document, ionic);
2533
 
2534
 
2535
/**
2536
 * @ngdoc page
2537
 * @name tap
2538
 * @module ionic
2539
 * @description
2540
 * On touch devices such as a phone or tablet, some browsers implement a 300ms delay between
2541
 * the time the user stops touching the display and the moment the browser executes the
2542
 * click. This delay was initially introduced so the browser can know whether the user wants to
2543
 * double-tap to zoom in on the webpage.  Basically, the browser waits roughly 300ms to see if
2544
 * the user is double-tapping, or just tapping on the display once.
2545
 *
2546
 * Out of the box, Ionic automatically removes the 300ms delay in order to make Ionic apps
2547
 * feel more "native" like. Resultingly, other solutions such as
2548
 * [fastclick](https://github.com/ftlabs/fastclick) and Angular's
2549
 * [ngTouch](https://docs.angularjs.org/api/ngTouch) should not be included, to avoid conflicts.
2550
 *
2551
 * Some browsers already remove the delay with certain settings, such as the CSS property
2552
 * `touch-events: none` or with specific meta tag viewport values. However, each of these
2553
 * browsers still handle clicks differently, such as when to fire off or cancel the event
2554
 * (like scrolling when the target is a button, or holding a button down).
2555
 * For browsers that already remove the 300ms delay, consider Ionic's tap system as a way to
2556
 * normalize how clicks are handled across the various devices so there's an expected response
2557
 * no matter what the device, platform or version. Additionally, Ionic will prevent
2558
 * ghostclicks which even browsers that remove the delay still experience.
2559
 *
2560
 * In some cases, third-party libraries may also be working with touch events which can interfere
2561
 * with the tap system. For example, mapping libraries like Google or Leaflet Maps often implement
2562
 * a touch detection system which conflicts with Ionic's tap system.
2563
 *
2564
 * ### Disabling the tap system
2565
 *
2566
 * To disable the tap for an element and all of its children elements,
2567
 * add the attribute `data-tap-disabled="true"`.
2568
 *
2569
 * ```html
2570
 * <div data-tap-disabled="true">
2571
 *     <div id="google-map"></div>
2572
 * </div>
2573
 * ```
2574
 *
2575
 * ### Additional Notes:
2576
 *
2577
 * - Ionic tap  works with Ionic's JavaScript scrolling
2578
 * - Elements can come and go from the DOM and Ionic tap doesn't keep adding and removing
2579
 *   listeners
2580
 * - No "tap delay" after the first "tap" (you can tap as fast as you want, they all click)
2581
 * - Minimal events listeners, only being added to document
2582
 * - Correct focus in/out on each input type (select, textearea, range) on each platform/device
2583
 * - Shows and hides virtual keyboard correctly for each platform/device
2584
 * - Works with labels surrounding inputs
2585
 * - Does not fire off a click if the user moves the pointer too far
2586
 * - Adds and removes an 'activated' css class
2587
 * - Multiple [unit tests](https://github.com/driftyco/ionic/blob/master/test/unit/utils/tap.unit.js) for each scenario
2588
 *
2589
 */
2590
/*
2591
 
2592
 IONIC TAP
2593
 ---------------
2594
 - Both touch and mouse events are added to the document.body on DOM ready
2595
 - If a touch event happens, it does not use mouse event listeners
2596
 - On touchend, if the distance between start and end was small, trigger a click
2597
 - In the triggered click event, add a 'isIonicTap' property
2598
 - The triggered click receives the same x,y coordinates as as the end event
2599
 - On document.body click listener (with useCapture=true), only allow clicks with 'isIonicTap'
2600
 - Triggering clicks with mouse events work the same as touch, except with mousedown/mouseup
2601
 - Tapping inputs is disabled during scrolling
2602
*/
2603
 
2604
var tapDoc; // the element which the listeners are on (document.body)
2605
var tapActiveEle; // the element which is active (probably has focus)
2606
var tapEnabledTouchEvents;
2607
var tapMouseResetTimer;
2608
var tapPointerMoved;
2609
var tapPointerStart;
2610
var tapTouchFocusedInput;
2611
var tapLastTouchTarget;
2612
var tapTouchMoveListener = 'touchmove';
2613
 
2614
// how much the coordinates can be off between start/end, but still a click
2615
var TAP_RELEASE_TOLERANCE = 12; // default tolerance
2616
var TAP_RELEASE_BUTTON_TOLERANCE = 50; // button elements should have a larger tolerance
2617
 
2618
var tapEventListeners = {
2619
  'click': tapClickGateKeeper,
2620
 
2621
  'mousedown': tapMouseDown,
2622
  'mouseup': tapMouseUp,
2623
  'mousemove': tapMouseMove,
2624
 
2625
  'touchstart': tapTouchStart,
2626
  'touchend': tapTouchEnd,
2627
  'touchcancel': tapTouchCancel,
2628
  'touchmove': tapTouchMove,
2629
 
2630
  'pointerdown': tapTouchStart,
2631
  'pointerup': tapTouchEnd,
2632
  'pointercancel': tapTouchCancel,
2633
  'pointermove': tapTouchMove,
2634
 
2635
  'MSPointerDown': tapTouchStart,
2636
  'MSPointerUp': tapTouchEnd,
2637
  'MSPointerCancel': tapTouchCancel,
2638
  'MSPointerMove': tapTouchMove,
2639
 
2640
  'focusin': tapFocusIn,
2641
  'focusout': tapFocusOut
2642
};
2643
 
2644
ionic.tap = {
2645
 
2646
  register: function(ele) {
2647
    tapDoc = ele;
2648
 
2649
    tapEventListener('click', true, true);
2650
    tapEventListener('mouseup');
2651
    tapEventListener('mousedown');
2652
 
2653
    if (window.navigator.pointerEnabled) {
2654
      tapEventListener('pointerdown');
2655
      tapEventListener('pointerup');
2656
      tapEventListener('pointcancel');
2657
      tapTouchMoveListener = 'pointermove';
2658
 
2659
    } else if (window.navigator.msPointerEnabled) {
2660
      tapEventListener('MSPointerDown');
2661
      tapEventListener('MSPointerUp');
2662
      tapEventListener('MSPointerCancel');
2663
      tapTouchMoveListener = 'MSPointerMove';
2664
 
2665
    } else {
2666
      tapEventListener('touchstart');
2667
      tapEventListener('touchend');
2668
      tapEventListener('touchcancel');
2669
    }
2670
 
2671
    tapEventListener('focusin');
2672
    tapEventListener('focusout');
2673
 
2674
    return function() {
2675
      for (var type in tapEventListeners) {
2676
        tapEventListener(type, false);
2677
      }
2678
      tapDoc = null;
2679
      tapActiveEle = null;
2680
      tapEnabledTouchEvents = false;
2681
      tapPointerMoved = false;
2682
      tapPointerStart = null;
2683
    };
2684
  },
2685
 
2686
  ignoreScrollStart: function(e) {
2687
    return (e.defaultPrevented) ||  // defaultPrevented has been assigned by another component handling the event
2688
           (/^(file|range)$/i).test(e.target.type) ||
2689
           (e.target.dataset ? e.target.dataset.preventScroll : e.target.getAttribute('data-prevent-scroll')) == 'true' || // manually set within an elements attributes
2690
           (!!(/^(object|embed)$/i).test(e.target.tagName)) ||  // flash/movie/object touches should not try to scroll
2691
           ionic.tap.isElementTapDisabled(e.target); // check if this element, or an ancestor, has `data-tap-disabled` attribute
2692
  },
2693
 
2694
  isTextInput: function(ele) {
2695
    return !!ele &&
2696
           (ele.tagName == 'TEXTAREA' ||
2697
            ele.contentEditable === 'true' ||
2698
            (ele.tagName == 'INPUT' && !(/^(radio|checkbox|range|file|submit|reset|color|image|button)$/i).test(ele.type)));
2699
  },
2700
 
2701
  isDateInput: function(ele) {
2702
    return !!ele &&
2703
            (ele.tagName == 'INPUT' && (/^(date|time|datetime-local|month|week)$/i).test(ele.type));
2704
  },
2705
 
2706
  isKeyboardElement: function(ele) {
2707
    if ( !ionic.Platform.isIOS() || ionic.Platform.isIPad() ) {
2708
      return ionic.tap.isTextInput(ele) && !ionic.tap.isDateInput(ele);
2709
    } else {
2710
      return ionic.tap.isTextInput(ele) || ( !!ele && ele.tagName == "SELECT");
2711
    }
2712
  },
2713
 
2714
  isLabelWithTextInput: function(ele) {
2715
    var container = tapContainingElement(ele, false);
2716
 
2717
    return !!container &&
2718
           ionic.tap.isTextInput(tapTargetElement(container));
2719
  },
2720
 
2721
  containsOrIsTextInput: function(ele) {
2722
    return ionic.tap.isTextInput(ele) || ionic.tap.isLabelWithTextInput(ele);
2723
  },
2724
 
2725
  cloneFocusedInput: function(container) {
2726
    if (ionic.tap.hasCheckedClone) return;
2727
    ionic.tap.hasCheckedClone = true;
2728
 
2729
    ionic.requestAnimationFrame(function() {
2730
      var focusInput = container.querySelector(':focus');
2731
      if (ionic.tap.isTextInput(focusInput)) {
2732
        var clonedInput = focusInput.cloneNode(true);
2733
 
2734
        clonedInput.value = focusInput.value;
2735
        clonedInput.classList.add('cloned-text-input');
2736
        clonedInput.readOnly = true;
2737
        if (focusInput.isContentEditable) {
2738
          clonedInput.contentEditable = focusInput.contentEditable;
2739
          clonedInput.innerHTML = focusInput.innerHTML;
2740
        }
2741
        focusInput.parentElement.insertBefore(clonedInput, focusInput);
2742
        focusInput.classList.add('previous-input-focus');
2743
 
2744
        clonedInput.scrollTop = focusInput.scrollTop;
2745
      }
2746
    });
2747
  },
2748
 
2749
  hasCheckedClone: false,
2750
 
2751
  removeClonedInputs: function(container) {
2752
    ionic.tap.hasCheckedClone = false;
2753
 
2754
    ionic.requestAnimationFrame(function() {
2755
      var clonedInputs = container.querySelectorAll('.cloned-text-input');
2756
      var previousInputFocus = container.querySelectorAll('.previous-input-focus');
2757
      var x;
2758
 
2759
      for (x = 0; x < clonedInputs.length; x++) {
2760
        clonedInputs[x].parentElement.removeChild(clonedInputs[x]);
2761
      }
2762
 
2763
      for (x = 0; x < previousInputFocus.length; x++) {
2764
        previousInputFocus[x].classList.remove('previous-input-focus');
2765
        previousInputFocus[x].style.top = '';
2766
        if ( ionic.keyboard.isOpen && !ionic.keyboard.isClosing ) previousInputFocus[x].focus();
2767
      }
2768
    });
2769
  },
2770
 
2771
  requiresNativeClick: function(ele) {
2772
    if (!ele || ele.disabled || (/^(file|range)$/i).test(ele.type) || (/^(object|video)$/i).test(ele.tagName) || ionic.tap.isLabelContainingFileInput(ele)) {
2773
      return true;
2774
    }
2775
    return ionic.tap.isElementTapDisabled(ele);
2776
  },
2777
 
2778
  isLabelContainingFileInput: function(ele) {
2779
    var lbl = tapContainingElement(ele);
2780
    if (lbl.tagName !== 'LABEL') return false;
2781
    var fileInput = lbl.querySelector('input[type=file]');
2782
    if (fileInput && fileInput.disabled === false) return true;
2783
    return false;
2784
  },
2785
 
2786
  isElementTapDisabled: function(ele) {
2787
    if (ele && ele.nodeType === 1) {
2788
      var element = ele;
2789
      while (element) {
2790
        if ((element.dataset ? element.dataset.tapDisabled : element.getAttribute('data-tap-disabled')) == 'true') {
2791
          return true;
2792
        }
2793
        element = element.parentElement;
2794
      }
2795
    }
2796
    return false;
2797
  },
2798
 
2799
  setTolerance: function(releaseTolerance, releaseButtonTolerance) {
2800
    TAP_RELEASE_TOLERANCE = releaseTolerance;
2801
    TAP_RELEASE_BUTTON_TOLERANCE = releaseButtonTolerance;
2802
  },
2803
 
2804
  cancelClick: function() {
2805
    // used to cancel any simulated clicks which may happen on a touchend/mouseup
2806
    // gestures uses this method within its tap and hold events
2807
    tapPointerMoved = true;
2808
  },
2809
 
2810
  pointerCoord: function(event) {
2811
    // This method can get coordinates for both a mouse click
2812
    // or a touch depending on the given event
2813
    var c = { x: 0, y: 0 };
2814
    if (event) {
2815
      var touches = event.touches && event.touches.length ? event.touches : [event];
2816
      var e = (event.changedTouches && event.changedTouches[0]) || touches[0];
2817
      if (e) {
2818
        c.x = e.clientX || e.pageX || 0;
2819
        c.y = e.clientY || e.pageY || 0;
2820
      }
2821
    }
2822
    return c;
2823
  }
2824
 
2825
};
2826
 
2827
function tapEventListener(type, enable, useCapture) {
2828
  if (enable !== false) {
2829
    tapDoc.addEventListener(type, tapEventListeners[type], useCapture);
2830
  } else {
2831
    tapDoc.removeEventListener(type, tapEventListeners[type]);
2832
  }
2833
}
2834
 
2835
function tapClick(e) {
2836
  // simulate a normal click by running the element's click method then focus on it
2837
  var container = tapContainingElement(e.target);
2838
  var ele = tapTargetElement(container);
2839
 
2840
  if (ionic.tap.requiresNativeClick(ele) || tapPointerMoved) return false;
2841
 
2842
  var c = ionic.tap.pointerCoord(e);
2843
 
2844
  //console.log('tapClick', e.type, ele.tagName, '('+c.x+','+c.y+')');
2845
  triggerMouseEvent('click', ele, c.x, c.y);
2846
 
2847
  // if it's an input, focus in on the target, otherwise blur
2848
  tapHandleFocus(ele);
2849
}
2850
 
2851
function triggerMouseEvent(type, ele, x, y) {
2852
  // using initMouseEvent instead of MouseEvent for our Android friends
2853
  var clickEvent = document.createEvent("MouseEvents");
2854
  clickEvent.initMouseEvent(type, true, true, window, 1, 0, 0, x, y, false, false, false, false, 0, null);
2855
  clickEvent.isIonicTap = true;
2856
  ele.dispatchEvent(clickEvent);
2857
}
2858
 
2859
function tapClickGateKeeper(e) {
2860
  //console.log('click ' + Date.now() + ' isIonicTap: ' + (e.isIonicTap ? true : false));
2861
  if (e.target.type == 'submit' && e.detail === 0) {
2862
    // do not prevent click if it came from an "Enter" or "Go" keypress submit
2863
    return null;
2864
  }
2865
 
2866
  // do not allow through any click events that were not created by ionic.tap
2867
  if ((ionic.scroll.isScrolling && ionic.tap.containsOrIsTextInput(e.target)) ||
2868
      (!e.isIonicTap && !ionic.tap.requiresNativeClick(e.target))) {
2869
    //console.log('clickPrevent', e.target.tagName);
2870
    e.stopPropagation();
2871
 
2872
    if (!ionic.tap.isLabelWithTextInput(e.target)) {
2873
      // labels clicks from native should not preventDefault othersize keyboard will not show on input focus
2874
      e.preventDefault();
2875
    }
2876
    return false;
2877
  }
2878
}
2879
 
2880
// MOUSE
2881
function tapMouseDown(e) {
2882
  //console.log('mousedown ' + Date.now());
2883
  if (e.isIonicTap || tapIgnoreEvent(e)) return null;
2884
 
2885
  if (tapEnabledTouchEvents) {
2886
    void 0;
2887
    e.stopPropagation();
2888
 
2889
    if ((!ionic.tap.isTextInput(e.target) || tapLastTouchTarget !== e.target) && !(/^(select|option)$/i).test(e.target.tagName)) {
2890
      // If you preventDefault on a text input then you cannot move its text caret/cursor.
2891
      // Allow through only the text input default. However, without preventDefault on an
2892
      // input the 300ms delay can change focus on inputs after the keyboard shows up.
2893
      // The focusin event handles the chance of focus changing after the keyboard shows.
2894
      e.preventDefault();
2895
    }
2896
 
2897
    return false;
2898
  }
2899
 
2900
  tapPointerMoved = false;
2901
  tapPointerStart = ionic.tap.pointerCoord(e);
2902
 
2903
  tapEventListener('mousemove');
2904
  ionic.activator.start(e);
2905
}
2906
 
2907
function tapMouseUp(e) {
2908
  //console.log("mouseup " + Date.now());
2909
  if (tapEnabledTouchEvents) {
2910
    e.stopPropagation();
2911
    e.preventDefault();
2912
    return false;
2913
  }
2914
 
2915
  if (tapIgnoreEvent(e) || (/^(select|option)$/i).test(e.target.tagName)) return false;
2916
 
2917
  if (!tapHasPointerMoved(e)) {
2918
    tapClick(e);
2919
  }
2920
  tapEventListener('mousemove', false);
2921
  ionic.activator.end();
2922
  tapPointerMoved = false;
2923
}
2924
 
2925
function tapMouseMove(e) {
2926
  if (tapHasPointerMoved(e)) {
2927
    tapEventListener('mousemove', false);
2928
    ionic.activator.end();
2929
    tapPointerMoved = true;
2930
    return false;
2931
  }
2932
}
2933
 
2934
 
2935
// TOUCH
2936
function tapTouchStart(e) {
2937
  //console.log("touchstart " + Date.now());
2938
  if (tapIgnoreEvent(e)) return;
2939
 
2940
  tapPointerMoved = false;
2941
 
2942
  tapEnableTouchEvents();
2943
  tapPointerStart = ionic.tap.pointerCoord(e);
2944
 
2945
  tapEventListener(tapTouchMoveListener);
2946
  ionic.activator.start(e);
2947
 
2948
  if (ionic.Platform.isIOS() && ionic.tap.isLabelWithTextInput(e.target)) {
2949
    // if the tapped element is a label, which has a child input
2950
    // then preventDefault so iOS doesn't ugly auto scroll to the input
2951
    // but do not prevent default on Android or else you cannot move the text caret
2952
    // and do not prevent default on Android or else no virtual keyboard shows up
2953
 
2954
    var textInput = tapTargetElement(tapContainingElement(e.target));
2955
    if (textInput !== tapActiveEle) {
2956
      // don't preventDefault on an already focused input or else iOS's text caret isn't usable
2957
      e.preventDefault();
2958
    }
2959
  }
2960
}
2961
 
2962
function tapTouchEnd(e) {
2963
  //console.log('touchend ' + Date.now());
2964
  if (tapIgnoreEvent(e)) return;
2965
 
2966
  tapEnableTouchEvents();
2967
  if (!tapHasPointerMoved(e)) {
2968
    tapClick(e);
2969
 
2970
    if ((/^(select|option)$/i).test(e.target.tagName)) {
2971
      e.preventDefault();
2972
    }
2973
  }
2974
 
2975
  tapLastTouchTarget = e.target;
2976
  tapTouchCancel();
2977
}
2978
 
2979
function tapTouchMove(e) {
2980
  if (tapHasPointerMoved(e)) {
2981
    tapPointerMoved = true;
2982
    tapEventListener(tapTouchMoveListener, false);
2983
    ionic.activator.end();
2984
    return false;
2985
  }
2986
}
2987
 
2988
function tapTouchCancel() {
2989
  tapEventListener(tapTouchMoveListener, false);
2990
  ionic.activator.end();
2991
  tapPointerMoved = false;
2992
}
2993
 
2994
function tapEnableTouchEvents() {
2995
  tapEnabledTouchEvents = true;
2996
  clearTimeout(tapMouseResetTimer);
2997
  tapMouseResetTimer = setTimeout(function() {
2998
    tapEnabledTouchEvents = false;
2999
  }, 600);
3000
}
3001
 
3002
function tapIgnoreEvent(e) {
3003
  if (e.isTapHandled) return true;
3004
  e.isTapHandled = true;
3005
 
3006
  if (ionic.scroll.isScrolling && ionic.tap.containsOrIsTextInput(e.target)) {
3007
    e.preventDefault();
3008
    return true;
3009
  }
3010
}
3011
 
3012
function tapHandleFocus(ele) {
3013
  tapTouchFocusedInput = null;
3014
 
3015
  var triggerFocusIn = false;
3016
 
3017
  if (ele.tagName == 'SELECT') {
3018
    // trick to force Android options to show up
3019
    triggerMouseEvent('mousedown', ele, 0, 0);
3020
    ele.focus && ele.focus();
3021
    triggerFocusIn = true;
3022
 
3023
  } else if (tapActiveElement() === ele) {
3024
    // already is the active element and has focus
3025
    triggerFocusIn = true;
3026
 
3027
  } else if ((/^(input|textarea)$/i).test(ele.tagName) || ele.isContentEditable) {
3028
    triggerFocusIn = true;
3029
    ele.focus && ele.focus();
3030
    ele.value = ele.value;
3031
    if (tapEnabledTouchEvents) {
3032
      tapTouchFocusedInput = ele;
3033
    }
3034
 
3035
  } else {
3036
    tapFocusOutActive();
3037
  }
3038
 
3039
  if (triggerFocusIn) {
3040
    tapActiveElement(ele);
3041
    ionic.trigger('ionic.focusin', {
3042
      target: ele
3043
    }, true);
3044
  }
3045
}
3046
 
3047
function tapFocusOutActive() {
3048
  var ele = tapActiveElement();
3049
  if (ele && ((/^(input|textarea|select)$/i).test(ele.tagName) || ele.isContentEditable)) {
3050
    void 0;
3051
    ele.blur();
3052
  }
3053
  tapActiveElement(null);
3054
}
3055
 
3056
function tapFocusIn(e) {
3057
  //console.log('focusin ' + Date.now());
3058
  // Because a text input doesn't preventDefault (so the caret still works) there's a chance
3059
  // that its mousedown event 300ms later will change the focus to another element after
3060
  // the keyboard shows up.
3061
 
3062
  if (tapEnabledTouchEvents &&
3063
      ionic.tap.isTextInput(tapActiveElement()) &&
3064
      ionic.tap.isTextInput(tapTouchFocusedInput) &&
3065
      tapTouchFocusedInput !== e.target) {
3066
 
3067
    // 1) The pointer is from touch events
3068
    // 2) There is an active element which is a text input
3069
    // 3) A text input was just set to be focused on by a touch event
3070
    // 4) A new focus has been set, however the target isn't the one the touch event wanted
3071
    void 0;
3072
    tapTouchFocusedInput.focus();
3073
    tapTouchFocusedInput = null;
3074
  }
3075
  ionic.scroll.isScrolling = false;
3076
}
3077
 
3078
function tapFocusOut() {
3079
  //console.log("focusout");
3080
  tapActiveElement(null);
3081
}
3082
 
3083
function tapActiveElement(ele) {
3084
  if (arguments.length) {
3085
    tapActiveEle = ele;
3086
  }
3087
  return tapActiveEle || document.activeElement;
3088
}
3089
 
3090
function tapHasPointerMoved(endEvent) {
3091
  if (!endEvent || endEvent.target.nodeType !== 1 || !tapPointerStart || (tapPointerStart.x === 0 && tapPointerStart.y === 0)) {
3092
    return false;
3093
  }
3094
  var endCoordinates = ionic.tap.pointerCoord(endEvent);
3095
 
3096
  var hasClassList = !!(endEvent.target.classList && endEvent.target.classList.contains &&
3097
    typeof endEvent.target.classList.contains === 'function');
3098
  var releaseTolerance = hasClassList && endEvent.target.classList.contains('button') ?
3099
    TAP_RELEASE_BUTTON_TOLERANCE :
3100
    TAP_RELEASE_TOLERANCE;
3101
 
3102
  return Math.abs(tapPointerStart.x - endCoordinates.x) > releaseTolerance ||
3103
         Math.abs(tapPointerStart.y - endCoordinates.y) > releaseTolerance;
3104
}
3105
 
3106
function tapContainingElement(ele, allowSelf) {
3107
  var climbEle = ele;
3108
  for (var x = 0; x < 6; x++) {
3109
    if (!climbEle) break;
3110
    if (climbEle.tagName === 'LABEL') return climbEle;
3111
    climbEle = climbEle.parentElement;
3112
  }
3113
  if (allowSelf !== false) return ele;
3114
}
3115
 
3116
function tapTargetElement(ele) {
3117
  if (ele && ele.tagName === 'LABEL') {
3118
    if (ele.control) return ele.control;
3119
 
3120
    // older devices do not support the "control" property
3121
    if (ele.querySelector) {
3122
      var control = ele.querySelector('input,textarea,select');
3123
      if (control) return control;
3124
    }
3125
  }
3126
  return ele;
3127
}
3128
 
3129
ionic.DomUtil.ready(function() {
3130
  var ng = typeof angular !== 'undefined' ? angular : null;
3131
  //do nothing for e2e tests
3132
  if (!ng || (ng && !ng.scenario)) {
3133
    ionic.tap.register(document);
3134
  }
3135
});
3136
 
3137
(function(document, ionic) {
3138
  'use strict';
3139
 
3140
  var queueElements = {};   // elements that should get an active state in XX milliseconds
3141
  var activeElements = {};  // elements that are currently active
3142
  var keyId = 0;            // a counter for unique keys for the above ojects
3143
  var ACTIVATED_CLASS = 'activated';
3144
 
3145
  ionic.activator = {
3146
 
3147
    start: function(e) {
3148
      var hitX = ionic.tap.pointerCoord(e).x;
3149
      if (hitX > 0 && hitX < 30) {
3150
        return;
3151
      }
3152
 
3153
      // when an element is touched/clicked, it climbs up a few
3154
      // parents to see if it is an .item or .button element
3155
      ionic.requestAnimationFrame(function() {
3156
        if ((ionic.scroll && ionic.scroll.isScrolling) || ionic.tap.requiresNativeClick(e.target)) return;
3157
        var ele = e.target;
3158
        var eleToActivate;
3159
 
3160
        for (var x = 0; x < 6; x++) {
3161
          if (!ele || ele.nodeType !== 1) break;
3162
          if (eleToActivate && ele.classList && ele.classList.contains('item')) {
3163
            eleToActivate = ele;
3164
            break;
3165
          }
3166
          if (ele.tagName == 'A' || ele.tagName == 'BUTTON' || ele.hasAttribute('ng-click')) {
3167
            eleToActivate = ele;
3168
            break;
3169
          }
3170
          if (ele.classList.contains('button')) {
3171
            eleToActivate = ele;
3172
            break;
3173
          }
3174
          // no sense climbing past these
3175
          if (ele.tagName == 'ION-CONTENT' || (ele.classList && ele.classList.contains('pane')) || ele.tagName == 'BODY') {
3176
            break;
3177
          }
3178
          ele = ele.parentElement;
3179
        }
3180
 
3181
        if (eleToActivate) {
3182
          // queue that this element should be set to active
3183
          queueElements[keyId] = eleToActivate;
3184
 
3185
          // on the next frame, set the queued elements to active
3186
          ionic.requestAnimationFrame(activateElements);
3187
 
3188
          keyId = (keyId > 29 ? 0 : keyId + 1);
3189
        }
3190
 
3191
      });
3192
    },
3193
 
3194
    end: function() {
3195
      // clear out any active/queued elements after XX milliseconds
3196
      setTimeout(clear, 200);
3197
    }
3198
 
3199
  };
3200
 
3201
  function clear() {
3202
    // clear out any elements that are queued to be set to active
3203
    queueElements = {};
3204
 
3205
    // in the next frame, remove the active class from all active elements
3206
    ionic.requestAnimationFrame(deactivateElements);
3207
  }
3208
 
3209
  function activateElements() {
3210
    // activate all elements in the queue
3211
    for (var key in queueElements) {
3212
      if (queueElements[key]) {
3213
        queueElements[key].classList.add(ACTIVATED_CLASS);
3214
        activeElements[key] = queueElements[key];
3215
      }
3216
    }
3217
    queueElements = {};
3218
  }
3219
 
3220
  function deactivateElements() {
3221
    if (ionic.transition && ionic.transition.isActive) {
3222
      setTimeout(deactivateElements, 400);
3223
      return;
3224
    }
3225
 
3226
    for (var key in activeElements) {
3227
      if (activeElements[key]) {
3228
        activeElements[key].classList.remove(ACTIVATED_CLASS);
3229
        delete activeElements[key];
3230
      }
3231
    }
3232
  }
3233
 
3234
})(document, ionic);
3235
 
3236
(function(ionic) {
3237
  /* for nextUid function below */
3238
  var nextId = 0;
3239
 
3240
  /**
3241
   * Various utilities used throughout Ionic
3242
   *
3243
   * Some of these are adopted from underscore.js and backbone.js, both also MIT licensed.
3244
   */
3245
  ionic.Utils = {
3246
 
3247
    arrayMove: function(arr, oldIndex, newIndex) {
3248
      if (newIndex >= arr.length) {
3249
        var k = newIndex - arr.length;
3250
        while ((k--) + 1) {
3251
          arr.push(undefined);
3252
        }
3253
      }
3254
      arr.splice(newIndex, 0, arr.splice(oldIndex, 1)[0]);
3255
      return arr;
3256
    },
3257
 
3258
    /**
3259
     * Return a function that will be called with the given context
3260
     */
3261
    proxy: function(func, context) {
3262
      var args = Array.prototype.slice.call(arguments, 2);
3263
      return function() {
3264
        return func.apply(context, args.concat(Array.prototype.slice.call(arguments)));
3265
      };
3266
    },
3267
 
3268
    /**
3269
     * Only call a function once in the given interval.
3270
     *
3271
     * @param func {Function} the function to call
3272
     * @param wait {int} how long to wait before/after to allow function calls
3273
     * @param immediate {boolean} whether to call immediately or after the wait interval
3274
     */
3275
     debounce: function(func, wait, immediate) {
3276
      var timeout, args, context, timestamp, result;
3277
      return function() {
3278
        context = this;
3279
        args = arguments;
3280
        timestamp = new Date();
3281
        var later = function() {
3282
          var last = (new Date()) - timestamp;
3283
          if (last < wait) {
3284
            timeout = setTimeout(later, wait - last);
3285
          } else {
3286
            timeout = null;
3287
            if (!immediate) result = func.apply(context, args);
3288
          }
3289
        };
3290
        var callNow = immediate && !timeout;
3291
        if (!timeout) {
3292
          timeout = setTimeout(later, wait);
3293
        }
3294
        if (callNow) result = func.apply(context, args);
3295
        return result;
3296
      };
3297
    },
3298
 
3299
    /**
3300
     * Throttle the given fun, only allowing it to be
3301
     * called at most every `wait` ms.
3302
     */
3303
    throttle: function(func, wait, options) {
3304
      var context, args, result;
3305
      var timeout = null;
3306
      var previous = 0;
3307
      options || (options = {});
3308
      var later = function() {
3309
        previous = options.leading === false ? 0 : Date.now();
3310
        timeout = null;
3311
        result = func.apply(context, args);
3312
      };
3313
      return function() {
3314
        var now = Date.now();
3315
        if (!previous && options.leading === false) previous = now;
3316
        var remaining = wait - (now - previous);
3317
        context = this;
3318
        args = arguments;
3319
        if (remaining <= 0) {
3320
          clearTimeout(timeout);
3321
          timeout = null;
3322
          previous = now;
3323
          result = func.apply(context, args);
3324
        } else if (!timeout && options.trailing !== false) {
3325
          timeout = setTimeout(later, remaining);
3326
        }
3327
        return result;
3328
      };
3329
    },
3330
     // Borrowed from Backbone.js's extend
3331
     // Helper function to correctly set up the prototype chain, for subclasses.
3332
     // Similar to `goog.inherits`, but uses a hash of prototype properties and
3333
     // class properties to be extended.
3334
    inherit: function(protoProps, staticProps) {
3335
      var parent = this;
3336
      var child;
3337
 
3338
      // The constructor function for the new subclass is either defined by you
3339
      // (the "constructor" property in your `extend` definition), or defaulted
3340
      // by us to simply call the parent's constructor.
3341
      if (protoProps && protoProps.hasOwnProperty('constructor')) {
3342
        child = protoProps.constructor;
3343
      } else {
3344
        child = function() { return parent.apply(this, arguments); };
3345
      }
3346
 
3347
      // Add static properties to the constructor function, if supplied.
3348
      ionic.extend(child, parent, staticProps);
3349
 
3350
      // Set the prototype chain to inherit from `parent`, without calling
3351
      // `parent`'s constructor function.
3352
      var Surrogate = function() { this.constructor = child; };
3353
      Surrogate.prototype = parent.prototype;
3354
      child.prototype = new Surrogate();
3355
 
3356
      // Add prototype properties (instance properties) to the subclass,
3357
      // if supplied.
3358
      if (protoProps) ionic.extend(child.prototype, protoProps);
3359
 
3360
      // Set a convenience property in case the parent's prototype is needed
3361
      // later.
3362
      child.__super__ = parent.prototype;
3363
 
3364
      return child;
3365
    },
3366
 
3367
    // Extend adapted from Underscore.js
3368
    extend: function(obj) {
3369
       var args = Array.prototype.slice.call(arguments, 1);
3370
       for (var i = 0; i < args.length; i++) {
3371
         var source = args[i];
3372
         if (source) {
3373
           for (var prop in source) {
3374
             obj[prop] = source[prop];
3375
           }
3376
         }
3377
       }
3378
       return obj;
3379
    },
3380
 
3381
    nextUid: function() {
3382
      return 'ion' + (nextId++);
3383
    },
3384
 
3385
    disconnectScope: function disconnectScope(scope) {
3386
      if (!scope) return;
3387
 
3388
      if (scope.$root === scope) {
3389
        return; // we can't disconnect the root node;
3390
      }
3391
      var parent = scope.$parent;
3392
      scope.$$disconnected = true;
3393
      scope.$broadcast('$ionic.disconnectScope', scope);
3394
 
3395
      // See Scope.$destroy
3396
      if (parent.$$childHead === scope) {
3397
        parent.$$childHead = scope.$$nextSibling;
3398
      }
3399
      if (parent.$$childTail === scope) {
3400
        parent.$$childTail = scope.$$prevSibling;
3401
      }
3402
      if (scope.$$prevSibling) {
3403
        scope.$$prevSibling.$$nextSibling = scope.$$nextSibling;
3404
      }
3405
      if (scope.$$nextSibling) {
3406
        scope.$$nextSibling.$$prevSibling = scope.$$prevSibling;
3407
      }
3408
      scope.$$nextSibling = scope.$$prevSibling = null;
3409
    },
3410
 
3411
    reconnectScope: function reconnectScope(scope) {
3412
      if (!scope) return;
3413
 
3414
      if (scope.$root === scope) {
3415
        return; // we can't disconnect the root node;
3416
      }
3417
      if (!scope.$$disconnected) {
3418
        return;
3419
      }
3420
      var parent = scope.$parent;
3421
      scope.$$disconnected = false;
3422
      scope.$broadcast('$ionic.reconnectScope', scope);
3423
      // See Scope.$new for this logic...
3424
      scope.$$prevSibling = parent.$$childTail;
3425
      if (parent.$$childHead) {
3426
        parent.$$childTail.$$nextSibling = scope;
3427
        parent.$$childTail = scope;
3428
      } else {
3429
        parent.$$childHead = parent.$$childTail = scope;
3430
      }
3431
    },
3432
 
3433
    isScopeDisconnected: function(scope) {
3434
      var climbScope = scope;
3435
      while (climbScope) {
3436
        if (climbScope.$$disconnected) return true;
3437
        climbScope = climbScope.$parent;
3438
      }
3439
      return false;
3440
    }
3441
  };
3442
 
3443
  // Bind a few of the most useful functions to the ionic scope
3444
  ionic.inherit = ionic.Utils.inherit;
3445
  ionic.extend = ionic.Utils.extend;
3446
  ionic.throttle = ionic.Utils.throttle;
3447
  ionic.proxy = ionic.Utils.proxy;
3448
  ionic.debounce = ionic.Utils.debounce;
3449
 
3450
})(window.ionic);
3451
 
3452
/**
3453
 * @ngdoc page
3454
 * @name keyboard
3455
 * @module ionic
3456
 * @description
3457
 * On both Android and iOS, Ionic will attempt to prevent the keyboard from
3458
 * obscuring inputs and focusable elements when it appears by scrolling them
3459
 * into view.  In order for this to work, any focusable elements must be within
3460
 * a [Scroll View](http://ionicframework.com/docs/api/directive/ionScroll/)
3461
 * or a directive such as [Content](http://ionicframework.com/docs/api/directive/ionContent/)
3462
 * that has a Scroll View.
3463
 *
3464
 * It will also attempt to prevent the native overflow scrolling on focus,
3465
 * which can cause layout issues such as pushing headers up and out of view.
3466
 *
3467
 * The keyboard fixes work best in conjunction with the
3468
 * [Ionic Keyboard Plugin](https://github.com/driftyco/ionic-plugins-keyboard),
3469
 * although it will perform reasonably well without.  However, if you are using
3470
 * Cordova there is no reason not to use the plugin.
3471
 *
3472
 * ### Hide when keyboard shows
3473
 *
3474
 * To hide an element when the keyboard is open, add the class `hide-on-keyboard-open`.
3475
 *
3476
 * ```html
3477
 * <div class="hide-on-keyboard-open">
3478
 *   <div id="google-map"></div>
3479
 * </div>
3480
 * ```
3481
 * ----------
3482
 *
3483
 * ### Plugin Usage
3484
 * Information on using the plugin can be found at
3485
 * [https://github.com/driftyco/ionic-plugins-keyboard](https://github.com/driftyco/ionic-plugins-keyboard).
3486
 *
3487
 * ----------
3488
 *
3489
 * ### Android Notes
3490
 * - If your app is running in fullscreen, i.e. you have
3491
 *   `<preference name="Fullscreen" value="true" />` in your `config.xml` file
3492
 *   you will need to set `ionic.Platform.isFullScreen = true` manually.
3493
 *
3494
 * - You can configure the behavior of the web view when the keyboard shows by setting
3495
 *   [android:windowSoftInputMode](http://developer.android.com/reference/android/R.attr.html#windowSoftInputMode)
3496
 *   to either `adjustPan`, `adjustResize` or `adjustNothing` in your app's
3497
 *   activity in `AndroidManifest.xml`. `adjustResize` is the recommended setting
3498
 *   for Ionic, but if for some reason you do use `adjustPan` you will need to
3499
 *   set `ionic.Platform.isFullScreen = true`.
3500
 *
3501
 *   ```xml
3502
 *   <activity android:windowSoftInputMode="adjustResize">
3503
 *
3504
 *   ```
3505
 *
3506
 * ### iOS Notes
3507
 * - If the content of your app (including the header) is being pushed up and
3508
 *   out of view on input focus, try setting `cordova.plugins.Keyboard.disableScroll(true)`.
3509
 *   This does **not** disable scrolling in the Ionic scroll view, rather it
3510
 *   disables the native overflow scrolling that happens automatically as a
3511
 *   result of focusing on inputs below the keyboard.
3512
 *
3513
 */
3514
 
3515
/**
3516
 * The current viewport height.
3517
 */
3518
var keyboardCurrentViewportHeight = 0;
3519
 
3520
/**
3521
 * The viewport height when in portrait orientation.
3522
 */
3523
var keyboardPortraitViewportHeight = 0;
3524
 
3525
/**
3526
 * The viewport height when in landscape orientation.
3527
 */
3528
var keyboardLandscapeViewportHeight = 0;
3529
 
3530
/**
3531
 * The currently focused input.
3532
 */
3533
var keyboardActiveElement;
3534
 
3535
/**
3536
 * The scroll view containing the currently focused input.
3537
 */
3538
var scrollView;
3539
 
3540
/**
3541
 * Timer for the setInterval that polls window.innerHeight to determine whether
3542
 * the layout has updated for the keyboard showing/hiding.
3543
 */
3544
var waitForResizeTimer;
3545
 
3546
/**
3547
 * Sometimes when switching inputs or orientations, focusout will fire before
3548
 * focusin, so this timer is for the small setTimeout to determine if we should
3549
 * really focusout/hide the keyboard.
3550
 */
3551
var keyboardFocusOutTimer;
3552
 
3553
/**
3554
 * on Android, orientationchange will fire before the keyboard plugin notifies
3555
 * the browser that the keyboard will show/is showing, so this flag indicates
3556
 * to nativeShow that there was an orientationChange and we should update
3557
 * the viewport height with an accurate keyboard height value
3558
 */
3559
var wasOrientationChange = false;
3560
 
3561
/**
3562
 * CSS class added to the body indicating the keyboard is open.
3563
 */
3564
var KEYBOARD_OPEN_CSS = 'keyboard-open';
3565
 
3566
/**
3567
 * CSS class that indicates a scroll container.
3568
 */
3569
var SCROLL_CONTAINER_CSS = 'scroll-content';
3570
 
3571
/**
3572
 * Debounced keyboardFocusIn function
3573
 */
3574
var debouncedKeyboardFocusIn = ionic.debounce(keyboardFocusIn, 200, true);
3575
 
3576
/**
3577
 * Debounced keyboardNativeShow function
3578
 */
3579
var debouncedKeyboardNativeShow = ionic.debounce(keyboardNativeShow, 100, true);
3580
 
3581
/**
3582
 * Ionic keyboard namespace.
3583
 * @namespace keyboard
3584
 */
3585
ionic.keyboard = {
3586
 
3587
  /**
3588
   * Whether the keyboard is open or not.
3589
   */
3590
  isOpen: false,
3591
 
3592
  /**
3593
   * Whether the keyboard is closing or not.
3594
   */
3595
  isClosing: false,
3596
 
3597
  /**
3598
   * Whether the keyboard is opening or not.
3599
   */
3600
  isOpening: false,
3601
 
3602
  /**
3603
   * The height of the keyboard in pixels, as reported by the keyboard plugin.
3604
   * If the plugin is not available, calculated as the difference in
3605
   * window.innerHeight after the keyboard has shown.
3606
   */
3607
  height: 0,
3608
 
3609
  /**
3610
   * Whether the device is in landscape orientation or not.
3611
   */
3612
  isLandscape: false,
3613
 
3614
  /**
3615
   * Whether the keyboard event listeners have been added or not
3616
   */
3617
  isInitialized: false,
3618
 
3619
  /**
3620
   * Hide the keyboard, if it is open.
3621
   */
3622
  hide: function() {
3623
    if (keyboardHasPlugin()) {
3624
      cordova.plugins.Keyboard.close();
3625
    }
3626
    keyboardActiveElement && keyboardActiveElement.blur();
3627
  },
3628
 
3629
  /**
3630
   * An alias for cordova.plugins.Keyboard.show(). If the keyboard plugin
3631
   * is installed, show the keyboard.
3632
   */
3633
  show: function() {
3634
    if (keyboardHasPlugin()) {
3635
      cordova.plugins.Keyboard.show();
3636
    }
3637
  },
3638
 
3639
  /**
3640
   * Remove all keyboard related event listeners, effectively disabling Ionic's
3641
   * keyboard adjustments.
3642
   */
3643
  disable: function() {
3644
    if (keyboardHasPlugin()) {
3645
      window.removeEventListener('native.keyboardshow', debouncedKeyboardNativeShow );
3646
      window.removeEventListener('native.keyboardhide', keyboardFocusOut);
3647
    } else {
3648
      document.body.removeEventListener('focusout', keyboardFocusOut);
3649
    }
3650
 
3651
    document.body.removeEventListener('ionic.focusin', debouncedKeyboardFocusIn);
3652
    document.body.removeEventListener('focusin', debouncedKeyboardFocusIn);
3653
 
3654
    window.removeEventListener('orientationchange', keyboardOrientationChange);
3655
 
3656
    if ( window.navigator.msPointerEnabled ) {
3657
      document.removeEventListener("MSPointerDown", keyboardInit);
3658
    } else {
3659
      document.removeEventListener('touchstart', keyboardInit);
3660
    }
3661
    ionic.keyboard.isInitialized = false;
3662
  },
3663
 
3664
  /**
3665
   * Alias for keyboardInit, initialize all keyboard related event listeners.
3666
   */
3667
  enable: function() {
3668
    keyboardInit();
3669
  }
3670
};
3671
 
3672
// Initialize the viewport height (after ionic.keyboard.height has been
3673
// defined).
3674
keyboardCurrentViewportHeight = getViewportHeight();
3675
 
3676
 
3677
                             /* Event handlers */
3678
/* ------------------------------------------------------------------------- */
3679
 
3680
/**
3681
 * Event handler for first touch event, initializes all event listeners
3682
 * for keyboard related events. Also aliased by ionic.keyboard.enable.
3683
 */
3684
function keyboardInit() {
3685
 
3686
  if (ionic.keyboard.isInitialized) return;
3687
 
3688
  if (keyboardHasPlugin()) {
3689
    window.addEventListener('native.keyboardshow', debouncedKeyboardNativeShow);
3690
    window.addEventListener('native.keyboardhide', keyboardFocusOut);
3691
  } else {
3692
    document.body.addEventListener('focusout', keyboardFocusOut);
3693
  }
3694
 
3695
  document.body.addEventListener('ionic.focusin', debouncedKeyboardFocusIn);
3696
  document.body.addEventListener('focusin', debouncedKeyboardFocusIn);
3697
 
3698
  if (window.navigator.msPointerEnabled) {
3699
    document.removeEventListener("MSPointerDown", keyboardInit);
3700
  } else {
3701
    document.removeEventListener('touchstart', keyboardInit);
3702
  }
3703
 
3704
  ionic.keyboard.isInitialized = true;
3705
}
3706
 
3707
/**
3708
 * Event handler for 'native.keyboardshow' event, sets keyboard.height to the
3709
 * reported height and keyboard.isOpening to true. Then calls
3710
 * keyboardWaitForResize with keyboardShow or keyboardUpdateViewportHeight as
3711
 * the callback depending on whether the event was triggered by a focusin or
3712
 * an orientationchange.
3713
 */
3714
function keyboardNativeShow(e) {
3715
  clearTimeout(keyboardFocusOutTimer);
3716
  //console.log("keyboardNativeShow fired at: " + Date.now());
3717
  //console.log("keyboardNativeshow window.innerHeight: " + window.innerHeight);
3718
 
3719
  if (!ionic.keyboard.isOpen || ionic.keyboard.isClosing) {
3720
    ionic.keyboard.isOpening = true;
3721
    ionic.keyboard.isClosing = false;
3722
  }
3723
 
3724
  ionic.keyboard.height = e.keyboardHeight;
3725
  //console.log('nativeshow keyboard height:' + e.keyboardHeight);
3726
 
3727
  if (wasOrientationChange) {
3728
    keyboardWaitForResize(keyboardUpdateViewportHeight, true);
3729
  } else {
3730
    keyboardWaitForResize(keyboardShow, true);
3731
  }
3732
}
3733
 
3734
/**
3735
 * Event handler for 'focusin' and 'ionic.focusin' events. Initializes
3736
 * keyboard state (keyboardActiveElement and keyboard.isOpening) for the
3737
 * appropriate adjustments once the window has resized.  If not using the
3738
 * keyboard plugin, calls keyboardWaitForResize with keyboardShow as the
3739
 * callback or keyboardShow right away if the keyboard is already open.  If
3740
 * using the keyboard plugin does nothing and lets keyboardNativeShow handle
3741
 * adjustments with a more accurate keyboard height.
3742
 */
3743
function keyboardFocusIn(e) {
3744
  clearTimeout(keyboardFocusOutTimer);
3745
  //console.log("keyboardFocusIn from: " + e.type + " at: " + Date.now());
3746
 
3747
  if (!e.target ||
3748
      e.target.readOnly ||
3749
      !ionic.tap.isKeyboardElement(e.target) ||
3750
      !(scrollView = inputScrollView(e.target))) {
3751
    keyboardActiveElement = null;
3752
    return;
3753
  }
3754
 
3755
  keyboardActiveElement = e.target;
3756
 
3757
  // if using JS scrolling, undo the effects of native overflow scroll so the
3758
  // scroll view is positioned correctly
3759
  document.body.scrollTop = 0;
3760
  scrollView.scrollTop = 0;
3761
  ionic.requestAnimationFrame(function(){
3762
    document.body.scrollTop = 0;
3763
    scrollView.scrollTop = 0;
3764
  });
3765
 
3766
  if (!ionic.keyboard.isOpen || ionic.keyboard.isClosing) {
3767
    ionic.keyboard.isOpening = true;
3768
    ionic.keyboard.isClosing = false;
3769
  }
3770
 
3771
  // attempt to prevent browser from natively scrolling input into view while
3772
  // we are trying to do the same (while we are scrolling) if the user taps the
3773
  // keyboard
3774
  document.addEventListener('keydown', keyboardOnKeyDown, false);
3775
 
3776
  // any showing part of the document that isn't within the scroll the user
3777
  // could touchmove and cause some ugly changes to the app, so disable
3778
  // any touchmove events while the keyboard is open using e.preventDefault()
3779
  if (window.navigator.msPointerEnabled) {
3780
    document.addEventListener("MSPointerMove", keyboardPreventDefault, false);
3781
  } else {
3782
    document.addEventListener('touchmove', keyboardPreventDefault, false);
3783
  }
3784
 
3785
  // if we aren't using the plugin and the keyboard isn't open yet, wait for the
3786
  // window to resize so we can get an accurate estimate of the keyboard size,
3787
  // otherwise we do nothing and let nativeShow call keyboardShow once we have
3788
  // an exact keyboard height
3789
  // if the keyboard is already open, go ahead and scroll the input into view
3790
  // if necessary
3791
  if (!ionic.keyboard.isOpen && !keyboardHasPlugin()) {
3792
    keyboardWaitForResize(keyboardShow, true);
3793
 
3794
  } else if (ionic.keyboard.isOpen) {
3795
    keyboardShow();
3796
  }
3797
}
3798
 
3799
/**
3800
 * Event handler for 'focusout' events. Sets keyboard.isClosing to true and
3801
 * calls keyboardWaitForResize with keyboardHide as the callback after a small
3802
 * timeout.
3803
 */
3804
function keyboardFocusOut() {
3805
  clearTimeout(keyboardFocusOutTimer);
3806
  //console.log("keyboardFocusOut fired at: " + Date.now());
3807
  //console.log("keyboardFocusOut event type: " + e.type);
3808
 
3809
  if (ionic.keyboard.isOpen || ionic.keyboard.isOpening) {
3810
    ionic.keyboard.isClosing = true;
3811
    ionic.keyboard.isOpening = false;
3812
  }
3813
 
3814
  // Call keyboardHide with a slight delay because sometimes on focus or
3815
  // orientation change focusin is called immediately after, so we give it time
3816
  // to cancel keyboardHide
3817
  keyboardFocusOutTimer = setTimeout(function() {
3818
    ionic.requestAnimationFrame(function() {
3819
      // focusOut during or right after an orientationchange, so we didn't get
3820
      // a chance to update the viewport height yet, do it and keyboardHide
3821
      //console.log("focusOut, wasOrientationChange: " + wasOrientationChange);
3822
      if (wasOrientationChange) {
3823
        keyboardWaitForResize(function(){
3824
          keyboardUpdateViewportHeight();
3825
          keyboardHide();
3826
        }, false);
3827
      } else {
3828
        keyboardWaitForResize(keyboardHide, false);
3829
      }
3830
    });
3831
  }, 50);
3832
}
3833
 
3834
/**
3835
 * Event handler for 'orientationchange' events. If using the keyboard plugin
3836
 * and the keyboard is open on Android, sets wasOrientationChange to true so
3837
 * nativeShow can update the viewport height with an accurate keyboard height.
3838
 * If the keyboard isn't open or keyboard plugin isn't being used,
3839
 * waits for the window to resize before updating the viewport height.
3840
 *
3841
 * On iOS, where orientationchange fires after the keyboard has already shown,
3842
 * updates the viewport immediately, regardless of if the keyboard is already
3843
 * open.
3844
 */
3845
function keyboardOrientationChange() {
3846
  //console.log("orientationchange fired at: " + Date.now());
3847
  //console.log("orientation was: " + (ionic.keyboard.isLandscape ? "landscape" : "portrait"));
3848
 
3849
  // toggle orientation
3850
  ionic.keyboard.isLandscape = !ionic.keyboard.isLandscape;
3851
  // //console.log("now orientation is: " + (ionic.keyboard.isLandscape ? "landscape" : "portrait"));
3852
 
3853
  // no need to wait for resizing on iOS, and orientationchange always fires
3854
  // after the keyboard has opened, so it doesn't matter if it's open or not
3855
  if (ionic.Platform.isIOS()) {
3856
    keyboardUpdateViewportHeight();
3857
  }
3858
 
3859
  // On Android, if the keyboard isn't open or we aren't using the keyboard
3860
  // plugin, update the viewport height once everything has resized. If the
3861
  // keyboard is open and we are using the keyboard plugin do nothing and let
3862
  // nativeShow handle it using an accurate keyboard height.
3863
  if ( ionic.Platform.isAndroid()) {
3864
    if (!ionic.keyboard.isOpen || !keyboardHasPlugin()) {
3865
      keyboardWaitForResize(keyboardUpdateViewportHeight, false);
3866
    } else {
3867
      wasOrientationChange = true;
3868
    }
3869
  }
3870
}
3871
 
3872
/**
3873
 * Event handler for 'keydown' event. Tries to prevent browser from natively
3874
 * scrolling an input into view when a user taps the keyboard while we are
3875
 * scrolling the input into view ourselves with JS.
3876
 */
3877
function keyboardOnKeyDown(e) {
3878
  if (ionic.scroll.isScrolling) {
3879
    keyboardPreventDefault(e);
3880
  }
3881
}
3882
 
3883
/**
3884
 * Event for 'touchmove' or 'MSPointerMove'. Prevents native scrolling on
3885
 * elements outside the scroll view while the keyboard is open.
3886
 */
3887
function keyboardPreventDefault(e) {
3888
  if (e.target.tagName !== 'TEXTAREA') {
3889
    e.preventDefault();
3890
  }
3891
}
3892
 
3893
                              /* Private API */
3894
/* -------------------------------------------------------------------------- */
3895
 
3896
/**
3897
 * Polls window.innerHeight until it has updated to an expected value (or
3898
 * sufficient time has passed) before calling the specified callback function.
3899
 * Only necessary for non-fullscreen Android which sometimes reports multiple
3900
 * window.innerHeight values during interim layouts while it is resizing.
3901
 *
3902
 * On iOS, the window.innerHeight will already be updated, but we use the 50ms
3903
 * delay as essentially a timeout so that scroll view adjustments happen after
3904
 * the keyboard has shown so there isn't a white flash from us resizing too
3905
 * quickly.
3906
 *
3907
 * @param {Function} callback the function to call once the window has resized
3908
 * @param {boolean} isOpening whether the resize is from the keyboard opening
3909
 * or not
3910
 */
3911
function keyboardWaitForResize(callback, isOpening) {
3912
  clearInterval(waitForResizeTimer);
3913
  var count = 0;
3914
  var maxCount;
3915
  var initialHeight = getViewportHeight();
3916
  var viewportHeight = initialHeight;
3917
 
3918
  //console.log("waitForResize initial viewport height: " + viewportHeight);
3919
  //var start = Date.now();
3920
  //console.log("start: " + start);
3921
 
3922
  // want to fail relatively quickly on modern android devices, since it's much
3923
  // more likely we just have a bad keyboard height
3924
  if (ionic.Platform.isAndroid() && ionic.Platform.version() < 4.4) {
3925
    maxCount = 30;
3926
  } else if (ionic.Platform.isAndroid()) {
3927
    maxCount = 10;
3928
  } else {
3929
    maxCount = 1;
3930
  }
3931
 
3932
  // poll timer
3933
  waitForResizeTimer = setInterval(function(){
3934
    viewportHeight = getViewportHeight();
3935
 
3936
    // height hasn't updated yet, try again in 50ms
3937
    // if not using plugin, wait for maxCount to ensure we have waited long enough
3938
    // to get an accurate keyboard height
3939
    if (++count < maxCount &&
3940
        ((!isPortraitViewportHeight(viewportHeight) &&
3941
         !isLandscapeViewportHeight(viewportHeight)) ||
3942
         !ionic.keyboard.height)) {
3943
      return;
3944
    }
3945
 
3946
    // infer the keyboard height from the resize if not using the keyboard plugin
3947
    if (!keyboardHasPlugin()) {
3948
      ionic.keyboard.height = Math.abs(initialHeight - window.innerHeight);
3949
    }
3950
 
3951
    // set to true if we were waiting for the keyboard to open
3952
    ionic.keyboard.isOpen = isOpening;
3953
 
3954
    clearInterval(waitForResizeTimer);
3955
    //var end = Date.now();
3956
    //console.log("waitForResize count: " + count);
3957
    //console.log("end: " + end);
3958
    //console.log("difference: " + ( end - start ) + "ms");
3959
 
3960
    //console.log("callback: " + callback.name);
3961
    callback();
3962
 
3963
  }, 50);
3964
 
3965
  return maxCount; //for tests
3966
}
3967
 
3968
/**
3969
 * On keyboard close sets keyboard state to closed, resets the scroll view,
3970
 * removes CSS from body indicating keyboard was open, removes any event
3971
 * listeners for when the keyboard is open and on Android blurs the active
3972
 * element (which in some cases will still have focus even if the keyboard
3973
 * is closed and can cause it to reappear on subsequent taps).
3974
 */
3975
function keyboardHide() {
3976
  clearTimeout(keyboardFocusOutTimer);
3977
  //console.log("keyboardHide");
3978
 
3979
  ionic.keyboard.isOpen = false;
3980
  ionic.keyboard.isClosing = false;
3981
 
3982
  if (keyboardActiveElement) {
3983
    ionic.trigger('resetScrollView', {
3984
      target: keyboardActiveElement
3985
    }, true);
3986
  }
3987
 
3988
  ionic.requestAnimationFrame(function(){
3989
    document.body.classList.remove(KEYBOARD_OPEN_CSS);
3990
  });
3991
 
3992
  // the keyboard is gone now, remove the touchmove that disables native scroll
3993
  if (window.navigator.msPointerEnabled) {
3994
    document.removeEventListener("MSPointerMove", keyboardPreventDefault);
3995
  } else {
3996
    document.removeEventListener('touchmove', keyboardPreventDefault);
3997
  }
3998
  document.removeEventListener('keydown', keyboardOnKeyDown);
3999
 
4000
  if (ionic.Platform.isAndroid()) {
4001
    // on android closing the keyboard with the back/dismiss button won't remove
4002
    // focus and keyboard can re-appear on subsequent taps (like scrolling)
4003
    if (keyboardHasPlugin()) cordova.plugins.Keyboard.close();
4004
    keyboardActiveElement && keyboardActiveElement.blur();
4005
  }
4006
 
4007
  keyboardActiveElement = null;
4008
}
4009
 
4010
/**
4011
 * On keyboard open sets keyboard state to open, adds CSS to the body
4012
 * indicating the keyboard is open and tells the scroll view to resize and
4013
 * the currently focused input into view if necessary.
4014
 */
4015
function keyboardShow() {
4016
 
4017
  ionic.keyboard.isOpen = true;
4018
  ionic.keyboard.isOpening = false;
4019
 
4020
  var details = {
4021
    keyboardHeight: keyboardGetHeight(),
4022
    viewportHeight: keyboardCurrentViewportHeight
4023
  };
4024
 
4025
  if (keyboardActiveElement) {
4026
    details.target = keyboardActiveElement;
4027
 
4028
    var elementBounds = keyboardActiveElement.getBoundingClientRect();
4029
 
4030
    details.elementTop = Math.round(elementBounds.top);
4031
    details.elementBottom = Math.round(elementBounds.bottom);
4032
 
4033
    details.windowHeight = details.viewportHeight - details.keyboardHeight;
4034
    //console.log("keyboardShow viewportHeight: " + details.viewportHeight +
4035
    //", windowHeight: " + details.windowHeight +
4036
    //", keyboardHeight: " + details.keyboardHeight);
4037
 
4038
    // figure out if the element is under the keyboard
4039
    details.isElementUnderKeyboard = (details.elementBottom > details.windowHeight);
4040
    //console.log("isUnderKeyboard: " + details.isElementUnderKeyboard);
4041
    //console.log("elementBottom: " + details.elementBottom);
4042
 
4043
    // send event so the scroll view adjusts
4044
    ionic.trigger('scrollChildIntoView', details, true);
4045
  }
4046
 
4047
  setTimeout(function(){
4048
    document.body.classList.add(KEYBOARD_OPEN_CSS);
4049
  }, 400);
4050
 
4051
  return details; //for testing
4052
}
4053
 
4054
/* eslint no-unused-vars:0 */
4055
function keyboardGetHeight() {
4056
  // check if we already have a keyboard height from the plugin or resize calculations
4057
  if (ionic.keyboard.height) {
4058
    return ionic.keyboard.height;
4059
  }
4060
 
4061
  if (ionic.Platform.isAndroid()) {
4062
    // should be using the plugin, no way to know how big the keyboard is, so guess
4063
    if ( ionic.Platform.isFullScreen ) {
4064
      return 275;
4065
    }
4066
    // otherwise just calculate it
4067
    var contentHeight = window.innerHeight;
4068
    if (contentHeight < keyboardCurrentViewportHeight) {
4069
      return keyboardCurrentViewportHeight - contentHeight;
4070
    } else {
4071
      return 0;
4072
    }
4073
  }
4074
 
4075
  // fallback for when it's the webview without the plugin
4076
  // or for just the standard web browser
4077
  // TODO: have these be based on device
4078
  if (ionic.Platform.isIOS()) {
4079
    if (ionic.keyboard.isLandscape) {
4080
      return 206;
4081
    }
4082
 
4083
    if (!ionic.Platform.isWebView()) {
4084
      return 216;
4085
    }
4086
 
4087
    return 260;
4088
  }
4089
 
4090
  // safe guess
4091
  return 275;
4092
}
4093
 
4094
function isPortraitViewportHeight(viewportHeight) {
4095
  return !!(!ionic.keyboard.isLandscape &&
4096
         keyboardPortraitViewportHeight &&
4097
         (Math.abs(keyboardPortraitViewportHeight - viewportHeight) < 2));
4098
}
4099
 
4100
function isLandscapeViewportHeight(viewportHeight) {
4101
  return !!(ionic.keyboard.isLandscape &&
4102
         keyboardLandscapeViewportHeight &&
4103
         (Math.abs(keyboardLandscapeViewportHeight - viewportHeight) < 2));
4104
}
4105
 
4106
function keyboardUpdateViewportHeight() {
4107
  wasOrientationChange = false;
4108
  keyboardCurrentViewportHeight = getViewportHeight();
4109
 
4110
  if (ionic.keyboard.isLandscape && !keyboardLandscapeViewportHeight) {
4111
    //console.log("saved landscape: " + keyboardCurrentViewportHeight);
4112
    keyboardLandscapeViewportHeight = keyboardCurrentViewportHeight;
4113
 
4114
  } else if (!ionic.keyboard.isLandscape && !keyboardPortraitViewportHeight) {
4115
    //console.log("saved portrait: " + keyboardCurrentViewportHeight);
4116
    keyboardPortraitViewportHeight = keyboardCurrentViewportHeight;
4117
  }
4118
 
4119
  if (keyboardActiveElement) {
4120
    ionic.trigger('resetScrollView', {
4121
      target: keyboardActiveElement
4122
    }, true);
4123
  }
4124
 
4125
  if (ionic.keyboard.isOpen && ionic.tap.isTextInput(keyboardActiveElement)) {
4126
    keyboardShow();
4127
  }
4128
}
4129
 
4130
function keyboardInitViewportHeight() {
4131
  var viewportHeight = getViewportHeight();
4132
  //console.log("Keyboard init VP: " + viewportHeight + " " + window.innerWidth);
4133
  // can't just use window.innerHeight in case the keyboard is opened immediately
4134
  if ((viewportHeight / window.innerWidth) < 1) {
4135
    ionic.keyboard.isLandscape = true;
4136
  }
4137
  //console.log("ionic.keyboard.isLandscape is: " + ionic.keyboard.isLandscape);
4138
 
4139
  // initialize or update the current viewport height values
4140
  keyboardCurrentViewportHeight = viewportHeight;
4141
  if (ionic.keyboard.isLandscape && !keyboardLandscapeViewportHeight) {
4142
    keyboardLandscapeViewportHeight = keyboardCurrentViewportHeight;
4143
  } else if (!ionic.keyboard.isLandscape && !keyboardPortraitViewportHeight) {
4144
    keyboardPortraitViewportHeight = keyboardCurrentViewportHeight;
4145
  }
4146
}
4147
 
4148
function getViewportHeight() {
4149
  var windowHeight = window.innerHeight;
4150
  //console.log('window.innerHeight is: ' + windowHeight);
4151
  //console.log('kb height is: ' + ionic.keyboard.height);
4152
  //console.log('kb isOpen: ' + ionic.keyboard.isOpen);
4153
 
4154
  //TODO: add iPad undocked/split kb once kb plugin supports it
4155
  // the keyboard overlays the window on Android fullscreen
4156
  if (!(ionic.Platform.isAndroid() && ionic.Platform.isFullScreen) &&
4157
      (ionic.keyboard.isOpen || ionic.keyboard.isOpening) &&
4158
      !ionic.keyboard.isClosing) {
4159
 
4160
     return windowHeight + keyboardGetHeight();
4161
  }
4162
  return windowHeight;
4163
}
4164
 
4165
function inputScrollView(ele) {
4166
  while(ele) {
4167
    if (ele.classList.contains(SCROLL_CONTAINER_CSS)) {
4168
      return ele;
4169
    }
4170
    ele = ele.parentElement;
4171
  }
4172
  return null;
4173
}
4174
 
4175
function keyboardHasPlugin() {
4176
  return !!(window.cordova && cordova.plugins && cordova.plugins.Keyboard);
4177
}
4178
 
4179
ionic.Platform.ready(function() {
4180
  keyboardInitViewportHeight();
4181
 
4182
  window.addEventListener('orientationchange', keyboardOrientationChange);
4183
 
4184
  // if orientation changes while app is in background, update on resuming
4185
  /*
4186
  if ( ionic.Platform.isWebView() ) {
4187
    document.addEventListener('resume', keyboardInitViewportHeight);
4188
 
4189
    if (ionic.Platform.isAndroid()) {
4190
      //TODO: onbackpressed to detect keyboard close without focusout or plugin
4191
    }
4192
  }
4193
  */
4194
 
4195
  // if orientation changes while app is in background, update on resuming
4196
/*  if ( ionic.Platform.isWebView() ) {
4197
    document.addEventListener('pause', function() {
4198
      window.removeEventListener('orientationchange', keyboardOrientationChange);
4199
    })
4200
    document.addEventListener('resume', function() {
4201
      keyboardInitViewportHeight();
4202
      window.addEventListener('orientationchange', keyboardOrientationChange)
4203
    });
4204
  }*/
4205
 
4206
  // Android sometimes reports bad innerHeight on window.load
4207
  // try it again in a lil bit to play it safe
4208
  setTimeout(keyboardInitViewportHeight, 999);
4209
 
4210
  // only initialize the adjustments for the virtual keyboard
4211
  // if a touchstart event happens
4212
  if (window.navigator.msPointerEnabled) {
4213
    document.addEventListener("MSPointerDown", keyboardInit, false);
4214
  } else {
4215
    document.addEventListener('touchstart', keyboardInit, false);
4216
  }
4217
});
4218
 
4219
 
4220
 
4221
var viewportTag;
4222
var viewportProperties = {};
4223
 
4224
ionic.viewport = {
4225
  orientation: function() {
4226
    // 0 = Portrait
4227
    // 90 = Landscape
4228
    // not using window.orientation because each device has a different implementation
4229
    return (window.innerWidth > window.innerHeight ? 90 : 0);
4230
  }
4231
};
4232
 
4233
function viewportLoadTag() {
4234
  var x;
4235
 
4236
  for (x = 0; x < document.head.children.length; x++) {
4237
    if (document.head.children[x].name == 'viewport') {
4238
      viewportTag = document.head.children[x];
4239
      break;
4240
    }
4241
  }
4242
 
4243
  if (viewportTag) {
4244
    var props = viewportTag.content.toLowerCase().replace(/\s+/g, '').split(',');
4245
    var keyValue;
4246
    for (x = 0; x < props.length; x++) {
4247
      if (props[x]) {
4248
        keyValue = props[x].split('=');
4249
        viewportProperties[ keyValue[0] ] = (keyValue.length > 1 ? keyValue[1] : '_');
4250
      }
4251
    }
4252
    viewportUpdate();
4253
  }
4254
}
4255
 
4256
function viewportUpdate() {
4257
  // unit tests in viewport.unit.js
4258
 
4259
  var initWidth = viewportProperties.width;
4260
  var initHeight = viewportProperties.height;
4261
  var p = ionic.Platform;
4262
  var version = p.version();
4263
  var DEVICE_WIDTH = 'device-width';
4264
  var DEVICE_HEIGHT = 'device-height';
4265
  var orientation = ionic.viewport.orientation();
4266
 
4267
  // Most times we're removing the height and adding the width
4268
  // So this is the default to start with, then modify per platform/version/oreintation
4269
  delete viewportProperties.height;
4270
  viewportProperties.width = DEVICE_WIDTH;
4271
 
4272
  if (p.isIPad()) {
4273
    // iPad
4274
 
4275
    if (version > 7) {
4276
      // iPad >= 7.1
4277
      // https://issues.apache.org/jira/browse/CB-4323
4278
      delete viewportProperties.width;
4279
 
4280
    } else {
4281
      // iPad <= 7.0
4282
 
4283
      if (p.isWebView()) {
4284
        // iPad <= 7.0 WebView
4285
 
4286
        if (orientation == 90) {
4287
          // iPad <= 7.0 WebView Landscape
4288
          viewportProperties.height = '0';
4289
 
4290
        } else if (version == 7) {
4291
          // iPad <= 7.0 WebView Portait
4292
          viewportProperties.height = DEVICE_HEIGHT;
4293
        }
4294
      } else {
4295
        // iPad <= 6.1 Browser
4296
        if (version < 7) {
4297
          viewportProperties.height = '0';
4298
        }
4299
      }
4300
    }
4301
 
4302
  } else if (p.isIOS()) {
4303
    // iPhone
4304
 
4305
    if (p.isWebView()) {
4306
      // iPhone WebView
4307
 
4308
      if (version > 7) {
4309
        // iPhone >= 7.1 WebView
4310
        delete viewportProperties.width;
4311
 
4312
      } else if (version < 7) {
4313
        // iPhone <= 6.1 WebView
4314
        // if height was set it needs to get removed with this hack for <= 6.1
4315
        if (initHeight) viewportProperties.height = '0';
4316
 
4317
      } else if (version == 7) {
4318
        //iPhone == 7.0 WebView
4319
        viewportProperties.height = DEVICE_HEIGHT;
4320
      }
4321
 
4322
    } else {
4323
      // iPhone Browser
4324
 
4325
      if (version < 7) {
4326
        // iPhone <= 6.1 Browser
4327
        // if height was set it needs to get removed with this hack for <= 6.1
4328
        if (initHeight) viewportProperties.height = '0';
4329
      }
4330
    }
4331
 
4332
  }
4333
 
4334
  // only update the viewport tag if there was a change
4335
  if (initWidth !== viewportProperties.width || initHeight !== viewportProperties.height) {
4336
    viewportTagUpdate();
4337
  }
4338
}
4339
 
4340
function viewportTagUpdate() {
4341
  var key, props = [];
4342
  for (key in viewportProperties) {
4343
    if (viewportProperties[key]) {
4344
      props.push(key + (viewportProperties[key] == '_' ? '' : '=' + viewportProperties[key]));
4345
    }
4346
  }
4347
 
4348
  viewportTag.content = props.join(', ');
4349
}
4350
 
4351
ionic.Platform.ready(function() {
4352
  viewportLoadTag();
4353
 
4354
  window.addEventListener("orientationchange", function() {
4355
    setTimeout(viewportUpdate, 1000);
4356
  }, false);
4357
});
4358
 
4359
(function(ionic) {
4360
'use strict';
4361
  ionic.views.View = function() {
4362
    this.initialize.apply(this, arguments);
4363
  };
4364
 
4365
  ionic.views.View.inherit = ionic.inherit;
4366
 
4367
  ionic.extend(ionic.views.View.prototype, {
4368
    initialize: function() {}
4369
  });
4370
 
4371
})(window.ionic);
4372
 
4373
/*
4374
 * Scroller
4375
 * http://github.com/zynga/scroller
4376
 *
4377
 * Copyright 2011, Zynga Inc.
4378
 * Licensed under the MIT License.
4379
 * https://raw.github.com/zynga/scroller/master/MIT-LICENSE.txt
4380
 *
4381
 * Based on the work of: Unify Project (unify-project.org)
4382
 * http://unify-project.org
4383
 * Copyright 2011, Deutsche Telekom AG
4384
 * License: MIT + Apache (V2)
4385
 */
4386
 
4387
/* jshint eqnull: true */
4388
 
4389
/**
4390
 * Generic animation class with support for dropped frames both optional easing and duration.
4391
 *
4392
 * Optional duration is useful when the lifetime is defined by another condition than time
4393
 * e.g. speed of an animating object, etc.
4394
 *
4395
 * Dropped frame logic allows to keep using the same updater logic independent from the actual
4396
 * rendering. This eases a lot of cases where it might be pretty complex to break down a state
4397
 * based on the pure time difference.
4398
 */
4399
var zyngaCore = { effect: {} };
4400
(function(global) {
4401
  var time = Date.now || function() {
4402
    return +new Date();
4403
  };
4404
  var desiredFrames = 60;
4405
  var millisecondsPerSecond = 1000;
4406
  var running = {};
4407
  var counter = 1;
4408
 
4409
  zyngaCore.effect.Animate = {
4410
 
4411
    /**
4412
     * A requestAnimationFrame wrapper / polyfill.
4413
     *
4414
     * @param callback {Function} The callback to be invoked before the next repaint.
4415
     * @param root {HTMLElement} The root element for the repaint
4416
     */
4417
    requestAnimationFrame: (function() {
4418
 
4419
      // Check for request animation Frame support
4420
      var requestFrame = global.requestAnimationFrame || global.webkitRequestAnimationFrame || global.mozRequestAnimationFrame || global.oRequestAnimationFrame;
4421
      var isNative = !!requestFrame;
4422
 
4423
      if (requestFrame && !/requestAnimationFrame\(\)\s*\{\s*\[native code\]\s*\}/i.test(requestFrame.toString())) {
4424
        isNative = false;
4425
      }
4426
 
4427
      if (isNative) {
4428
        return function(callback, root) {
4429
          requestFrame(callback, root);
4430
        };
4431
      }
4432
 
4433
      var TARGET_FPS = 60;
4434
      var requests = {};
4435
      var requestCount = 0;
4436
      var rafHandle = 1;
4437
      var intervalHandle = null;
4438
      var lastActive = +new Date();
4439
 
4440
      return function(callback) {
4441
        var callbackHandle = rafHandle++;
4442
 
4443
        // Store callback
4444
        requests[callbackHandle] = callback;
4445
        requestCount++;
4446
 
4447
        // Create timeout at first request
4448
        if (intervalHandle === null) {
4449
 
4450
          intervalHandle = setInterval(function() {
4451
 
4452
            var time = +new Date();
4453
            var currentRequests = requests;
4454
 
4455
            // Reset data structure before executing callbacks
4456
            requests = {};
4457
            requestCount = 0;
4458
 
4459
            for(var key in currentRequests) {
4460
              if (currentRequests.hasOwnProperty(key)) {
4461
                currentRequests[key](time);
4462
                lastActive = time;
4463
              }
4464
            }
4465
 
4466
            // Disable the timeout when nothing happens for a certain
4467
            // period of time
4468
            if (time - lastActive > 2500) {
4469
              clearInterval(intervalHandle);
4470
              intervalHandle = null;
4471
            }
4472
 
4473
          }, 1000 / TARGET_FPS);
4474
        }
4475
 
4476
        return callbackHandle;
4477
      };
4478
 
4479
    })(),
4480
 
4481
 
4482
    /**
4483
     * Stops the given animation.
4484
     *
4485
     * @param id {Integer} Unique animation ID
4486
     * @return {Boolean} Whether the animation was stopped (aka, was running before)
4487
     */
4488
    stop: function(id) {
4489
      var cleared = running[id] != null;
4490
      if (cleared) {
4491
        running[id] = null;
4492
      }
4493
 
4494
      return cleared;
4495
    },
4496
 
4497
 
4498
    /**
4499
     * Whether the given animation is still running.
4500
     *
4501
     * @param id {Integer} Unique animation ID
4502
     * @return {Boolean} Whether the animation is still running
4503
     */
4504
    isRunning: function(id) {
4505
      return running[id] != null;
4506
    },
4507
 
4508
 
4509
    /**
4510
     * Start the animation.
4511
     *
4512
     * @param stepCallback {Function} Pointer to function which is executed on every step.
4513
     *   Signature of the method should be `function(percent, now, virtual) { return continueWithAnimation; }`
4514
     * @param verifyCallback {Function} Executed before every animation step.
4515
     *   Signature of the method should be `function() { return continueWithAnimation; }`
4516
     * @param completedCallback {Function}
4517
     *   Signature of the method should be `function(droppedFrames, finishedAnimation) {}`
4518
     * @param duration {Integer} Milliseconds to run the animation
4519
     * @param easingMethod {Function} Pointer to easing function
4520
     *   Signature of the method should be `function(percent) { return modifiedValue; }`
4521
     * @param root {Element} Render root, when available. Used for internal
4522
     *   usage of requestAnimationFrame.
4523
     * @return {Integer} Identifier of animation. Can be used to stop it any time.
4524
     */
4525
    start: function(stepCallback, verifyCallback, completedCallback, duration, easingMethod, root) {
4526
 
4527
      var start = time();
4528
      var lastFrame = start;
4529
      var percent = 0;
4530
      var dropCounter = 0;
4531
      var id = counter++;
4532
 
4533
      if (!root) {
4534
        root = document.body;
4535
      }
4536
 
4537
      // Compacting running db automatically every few new animations
4538
      if (id % 20 === 0) {
4539
        var newRunning = {};
4540
        for (var usedId in running) {
4541
          newRunning[usedId] = true;
4542
        }
4543
        running = newRunning;
4544
      }
4545
 
4546
      // This is the internal step method which is called every few milliseconds
4547
      var step = function(virtual) {
4548
 
4549
        // Normalize virtual value
4550
        var render = virtual !== true;
4551
 
4552
        // Get current time
4553
        var now = time();
4554
 
4555
        // Verification is executed before next animation step
4556
        if (!running[id] || (verifyCallback && !verifyCallback(id))) {
4557
 
4558
          running[id] = null;
4559
          completedCallback && completedCallback(desiredFrames - (dropCounter / ((now - start) / millisecondsPerSecond)), id, false);
4560
          return;
4561
 
4562
        }
4563
 
4564
        // For the current rendering to apply let's update omitted steps in memory.
4565
        // This is important to bring internal state variables up-to-date with progress in time.
4566
        if (render) {
4567
 
4568
          var droppedFrames = Math.round((now - lastFrame) / (millisecondsPerSecond / desiredFrames)) - 1;
4569
          for (var j = 0; j < Math.min(droppedFrames, 4); j++) {
4570
            step(true);
4571
            dropCounter++;
4572
          }
4573
 
4574
        }
4575
 
4576
        // Compute percent value
4577
        if (duration) {
4578
          percent = (now - start) / duration;
4579
          if (percent > 1) {
4580
            percent = 1;
4581
          }
4582
        }
4583
 
4584
        // Execute step callback, then...
4585
        var value = easingMethod ? easingMethod(percent) : percent;
4586
        if ((stepCallback(value, now, render) === false || percent === 1) && render) {
4587
          running[id] = null;
4588
          completedCallback && completedCallback(desiredFrames - (dropCounter / ((now - start) / millisecondsPerSecond)), id, percent === 1 || duration == null);
4589
        } else if (render) {
4590
          lastFrame = now;
4591
          zyngaCore.effect.Animate.requestAnimationFrame(step, root);
4592
        }
4593
      };
4594
 
4595
      // Mark as running
4596
      running[id] = true;
4597
 
4598
      // Init first step
4599
      zyngaCore.effect.Animate.requestAnimationFrame(step, root);
4600
 
4601
      // Return unique animation ID
4602
      return id;
4603
    }
4604
  };
4605
})(this);
4606
 
4607
/*
4608
 * Scroller
4609
 * http://github.com/zynga/scroller
4610
 *
4611
 * Copyright 2011, Zynga Inc.
4612
 * Licensed under the MIT License.
4613
 * https://raw.github.com/zynga/scroller/master/MIT-LICENSE.txt
4614
 *
4615
 * Based on the work of: Unify Project (unify-project.org)
4616
 * http://unify-project.org
4617
 * Copyright 2011, Deutsche Telekom AG
4618
 * License: MIT + Apache (V2)
4619
 */
4620
 
4621
(function(ionic) {
4622
  var NOOP = function(){};
4623
 
4624
  // Easing Equations (c) 2003 Robert Penner, all rights reserved.
4625
  // Open source under the BSD License.
4626
 
4627
  /**
4628
   * @param pos {Number} position between 0 (start of effect) and 1 (end of effect)
4629
  **/
4630
  var easeOutCubic = function(pos) {
4631
    return (Math.pow((pos - 1), 3) + 1);
4632
  };
4633
 
4634
  /**
4635
   * @param pos {Number} position between 0 (start of effect) and 1 (end of effect)
4636
  **/
4637
  var easeInOutCubic = function(pos) {
4638
    if ((pos /= 0.5) < 1) {
4639
      return 0.5 * Math.pow(pos, 3);
4640
    }
4641
 
4642
    return 0.5 * (Math.pow((pos - 2), 3) + 2);
4643
  };
4644
 
4645
 
4646
/**
4647
 * ionic.views.Scroll
4648
 * A powerful scroll view with support for bouncing, pull to refresh, and paging.
4649
 * @param   {Object}        options options for the scroll view
4650
 * @class A scroll view system
4651
 * @memberof ionic.views
4652
 */
4653
ionic.views.Scroll = ionic.views.View.inherit({
4654
  initialize: function(options) {
4655
    var self = this;
4656
 
4657
    self.__container = options.el;
4658
    self.__content = options.el.firstElementChild;
4659
 
4660
    //Remove any scrollTop attached to these elements; they are virtual scroll now
4661
    //This also stops on-load-scroll-to-window.location.hash that the browser does
4662
    setTimeout(function() {
4663
      if (self.__container && self.__content) {
4664
        self.__container.scrollTop = 0;
4665
        self.__content.scrollTop = 0;
4666
      }
4667
    });
4668
 
4669
    self.options = {
4670
 
4671
      /** Disable scrolling on x-axis by default */
4672
      scrollingX: false,
4673
      scrollbarX: true,
4674
 
4675
      /** Enable scrolling on y-axis */
4676
      scrollingY: true,
4677
      scrollbarY: true,
4678
 
4679
      startX: 0,
4680
      startY: 0,
4681
 
4682
      /** The amount to dampen mousewheel events */
4683
      wheelDampen: 6,
4684
 
4685
      /** The minimum size the scrollbars scale to while scrolling */
4686
      minScrollbarSizeX: 5,
4687
      minScrollbarSizeY: 5,
4688
 
4689
      /** Scrollbar fading after scrolling */
4690
      scrollbarsFade: true,
4691
      scrollbarFadeDelay: 300,
4692
      /** The initial fade delay when the pane is resized or initialized */
4693
      scrollbarResizeFadeDelay: 1000,
4694
 
4695
      /** Enable animations for deceleration, snap back, zooming and scrolling */
4696
      animating: true,
4697
 
4698
      /** duration for animations triggered by scrollTo/zoomTo */
4699
      animationDuration: 250,
4700
 
4701
      /** The velocity required to make the scroll view "slide" after touchend */
4702
      decelVelocityThreshold: 4,
4703
 
4704
      /** The velocity required to make the scroll view "slide" after touchend when using paging */
4705
      decelVelocityThresholdPaging: 4,
4706
 
4707
      /** Enable bouncing (content can be slowly moved outside and jumps back after releasing) */
4708
      bouncing: true,
4709
 
4710
      /** Enable locking to the main axis if user moves only slightly on one of them at start */
4711
      locking: true,
4712
 
4713
      /** Enable pagination mode (switching between full page content panes) */
4714
      paging: false,
4715
 
4716
      /** Enable snapping of content to a configured pixel grid */
4717
      snapping: false,
4718
 
4719
      /** Enable zooming of content via API, fingers and mouse wheel */
4720
      zooming: false,
4721
 
4722
      /** Minimum zoom level */
4723
      minZoom: 0.5,
4724
 
4725
      /** Maximum zoom level */
4726
      maxZoom: 3,
4727
 
4728
      /** Multiply or decrease scrolling speed **/
4729
      speedMultiplier: 1,
4730
 
4731
      deceleration: 0.97,
4732
 
4733
      /** Whether to prevent default on a scroll operation to capture drag events **/
4734
      preventDefault: false,
4735
 
4736
      /** Callback that is fired on the later of touch end or deceleration end,
4737
        provided that another scrolling action has not begun. Used to know
4738
        when to fade out a scrollbar. */
4739
      scrollingComplete: NOOP,
4740
 
4741
      /** This configures the amount of change applied to deceleration when reaching boundaries  **/
4742
      penetrationDeceleration: 0.03,
4743
 
4744
      /** This configures the amount of change applied to acceleration when reaching boundaries  **/
4745
      penetrationAcceleration: 0.08,
4746
 
4747
      // The ms interval for triggering scroll events
4748
      scrollEventInterval: 10,
4749
 
4750
      freeze: false,
4751
 
4752
      getContentWidth: function() {
4753
        return Math.max(self.__content.scrollWidth, self.__content.offsetWidth);
4754
      },
4755
      getContentHeight: function() {
4756
        return Math.max(self.__content.scrollHeight, self.__content.offsetHeight + (self.__content.offsetTop * 2));
4757
      }
4758
    };
4759
 
4760
    for (var key in options) {
4761
      self.options[key] = options[key];
4762
    }
4763
 
4764
    self.hintResize = ionic.debounce(function() {
4765
      self.resize();
4766
    }, 1000, true);
4767
 
4768
    self.onScroll = function() {
4769
 
4770
      if (!ionic.scroll.isScrolling) {
4771
        setTimeout(self.setScrollStart, 50);
4772
      } else {
4773
        clearTimeout(self.scrollTimer);
4774
        self.scrollTimer = setTimeout(self.setScrollStop, 80);
4775
      }
4776
 
4777
    };
4778
 
4779
    self.freeze = function(shouldFreeze) {
4780
      if (arguments.length) {
4781
        self.options.freeze = shouldFreeze;
4782
      }
4783
      return self.options.freeze;
4784
    };
4785
 
4786
    self.setScrollStart = function() {
4787
      ionic.scroll.isScrolling = Math.abs(ionic.scroll.lastTop - self.__scrollTop) > 1;
4788
      clearTimeout(self.scrollTimer);
4789
      self.scrollTimer = setTimeout(self.setScrollStop, 80);
4790
    };
4791
 
4792
    self.setScrollStop = function() {
4793
      ionic.scroll.isScrolling = false;
4794
      ionic.scroll.lastTop = self.__scrollTop;
4795
    };
4796
 
4797
    self.triggerScrollEvent = ionic.throttle(function() {
4798
      self.onScroll();
4799
      ionic.trigger('scroll', {
4800
        scrollTop: self.__scrollTop,
4801
        scrollLeft: self.__scrollLeft,
4802
        target: self.__container
4803
      });
4804
    }, self.options.scrollEventInterval);
4805
 
4806
    self.triggerScrollEndEvent = function() {
4807
      ionic.trigger('scrollend', {
4808
        scrollTop: self.__scrollTop,
4809
        scrollLeft: self.__scrollLeft,
4810
        target: self.__container
4811
      });
4812
    };
4813
 
4814
    self.__scrollLeft = self.options.startX;
4815
    self.__scrollTop = self.options.startY;
4816
 
4817
    // Get the render update function, initialize event handlers,
4818
    // and calculate the size of the scroll container
4819
    self.__callback = self.getRenderFn();
4820
    self.__initEventHandlers();
4821
    self.__createScrollbars();
4822
 
4823
  },
4824
 
4825
  run: function() {
4826
    this.resize();
4827
 
4828
    // Fade them out
4829
    this.__fadeScrollbars('out', this.options.scrollbarResizeFadeDelay);
4830
  },
4831
 
4832
 
4833
 
4834
  /*
4835
  ---------------------------------------------------------------------------
4836
    INTERNAL FIELDS :: STATUS
4837
  ---------------------------------------------------------------------------
4838
  */
4839
 
4840
  /** Whether only a single finger is used in touch handling */
4841
  __isSingleTouch: false,
4842
 
4843
  /** Whether a touch event sequence is in progress */
4844
  __isTracking: false,
4845
 
4846
  /** Whether a deceleration animation went to completion. */
4847
  __didDecelerationComplete: false,
4848
 
4849
  /**
4850
   * Whether a gesture zoom/rotate event is in progress. Activates when
4851
   * a gesturestart event happens. This has higher priority than dragging.
4852
   */
4853
  __isGesturing: false,
4854
 
4855
  /**
4856
   * Whether the user has moved by such a distance that we have enabled
4857
   * dragging mode. Hint: It's only enabled after some pixels of movement to
4858
   * not interrupt with clicks etc.
4859
   */
4860
  __isDragging: false,
4861
 
4862
  /**
4863
   * Not touching and dragging anymore, and smoothly animating the
4864
   * touch sequence using deceleration.
4865
   */
4866
  __isDecelerating: false,
4867
 
4868
  /**
4869
   * Smoothly animating the currently configured change
4870
   */
4871
  __isAnimating: false,
4872
 
4873
 
4874
 
4875
  /*
4876
  ---------------------------------------------------------------------------
4877
    INTERNAL FIELDS :: DIMENSIONS
4878
  ---------------------------------------------------------------------------
4879
  */
4880
 
4881
  /** Available outer left position (from document perspective) */
4882
  __clientLeft: 0,
4883
 
4884
  /** Available outer top position (from document perspective) */
4885
  __clientTop: 0,
4886
 
4887
  /** Available outer width */
4888
  __clientWidth: 0,
4889
 
4890
  /** Available outer height */
4891
  __clientHeight: 0,
4892
 
4893
  /** Outer width of content */
4894
  __contentWidth: 0,
4895
 
4896
  /** Outer height of content */
4897
  __contentHeight: 0,
4898
 
4899
  /** Snapping width for content */
4900
  __snapWidth: 100,
4901
 
4902
  /** Snapping height for content */
4903
  __snapHeight: 100,
4904
 
4905
  /** Height to assign to refresh area */
4906
  __refreshHeight: null,
4907
 
4908
  /** Whether the refresh process is enabled when the event is released now */
4909
  __refreshActive: false,
4910
 
4911
  /** Callback to execute on activation. This is for signalling the user about a refresh is about to happen when he release */
4912
  __refreshActivate: null,
4913
 
4914
  /** Callback to execute on deactivation. This is for signalling the user about the refresh being cancelled */
4915
  __refreshDeactivate: null,
4916
 
4917
  /** Callback to execute to start the actual refresh. Call {@link #refreshFinish} when done */
4918
  __refreshStart: null,
4919
 
4920
  /** Zoom level */
4921
  __zoomLevel: 1,
4922
 
4923
  /** Scroll position on x-axis */
4924
  __scrollLeft: 0,
4925
 
4926
  /** Scroll position on y-axis */
4927
  __scrollTop: 0,
4928
 
4929
  /** Maximum allowed scroll position on x-axis */
4930
  __maxScrollLeft: 0,
4931
 
4932
  /** Maximum allowed scroll position on y-axis */
4933
  __maxScrollTop: 0,
4934
 
4935
  /* Scheduled left position (final position when animating) */
4936
  __scheduledLeft: 0,
4937
 
4938
  /* Scheduled top position (final position when animating) */
4939
  __scheduledTop: 0,
4940
 
4941
  /* Scheduled zoom level (final scale when animating) */
4942
  __scheduledZoom: 0,
4943
 
4944
 
4945
 
4946
  /*
4947
  ---------------------------------------------------------------------------
4948
    INTERNAL FIELDS :: LAST POSITIONS
4949
  ---------------------------------------------------------------------------
4950
  */
4951
 
4952
  /** Left position of finger at start */
4953
  __lastTouchLeft: null,
4954
 
4955
  /** Top position of finger at start */
4956
  __lastTouchTop: null,
4957
 
4958
  /** Timestamp of last move of finger. Used to limit tracking range for deceleration speed. */
4959
  __lastTouchMove: null,
4960
 
4961
  /** List of positions, uses three indexes for each state: left, top, timestamp */
4962
  __positions: null,
4963
 
4964
 
4965
 
4966
  /*
4967
  ---------------------------------------------------------------------------
4968
    INTERNAL FIELDS :: DECELERATION SUPPORT
4969
  ---------------------------------------------------------------------------
4970
  */
4971
 
4972
  /** Minimum left scroll position during deceleration */
4973
  __minDecelerationScrollLeft: null,
4974
 
4975
  /** Minimum top scroll position during deceleration */
4976
  __minDecelerationScrollTop: null,
4977
 
4978
  /** Maximum left scroll position during deceleration */
4979
  __maxDecelerationScrollLeft: null,
4980
 
4981
  /** Maximum top scroll position during deceleration */
4982
  __maxDecelerationScrollTop: null,
4983
 
4984
  /** Current factor to modify horizontal scroll position with on every step */
4985
  __decelerationVelocityX: null,
4986
 
4987
  /** Current factor to modify vertical scroll position with on every step */
4988
  __decelerationVelocityY: null,
4989
 
4990
 
4991
  /** the browser-specific property to use for transforms */
4992
  __transformProperty: null,
4993
  __perspectiveProperty: null,
4994
 
4995
  /** scrollbar indicators */
4996
  __indicatorX: null,
4997
  __indicatorY: null,
4998
 
4999
  /** Timeout for scrollbar fading */
5000
  __scrollbarFadeTimeout: null,
5001
 
5002
  /** whether we've tried to wait for size already */
5003
  __didWaitForSize: null,
5004
  __sizerTimeout: null,
5005
 
5006
  __initEventHandlers: function() {
5007
    var self = this;
5008
 
5009
    // Event Handler
5010
    var container = self.__container;
5011
 
5012
    // save height when scroll view is shrunk so we don't need to reflow
5013
    var scrollViewOffsetHeight;
5014
 
5015
    /**
5016
     * Shrink the scroll view when the keyboard is up if necessary and if the
5017
     * focused input is below the bottom of the shrunk scroll view, scroll it
5018
     * into view.
5019
     */
5020
    self.scrollChildIntoView = function(e) {
5021
      //console.log("scrollChildIntoView at: " + Date.now());
5022
 
5023
      // D
5024
      var scrollBottomOffsetToTop = container.getBoundingClientRect().bottom;
5025
      // D - A
5026
      scrollViewOffsetHeight = container.offsetHeight;
5027
      var alreadyShrunk = self.isShrunkForKeyboard;
5028
 
5029
      var isModal = container.parentNode.classList.contains('modal');
5030
      // 680px is when the media query for 60% modal width kicks in
5031
      var isInsetModal = isModal && window.innerWidth >= 680;
5032
 
5033
     /*
5034
      *  _______
5035
      * |---A---| <- top of scroll view
5036
      * |       |
5037
      * |---B---| <- keyboard
5038
      * |   C   | <- input
5039
      * |---D---| <- initial bottom of scroll view
5040
      * |___E___| <- bottom of viewport
5041
      *
5042
      *  All commented calculations relative to the top of the viewport (ie E
5043
      *  is the viewport height, not 0)
5044
      */
5045
      if (!alreadyShrunk) {
5046
        // shrink scrollview so we can actually scroll if the input is hidden
5047
        // if it isn't shrink so we can scroll to inputs under the keyboard
5048
        // inset modals won't shrink on Android on their own when the keyboard appears
5049
        if ( ionic.Platform.isIOS() || ionic.Platform.isFullScreen || isInsetModal ) {
5050
          // if there are things below the scroll view account for them and
5051
          // subtract them from the keyboard height when resizing
5052
          // E - D                         E                         D
5053
          var scrollBottomOffsetToBottom = e.detail.viewportHeight - scrollBottomOffsetToTop;
5054
 
5055
          // 0 or D - B if D > B           E - B                     E - D
5056
          var keyboardOffset = Math.max(0, e.detail.keyboardHeight - scrollBottomOffsetToBottom);
5057
 
5058
          ionic.requestAnimationFrame(function(){
5059
            // D - A or B - A if D > B       D - A             max(0, D - B)
5060
            scrollViewOffsetHeight = scrollViewOffsetHeight - keyboardOffset;
5061
            container.style.height = scrollViewOffsetHeight + "px";
5062
            container.style.overflow = "visible";
5063
 
5064
            //update scroll view
5065
            self.resize();
5066
          });
5067
        }
5068
 
5069
        self.isShrunkForKeyboard = true;
5070
      }
5071
 
5072
      /*
5073
       *  _______
5074
       * |---A---| <- top of scroll view
5075
       * |   *   | <- where we want to scroll to
5076
       * |--B-D--| <- keyboard, bottom of scroll view
5077
       * |   C   | <- input
5078
       * |       |
5079
       * |___E___| <- bottom of viewport
5080
       *
5081
       *  All commented calculations relative to the top of the viewport (ie E
5082
       *  is the viewport height, not 0)
5083
       */
5084
      // if the element is positioned under the keyboard scroll it into view
5085
      if (e.detail.isElementUnderKeyboard) {
5086
 
5087
        ionic.requestAnimationFrame(function(){
5088
          container.scrollTop = 0;
5089
          // update D if we shrunk
5090
          if (self.isShrunkForKeyboard && !alreadyShrunk) {
5091
            scrollBottomOffsetToTop = container.getBoundingClientRect().bottom;
5092
          }
5093
 
5094
          // middle of the scrollview, this is where we want to scroll to
5095
          // (D - A) / 2
5096
          var scrollMidpointOffset = scrollViewOffsetHeight * 0.5;
5097
          //console.log("container.offsetHeight: " + scrollViewOffsetHeight);
5098
 
5099
          // middle of the input we want to scroll into view
5100
          // C
5101
          var inputMidpoint = ((e.detail.elementBottom + e.detail.elementTop) / 2);
5102
 
5103
          // distance from middle of input to the bottom of the scroll view
5104
          // C - D                                C               D
5105
          var inputMidpointOffsetToScrollBottom = inputMidpoint - scrollBottomOffsetToTop;
5106
 
5107
          //C - D + (D - A)/2          C - D                     (D - A)/ 2
5108
          var scrollTop = inputMidpointOffsetToScrollBottom + scrollMidpointOffset;
5109
 
5110
          if ( scrollTop > 0) {
5111
            if (ionic.Platform.isIOS()) ionic.tap.cloneFocusedInput(container, self);
5112
            self.scrollBy(0, scrollTop, true);
5113
            self.onScroll();
5114
          }
5115
        });
5116
      }
5117
 
5118
      // Only the first scrollView parent of the element that broadcasted this event
5119
      // (the active element that needs to be shown) should receive this event
5120
      e.stopPropagation();
5121
    };
5122
 
5123
    self.resetScrollView = function() {
5124
      //return scrollview to original height once keyboard has hidden
5125
      if ( self.isShrunkForKeyboard ) {
5126
        self.isShrunkForKeyboard = false;
5127
        container.style.height = "";
5128
        container.style.overflow = "";
5129
      }
5130
      self.resize();
5131
    };
5132
 
5133
    //Broadcasted when keyboard is shown on some platforms.
5134
    //See js/utils/keyboard.js
5135
    container.addEventListener('scrollChildIntoView', self.scrollChildIntoView);
5136
 
5137
    // Listen on document because container may not have had the last
5138
    // keyboardActiveElement, for example after closing a modal with a focused
5139
    // input and returning to a previously resized scroll view in an ion-content.
5140
    // Since we can only resize scroll views that are currently visible, just resize
5141
    // the current scroll view when the keyboard is closed.
5142
    document.addEventListener('resetScrollView', self.resetScrollView);
5143
 
5144
    function getEventTouches(e) {
5145
      return e.touches && e.touches.length ? e.touches : [{
5146
        pageX: e.pageX,
5147
        pageY: e.pageY
5148
      }];
5149
    }
5150
 
5151
    self.touchStart = function(e) {
5152
      self.startCoordinates = ionic.tap.pointerCoord(e);
5153
 
5154
      if ( ionic.tap.ignoreScrollStart(e) ) {
5155
        return;
5156
      }
5157
 
5158
      self.__isDown = true;
5159
 
5160
      if ( ionic.tap.containsOrIsTextInput(e.target) || e.target.tagName === 'SELECT' ) {
5161
        // do not start if the target is a text input
5162
        // if there is a touchmove on this input, then we can start the scroll
5163
        self.__hasStarted = false;
5164
        return;
5165
      }
5166
 
5167
      self.__isSelectable = true;
5168
      self.__enableScrollY = true;
5169
      self.__hasStarted = true;
5170
      self.doTouchStart(getEventTouches(e), e.timeStamp);
5171
      e.preventDefault();
5172
    };
5173
 
5174
    self.touchMove = function(e) {
5175
      if (self.options.freeze || !self.__isDown ||
5176
        (!self.__isDown && e.defaultPrevented) ||
5177
        (e.target.tagName === 'TEXTAREA' && e.target.parentElement.querySelector(':focus')) ) {
5178
        return;
5179
      }
5180
 
5181
      if ( !self.__hasStarted && ( ionic.tap.containsOrIsTextInput(e.target) || e.target.tagName === 'SELECT' ) ) {
5182
        // the target is a text input and scroll has started
5183
        // since the text input doesn't start on touchStart, do it here
5184
        self.__hasStarted = true;
5185
        self.doTouchStart(getEventTouches(e), e.timeStamp);
5186
        e.preventDefault();
5187
        return;
5188
      }
5189
 
5190
      if (self.startCoordinates) {
5191
        // we have start coordinates, so get this touch move's current coordinates
5192
        var currentCoordinates = ionic.tap.pointerCoord(e);
5193
 
5194
        if ( self.__isSelectable &&
5195
            ionic.tap.isTextInput(e.target) &&
5196
            Math.abs(self.startCoordinates.x - currentCoordinates.x) > 20 ) {
5197
          // user slid the text input's caret on its x axis, disable any future y scrolling
5198
          self.__enableScrollY = false;
5199
          self.__isSelectable = true;
5200
        }
5201
 
5202
        if ( self.__enableScrollY && Math.abs(self.startCoordinates.y - currentCoordinates.y) > 10 ) {
5203
          // user scrolled the entire view on the y axis
5204
          // disabled being able to select text on an input
5205
          // hide the input which has focus, and show a cloned one that doesn't have focus
5206
          self.__isSelectable = false;
5207
          ionic.tap.cloneFocusedInput(container, self);
5208
        }
5209
      }
5210
 
5211
      self.doTouchMove(getEventTouches(e), e.timeStamp, e.scale);
5212
      self.__isDown = true;
5213
    };
5214
 
5215
    self.touchMoveBubble = function(e) {
5216
      if(self.__isDown && self.options.preventDefault) {
5217
        e.preventDefault();
5218
      }
5219
    };
5220
 
5221
    self.touchEnd = function(e) {
5222
      if (!self.__isDown) return;
5223
 
5224
      self.doTouchEnd(e, e.timeStamp);
5225
      self.__isDown = false;
5226
      self.__hasStarted = false;
5227
      self.__isSelectable = true;
5228
      self.__enableScrollY = true;
5229
 
5230
      if ( !self.__isDragging && !self.__isDecelerating && !self.__isAnimating ) {
5231
        ionic.tap.removeClonedInputs(container, self);
5232
      }
5233
    };
5234
 
5235
    self.mouseWheel = ionic.animationFrameThrottle(function(e) {
5236
      var scrollParent = ionic.DomUtil.getParentOrSelfWithClass(e.target, 'ionic-scroll');
5237
      if (!self.options.freeze && scrollParent === self.__container) {
5238
 
5239
        self.hintResize();
5240
        self.scrollBy(
5241
          (e.wheelDeltaX || e.deltaX || 0) / self.options.wheelDampen,
5242
          (-e.wheelDeltaY || e.deltaY || 0) / self.options.wheelDampen
5243
        );
5244
 
5245
        self.__fadeScrollbars('in');
5246
        clearTimeout(self.__wheelHideBarTimeout);
5247
        self.__wheelHideBarTimeout = setTimeout(function() {
5248
          self.__fadeScrollbars('out');
5249
        }, 100);
5250
      }
5251
    });
5252
 
5253
    if ('ontouchstart' in window) {
5254
      // Touch Events
5255
      container.addEventListener("touchstart", self.touchStart, false);
5256
      if(self.options.preventDefault) container.addEventListener("touchmove", self.touchMoveBubble, false);
5257
      document.addEventListener("touchmove", self.touchMove, false);
5258
      document.addEventListener("touchend", self.touchEnd, false);
5259
      document.addEventListener("touchcancel", self.touchEnd, false);
5260
 
5261
    } else if (window.navigator.pointerEnabled) {
5262
      // Pointer Events
5263
      container.addEventListener("pointerdown", self.touchStart, false);
5264
      if(self.options.preventDefault) container.addEventListener("pointermove", self.touchMoveBubble, false);
5265
      document.addEventListener("pointermove", self.touchMove, false);
5266
      document.addEventListener("pointerup", self.touchEnd, false);
5267
      document.addEventListener("pointercancel", self.touchEnd, false);
5268
      document.addEventListener("wheel", self.mouseWheel, false);
5269
 
5270
    } else if (window.navigator.msPointerEnabled) {
5271
      // IE10, WP8 (Pointer Events)
5272
      container.addEventListener("MSPointerDown", self.touchStart, false);
5273
      if(self.options.preventDefault) container.addEventListener("MSPointerMove", self.touchMoveBubble, false);
5274
      document.addEventListener("MSPointerMove", self.touchMove, false);
5275
      document.addEventListener("MSPointerUp", self.touchEnd, false);
5276
      document.addEventListener("MSPointerCancel", self.touchEnd, false);
5277
      document.addEventListener("wheel", self.mouseWheel, false);
5278
 
5279
    } else {
5280
      // Mouse Events
5281
      var mousedown = false;
5282
 
5283
      self.mouseDown = function(e) {
5284
        if ( ionic.tap.ignoreScrollStart(e) || e.target.tagName === 'SELECT' ) {
5285
          return;
5286
        }
5287
        self.doTouchStart(getEventTouches(e), e.timeStamp);
5288
 
5289
        if ( !ionic.tap.isTextInput(e.target) ) {
5290
          e.preventDefault();
5291
        }
5292
        mousedown = true;
5293
      };
5294
 
5295
      self.mouseMove = function(e) {
5296
        if (self.options.freeze || !mousedown || (!mousedown && e.defaultPrevented)) {
5297
          return;
5298
        }
5299
 
5300
        self.doTouchMove(getEventTouches(e), e.timeStamp);
5301
 
5302
        mousedown = true;
5303
      };
5304
 
5305
      self.mouseMoveBubble = function(e) {
5306
        if (mousedown && self.options.preventDefault) {
5307
          e.preventDefault();
5308
        }
5309
      };
5310
 
5311
      self.mouseUp = function(e) {
5312
        if (!mousedown) {
5313
          return;
5314
        }
5315
 
5316
        self.doTouchEnd(e, e.timeStamp);
5317
 
5318
        mousedown = false;
5319
      };
5320
 
5321
      container.addEventListener("mousedown", self.mouseDown, false);
5322
      if(self.options.preventDefault) container.addEventListener("mousemove", self.mouseMoveBubble, false);
5323
      document.addEventListener("mousemove", self.mouseMove, false);
5324
      document.addEventListener("mouseup", self.mouseUp, false);
5325
      document.addEventListener('mousewheel', self.mouseWheel, false);
5326
      document.addEventListener('wheel', self.mouseWheel, false);
5327
    }
5328
  },
5329
 
5330
  __cleanup: function() {
5331
    var self = this;
5332
    var container = self.__container;
5333
 
5334
    container.removeEventListener('touchstart', self.touchStart);
5335
    container.removeEventListener('touchmove', self.touchMoveBubble);
5336
    document.removeEventListener('touchmove', self.touchMove);
5337
    document.removeEventListener('touchend', self.touchEnd);
5338
    document.removeEventListener('touchcancel', self.touchCancel);
5339
 
5340
    container.removeEventListener("pointerdown", self.touchStart);
5341
    container.removeEventListener("pointermove", self.touchMoveBubble);
5342
    document.removeEventListener("pointermove", self.touchMove);
5343
    document.removeEventListener("pointerup", self.touchEnd);
5344
    document.removeEventListener("pointercancel", self.touchEnd);
5345
 
5346
    container.removeEventListener("MSPointerDown", self.touchStart);
5347
    container.removeEventListener("MSPointerMove", self.touchMoveBubble);
5348
    document.removeEventListener("MSPointerMove", self.touchMove);
5349
    document.removeEventListener("MSPointerUp", self.touchEnd);
5350
    document.removeEventListener("MSPointerCancel", self.touchEnd);
5351
 
5352
    container.removeEventListener("mousedown", self.mouseDown);
5353
    container.removeEventListener("mousemove", self.mouseMoveBubble);
5354
    document.removeEventListener("mousemove", self.mouseMove);
5355
    document.removeEventListener("mouseup", self.mouseUp);
5356
    document.removeEventListener('mousewheel', self.mouseWheel);
5357
    document.removeEventListener('wheel', self.mouseWheel);
5358
 
5359
    container.removeEventListener('scrollChildIntoView', self.scrollChildIntoView);
5360
    document.removeEventListener('resetScrollView', self.resetScrollView);
5361
 
5362
    ionic.tap.removeClonedInputs(container, self);
5363
 
5364
    delete self.__container;
5365
    delete self.__content;
5366
    delete self.__indicatorX;
5367
    delete self.__indicatorY;
5368
    delete self.options.el;
5369
 
5370
    self.__callback = self.scrollChildIntoView = self.resetScrollView = NOOP;
5371
 
5372
    self.mouseMove = self.mouseDown = self.mouseUp = self.mouseWheel =
5373
      self.touchStart = self.touchMove = self.touchEnd = self.touchCancel = NOOP;
5374
 
5375
    self.resize = self.scrollTo = self.zoomTo =
5376
      self.__scrollingComplete = NOOP;
5377
    container = null;
5378
  },
5379
 
5380
  /** Create a scroll bar div with the given direction **/
5381
  __createScrollbar: function(direction) {
5382
    var bar = document.createElement('div'),
5383
      indicator = document.createElement('div');
5384
 
5385
    indicator.className = 'scroll-bar-indicator scroll-bar-fade-out';
5386
 
5387
    if (direction == 'h') {
5388
      bar.className = 'scroll-bar scroll-bar-h';
5389
    } else {
5390
      bar.className = 'scroll-bar scroll-bar-v';
5391
    }
5392
 
5393
    bar.appendChild(indicator);
5394
    return bar;
5395
  },
5396
 
5397
  __createScrollbars: function() {
5398
    var self = this;
5399
    var indicatorX, indicatorY;
5400
 
5401
    if (self.options.scrollingX) {
5402
      indicatorX = {
5403
        el: self.__createScrollbar('h'),
5404
        sizeRatio: 1
5405
      };
5406
      indicatorX.indicator = indicatorX.el.children[0];
5407
 
5408
      if (self.options.scrollbarX) {
5409
        self.__container.appendChild(indicatorX.el);
5410
      }
5411
      self.__indicatorX = indicatorX;
5412
    }
5413
 
5414
    if (self.options.scrollingY) {
5415
      indicatorY = {
5416
        el: self.__createScrollbar('v'),
5417
        sizeRatio: 1
5418
      };
5419
      indicatorY.indicator = indicatorY.el.children[0];
5420
 
5421
      if (self.options.scrollbarY) {
5422
        self.__container.appendChild(indicatorY.el);
5423
      }
5424
      self.__indicatorY = indicatorY;
5425
    }
5426
  },
5427
 
5428
  __resizeScrollbars: function() {
5429
    var self = this;
5430
 
5431
    // Update horiz bar
5432
    if (self.__indicatorX) {
5433
      var width = Math.max(Math.round(self.__clientWidth * self.__clientWidth / (self.__contentWidth)), 20);
5434
      if (width > self.__contentWidth) {
5435
        width = 0;
5436
      }
5437
      if (width !== self.__indicatorX.size) {
5438
        ionic.requestAnimationFrame(function(){
5439
          self.__indicatorX.indicator.style.width = width + 'px';
5440
        });
5441
      }
5442
      self.__indicatorX.size = width;
5443
      self.__indicatorX.minScale = self.options.minScrollbarSizeX / width;
5444
      self.__indicatorX.maxPos = self.__clientWidth - width;
5445
      self.__indicatorX.sizeRatio = self.__maxScrollLeft ? self.__indicatorX.maxPos / self.__maxScrollLeft : 1;
5446
    }
5447
 
5448
    // Update vert bar
5449
    if (self.__indicatorY) {
5450
      var height = Math.max(Math.round(self.__clientHeight * self.__clientHeight / (self.__contentHeight)), 20);
5451
      if (height > self.__contentHeight) {
5452
        height = 0;
5453
      }
5454
      if (height !== self.__indicatorY.size) {
5455
        ionic.requestAnimationFrame(function(){
5456
          self.__indicatorY && (self.__indicatorY.indicator.style.height = height + 'px');
5457
        });
5458
      }
5459
      self.__indicatorY.size = height;
5460
      self.__indicatorY.minScale = self.options.minScrollbarSizeY / height;
5461
      self.__indicatorY.maxPos = self.__clientHeight - height;
5462
      self.__indicatorY.sizeRatio = self.__maxScrollTop ? self.__indicatorY.maxPos / self.__maxScrollTop : 1;
5463
    }
5464
  },
5465
 
5466
  /**
5467
   * Move and scale the scrollbars as the page scrolls.
5468
   */
5469
  __repositionScrollbars: function() {
5470
    var self = this,
5471
        heightScale, widthScale,
5472
        widthDiff, heightDiff,
5473
        x, y,
5474
        xstop = 0, ystop = 0;
5475
 
5476
    if (self.__indicatorX) {
5477
      // Handle the X scrollbar
5478
 
5479
      // Don't go all the way to the right if we have a vertical scrollbar as well
5480
      if (self.__indicatorY) xstop = 10;
5481
 
5482
      x = Math.round(self.__indicatorX.sizeRatio * self.__scrollLeft) || 0;
5483
 
5484
      // The the difference between the last content X position, and our overscrolled one
5485
      widthDiff = self.__scrollLeft - (self.__maxScrollLeft - xstop);
5486
 
5487
      if (self.__scrollLeft < 0) {
5488
 
5489
        widthScale = Math.max(self.__indicatorX.minScale,
5490
            (self.__indicatorX.size - Math.abs(self.__scrollLeft)) / self.__indicatorX.size);
5491
 
5492
        // Stay at left
5493
        x = 0;
5494
 
5495
        // Make sure scale is transformed from the left/center origin point
5496
        self.__indicatorX.indicator.style[self.__transformOriginProperty] = 'left center';
5497
      } else if (widthDiff > 0) {
5498
 
5499
        widthScale = Math.max(self.__indicatorX.minScale,
5500
            (self.__indicatorX.size - widthDiff) / self.__indicatorX.size);
5501
 
5502
        // Stay at the furthest x for the scrollable viewport
5503
        x = self.__indicatorX.maxPos - xstop;
5504
 
5505
        // Make sure scale is transformed from the right/center origin point
5506
        self.__indicatorX.indicator.style[self.__transformOriginProperty] = 'right center';
5507
 
5508
      } else {
5509
 
5510
        // Normal motion
5511
        x = Math.min(self.__maxScrollLeft, Math.max(0, x));
5512
        widthScale = 1;
5513
 
5514
      }
5515
 
5516
      var translate3dX = 'translate3d(' + x + 'px, 0, 0) scaleX(' + widthScale + ')';
5517
      if (self.__indicatorX.transformProp !== translate3dX) {
5518
        self.__indicatorX.indicator.style[self.__transformProperty] = translate3dX;
5519
        self.__indicatorX.transformProp = translate3dX;
5520
      }
5521
    }
5522
 
5523
    if (self.__indicatorY) {
5524
 
5525
      y = Math.round(self.__indicatorY.sizeRatio * self.__scrollTop) || 0;
5526
 
5527
      // Don't go all the way to the right if we have a vertical scrollbar as well
5528
      if (self.__indicatorX) ystop = 10;
5529
 
5530
      heightDiff = self.__scrollTop - (self.__maxScrollTop - ystop);
5531
 
5532
      if (self.__scrollTop < 0) {
5533
 
5534
        heightScale = Math.max(self.__indicatorY.minScale, (self.__indicatorY.size - Math.abs(self.__scrollTop)) / self.__indicatorY.size);
5535
 
5536
        // Stay at top
5537
        y = 0;
5538
 
5539
        // Make sure scale is transformed from the center/top origin point
5540
        if (self.__indicatorY.originProp !== 'center top') {
5541
          self.__indicatorY.indicator.style[self.__transformOriginProperty] = 'center top';
5542
          self.__indicatorY.originProp = 'center top';
5543
        }
5544
 
5545
      } else if (heightDiff > 0) {
5546
 
5547
        heightScale = Math.max(self.__indicatorY.minScale, (self.__indicatorY.size - heightDiff) / self.__indicatorY.size);
5548
 
5549
        // Stay at bottom of scrollable viewport
5550
        y = self.__indicatorY.maxPos - ystop;
5551
 
5552
        // Make sure scale is transformed from the center/bottom origin point
5553
        if (self.__indicatorY.originProp !== 'center bottom') {
5554
          self.__indicatorY.indicator.style[self.__transformOriginProperty] = 'center bottom';
5555
          self.__indicatorY.originProp = 'center bottom';
5556
        }
5557
 
5558
      } else {
5559
 
5560
        // Normal motion
5561
        y = Math.min(self.__maxScrollTop, Math.max(0, y));
5562
        heightScale = 1;
5563
 
5564
      }
5565
 
5566
      var translate3dY = 'translate3d(0,' + y + 'px, 0) scaleY(' + heightScale + ')';
5567
      if (self.__indicatorY.transformProp !== translate3dY) {
5568
        self.__indicatorY.indicator.style[self.__transformProperty] = translate3dY;
5569
        self.__indicatorY.transformProp = translate3dY;
5570
      }
5571
    }
5572
  },
5573
 
5574
  __fadeScrollbars: function(direction, delay) {
5575
    var self = this;
5576
 
5577
    if (!self.options.scrollbarsFade) {
5578
      return;
5579
    }
5580
 
5581
    var className = 'scroll-bar-fade-out';
5582
 
5583
    if (self.options.scrollbarsFade === true) {
5584
      clearTimeout(self.__scrollbarFadeTimeout);
5585
 
5586
      if (direction == 'in') {
5587
        if (self.__indicatorX) { self.__indicatorX.indicator.classList.remove(className); }
5588
        if (self.__indicatorY) { self.__indicatorY.indicator.classList.remove(className); }
5589
      } else {
5590
        self.__scrollbarFadeTimeout = setTimeout(function() {
5591
          if (self.__indicatorX) { self.__indicatorX.indicator.classList.add(className); }
5592
          if (self.__indicatorY) { self.__indicatorY.indicator.classList.add(className); }
5593
        }, delay || self.options.scrollbarFadeDelay);
5594
      }
5595
    }
5596
  },
5597
 
5598
  __scrollingComplete: function() {
5599
    this.options.scrollingComplete();
5600
    ionic.tap.removeClonedInputs(this.__container, this);
5601
    this.__fadeScrollbars('out');
5602
  },
5603
 
5604
  resize: function(continueScrolling) {
5605
    var self = this;
5606
    if (!self.__container || !self.options) return;
5607
 
5608
    // Update Scroller dimensions for changed content
5609
    // Add padding to bottom of content
5610
    self.setDimensions(
5611
      self.__container.clientWidth,
5612
      self.__container.clientHeight,
5613
      self.options.getContentWidth(),
5614
      self.options.getContentHeight(),
5615
      continueScrolling
5616
    );
5617
  },
5618
  /*
5619
  ---------------------------------------------------------------------------
5620
    PUBLIC API
5621
  ---------------------------------------------------------------------------
5622
  */
5623
 
5624
  getRenderFn: function() {
5625
    var self = this;
5626
 
5627
    var content = self.__content;
5628
 
5629
    var docStyle = document.documentElement.style;
5630
 
5631
    var engine;
5632
    if ('MozAppearance' in docStyle) {
5633
      engine = 'gecko';
5634
    } else if ('WebkitAppearance' in docStyle) {
5635
      engine = 'webkit';
5636
    } else if (typeof navigator.cpuClass === 'string') {
5637
      engine = 'trident';
5638
    }
5639
 
5640
    var vendorPrefix = {
5641
      trident: 'ms',
5642
      gecko: 'Moz',
5643
      webkit: 'Webkit',
5644
      presto: 'O'
5645
    }[engine];
5646
 
5647
    var helperElem = document.createElement("div");
5648
    var undef;
5649
 
5650
    var perspectiveProperty = vendorPrefix + "Perspective";
5651
    var transformProperty = vendorPrefix + "Transform";
5652
    var transformOriginProperty = vendorPrefix + 'TransformOrigin';
5653
 
5654
    self.__perspectiveProperty = transformProperty;
5655
    self.__transformProperty = transformProperty;
5656
    self.__transformOriginProperty = transformOriginProperty;
5657
 
5658
    if (helperElem.style[perspectiveProperty] !== undef) {
5659
 
5660
      return function(left, top, zoom, wasResize) {
5661
        var translate3d = 'translate3d(' + (-left) + 'px,' + (-top) + 'px,0) scale(' + zoom + ')';
5662
        if (translate3d !== self.contentTransform) {
5663
          content.style[transformProperty] = translate3d;
5664
          self.contentTransform = translate3d;
5665
        }
5666
        self.__repositionScrollbars();
5667
        if (!wasResize) {
5668
          self.triggerScrollEvent();
5669
        }
5670
      };
5671
 
5672
    } else if (helperElem.style[transformProperty] !== undef) {
5673
 
5674
      return function(left, top, zoom, wasResize) {
5675
        content.style[transformProperty] = 'translate(' + (-left) + 'px,' + (-top) + 'px) scale(' + zoom + ')';
5676
        self.__repositionScrollbars();
5677
        if (!wasResize) {
5678
          self.triggerScrollEvent();
5679
        }
5680
      };
5681
 
5682
    } else {
5683
 
5684
      return function(left, top, zoom, wasResize) {
5685
        content.style.marginLeft = left ? (-left / zoom) + 'px' : '';
5686
        content.style.marginTop = top ? (-top / zoom) + 'px' : '';
5687
        content.style.zoom = zoom || '';
5688
        self.__repositionScrollbars();
5689
        if (!wasResize) {
5690
          self.triggerScrollEvent();
5691
        }
5692
      };
5693
 
5694
    }
5695
  },
5696
 
5697
 
5698
  /**
5699
   * Configures the dimensions of the client (outer) and content (inner) elements.
5700
   * Requires the available space for the outer element and the outer size of the inner element.
5701
   * All values which are falsy (null or zero etc.) are ignored and the old value is kept.
5702
   *
5703
   * @param clientWidth {Integer} Inner width of outer element
5704
   * @param clientHeight {Integer} Inner height of outer element
5705
   * @param contentWidth {Integer} Outer width of inner element
5706
   * @param contentHeight {Integer} Outer height of inner element
5707
   */
5708
  setDimensions: function(clientWidth, clientHeight, contentWidth, contentHeight, continueScrolling) {
5709
    var self = this;
5710
 
5711
    if (!clientWidth && !clientHeight && !contentWidth && !contentHeight) {
5712
      // this scrollview isn't rendered, don't bother
5713
      return;
5714
    }
5715
 
5716
    // Only update values which are defined
5717
    if (clientWidth === +clientWidth) {
5718
      self.__clientWidth = clientWidth;
5719
    }
5720
 
5721
    if (clientHeight === +clientHeight) {
5722
      self.__clientHeight = clientHeight;
5723
    }
5724
 
5725
    if (contentWidth === +contentWidth) {
5726
      self.__contentWidth = contentWidth;
5727
    }
5728
 
5729
    if (contentHeight === +contentHeight) {
5730
      self.__contentHeight = contentHeight;
5731
    }
5732
 
5733
    // Refresh maximums
5734
    self.__computeScrollMax();
5735
    self.__resizeScrollbars();
5736
 
5737
    // Refresh scroll position
5738
    if (!continueScrolling) {
5739
      self.scrollTo(self.__scrollLeft, self.__scrollTop, true, null, true);
5740
    }
5741
 
5742
  },
5743
 
5744
 
5745
  /**
5746
   * Sets the client coordinates in relation to the document.
5747
   *
5748
   * @param left {Integer} Left position of outer element
5749
   * @param top {Integer} Top position of outer element
5750
   */
5751
  setPosition: function(left, top) {
5752
    this.__clientLeft = left || 0;
5753
    this.__clientTop = top || 0;
5754
  },
5755
 
5756
 
5757
  /**
5758
   * Configures the snapping (when snapping is active)
5759
   *
5760
   * @param width {Integer} Snapping width
5761
   * @param height {Integer} Snapping height
5762
   */
5763
  setSnapSize: function(width, height) {
5764
    this.__snapWidth = width;
5765
    this.__snapHeight = height;
5766
  },
5767
 
5768
 
5769
  /**
5770
   * Activates pull-to-refresh. A special zone on the top of the list to start a list refresh whenever
5771
   * the user event is released during visibility of this zone. This was introduced by some apps on iOS like
5772
   * the official Twitter client.
5773
   *
5774
   * @param height {Integer} Height of pull-to-refresh zone on top of rendered list
5775
   * @param activateCallback {Function} Callback to execute on activation. This is for signalling the user about a refresh is about to happen when he release.
5776
   * @param deactivateCallback {Function} Callback to execute on deactivation. This is for signalling the user about the refresh being cancelled.
5777
   * @param startCallback {Function} Callback to execute to start the real async refresh action. Call {@link #finishPullToRefresh} after finish of refresh.
5778
   * @param showCallback {Function} Callback to execute when the refresher should be shown. This is for showing the refresher during a negative scrollTop.
5779
   * @param hideCallback {Function} Callback to execute when the refresher should be hidden. This is for hiding the refresher when it's behind the nav bar.
5780
   * @param tailCallback {Function} Callback to execute just before the refresher returns to it's original state. This is for zooming out the refresher.
5781
   * @param pullProgressCallback Callback to state the progress while pulling to refresh
5782
   */
5783
  activatePullToRefresh: function(height, refresherMethods) {
5784
    var self = this;
5785
 
5786
    self.__refreshHeight = height;
5787
    self.__refreshActivate = function() { ionic.requestAnimationFrame(refresherMethods.activate); };
5788
    self.__refreshDeactivate = function() { ionic.requestAnimationFrame(refresherMethods.deactivate); };
5789
    self.__refreshStart = function() { ionic.requestAnimationFrame(refresherMethods.start); };
5790
    self.__refreshShow = function() { ionic.requestAnimationFrame(refresherMethods.show); };
5791
    self.__refreshHide = function() { ionic.requestAnimationFrame(refresherMethods.hide); };
5792
    self.__refreshTail = function() { ionic.requestAnimationFrame(refresherMethods.tail); };
5793
    self.__refreshTailTime = 100;
5794
    self.__minSpinTime = 600;
5795
  },
5796
 
5797
 
5798
  /**
5799
   * Starts pull-to-refresh manually.
5800
   */
5801
  triggerPullToRefresh: function() {
5802
    // Use publish instead of scrollTo to allow scrolling to out of boundary position
5803
    // We don't need to normalize scrollLeft, zoomLevel, etc. here because we only y-scrolling when pull-to-refresh is enabled
5804
    this.__publish(this.__scrollLeft, -this.__refreshHeight, this.__zoomLevel, true);
5805
 
5806
    var d = new Date();
5807
    this.refreshStartTime = d.getTime();
5808
 
5809
    if (this.__refreshStart) {
5810
      this.__refreshStart();
5811
    }
5812
  },
5813
 
5814
 
5815
  /**
5816
   * Signalizes that pull-to-refresh is finished.
5817
   */
5818
  finishPullToRefresh: function() {
5819
    var self = this;
5820
    // delay to make sure the spinner has a chance to spin for a split second before it's dismissed
5821
    var d = new Date();
5822
    var delay = 0;
5823
    if (self.refreshStartTime + self.__minSpinTime > d.getTime()) {
5824
      delay = self.refreshStartTime + self.__minSpinTime - d.getTime();
5825
    }
5826
    setTimeout(function() {
5827
      if (self.__refreshTail) {
5828
        self.__refreshTail();
5829
      }
5830
      setTimeout(function() {
5831
        self.__refreshActive = false;
5832
        if (self.__refreshDeactivate) {
5833
          self.__refreshDeactivate();
5834
        }
5835
        if (self.__refreshHide) {
5836
          self.__refreshHide();
5837
        }
5838
 
5839
        self.scrollTo(self.__scrollLeft, self.__scrollTop, true);
5840
      }, self.__refreshTailTime);
5841
    }, delay);
5842
  },
5843
 
5844
 
5845
  /**
5846
   * Returns the scroll position and zooming values
5847
   *
5848
   * @return {Map} `left` and `top` scroll position and `zoom` level
5849
   */
5850
  getValues: function() {
5851
    return {
5852
      left: this.__scrollLeft,
5853
      top: this.__scrollTop,
5854
      zoom: this.__zoomLevel
5855
    };
5856
  },
5857
 
5858
 
5859
  /**
5860
   * Returns the maximum scroll values
5861
   *
5862
   * @return {Map} `left` and `top` maximum scroll values
5863
   */
5864
  getScrollMax: function() {
5865
    return {
5866
      left: this.__maxScrollLeft,
5867
      top: this.__maxScrollTop
5868
    };
5869
  },
5870
 
5871
 
5872
  /**
5873
   * Zooms to the given level. Supports optional animation. Zooms
5874
   * the center when no coordinates are given.
5875
   *
5876
   * @param level {Number} Level to zoom to
5877
   * @param animate {Boolean} Whether to use animation
5878
   * @param originLeft {Number} Zoom in at given left coordinate
5879
   * @param originTop {Number} Zoom in at given top coordinate
5880
   */
5881
  zoomTo: function(level, animate, originLeft, originTop) {
5882
    var self = this;
5883
 
5884
    if (!self.options.zooming) {
5885
      throw new Error("Zooming is not enabled!");
5886
    }
5887
 
5888
    // Stop deceleration
5889
    if (self.__isDecelerating) {
5890
      zyngaCore.effect.Animate.stop(self.__isDecelerating);
5891
      self.__isDecelerating = false;
5892
    }
5893
 
5894
    var oldLevel = self.__zoomLevel;
5895
 
5896
    // Normalize input origin to center of viewport if not defined
5897
    if (originLeft == null) {
5898
      originLeft = self.__clientWidth / 2;
5899
    }
5900
 
5901
    if (originTop == null) {
5902
      originTop = self.__clientHeight / 2;
5903
    }
5904
 
5905
    // Limit level according to configuration
5906
    level = Math.max(Math.min(level, self.options.maxZoom), self.options.minZoom);
5907
 
5908
    // Recompute maximum values while temporary tweaking maximum scroll ranges
5909
    self.__computeScrollMax(level);
5910
 
5911
    // Recompute left and top coordinates based on new zoom level
5912
    var left = ((originLeft + self.__scrollLeft) * level / oldLevel) - originLeft;
5913
    var top = ((originTop + self.__scrollTop) * level / oldLevel) - originTop;
5914
 
5915
    // Limit x-axis
5916
    if (left > self.__maxScrollLeft) {
5917
      left = self.__maxScrollLeft;
5918
    } else if (left < 0) {
5919
      left = 0;
5920
    }
5921
 
5922
    // Limit y-axis
5923
    if (top > self.__maxScrollTop) {
5924
      top = self.__maxScrollTop;
5925
    } else if (top < 0) {
5926
      top = 0;
5927
    }
5928
 
5929
    // Push values out
5930
    self.__publish(left, top, level, animate);
5931
 
5932
  },
5933
 
5934
 
5935
  /**
5936
   * Zooms the content by the given factor.
5937
   *
5938
   * @param factor {Number} Zoom by given factor
5939
   * @param animate {Boolean} Whether to use animation
5940
   * @param originLeft {Number} Zoom in at given left coordinate
5941
   * @param originTop {Number} Zoom in at given top coordinate
5942
   */
5943
  zoomBy: function(factor, animate, originLeft, originTop) {
5944
    this.zoomTo(this.__zoomLevel * factor, animate, originLeft, originTop);
5945
  },
5946
 
5947
 
5948
  /**
5949
   * Scrolls to the given position. Respect limitations and snapping automatically.
5950
   *
5951
   * @param left {Number} Horizontal scroll position, keeps current if value is <code>null</code>
5952
   * @param top {Number} Vertical scroll position, keeps current if value is <code>null</code>
5953
   * @param animate {Boolean} Whether the scrolling should happen using an animation
5954
   * @param zoom {Number} Zoom level to go to
5955
   */
5956
  scrollTo: function(left, top, animate, zoom, wasResize) {
5957
    var self = this;
5958
 
5959
    // Stop deceleration
5960
    if (self.__isDecelerating) {
5961
      zyngaCore.effect.Animate.stop(self.__isDecelerating);
5962
      self.__isDecelerating = false;
5963
    }
5964
 
5965
    // Correct coordinates based on new zoom level
5966
    if (zoom != null && zoom !== self.__zoomLevel) {
5967
 
5968
      if (!self.options.zooming) {
5969
        throw new Error("Zooming is not enabled!");
5970
      }
5971
 
5972
      left *= zoom;
5973
      top *= zoom;
5974
 
5975
      // Recompute maximum values while temporary tweaking maximum scroll ranges
5976
      self.__computeScrollMax(zoom);
5977
 
5978
    } else {
5979
 
5980
      // Keep zoom when not defined
5981
      zoom = self.__zoomLevel;
5982
 
5983
    }
5984
 
5985
    if (!self.options.scrollingX) {
5986
 
5987
      left = self.__scrollLeft;
5988
 
5989
    } else {
5990
 
5991
      if (self.options.paging) {
5992
        left = Math.round(left / self.__clientWidth) * self.__clientWidth;
5993
      } else if (self.options.snapping) {
5994
        left = Math.round(left / self.__snapWidth) * self.__snapWidth;
5995
      }
5996
 
5997
    }
5998
 
5999
    if (!self.options.scrollingY) {
6000
 
6001
      top = self.__scrollTop;
6002
 
6003
    } else {
6004
 
6005
      if (self.options.paging) {
6006
        top = Math.round(top / self.__clientHeight) * self.__clientHeight;
6007
      } else if (self.options.snapping) {
6008
        top = Math.round(top / self.__snapHeight) * self.__snapHeight;
6009
      }
6010
 
6011
    }
6012
 
6013
    // Limit for allowed ranges
6014
    left = Math.max(Math.min(self.__maxScrollLeft, left), 0);
6015
    top = Math.max(Math.min(self.__maxScrollTop, top), 0);
6016
 
6017
    // Don't animate when no change detected, still call publish to make sure
6018
    // that rendered position is really in-sync with internal data
6019
    if (left === self.__scrollLeft && top === self.__scrollTop) {
6020
      animate = false;
6021
    }
6022
 
6023
    // Publish new values
6024
    self.__publish(left, top, zoom, animate, wasResize);
6025
 
6026
  },
6027
 
6028
 
6029
  /**
6030
   * Scroll by the given offset
6031
   *
6032
   * @param left {Number} Scroll x-axis by given offset
6033
   * @param top {Number} Scroll y-axis by given offset
6034
   * @param animate {Boolean} Whether to animate the given change
6035
   */
6036
  scrollBy: function(left, top, animate) {
6037
    var self = this;
6038
 
6039
    var startLeft = self.__isAnimating ? self.__scheduledLeft : self.__scrollLeft;
6040
    var startTop = self.__isAnimating ? self.__scheduledTop : self.__scrollTop;
6041
 
6042
    self.scrollTo(startLeft + (left || 0), startTop + (top || 0), animate);
6043
  },
6044
 
6045
 
6046
 
6047
  /*
6048
  ---------------------------------------------------------------------------
6049
    EVENT CALLBACKS
6050
  ---------------------------------------------------------------------------
6051
  */
6052
 
6053
  /**
6054
   * Mouse wheel handler for zooming support
6055
   */
6056
  doMouseZoom: function(wheelDelta, timeStamp, pageX, pageY) {
6057
    var change = wheelDelta > 0 ? 0.97 : 1.03;
6058
    return this.zoomTo(this.__zoomLevel * change, false, pageX - this.__clientLeft, pageY - this.__clientTop);
6059
  },
6060
 
6061
  /**
6062
   * Touch start handler for scrolling support
6063
   */
6064
  doTouchStart: function(touches, timeStamp) {
6065
    var self = this;
6066
 
6067
    // remember if the deceleration was just stopped
6068
    self.__decStopped = !!(self.__isDecelerating || self.__isAnimating);
6069
 
6070
    self.hintResize();
6071
 
6072
    if (timeStamp instanceof Date) {
6073
      timeStamp = timeStamp.valueOf();
6074
    }
6075
    if (typeof timeStamp !== "number") {
6076
      timeStamp = Date.now();
6077
    }
6078
 
6079
    // Reset interruptedAnimation flag
6080
    self.__interruptedAnimation = true;
6081
 
6082
    // Stop deceleration
6083
    if (self.__isDecelerating) {
6084
      zyngaCore.effect.Animate.stop(self.__isDecelerating);
6085
      self.__isDecelerating = false;
6086
      self.__interruptedAnimation = true;
6087
    }
6088
 
6089
    // Stop animation
6090
    if (self.__isAnimating) {
6091
      zyngaCore.effect.Animate.stop(self.__isAnimating);
6092
      self.__isAnimating = false;
6093
      self.__interruptedAnimation = true;
6094
    }
6095
 
6096
    // Use center point when dealing with two fingers
6097
    var currentTouchLeft, currentTouchTop;
6098
    var isSingleTouch = touches.length === 1;
6099
    if (isSingleTouch) {
6100
      currentTouchLeft = touches[0].pageX;
6101
      currentTouchTop = touches[0].pageY;
6102
    } else {
6103
      currentTouchLeft = Math.abs(touches[0].pageX + touches[1].pageX) / 2;
6104
      currentTouchTop = Math.abs(touches[0].pageY + touches[1].pageY) / 2;
6105
    }
6106
 
6107
    // Store initial positions
6108
    self.__initialTouchLeft = currentTouchLeft;
6109
    self.__initialTouchTop = currentTouchTop;
6110
 
6111
    // Store initial touchList for scale calculation
6112
    self.__initialTouches = touches;
6113
 
6114
    // Store current zoom level
6115
    self.__zoomLevelStart = self.__zoomLevel;
6116
 
6117
    // Store initial touch positions
6118
    self.__lastTouchLeft = currentTouchLeft;
6119
    self.__lastTouchTop = currentTouchTop;
6120
 
6121
    // Store initial move time stamp
6122
    self.__lastTouchMove = timeStamp;
6123
 
6124
    // Reset initial scale
6125
    self.__lastScale = 1;
6126
 
6127
    // Reset locking flags
6128
    self.__enableScrollX = !isSingleTouch && self.options.scrollingX;
6129
    self.__enableScrollY = !isSingleTouch && self.options.scrollingY;
6130
 
6131
    // Reset tracking flag
6132
    self.__isTracking = true;
6133
 
6134
    // Reset deceleration complete flag
6135
    self.__didDecelerationComplete = false;
6136
 
6137
    // Dragging starts directly with two fingers, otherwise lazy with an offset
6138
    self.__isDragging = !isSingleTouch;
6139
 
6140
    // Some features are disabled in multi touch scenarios
6141
    self.__isSingleTouch = isSingleTouch;
6142
 
6143
    // Clearing data structure
6144
    self.__positions = [];
6145
 
6146
  },
6147
 
6148
 
6149
  /**
6150
   * Touch move handler for scrolling support
6151
   */
6152
  doTouchMove: function(touches, timeStamp, scale) {
6153
    if (timeStamp instanceof Date) {
6154
      timeStamp = timeStamp.valueOf();
6155
    }
6156
    if (typeof timeStamp !== "number") {
6157
      timeStamp = Date.now();
6158
    }
6159
 
6160
    var self = this;
6161
 
6162
    // Ignore event when tracking is not enabled (event might be outside of element)
6163
    if (!self.__isTracking) {
6164
      return;
6165
    }
6166
 
6167
    var currentTouchLeft, currentTouchTop;
6168
 
6169
    // Compute move based around of center of fingers
6170
    if (touches.length === 2) {
6171
      currentTouchLeft = Math.abs(touches[0].pageX + touches[1].pageX) / 2;
6172
      currentTouchTop = Math.abs(touches[0].pageY + touches[1].pageY) / 2;
6173
 
6174
      // Calculate scale when not present and only when touches are used
6175
      if (!scale && self.options.zooming) {
6176
        scale = self.__getScale(self.__initialTouches, touches);
6177
      }
6178
    } else {
6179
      currentTouchLeft = touches[0].pageX;
6180
      currentTouchTop = touches[0].pageY;
6181
    }
6182
 
6183
    var positions = self.__positions;
6184
 
6185
    // Are we already is dragging mode?
6186
    if (self.__isDragging) {
6187
        self.__decStopped = false;
6188
 
6189
      // Compute move distance
6190
      var moveX = currentTouchLeft - self.__lastTouchLeft;
6191
      var moveY = currentTouchTop - self.__lastTouchTop;
6192
 
6193
      // Read previous scroll position and zooming
6194
      var scrollLeft = self.__scrollLeft;
6195
      var scrollTop = self.__scrollTop;
6196
      var level = self.__zoomLevel;
6197
 
6198
      // Work with scaling
6199
      if (scale != null && self.options.zooming) {
6200
 
6201
        var oldLevel = level;
6202
 
6203
        // Recompute level based on previous scale and new scale
6204
        level = level / self.__lastScale * scale;
6205
 
6206
        // Limit level according to configuration
6207
        level = Math.max(Math.min(level, self.options.maxZoom), self.options.minZoom);
6208
 
6209
        // Only do further compution when change happened
6210
        if (oldLevel !== level) {
6211
 
6212
          // Compute relative event position to container
6213
          var currentTouchLeftRel = currentTouchLeft - self.__clientLeft;
6214
          var currentTouchTopRel = currentTouchTop - self.__clientTop;
6215
 
6216
          // Recompute left and top coordinates based on new zoom level
6217
          scrollLeft = ((currentTouchLeftRel + scrollLeft) * level / oldLevel) - currentTouchLeftRel;
6218
          scrollTop = ((currentTouchTopRel + scrollTop) * level / oldLevel) - currentTouchTopRel;
6219
 
6220
          // Recompute max scroll values
6221
          self.__computeScrollMax(level);
6222
 
6223
        }
6224
      }
6225
 
6226
      if (self.__enableScrollX) {
6227
 
6228
        scrollLeft -= moveX * self.options.speedMultiplier;
6229
        var maxScrollLeft = self.__maxScrollLeft;
6230
 
6231
        if (scrollLeft > maxScrollLeft || scrollLeft < 0) {
6232
 
6233
          // Slow down on the edges
6234
          if (self.options.bouncing) {
6235
 
6236
            scrollLeft += (moveX / 2 * self.options.speedMultiplier);
6237
 
6238
          } else if (scrollLeft > maxScrollLeft) {
6239
 
6240
            scrollLeft = maxScrollLeft;
6241
 
6242
          } else {
6243
 
6244
            scrollLeft = 0;
6245
 
6246
          }
6247
        }
6248
      }
6249
 
6250
      // Compute new vertical scroll position
6251
      if (self.__enableScrollY) {
6252
 
6253
        scrollTop -= moveY * self.options.speedMultiplier;
6254
        var maxScrollTop = self.__maxScrollTop;
6255
 
6256
        if (scrollTop > maxScrollTop || scrollTop < 0) {
6257
 
6258
          // Slow down on the edges
6259
          if (self.options.bouncing || (self.__refreshHeight && scrollTop < 0)) {
6260
 
6261
            scrollTop += (moveY / 2 * self.options.speedMultiplier);
6262
 
6263
            // Support pull-to-refresh (only when only y is scrollable)
6264
            if (!self.__enableScrollX && self.__refreshHeight != null) {
6265
 
6266
              // hide the refresher when it's behind the header bar in case of header transparency
6267
              if (scrollTop < 0) {
6268
                self.__refreshHidden = false;
6269
                self.__refreshShow();
6270
              } else {
6271
                self.__refreshHide();
6272
                self.__refreshHidden = true;
6273
              }
6274
 
6275
              if (!self.__refreshActive && scrollTop <= -self.__refreshHeight) {
6276
 
6277
                self.__refreshActive = true;
6278
                if (self.__refreshActivate) {
6279
                  self.__refreshActivate();
6280
                }
6281
 
6282
              } else if (self.__refreshActive && scrollTop > -self.__refreshHeight) {
6283
 
6284
                self.__refreshActive = false;
6285
                if (self.__refreshDeactivate) {
6286
                  self.__refreshDeactivate();
6287
                }
6288
 
6289
              }
6290
            }
6291
 
6292
          } else if (scrollTop > maxScrollTop) {
6293
 
6294
            scrollTop = maxScrollTop;
6295
 
6296
          } else {
6297
 
6298
            scrollTop = 0;
6299
 
6300
          }
6301
        } else if (self.__refreshHeight && !self.__refreshHidden) {
6302
          // if a positive scroll value and the refresher is still not hidden, hide it
6303
          self.__refreshHide();
6304
          self.__refreshHidden = true;
6305
        }
6306
      }
6307
 
6308
      // Keep list from growing infinitely (holding min 10, max 20 measure points)
6309
      if (positions.length > 60) {
6310
        positions.splice(0, 30);
6311
      }
6312
 
6313
      // Track scroll movement for decleration
6314
      positions.push(scrollLeft, scrollTop, timeStamp);
6315
 
6316
      // Sync scroll position
6317
      self.__publish(scrollLeft, scrollTop, level);
6318
 
6319
    // Otherwise figure out whether we are switching into dragging mode now.
6320
    } else {
6321
 
6322
      var minimumTrackingForScroll = self.options.locking ? 3 : 0;
6323
      var minimumTrackingForDrag = 5;
6324
 
6325
      var distanceX = Math.abs(currentTouchLeft - self.__initialTouchLeft);
6326
      var distanceY = Math.abs(currentTouchTop - self.__initialTouchTop);
6327
 
6328
      self.__enableScrollX = self.options.scrollingX && distanceX >= minimumTrackingForScroll;
6329
      self.__enableScrollY = self.options.scrollingY && distanceY >= minimumTrackingForScroll;
6330
 
6331
      positions.push(self.__scrollLeft, self.__scrollTop, timeStamp);
6332
 
6333
      self.__isDragging = (self.__enableScrollX || self.__enableScrollY) && (distanceX >= minimumTrackingForDrag || distanceY >= minimumTrackingForDrag);
6334
      if (self.__isDragging) {
6335
        self.__interruptedAnimation = false;
6336
        self.__fadeScrollbars('in');
6337
      }
6338
 
6339
    }
6340
 
6341
    // Update last touch positions and time stamp for next event
6342
    self.__lastTouchLeft = currentTouchLeft;
6343
    self.__lastTouchTop = currentTouchTop;
6344
    self.__lastTouchMove = timeStamp;
6345
    self.__lastScale = scale;
6346
 
6347
  },
6348
 
6349
 
6350
  /**
6351
   * Touch end handler for scrolling support
6352
   */
6353
  doTouchEnd: function(e, timeStamp) {
6354
    if (timeStamp instanceof Date) {
6355
      timeStamp = timeStamp.valueOf();
6356
    }
6357
    if (typeof timeStamp !== "number") {
6358
      timeStamp = Date.now();
6359
    }
6360
 
6361
    var self = this;
6362
 
6363
    // Ignore event when tracking is not enabled (no touchstart event on element)
6364
    // This is required as this listener ('touchmove') sits on the document and not on the element itself.
6365
    if (!self.__isTracking) {
6366
      return;
6367
    }
6368
 
6369
    // Not touching anymore (when two finger hit the screen there are two touch end events)
6370
    self.__isTracking = false;
6371
 
6372
    // Be sure to reset the dragging flag now. Here we also detect whether
6373
    // the finger has moved fast enough to switch into a deceleration animation.
6374
    if (self.__isDragging) {
6375
 
6376
      // Reset dragging flag
6377
      self.__isDragging = false;
6378
 
6379
      // Start deceleration
6380
      // Verify that the last move detected was in some relevant time frame
6381
      if (self.__isSingleTouch && self.options.animating && (timeStamp - self.__lastTouchMove) <= 100) {
6382
 
6383
        // Then figure out what the scroll position was about 100ms ago
6384
        var positions = self.__positions;
6385
        var endPos = positions.length - 1;
6386
        var startPos = endPos;
6387
 
6388
        // Move pointer to position measured 100ms ago
6389
        for (var i = endPos; i > 0 && positions[i] > (self.__lastTouchMove - 100); i -= 3) {
6390
          startPos = i;
6391
        }
6392
 
6393
        // If start and stop position is identical in a 100ms timeframe,
6394
        // we cannot compute any useful deceleration.
6395
        if (startPos !== endPos) {
6396
 
6397
          // Compute relative movement between these two points
6398
          var timeOffset = positions[endPos] - positions[startPos];
6399
          var movedLeft = self.__scrollLeft - positions[startPos - 2];
6400
          var movedTop = self.__scrollTop - positions[startPos - 1];
6401
 
6402
          // Based on 50ms compute the movement to apply for each render step
6403
          self.__decelerationVelocityX = movedLeft / timeOffset * (1000 / 60);
6404
          self.__decelerationVelocityY = movedTop / timeOffset * (1000 / 60);
6405
 
6406
          // How much velocity is required to start the deceleration
6407
          var minVelocityToStartDeceleration = self.options.paging || self.options.snapping ? self.options.decelVelocityThresholdPaging : self.options.decelVelocityThreshold;
6408
 
6409
          // Verify that we have enough velocity to start deceleration
6410
          if (Math.abs(self.__decelerationVelocityX) > minVelocityToStartDeceleration || Math.abs(self.__decelerationVelocityY) > minVelocityToStartDeceleration) {
6411
 
6412
            // Deactivate pull-to-refresh when decelerating
6413
            if (!self.__refreshActive) {
6414
              self.__startDeceleration(timeStamp);
6415
            }
6416
          }
6417
        } else {
6418
          self.__scrollingComplete();
6419
        }
6420
      } else if ((timeStamp - self.__lastTouchMove) > 100) {
6421
        self.__scrollingComplete();
6422
      }
6423
 
6424
    } else if (self.__decStopped) {
6425
      // the deceleration was stopped
6426
      // user flicked the scroll fast, and stop dragging, then did a touchstart to stop the srolling
6427
      // tell the touchend event code to do nothing, we don't want to actually send a click
6428
      e.isTapHandled = true;
6429
      self.__decStopped = false;
6430
    }
6431
 
6432
    // If this was a slower move it is per default non decelerated, but this
6433
    // still means that we want snap back to the bounds which is done here.
6434
    // This is placed outside the condition above to improve edge case stability
6435
    // e.g. touchend fired without enabled dragging. This should normally do not
6436
    // have modified the scroll positions or even showed the scrollbars though.
6437
    if (!self.__isDecelerating) {
6438
 
6439
      if (self.__refreshActive && self.__refreshStart) {
6440
 
6441
        // Use publish instead of scrollTo to allow scrolling to out of boundary position
6442
        // We don't need to normalize scrollLeft, zoomLevel, etc. here because we only y-scrolling when pull-to-refresh is enabled
6443
        self.__publish(self.__scrollLeft, -self.__refreshHeight, self.__zoomLevel, true);
6444
 
6445
        var d = new Date();
6446
        self.refreshStartTime = d.getTime();
6447
 
6448
        if (self.__refreshStart) {
6449
          self.__refreshStart();
6450
        }
6451
        // for iOS-ey style scrolling
6452
        if (!ionic.Platform.isAndroid())self.__startDeceleration();
6453
      } else {
6454
 
6455
        if (self.__interruptedAnimation || self.__isDragging) {
6456
          self.__scrollingComplete();
6457
        }
6458
        self.scrollTo(self.__scrollLeft, self.__scrollTop, true, self.__zoomLevel);
6459
 
6460
        // Directly signalize deactivation (nothing todo on refresh?)
6461
        if (self.__refreshActive) {
6462
 
6463
          self.__refreshActive = false;
6464
          if (self.__refreshDeactivate) {
6465
            self.__refreshDeactivate();
6466
          }
6467
 
6468
        }
6469
      }
6470
    }
6471
 
6472
    // Fully cleanup list
6473
    self.__positions.length = 0;
6474
 
6475
  },
6476
 
6477
 
6478
 
6479
  /*
6480
  ---------------------------------------------------------------------------
6481
    PRIVATE API
6482
  ---------------------------------------------------------------------------
6483
  */
6484
 
6485
  /**
6486
   * Applies the scroll position to the content element
6487
   *
6488
   * @param left {Number} Left scroll position
6489
   * @param top {Number} Top scroll position
6490
   * @param animate {Boolean} Whether animation should be used to move to the new coordinates
6491
   */
6492
  __publish: function(left, top, zoom, animate, wasResize) {
6493
 
6494
    var self = this;
6495
 
6496
    // Remember whether we had an animation, then we try to continue based on the current "drive" of the animation
6497
    var wasAnimating = self.__isAnimating;
6498
    if (wasAnimating) {
6499
      zyngaCore.effect.Animate.stop(wasAnimating);
6500
      self.__isAnimating = false;
6501
    }
6502
 
6503
    if (animate && self.options.animating) {
6504
 
6505
      // Keep scheduled positions for scrollBy/zoomBy functionality
6506
      self.__scheduledLeft = left;
6507
      self.__scheduledTop = top;
6508
      self.__scheduledZoom = zoom;
6509
 
6510
      var oldLeft = self.__scrollLeft;
6511
      var oldTop = self.__scrollTop;
6512
      var oldZoom = self.__zoomLevel;
6513
 
6514
      var diffLeft = left - oldLeft;
6515
      var diffTop = top - oldTop;
6516
      var diffZoom = zoom - oldZoom;
6517
 
6518
      var step = function(percent, now, render) {
6519
 
6520
        if (render) {
6521
 
6522
          self.__scrollLeft = oldLeft + (diffLeft * percent);
6523
          self.__scrollTop = oldTop + (diffTop * percent);
6524
          self.__zoomLevel = oldZoom + (diffZoom * percent);
6525
 
6526
          // Push values out
6527
          if (self.__callback) {
6528
            self.__callback(self.__scrollLeft, self.__scrollTop, self.__zoomLevel, wasResize);
6529
          }
6530
 
6531
        }
6532
      };
6533
 
6534
      var verify = function(id) {
6535
        return self.__isAnimating === id;
6536
      };
6537
 
6538
      var completed = function(renderedFramesPerSecond, animationId, wasFinished) {
6539
        if (animationId === self.__isAnimating) {
6540
          self.__isAnimating = false;
6541
        }
6542
        if (self.__didDecelerationComplete || wasFinished) {
6543
          self.__scrollingComplete();
6544
        }
6545
 
6546
        if (self.options.zooming) {
6547
          self.__computeScrollMax();
6548
        }
6549
      };
6550
 
6551
      // When continuing based on previous animation we choose an ease-out animation instead of ease-in-out
6552
      self.__isAnimating = zyngaCore.effect.Animate.start(step, verify, completed, self.options.animationDuration, wasAnimating ? easeOutCubic : easeInOutCubic);
6553
 
6554
    } else {
6555
 
6556
      self.__scheduledLeft = self.__scrollLeft = left;
6557
      self.__scheduledTop = self.__scrollTop = top;
6558
      self.__scheduledZoom = self.__zoomLevel = zoom;
6559
 
6560
      // Push values out
6561
      if (self.__callback) {
6562
        self.__callback(left, top, zoom, wasResize);
6563
      }
6564
 
6565
      // Fix max scroll ranges
6566
      if (self.options.zooming) {
6567
        self.__computeScrollMax();
6568
      }
6569
    }
6570
  },
6571
 
6572
 
6573
  /**
6574
   * Recomputes scroll minimum values based on client dimensions and content dimensions.
6575
   */
6576
  __computeScrollMax: function(zoomLevel) {
6577
    var self = this;
6578
 
6579
    if (zoomLevel == null) {
6580
      zoomLevel = self.__zoomLevel;
6581
    }
6582
 
6583
    self.__maxScrollLeft = Math.max((self.__contentWidth * zoomLevel) - self.__clientWidth, 0);
6584
    self.__maxScrollTop = Math.max((self.__contentHeight * zoomLevel) - self.__clientHeight, 0);
6585
 
6586
    if (!self.__didWaitForSize && !self.__maxScrollLeft && !self.__maxScrollTop) {
6587
      self.__didWaitForSize = true;
6588
      self.__waitForSize();
6589
    }
6590
  },
6591
 
6592
 
6593
  /**
6594
   * If the scroll view isn't sized correctly on start, wait until we have at least some size
6595
   */
6596
  __waitForSize: function() {
6597
    var self = this;
6598
 
6599
    clearTimeout(self.__sizerTimeout);
6600
 
6601
    var sizer = function() {
6602
      self.resize(true);
6603
    };
6604
 
6605
    sizer();
6606
    self.__sizerTimeout = setTimeout(sizer, 500);
6607
  },
6608
 
6609
  /*
6610
  ---------------------------------------------------------------------------
6611
    ANIMATION (DECELERATION) SUPPORT
6612
  ---------------------------------------------------------------------------
6613
  */
6614
 
6615
  /**
6616
   * Called when a touch sequence end and the speed of the finger was high enough
6617
   * to switch into deceleration mode.
6618
   */
6619
  __startDeceleration: function() {
6620
    var self = this;
6621
 
6622
    if (self.options.paging) {
6623
 
6624
      var scrollLeft = Math.max(Math.min(self.__scrollLeft, self.__maxScrollLeft), 0);
6625
      var scrollTop = Math.max(Math.min(self.__scrollTop, self.__maxScrollTop), 0);
6626
      var clientWidth = self.__clientWidth;
6627
      var clientHeight = self.__clientHeight;
6628
 
6629
      // We limit deceleration not to the min/max values of the allowed range, but to the size of the visible client area.
6630
      // Each page should have exactly the size of the client area.
6631
      self.__minDecelerationScrollLeft = Math.floor(scrollLeft / clientWidth) * clientWidth;
6632
      self.__minDecelerationScrollTop = Math.floor(scrollTop / clientHeight) * clientHeight;
6633
      self.__maxDecelerationScrollLeft = Math.ceil(scrollLeft / clientWidth) * clientWidth;
6634
      self.__maxDecelerationScrollTop = Math.ceil(scrollTop / clientHeight) * clientHeight;
6635
 
6636
    } else {
6637
 
6638
      self.__minDecelerationScrollLeft = 0;
6639
      self.__minDecelerationScrollTop = 0;
6640
      self.__maxDecelerationScrollLeft = self.__maxScrollLeft;
6641
      self.__maxDecelerationScrollTop = self.__maxScrollTop;
6642
      if (self.__refreshActive) self.__minDecelerationScrollTop = self.__refreshHeight * -1;
6643
    }
6644
 
6645
    // Wrap class method
6646
    var step = function(percent, now, render) {
6647
      self.__stepThroughDeceleration(render);
6648
    };
6649
 
6650
    // How much velocity is required to keep the deceleration running
6651
    self.__minVelocityToKeepDecelerating = self.options.snapping ? 4 : 0.1;
6652
 
6653
    // Detect whether it's still worth to continue animating steps
6654
    // If we are already slow enough to not being user perceivable anymore, we stop the whole process here.
6655
    var verify = function() {
6656
      var shouldContinue = Math.abs(self.__decelerationVelocityX) >= self.__minVelocityToKeepDecelerating ||
6657
        Math.abs(self.__decelerationVelocityY) >= self.__minVelocityToKeepDecelerating;
6658
      if (!shouldContinue) {
6659
        self.__didDecelerationComplete = true;
6660
 
6661
        //Make sure the scroll values are within the boundaries after a bounce,
6662
        //not below 0 or above maximum
6663
        if (self.options.bouncing && !self.__refreshActive) {
6664
          self.scrollTo(
6665
            Math.min( Math.max(self.__scrollLeft, 0), self.__maxScrollLeft ),
6666
            Math.min( Math.max(self.__scrollTop, 0), self.__maxScrollTop ),
6667
            self.__refreshActive
6668
          );
6669
        }
6670
      }
6671
      return shouldContinue;
6672
    };
6673
 
6674
    var completed = function() {
6675
      self.__isDecelerating = false;
6676
      if (self.__didDecelerationComplete) {
6677
        self.__scrollingComplete();
6678
      }
6679
 
6680
      // Animate to grid when snapping is active, otherwise just fix out-of-boundary positions
6681
      if (self.options.paging) {
6682
        self.scrollTo(self.__scrollLeft, self.__scrollTop, self.options.snapping);
6683
      }
6684
    };
6685
 
6686
    // Start animation and switch on flag
6687
    self.__isDecelerating = zyngaCore.effect.Animate.start(step, verify, completed);
6688
 
6689
  },
6690
 
6691
 
6692
  /**
6693
   * Called on every step of the animation
6694
   *
6695
   * @param inMemory {Boolean} Whether to not render the current step, but keep it in memory only. Used internally only!
6696
   */
6697
  __stepThroughDeceleration: function(render) {
6698
    var self = this;
6699
 
6700
 
6701
    //
6702
    // COMPUTE NEXT SCROLL POSITION
6703
    //
6704
 
6705
    // Add deceleration to scroll position
6706
    var scrollLeft = self.__scrollLeft + self.__decelerationVelocityX;// * self.options.deceleration);
6707
    var scrollTop = self.__scrollTop + self.__decelerationVelocityY;// * self.options.deceleration);
6708
 
6709
 
6710
    //
6711
    // HARD LIMIT SCROLL POSITION FOR NON BOUNCING MODE
6712
    //
6713
 
6714
    if (!self.options.bouncing) {
6715
 
6716
      var scrollLeftFixed = Math.max(Math.min(self.__maxDecelerationScrollLeft, scrollLeft), self.__minDecelerationScrollLeft);
6717
      if (scrollLeftFixed !== scrollLeft) {
6718
        scrollLeft = scrollLeftFixed;
6719
        self.__decelerationVelocityX = 0;
6720
      }
6721
 
6722
      var scrollTopFixed = Math.max(Math.min(self.__maxDecelerationScrollTop, scrollTop), self.__minDecelerationScrollTop);
6723
      if (scrollTopFixed !== scrollTop) {
6724
        scrollTop = scrollTopFixed;
6725
        self.__decelerationVelocityY = 0;
6726
      }
6727
 
6728
    }
6729
 
6730
 
6731
    //
6732
    // UPDATE SCROLL POSITION
6733
    //
6734
 
6735
    if (render) {
6736
 
6737
      self.__publish(scrollLeft, scrollTop, self.__zoomLevel);
6738
 
6739
    } else {
6740
 
6741
      self.__scrollLeft = scrollLeft;
6742
      self.__scrollTop = scrollTop;
6743
 
6744
    }
6745
 
6746
 
6747
    //
6748
    // SLOW DOWN
6749
    //
6750
 
6751
    // Slow down velocity on every iteration
6752
    if (!self.options.paging) {
6753
 
6754
      // This is the factor applied to every iteration of the animation
6755
      // to slow down the process. This should emulate natural behavior where
6756
      // objects slow down when the initiator of the movement is removed
6757
      var frictionFactor = self.options.deceleration;
6758
 
6759
      self.__decelerationVelocityX *= frictionFactor;
6760
      self.__decelerationVelocityY *= frictionFactor;
6761
 
6762
    }
6763
 
6764
 
6765
    //
6766
    // BOUNCING SUPPORT
6767
    //
6768
 
6769
    if (self.options.bouncing) {
6770
 
6771
      var scrollOutsideX = 0;
6772
      var scrollOutsideY = 0;
6773
 
6774
      // This configures the amount of change applied to deceleration/acceleration when reaching boundaries
6775
      var penetrationDeceleration = self.options.penetrationDeceleration;
6776
      var penetrationAcceleration = self.options.penetrationAcceleration;
6777
 
6778
      // Check limits
6779
      if (scrollLeft < self.__minDecelerationScrollLeft) {
6780
        scrollOutsideX = self.__minDecelerationScrollLeft - scrollLeft;
6781
      } else if (scrollLeft > self.__maxDecelerationScrollLeft) {
6782
        scrollOutsideX = self.__maxDecelerationScrollLeft - scrollLeft;
6783
      }
6784
 
6785
      if (scrollTop < self.__minDecelerationScrollTop) {
6786
        scrollOutsideY = self.__minDecelerationScrollTop - scrollTop;
6787
      } else if (scrollTop > self.__maxDecelerationScrollTop) {
6788
        scrollOutsideY = self.__maxDecelerationScrollTop - scrollTop;
6789
      }
6790
 
6791
      // Slow down until slow enough, then flip back to snap position
6792
      if (scrollOutsideX !== 0) {
6793
        var isHeadingOutwardsX = scrollOutsideX * self.__decelerationVelocityX <= self.__minDecelerationScrollLeft;
6794
        if (isHeadingOutwardsX) {
6795
          self.__decelerationVelocityX += scrollOutsideX * penetrationDeceleration;
6796
        }
6797
        var isStoppedX = Math.abs(self.__decelerationVelocityX) <= self.__minVelocityToKeepDecelerating;
6798
        //If we're not heading outwards, or if the above statement got us below minDeceleration, go back towards bounds
6799
        if (!isHeadingOutwardsX || isStoppedX) {
6800
          self.__decelerationVelocityX = scrollOutsideX * penetrationAcceleration;
6801
        }
6802
      }
6803
 
6804
      if (scrollOutsideY !== 0) {
6805
        var isHeadingOutwardsY = scrollOutsideY * self.__decelerationVelocityY <= self.__minDecelerationScrollTop;
6806
        if (isHeadingOutwardsY) {
6807
          self.__decelerationVelocityY += scrollOutsideY * penetrationDeceleration;
6808
        }
6809
        var isStoppedY = Math.abs(self.__decelerationVelocityY) <= self.__minVelocityToKeepDecelerating;
6810
        //If we're not heading outwards, or if the above statement got us below minDeceleration, go back towards bounds
6811
        if (!isHeadingOutwardsY || isStoppedY) {
6812
          self.__decelerationVelocityY = scrollOutsideY * penetrationAcceleration;
6813
        }
6814
      }
6815
    }
6816
  },
6817
 
6818
 
6819
  /**
6820
   * calculate the distance between two touches
6821
   * @param   {Touch}     touch1
6822
   * @param   {Touch}     touch2
6823
   * @returns {Number}    distance
6824
   */
6825
  __getDistance: function getDistance(touch1, touch2) {
6826
    var x = touch2.pageX - touch1.pageX,
6827
    y = touch2.pageY - touch1.pageY;
6828
    return Math.sqrt((x * x) + (y * y));
6829
  },
6830
 
6831
 
6832
  /**
6833
   * calculate the scale factor between two touchLists (fingers)
6834
   * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
6835
   * @param   {Array}     start
6836
   * @param   {Array}     end
6837
   * @returns {Number}    scale
6838
   */
6839
  __getScale: function getScale(start, end) {
6840
    // need two fingers...
6841
    if (start.length >= 2 && end.length >= 2) {
6842
      return this.__getDistance(end[0], end[1]) /
6843
        this.__getDistance(start[0], start[1]);
6844
    }
6845
    return 1;
6846
  }
6847
});
6848
 
6849
ionic.scroll = {
6850
  isScrolling: false,
6851
  lastTop: 0
6852
};
6853
 
6854
})(ionic);
6855
 
6856
(function(ionic) {
6857
  var NOOP = function() {};
6858
  var depreciated = function(name) {
6859
    void 0;
6860
  };
6861
  ionic.views.ScrollNative = ionic.views.View.inherit({
6862
 
6863
    initialize: function(options) {
6864
      var self = this;
6865
      self.__container = self.el = options.el;
6866
      self.__content = options.el.firstElementChild;
6867
      self.isNative = true;
6868
 
6869
      self.__scrollTop = self.el.scrollTop;
6870
      self.__scrollLeft = self.el.scrollLeft;
6871
      self.__clientHeight = self.__content.clientHeight;
6872
      self.__clientWidth = self.__content.clientWidth;
6873
      self.__maxScrollTop = Math.max((self.__contentHeight) - self.__clientHeight, 0);
6874
      self.__maxScrollLeft = Math.max((self.__contentWidth) - self.__clientWidth, 0);
6875
 
6876
      self.options = {
6877
 
6878
        freeze: false,
6879
 
6880
        getContentWidth: function() {
6881
          return Math.max(self.__content.scrollWidth, self.__content.offsetWidth);
6882
        },
6883
 
6884
        getContentHeight: function() {
6885
          return Math.max(self.__content.scrollHeight, self.__content.offsetHeight + (self.__content.offsetTop * 2));
6886
        }
6887
 
6888
      };
6889
 
6890
      for (var key in options) {
6891
        self.options[key] = options[key];
6892
      }
6893
 
6894
      /**
6895
       * Sets isScrolling to true, and automatically deactivates if not called again in 80ms.
6896
       */
6897
      self.onScroll = function() {
6898
        if (!ionic.scroll.isScrolling) {
6899
          ionic.scroll.isScrolling = true;
6900
        }
6901
 
6902
        clearTimeout(self.scrollTimer);
6903
        self.scrollTimer = setTimeout(function() {
6904
          ionic.scroll.isScrolling = false;
6905
        }, 80);
6906
      };
6907
 
6908
      self.freeze = NOOP;
6909
 
6910
      self.__initEventHandlers();
6911
    },
6912
 
6913
    /**  Methods not used in native scrolling */
6914
    __callback: function() { depreciated('__callback'); },
6915
    zoomTo: function() { depreciated('zoomTo'); },
6916
    zoomBy: function() { depreciated('zoomBy'); },
6917
    activatePullToRefresh: function() { depreciated('activatePullToRefresh'); },
6918
 
6919
    /**
6920
     * Returns the scroll position and zooming values
6921
     *
6922
     * @return {Map} `left` and `top` scroll position and `zoom` level
6923
     */
6924
    resize: function(continueScrolling) {
6925
      var self = this;
6926
      if (!self.__container || !self.options) return;
6927
 
6928
      // Update Scroller dimensions for changed content
6929
      // Add padding to bottom of content
6930
      self.setDimensions(
6931
        self.__container.clientWidth,
6932
        self.__container.clientHeight,
6933
        self.options.getContentWidth(),
6934
        self.options.getContentHeight(),
6935
        continueScrolling
6936
      );
6937
    },
6938
 
6939
    /**
6940
     * Initialize the scrollview
6941
     * In native scrolling, this only means we need to gather size information
6942
     */
6943
    run: function() {
6944
      this.resize();
6945
    },
6946
 
6947
    /**
6948
     * Returns the scroll position and zooming values
6949
     *
6950
     * @return {Map} `left` and `top` scroll position and `zoom` level
6951
     */
6952
    getValues: function() {
6953
      var self = this;
6954
      self.update();
6955
      return {
6956
        left: self.__scrollLeft,
6957
        top: self.__scrollTop,
6958
        zoom: 1
6959
      };
6960
    },
6961
 
6962
    /**
6963
     * Updates the __scrollLeft and __scrollTop values to el's current value
6964
     */
6965
    update: function() {
6966
      var self = this;
6967
      self.__scrollLeft = self.el.scrollLeft;
6968
      self.__scrollTop = self.el.scrollTop;
6969
    },
6970
 
6971
    /**
6972
     * Configures the dimensions of the client (outer) and content (inner) elements.
6973
     * Requires the available space for the outer element and the outer size of the inner element.
6974
     * All values which are falsy (null or zero etc.) are ignored and the old value is kept.
6975
     *
6976
     * @param clientWidth {Integer} Inner width of outer element
6977
     * @param clientHeight {Integer} Inner height of outer element
6978
     * @param contentWidth {Integer} Outer width of inner element
6979
     * @param contentHeight {Integer} Outer height of inner element
6980
     */
6981
    setDimensions: function(clientWidth, clientHeight, contentWidth, contentHeight) {
6982
      var self = this;
6983
 
6984
      if (!clientWidth && !clientHeight && !contentWidth && !contentHeight) {
6985
        // this scrollview isn't rendered, don't bother
6986
        return;
6987
      }
6988
 
6989
      // Only update values which are defined
6990
      if (clientWidth === +clientWidth) {
6991
        self.__clientWidth = clientWidth;
6992
      }
6993
 
6994
      if (clientHeight === +clientHeight) {
6995
        self.__clientHeight = clientHeight;
6996
      }
6997
 
6998
      if (contentWidth === +contentWidth) {
6999
        self.__contentWidth = contentWidth;
7000
      }
7001
 
7002
      if (contentHeight === +contentHeight) {
7003
        self.__contentHeight = contentHeight;
7004
      }
7005
 
7006
      // Refresh maximums
7007
      self.__computeScrollMax();
7008
    },
7009
 
7010
    /**
7011
     * Returns the maximum scroll values
7012
     *
7013
     * @return {Map} `left` and `top` maximum scroll values
7014
     */
7015
    getScrollMax: function() {
7016
      return {
7017
        left: this.__maxScrollLeft,
7018
        top: this.__maxScrollTop
7019
      };
7020
    },
7021
 
7022
    /**
7023
     * Scrolls by the given amount in px.
7024
     *
7025
     * @param left {Number} Horizontal scroll position, keeps current if value is <code>null</code>
7026
     * @param top {Number} Vertical scroll position, keeps current if value is <code>null</code>
7027
     * @param animate {Boolean} Whether the scrolling should happen using an animation
7028
     */
7029
 
7030
    scrollBy: function(left, top, animate) {
7031
      var self = this;
7032
 
7033
      // update scroll vars before refferencing them
7034
      self.update();
7035
 
7036
      var startLeft = self.__isAnimating ? self.__scheduledLeft : self.__scrollLeft;
7037
      var startTop = self.__isAnimating ? self.__scheduledTop : self.__scrollTop;
7038
 
7039
      self.scrollTo(startLeft + (left || 0), startTop + (top || 0), animate);
7040
    },
7041
 
7042
    /**
7043
     * Scrolls to the given position in px.
7044
     *
7045
     * @param left {Number} Horizontal scroll position, keeps current if value is <code>null</code>
7046
     * @param top {Number} Vertical scroll position, keeps current if value is <code>null</code>
7047
     * @param animate {Boolean} Whether the scrolling should happen using an animation
7048
     */
7049
    scrollTo: function(left, top, animate) {
7050
      var self = this;
7051
      if (!animate) {
7052
        self.el.scrollTop = top;
7053
        self.el.scrollLeft = left;
7054
        self.resize();
7055
        return;
7056
      }
7057
      animateScroll(top, left);
7058
 
7059
      function animateScroll(Y, X) {
7060
        // scroll animation loop w/ easing
7061
        // credit https://gist.github.com/dezinezync/5487119
7062
        var start = Date.now(),
7063
          duration = 1000, //milliseconds
7064
          fromY = self.el.scrollTop,
7065
          fromX = self.el.scrollLeft;
7066
 
7067
        if (fromY === Y && fromX === X) {
7068
          self.resize();
7069
          return; /* Prevent scrolling to the Y point if already there */
7070
        }
7071
 
7072
        // decelerating to zero velocity
7073
        function easeOutCubic(t) {
7074
          return (--t) * t * t + 1;
7075
        }
7076
 
7077
        // scroll loop
7078
        function animateScrollStep() {
7079
          var currentTime = Date.now(),
7080
            time = Math.min(1, ((currentTime - start) / duration)),
7081
          // where .5 would be 50% of time on a linear scale easedT gives a
7082
          // fraction based on the easing method
7083
            easedT = easeOutCubic(time);
7084
 
7085
          if (fromY != Y) {
7086
            self.el.scrollTop = parseInt((easedT * (Y - fromY)) + fromY, 10);
7087
          }
7088
          if (fromX != X) {
7089
            self.el.scrollLeft = parseInt((easedT * (X - fromX)) + fromX, 10);
7090
          }
7091
 
7092
          if (time < 1) {
7093
            ionic.requestAnimationFrame(animateScrollStep);
7094
 
7095
          } else {
7096
            // done
7097
            self.resize();
7098
          }
7099
        }
7100
 
7101
        // start scroll loop
7102
        ionic.requestAnimationFrame(animateScrollStep);
7103
      }
7104
    },
7105
 
7106
 
7107
 
7108
    /*
7109
     ---------------------------------------------------------------------------
7110
     PRIVATE API
7111
     ---------------------------------------------------------------------------
7112
     */
7113
 
7114
    /**
7115
     * If the scroll view isn't sized correctly on start, wait until we have at least some size
7116
     */
7117
    __waitForSize: function() {
7118
      var self = this;
7119
 
7120
      clearTimeout(self.__sizerTimeout);
7121
 
7122
      var sizer = function() {
7123
        self.resize(true);
7124
      };
7125
 
7126
      sizer();
7127
      self.__sizerTimeout = setTimeout(sizer, 500);
7128
    },
7129
 
7130
 
7131
    /**
7132
     * Recomputes scroll minimum values based on client dimensions and content dimensions.
7133
     */
7134
    __computeScrollMax: function() {
7135
      var self = this;
7136
 
7137
      self.__maxScrollLeft = Math.max((self.__contentWidth) - self.__clientWidth, 0);
7138
      self.__maxScrollTop = Math.max((self.__contentHeight) - self.__clientHeight, 0);
7139
 
7140
      if (!self.__didWaitForSize && !self.__maxScrollLeft && !self.__maxScrollTop) {
7141
        self.__didWaitForSize = true;
7142
        self.__waitForSize();
7143
      }
7144
    },
7145
 
7146
    __initEventHandlers: function() {
7147
      var self = this;
7148
 
7149
      // Event Handler
7150
      var container = self.__container;
7151
 
7152
      // should be unnecessary in native scrolling, but keep in case bugs show up
7153
      self.scrollChildIntoView = NOOP;
7154
 
7155
      self.resetScrollView = function() {
7156
        //return scrollview to original height once keyboard has hidden
7157
        if (self.isScrolledIntoView) {
7158
          self.isScrolledIntoView = false;
7159
          container.style.height = "";
7160
          container.style.overflow = "";
7161
          self.resize();
7162
          ionic.scroll.isScrolling = false;
7163
        }
7164
      };
7165
 
7166
      container.addEventListener('resetScrollView', self.resetScrollView);
7167
      container.addEventListener('scroll', self.onScroll);
7168
 
7169
      //Broadcasted when keyboard is shown on some platforms.
7170
      //See js/utils/keyboard.js
7171
      container.addEventListener('scrollChildIntoView', self.scrollChildIntoView);
7172
      container.addEventListener('resetScrollView', self.resetScrollView);
7173
    },
7174
 
7175
    __cleanup: function() {
7176
      var self = this;
7177
      var container = self.__container;
7178
 
7179
      container.removeEventListener('resetScrollView', self.resetScrollView);
7180
      container.removeEventListener('scroll', self.onScroll);
7181
 
7182
      container.removeEventListener('scrollChildIntoView', self.scrollChildIntoView);
7183
      container.removeEventListener('resetScrollView', self.resetScrollView);
7184
 
7185
      ionic.tap.removeClonedInputs(container, self);
7186
 
7187
      delete self.__container;
7188
      delete self.__content;
7189
      delete self.__indicatorX;
7190
      delete self.__indicatorY;
7191
      delete self.options.el;
7192
 
7193
      self.resize = self.scrollTo = self.onScroll = self.resetScrollView = NOOP;
7194
      container = null;
7195
    }
7196
  });
7197
 
7198
})(ionic);
7199
 
7200
 
7201
(function(ionic) {
7202
'use strict';
7203
 
7204
  var ITEM_CLASS = 'item';
7205
  var ITEM_CONTENT_CLASS = 'item-content';
7206
  var ITEM_SLIDING_CLASS = 'item-sliding';
7207
  var ITEM_OPTIONS_CLASS = 'item-options';
7208
  var ITEM_PLACEHOLDER_CLASS = 'item-placeholder';
7209
  var ITEM_REORDERING_CLASS = 'item-reordering';
7210
  var ITEM_REORDER_BTN_CLASS = 'item-reorder';
7211
 
7212
  var DragOp = function() {};
7213
  DragOp.prototype = {
7214
    start: function(){},
7215
    drag: function(){},
7216
    end: function(){},
7217
    isSameItem: function() {
7218
      return false;
7219
    }
7220
  };
7221
 
7222
  var SlideDrag = function(opts) {
7223
    this.dragThresholdX = opts.dragThresholdX || 10;
7224
    this.el = opts.el;
7225
    this.item = opts.item;
7226
    this.canSwipe = opts.canSwipe;
7227
  };
7228
 
7229
  SlideDrag.prototype = new DragOp();
7230
 
7231
  SlideDrag.prototype.start = function(e) {
7232
    var content, buttons, offsetX, buttonsWidth;
7233
 
7234
    if (!this.canSwipe()) {
7235
      return;
7236
    }
7237
 
7238
    if (e.target.classList.contains(ITEM_CONTENT_CLASS)) {
7239
      content = e.target;
7240
    } else if (e.target.classList.contains(ITEM_CLASS)) {
7241
      content = e.target.querySelector('.' + ITEM_CONTENT_CLASS);
7242
    } else {
7243
      content = ionic.DomUtil.getParentWithClass(e.target, ITEM_CONTENT_CLASS);
7244
    }
7245
 
7246
    // If we don't have a content area as one of our children (or ourselves), skip
7247
    if (!content) {
7248
      return;
7249
    }
7250
 
7251
    // Make sure we aren't animating as we slide
7252
    content.classList.remove(ITEM_SLIDING_CLASS);
7253
 
7254
    // Grab the starting X point for the item (for example, so we can tell whether it is open or closed to start)
7255
    offsetX = parseFloat(content.style[ionic.CSS.TRANSFORM].replace('translate3d(', '').split(',')[0]) || 0;
7256
 
7257
    // Grab the buttons
7258
    buttons = content.parentNode.querySelector('.' + ITEM_OPTIONS_CLASS);
7259
    if (!buttons) {
7260
      return;
7261
    }
7262
    buttons.classList.remove('invisible');
7263
 
7264
    buttonsWidth = buttons.offsetWidth;
7265
 
7266
    this._currentDrag = {
7267
      buttons: buttons,
7268
      buttonsWidth: buttonsWidth,
7269
      content: content,
7270
      startOffsetX: offsetX
7271
    };
7272
  };
7273
 
7274
  /**
7275
   * Check if this is the same item that was previously dragged.
7276
   */
7277
  SlideDrag.prototype.isSameItem = function(op) {
7278
    if (op._lastDrag && this._currentDrag) {
7279
      return this._currentDrag.content == op._lastDrag.content;
7280
    }
7281
    return false;
7282
  };
7283
 
7284
  SlideDrag.prototype.clean = function(isInstant) {
7285
    var lastDrag = this._lastDrag;
7286
 
7287
    if (!lastDrag || !lastDrag.content) return;
7288
 
7289
    lastDrag.content.style[ionic.CSS.TRANSITION] = '';
7290
    lastDrag.content.style[ionic.CSS.TRANSFORM] = '';
7291
    if (isInstant) {
7292
      lastDrag.content.style[ionic.CSS.TRANSITION] = 'none';
7293
      makeInvisible();
7294
      ionic.requestAnimationFrame(function() {
7295
        lastDrag.content.style[ionic.CSS.TRANSITION] = '';
7296
      });
7297
    } else {
7298
      ionic.requestAnimationFrame(function() {
7299
        setTimeout(makeInvisible, 250);
7300
      });
7301
    }
7302
    function makeInvisible() {
7303
      lastDrag.buttons && lastDrag.buttons.classList.add('invisible');
7304
    }
7305
  };
7306
 
7307
  SlideDrag.prototype.drag = ionic.animationFrameThrottle(function(e) {
7308
    var buttonsWidth;
7309
 
7310
    // We really aren't dragging
7311
    if (!this._currentDrag) {
7312
      return;
7313
    }
7314
 
7315
    // Check if we should start dragging. Check if we've dragged past the threshold,
7316
    // or we are starting from the open state.
7317
    if (!this._isDragging &&
7318
        ((Math.abs(e.gesture.deltaX) > this.dragThresholdX) ||
7319
        (Math.abs(this._currentDrag.startOffsetX) > 0))) {
7320
      this._isDragging = true;
7321
    }
7322
 
7323
    if (this._isDragging) {
7324
      buttonsWidth = this._currentDrag.buttonsWidth;
7325
 
7326
      // Grab the new X point, capping it at zero
7327
      var newX = Math.min(0, this._currentDrag.startOffsetX + e.gesture.deltaX);
7328
 
7329
      // If the new X position is past the buttons, we need to slow down the drag (rubber band style)
7330
      if (newX < -buttonsWidth) {
7331
        // Calculate the new X position, capped at the top of the buttons
7332
        newX = Math.min(-buttonsWidth, -buttonsWidth + (((e.gesture.deltaX + buttonsWidth) * 0.4)));
7333
      }
7334
 
7335
      this._currentDrag.content.$$ionicOptionsOpen = newX !== 0;
7336
 
7337
      this._currentDrag.content.style[ionic.CSS.TRANSFORM] = 'translate3d(' + newX + 'px, 0, 0)';
7338
      this._currentDrag.content.style[ionic.CSS.TRANSITION] = 'none';
7339
    }
7340
  });
7341
 
7342
  SlideDrag.prototype.end = function(e, doneCallback) {
7343
    var self = this;
7344
 
7345
    // There is no drag, just end immediately
7346
    if (!self._currentDrag) {
7347
      doneCallback && doneCallback();
7348
      return;
7349
    }
7350
 
7351
    // If we are currently dragging, we want to snap back into place
7352
    // The final resting point X will be the width of the exposed buttons
7353
    var restingPoint = -self._currentDrag.buttonsWidth;
7354
 
7355
    // Check if the drag didn't clear the buttons mid-point
7356
    // and we aren't moving fast enough to swipe open
7357
    if (e.gesture.deltaX > -(self._currentDrag.buttonsWidth / 2)) {
7358
 
7359
      // If we are going left but too slow, or going right, go back to resting
7360
      if (e.gesture.direction == "left" && Math.abs(e.gesture.velocityX) < 0.3) {
7361
        restingPoint = 0;
7362
 
7363
      } else if (e.gesture.direction == "right") {
7364
        restingPoint = 0;
7365
      }
7366
 
7367
    }
7368
 
7369
    ionic.requestAnimationFrame(function() {
7370
      if (restingPoint === 0) {
7371
        self._currentDrag.content.style[ionic.CSS.TRANSFORM] = '';
7372
        var buttons = self._currentDrag.buttons;
7373
        setTimeout(function() {
7374
          buttons && buttons.classList.add('invisible');
7375
        }, 250);
7376
      } else {
7377
        self._currentDrag.content.style[ionic.CSS.TRANSFORM] = 'translate3d(' + restingPoint + 'px,0,0)';
7378
      }
7379
      self._currentDrag.content.style[ionic.CSS.TRANSITION] = '';
7380
 
7381
 
7382
      // Kill the current drag
7383
      if (!self._lastDrag) {
7384
        self._lastDrag = {};
7385
      }
7386
      ionic.extend(self._lastDrag, self._currentDrag);
7387
      if (self._currentDrag) {
7388
        self._currentDrag.buttons = null;
7389
        self._currentDrag.content = null;
7390
      }
7391
      self._currentDrag = null;
7392
 
7393
      // We are done, notify caller
7394
      doneCallback && doneCallback();
7395
    });
7396
  };
7397
 
7398
  var ReorderDrag = function(opts) {
7399
    var self = this;
7400
 
7401
    self.dragThresholdY = opts.dragThresholdY || 0;
7402
    self.onReorder = opts.onReorder;
7403
    self.listEl = opts.listEl;
7404
    self.el = self.item = opts.el;
7405
    self.scrollEl = opts.scrollEl;
7406
    self.scrollView = opts.scrollView;
7407
    // Get the True Top of the list el http://www.quirksmode.org/js/findpos.html
7408
    self.listElTrueTop = 0;
7409
    if (self.listEl.offsetParent) {
7410
      var obj = self.listEl;
7411
      do {
7412
        self.listElTrueTop += obj.offsetTop;
7413
        obj = obj.offsetParent;
7414
      } while (obj);
7415
    }
7416
  };
7417
 
7418
  ReorderDrag.prototype = new DragOp();
7419
 
7420
  ReorderDrag.prototype._moveElement = function(e) {
7421
    var y = e.gesture.center.pageY +
7422
      this.scrollView.getValues().top -
7423
      (this._currentDrag.elementHeight / 2) -
7424
      this.listElTrueTop;
7425
    this.el.style[ionic.CSS.TRANSFORM] = 'translate3d(0, ' + y + 'px, 0)';
7426
  };
7427
 
7428
  ReorderDrag.prototype.deregister = function() {
7429
    this.listEl = this.el = this.scrollEl = this.scrollView = null;
7430
  };
7431
 
7432
  ReorderDrag.prototype.start = function(e) {
7433
 
7434
    var startIndex = ionic.DomUtil.getChildIndex(this.el, this.el.nodeName.toLowerCase());
7435
    var elementHeight = this.el.scrollHeight;
7436
    var placeholder = this.el.cloneNode(true);
7437
 
7438
    placeholder.classList.add(ITEM_PLACEHOLDER_CLASS);
7439
 
7440
    this.el.parentNode.insertBefore(placeholder, this.el);
7441
    this.el.classList.add(ITEM_REORDERING_CLASS);
7442
 
7443
    this._currentDrag = {
7444
      elementHeight: elementHeight,
7445
      startIndex: startIndex,
7446
      placeholder: placeholder,
7447
      scrollHeight: scroll,
7448
      list: placeholder.parentNode
7449
    };
7450
 
7451
    this._moveElement(e);
7452
  };
7453
 
7454
  ReorderDrag.prototype.drag = ionic.animationFrameThrottle(function(e) {
7455
    // We really aren't dragging
7456
    var self = this;
7457
    if (!this._currentDrag) {
7458
      return;
7459
    }
7460
 
7461
    var scrollY = 0;
7462
    var pageY = e.gesture.center.pageY;
7463
    var offset = this.listElTrueTop;
7464
 
7465
    //If we have a scrollView, check scroll boundaries for dragged element and scroll if necessary
7466
    if (this.scrollView) {
7467
 
7468
      var container = this.scrollView.__container;
7469
      scrollY = this.scrollView.getValues().top;
7470
 
7471
      var containerTop = container.offsetTop;
7472
      var pixelsPastTop = containerTop - pageY + this._currentDrag.elementHeight / 2;
7473
      var pixelsPastBottom = pageY + this._currentDrag.elementHeight / 2 - containerTop - container.offsetHeight;
7474
 
7475
      if (e.gesture.deltaY < 0 && pixelsPastTop > 0 && scrollY > 0) {
7476
        this.scrollView.scrollBy(null, -pixelsPastTop);
7477
        //Trigger another drag so the scrolling keeps going
7478
        ionic.requestAnimationFrame(function() {
7479
          self.drag(e);
7480
        });
7481
      }
7482
      if (e.gesture.deltaY > 0 && pixelsPastBottom > 0) {
7483
        if (scrollY < this.scrollView.getScrollMax().top) {
7484
          this.scrollView.scrollBy(null, pixelsPastBottom);
7485
          //Trigger another drag so the scrolling keeps going
7486
          ionic.requestAnimationFrame(function() {
7487
            self.drag(e);
7488
          });
7489
        }
7490
      }
7491
    }
7492
 
7493
    // Check if we should start dragging. Check if we've dragged past the threshold,
7494
    // or we are starting from the open state.
7495
    if (!this._isDragging && Math.abs(e.gesture.deltaY) > this.dragThresholdY) {
7496
      this._isDragging = true;
7497
    }
7498
 
7499
    if (this._isDragging) {
7500
      this._moveElement(e);
7501
 
7502
      this._currentDrag.currentY = scrollY + pageY - offset;
7503
 
7504
      // this._reorderItems();
7505
    }
7506
  });
7507
 
7508
  // When an item is dragged, we need to reorder any items for sorting purposes
7509
  ReorderDrag.prototype._getReorderIndex = function() {
7510
    var self = this;
7511
 
7512
    var siblings = Array.prototype.slice.call(self._currentDrag.placeholder.parentNode.children)
7513
      .filter(function(el) {
7514
        return el.nodeName === self.el.nodeName && el !== self.el;
7515
      });
7516
 
7517
    var dragOffsetTop = self._currentDrag.currentY;
7518
    var el;
7519
    for (var i = 0, len = siblings.length; i < len; i++) {
7520
      el = siblings[i];
7521
      if (i === len - 1) {
7522
        if (dragOffsetTop > el.offsetTop) {
7523
          return i;
7524
        }
7525
      } else if (i === 0) {
7526
        if (dragOffsetTop < el.offsetTop + el.offsetHeight) {
7527
          return i;
7528
        }
7529
      } else if (dragOffsetTop > el.offsetTop - el.offsetHeight / 2 &&
7530
                 dragOffsetTop < el.offsetTop + el.offsetHeight) {
7531
        return i;
7532
      }
7533
    }
7534
    return self._currentDrag.startIndex;
7535
  };
7536
 
7537
  ReorderDrag.prototype.end = function(e, doneCallback) {
7538
    if (!this._currentDrag) {
7539
      doneCallback && doneCallback();
7540
      return;
7541
    }
7542
 
7543
    var placeholder = this._currentDrag.placeholder;
7544
    var finalIndex = this._getReorderIndex();
7545
 
7546
    // Reposition the element
7547
    this.el.classList.remove(ITEM_REORDERING_CLASS);
7548
    this.el.style[ionic.CSS.TRANSFORM] = '';
7549
 
7550
    placeholder.parentNode.insertBefore(this.el, placeholder);
7551
    placeholder.parentNode.removeChild(placeholder);
7552
 
7553
    this.onReorder && this.onReorder(this.el, this._currentDrag.startIndex, finalIndex);
7554
 
7555
    this._currentDrag = {
7556
      placeholder: null,
7557
      content: null
7558
    };
7559
    this._currentDrag = null;
7560
    doneCallback && doneCallback();
7561
  };
7562
 
7563
 
7564
 
7565
  /**
7566
   * The ListView handles a list of items. It will process drag animations, edit mode,
7567
   * and other operations that are common on mobile lists or table views.
7568
   */
7569
  ionic.views.ListView = ionic.views.View.inherit({
7570
    initialize: function(opts) {
7571
      var self = this;
7572
 
7573
      opts = ionic.extend({
7574
        onReorder: function() {},
7575
        virtualRemoveThreshold: -200,
7576
        virtualAddThreshold: 200,
7577
        canSwipe: function() {
7578
          return true;
7579
        }
7580
      }, opts);
7581
 
7582
      ionic.extend(self, opts);
7583
 
7584
      if (!self.itemHeight && self.listEl) {
7585
        self.itemHeight = self.listEl.children[0] && parseInt(self.listEl.children[0].style.height, 10);
7586
      }
7587
 
7588
      self.onRefresh = opts.onRefresh || function() {};
7589
      self.onRefreshOpening = opts.onRefreshOpening || function() {};
7590
      self.onRefreshHolding = opts.onRefreshHolding || function() {};
7591
 
7592
      var gestureOpts = {};
7593
      // don't prevent native scrolling
7594
      if (ionic.DomUtil.getParentOrSelfWithClass(self.el, 'overflow-scroll')) {
7595
        gestureOpts.prevent_default_directions = ['left', 'right'];
7596
      }
7597
 
7598
      window.ionic.onGesture('release', function(e) {
7599
        self._handleEndDrag(e);
7600
      }, self.el, gestureOpts);
7601
 
7602
      window.ionic.onGesture('drag', function(e) {
7603
        self._handleDrag(e);
7604
      }, self.el, gestureOpts);
7605
      // Start the drag states
7606
      self._initDrag();
7607
    },
7608
 
7609
    /**
7610
     * Be sure to cleanup references.
7611
     */
7612
    deregister: function() {
7613
      this.el = this.listEl = this.scrollEl = this.scrollView = null;
7614
 
7615
      // ensure no scrolls have been left frozen
7616
      if (this.isScrollFreeze) {
7617
        self.scrollView.freeze(false);
7618
      }
7619
    },
7620
 
7621
    /**
7622
     * Called to tell the list to stop refreshing. This is useful
7623
     * if you are refreshing the list and are done with refreshing.
7624
     */
7625
    stopRefreshing: function() {
7626
      var refresher = this.el.querySelector('.list-refresher');
7627
      refresher.style.height = '0';
7628
    },
7629
 
7630
    /**
7631
     * If we scrolled and have virtual mode enabled, compute the window
7632
     * of active elements in order to figure out the viewport to render.
7633
     */
7634
    didScroll: function(e) {
7635
      var self = this;
7636
 
7637
      if (self.isVirtual) {
7638
        var itemHeight = self.itemHeight;
7639
 
7640
        // Grab the total height of the list
7641
        var scrollHeight = e.target.scrollHeight;
7642
 
7643
        // Get the viewport height
7644
        var viewportHeight = self.el.parentNode.offsetHeight;
7645
 
7646
        // High water is the pixel position of the first element to include (everything before
7647
        // that will be removed)
7648
        var highWater = Math.max(0, e.scrollTop + self.virtualRemoveThreshold);
7649
 
7650
        // Low water is the pixel position of the last element to include (everything after
7651
        // that will be removed)
7652
        var lowWater = Math.min(scrollHeight, Math.abs(e.scrollTop) + viewportHeight + self.virtualAddThreshold);
7653
 
7654
        // Get the first and last elements in the list based on how many can fit
7655
        // between the pixel range of lowWater and highWater
7656
        var first = parseInt(Math.abs(highWater / itemHeight), 10);
7657
        var last = parseInt(Math.abs(lowWater / itemHeight), 10);
7658
 
7659
        // Get the items we need to remove
7660
        self._virtualItemsToRemove = Array.prototype.slice.call(self.listEl.children, 0, first);
7661
 
7662
        self.renderViewport && self.renderViewport(highWater, lowWater, first, last);
7663
      }
7664
    },
7665
 
7666
    didStopScrolling: function() {
7667
      if (this.isVirtual) {
7668
        for (var i = 0; i < this._virtualItemsToRemove.length; i++) {
7669
          //el.parentNode.removeChild(el);
7670
          this.didHideItem && this.didHideItem(i);
7671
        }
7672
        // Once scrolling stops, check if we need to remove old items
7673
 
7674
      }
7675
    },
7676
 
7677
    /**
7678
     * Clear any active drag effects on the list.
7679
     */
7680
    clearDragEffects: function(isInstant) {
7681
      if (this._lastDragOp) {
7682
        this._lastDragOp.clean && this._lastDragOp.clean(isInstant);
7683
        this._lastDragOp.deregister && this._lastDragOp.deregister();
7684
        this._lastDragOp = null;
7685
      }
7686
    },
7687
 
7688
    _initDrag: function() {
7689
      // Store the last one
7690
      if (this._lastDragOp) {
7691
        this._lastDragOp.deregister && this._lastDragOp.deregister();
7692
      }
7693
      this._lastDragOp = this._dragOp;
7694
 
7695
      this._dragOp = null;
7696
    },
7697
 
7698
    // Return the list item from the given target
7699
    _getItem: function(target) {
7700
      while (target) {
7701
        if (target.classList && target.classList.contains(ITEM_CLASS)) {
7702
          return target;
7703
        }
7704
        target = target.parentNode;
7705
      }
7706
      return null;
7707
    },
7708
 
7709
 
7710
    _startDrag: function(e) {
7711
      var self = this;
7712
 
7713
      self._isDragging = false;
7714
 
7715
      var lastDragOp = self._lastDragOp;
7716
      var item;
7717
 
7718
      // If we have an open SlideDrag and we're scrolling the list. Clear it.
7719
      if (self._didDragUpOrDown && lastDragOp instanceof SlideDrag) {
7720
          lastDragOp.clean && lastDragOp.clean();
7721
      }
7722
 
7723
      // Check if this is a reorder drag
7724
      if (ionic.DomUtil.getParentOrSelfWithClass(e.target, ITEM_REORDER_BTN_CLASS) && (e.gesture.direction == 'up' || e.gesture.direction == 'down')) {
7725
        item = self._getItem(e.target);
7726
 
7727
        if (item) {
7728
          self._dragOp = new ReorderDrag({
7729
            listEl: self.el,
7730
            el: item,
7731
            scrollEl: self.scrollEl,
7732
            scrollView: self.scrollView,
7733
            onReorder: function(el, start, end) {
7734
              self.onReorder && self.onReorder(el, start, end);
7735
            }
7736
          });
7737
          self._dragOp.start(e);
7738
          e.preventDefault();
7739
        }
7740
      }
7741
 
7742
      // Or check if this is a swipe to the side drag
7743
      else if (!self._didDragUpOrDown && (e.gesture.direction == 'left' || e.gesture.direction == 'right') && Math.abs(e.gesture.deltaX) > 5) {
7744
 
7745
        // Make sure this is an item with buttons
7746
        item = self._getItem(e.target);
7747
        if (item && item.querySelector('.item-options')) {
7748
          self._dragOp = new SlideDrag({
7749
            el: self.el,
7750
            item: item,
7751
            canSwipe: self.canSwipe
7752
          });
7753
          self._dragOp.start(e);
7754
          e.preventDefault();
7755
          self.isScrollFreeze = self.scrollView.freeze(true);
7756
        }
7757
      }
7758
 
7759
      // If we had a last drag operation and this is a new one on a different item, clean that last one
7760
      if (lastDragOp && self._dragOp && !self._dragOp.isSameItem(lastDragOp) && e.defaultPrevented) {
7761
        lastDragOp.clean && lastDragOp.clean();
7762
      }
7763
    },
7764
 
7765
 
7766
    _handleEndDrag: function(e) {
7767
      var self = this;
7768
 
7769
      if (self.scrollView) {
7770
        self.isScrollFreeze = self.scrollView.freeze(false);
7771
      }
7772
 
7773
      self._didDragUpOrDown = false;
7774
 
7775
      if (!self._dragOp) {
7776
        return;
7777
      }
7778
 
7779
      self._dragOp.end(e, function() {
7780
        self._initDrag();
7781
      });
7782
    },
7783
 
7784
    /**
7785
     * Process the drag event to move the item to the left or right.
7786
     */
7787
    _handleDrag: function(e) {
7788
      var self = this;
7789
 
7790
      if (Math.abs(e.gesture.deltaY) > 5) {
7791
        self._didDragUpOrDown = true;
7792
      }
7793
 
7794
      // If we get a drag event, make sure we aren't in another drag, then check if we should
7795
      // start one
7796
      if (!self.isDragging && !self._dragOp) {
7797
        self._startDrag(e);
7798
      }
7799
 
7800
      // No drag still, pass it up
7801
      if (!self._dragOp) {
7802
        return;
7803
      }
7804
 
7805
      e.gesture.srcEvent.preventDefault();
7806
      self._dragOp.drag(e);
7807
    }
7808
 
7809
  });
7810
 
7811
})(ionic);
7812
 
7813
(function(ionic) {
7814
'use strict';
7815
 
7816
  ionic.views.Modal = ionic.views.View.inherit({
7817
    initialize: function(opts) {
7818
      opts = ionic.extend({
7819
        focusFirstInput: false,
7820
        unfocusOnHide: true,
7821
        focusFirstDelay: 600,
7822
        backdropClickToClose: true,
7823
        hardwareBackButtonClose: true,
7824
      }, opts);
7825
 
7826
      ionic.extend(this, opts);
7827
 
7828
      this.el = opts.el;
7829
    },
7830
    show: function() {
7831
      var self = this;
7832
 
7833
      if(self.focusFirstInput) {
7834
        // Let any animations run first
7835
        window.setTimeout(function() {
7836
          var input = self.el.querySelector('input, textarea');
7837
          input && input.focus && input.focus();
7838
        }, self.focusFirstDelay);
7839
      }
7840
    },
7841
    hide: function() {
7842
      // Unfocus all elements
7843
      if(this.unfocusOnHide) {
7844
        var inputs = this.el.querySelectorAll('input, textarea');
7845
        // Let any animations run first
7846
        window.setTimeout(function() {
7847
          for(var i = 0; i < inputs.length; i++) {
7848
            inputs[i].blur && inputs[i].blur();
7849
          }
7850
        });
7851
      }
7852
    }
7853
  });
7854
 
7855
})(ionic);
7856
 
7857
(function(ionic) {
7858
'use strict';
7859
 
7860
  /**
7861
   * The side menu view handles one of the side menu's in a Side Menu Controller
7862
   * configuration.
7863
   * It takes a DOM reference to that side menu element.
7864
   */
7865
  ionic.views.SideMenu = ionic.views.View.inherit({
7866
    initialize: function(opts) {
7867
      this.el = opts.el;
7868
      this.isEnabled = (typeof opts.isEnabled === 'undefined') ? true : opts.isEnabled;
7869
      this.setWidth(opts.width);
7870
    },
7871
    getFullWidth: function() {
7872
      return this.width;
7873
    },
7874
    setWidth: function(width) {
7875
      this.width = width;
7876
      this.el.style.width = width + 'px';
7877
    },
7878
    setIsEnabled: function(isEnabled) {
7879
      this.isEnabled = isEnabled;
7880
    },
7881
    bringUp: function() {
7882
      if(this.el.style.zIndex !== '0') {
7883
        this.el.style.zIndex = '0';
7884
      }
7885
    },
7886
    pushDown: function() {
7887
      if(this.el.style.zIndex !== '-1') {
7888
        this.el.style.zIndex = '-1';
7889
      }
7890
    }
7891
  });
7892
 
7893
  ionic.views.SideMenuContent = ionic.views.View.inherit({
7894
    initialize: function(opts) {
7895
      ionic.extend(this, {
7896
        animationClass: 'menu-animated',
7897
        onDrag: function() {},
7898
        onEndDrag: function() {}
7899
      }, opts);
7900
 
7901
      ionic.onGesture('drag', ionic.proxy(this._onDrag, this), this.el);
7902
      ionic.onGesture('release', ionic.proxy(this._onEndDrag, this), this.el);
7903
    },
7904
    _onDrag: function(e) {
7905
      this.onDrag && this.onDrag(e);
7906
    },
7907
    _onEndDrag: function(e) {
7908
      this.onEndDrag && this.onEndDrag(e);
7909
    },
7910
    disableAnimation: function() {
7911
      this.el.classList.remove(this.animationClass);
7912
    },
7913
    enableAnimation: function() {
7914
      this.el.classList.add(this.animationClass);
7915
    },
7916
    getTranslateX: function() {
7917
      return parseFloat(this.el.style[ionic.CSS.TRANSFORM].replace('translate3d(', '').split(',')[0]);
7918
    },
7919
    setTranslateX: ionic.animationFrameThrottle(function(x) {
7920
      this.el.style[ionic.CSS.TRANSFORM] = 'translate3d(' + x + 'px, 0, 0)';
7921
    })
7922
  });
7923
 
7924
})(ionic);
7925
 
7926
/*
7927
 * Adapted from Swipe.js 2.0
7928
 *
7929
 * Brad Birdsall
7930
 * Copyright 2013, MIT License
7931
 *
7932
*/
7933
 
7934
(function(ionic) {
7935
'use strict';
7936
 
7937
ionic.views.Slider = ionic.views.View.inherit({
7938
  initialize: function (options) {
7939
    var slider = this;
7940
 
7941
    // utilities
7942
    var noop = function() {}; // simple no operation function
7943
    var offloadFn = function(fn) { setTimeout(fn || noop, 0); }; // offload a functions execution
7944
 
7945
    // check browser capabilities
7946
    var browser = {
7947
      addEventListener: !!window.addEventListener,
7948
      touch: ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch,
7949
      transitions: (function(temp) {
7950
        var props = ['transitionProperty', 'WebkitTransition', 'MozTransition', 'OTransition', 'msTransition'];
7951
        for ( var i in props ) if (temp.style[ props[i] ] !== undefined) return true;
7952
        return false;
7953
      })(document.createElement('swipe'))
7954
    };
7955
 
7956
 
7957
    var container = options.el;
7958
 
7959
    // quit if no root element
7960
    if (!container) return;
7961
    var element = container.children[0];
7962
    var slides, slidePos, width, length;
7963
    options = options || {};
7964
    var index = parseInt(options.startSlide, 10) || 0;
7965
    var speed = options.speed || 300;
7966
    options.continuous = options.continuous !== undefined ? options.continuous : true;
7967
 
7968
    function setup() {
7969
 
7970
      // do not setup if the container has no width
7971
      if (!container.offsetWidth) {
7972
        return;
7973
      }
7974
 
7975
      // cache slides
7976
      slides = element.children;
7977
      length = slides.length;
7978
 
7979
      // set continuous to false if only one slide
7980
      if (slides.length < 2) options.continuous = false;
7981
 
7982
      //special case if two slides
7983
      if (browser.transitions && options.continuous && slides.length < 3) {
7984
        element.appendChild(slides[0].cloneNode(true));
7985
        element.appendChild(element.children[1].cloneNode(true));
7986
        slides = element.children;
7987
      }
7988
 
7989
      // create an array to store current positions of each slide
7990
      slidePos = new Array(slides.length);
7991
 
7992
      // determine width of each slide
7993
      width = container.offsetWidth || container.getBoundingClientRect().width;
7994
 
7995
      element.style.width = (slides.length * width) + 'px';
7996
 
7997
      // stack elements
7998
      var pos = slides.length;
7999
      while(pos--) {
8000
 
8001
        var slide = slides[pos];
8002
 
8003
        slide.style.width = width + 'px';
8004
        slide.setAttribute('data-index', pos);
8005
 
8006
        if (browser.transitions) {
8007
          slide.style.left = (pos * -width) + 'px';
8008
          move(pos, index > pos ? -width : (index < pos ? width : 0), 0);
8009
        }
8010
 
8011
      }
8012
 
8013
      // reposition elements before and after index
8014
      if (options.continuous && browser.transitions) {
8015
        move(circle(index - 1), -width, 0);
8016
        move(circle(index + 1), width, 0);
8017
      }
8018
 
8019
      if (!browser.transitions) element.style.left = (index * -width) + 'px';
8020
 
8021
      container.style.visibility = 'visible';
8022
 
8023
      options.slidesChanged && options.slidesChanged();
8024
    }
8025
 
8026
    function prev(slideSpeed) {
8027
 
8028
      if (options.continuous) slide(index - 1, slideSpeed);
8029
      else if (index) slide(index - 1, slideSpeed);
8030
 
8031
    }
8032
 
8033
    function next(slideSpeed) {
8034
 
8035
      if (options.continuous) slide(index + 1, slideSpeed);
8036
      else if (index < slides.length - 1) slide(index + 1, slideSpeed);
8037
 
8038
    }
8039
 
8040
    function circle(index) {
8041
 
8042
      // a simple positive modulo using slides.length
8043
      return (slides.length + (index % slides.length)) % slides.length;
8044
 
8045
    }
8046
 
8047
    function slide(to, slideSpeed) {
8048
 
8049
      // do nothing if already on requested slide
8050
      if (index == to) return;
8051
 
8052
      if (browser.transitions) {
8053
 
8054
        var direction = Math.abs(index - to) / (index - to); // 1: backward, -1: forward
8055
 
8056
        // get the actual position of the slide
8057
        if (options.continuous) {
8058
          var naturalDirection = direction;
8059
          direction = -slidePos[circle(to)] / width;
8060
 
8061
          // if going forward but to < index, use to = slides.length + to
8062
          // if going backward but to > index, use to = -slides.length + to
8063
          if (direction !== naturalDirection) to = -direction * slides.length + to;
8064
 
8065
        }
8066
 
8067
        var diff = Math.abs(index - to) - 1;
8068
 
8069
        // move all the slides between index and to in the right direction
8070
        while (diff--) move( circle((to > index ? to : index) - diff - 1), width * direction, 0);
8071
 
8072
        to = circle(to);
8073
 
8074
        move(index, width * direction, slideSpeed || speed);
8075
        move(to, 0, slideSpeed || speed);
8076
 
8077
        if (options.continuous) move(circle(to - direction), -(width * direction), 0); // we need to get the next in place
8078
 
8079
      } else {
8080
 
8081
        to = circle(to);
8082
        animate(index * -width, to * -width, slideSpeed || speed);
8083
        //no fallback for a circular continuous if the browser does not accept transitions
8084
      }
8085
 
8086
      index = to;
8087
      offloadFn(options.callback && options.callback(index, slides[index]));
8088
    }
8089
 
8090
    function move(index, dist, speed) {
8091
 
8092
      translate(index, dist, speed);
8093
      slidePos[index] = dist;
8094
 
8095
    }
8096
 
8097
    function translate(index, dist, speed) {
8098
 
8099
      var slide = slides[index];
8100
      var style = slide && slide.style;
8101
 
8102
      if (!style) return;
8103
 
8104
      style.webkitTransitionDuration =
8105
      style.MozTransitionDuration =
8106
      style.msTransitionDuration =
8107
      style.OTransitionDuration =
8108
      style.transitionDuration = speed + 'ms';
8109
 
8110
      style.webkitTransform = 'translate(' + dist + 'px,0)' + 'translateZ(0)';
8111
      style.msTransform =
8112
      style.MozTransform =
8113
      style.OTransform = 'translateX(' + dist + 'px)';
8114
 
8115
    }
8116
 
8117
    function animate(from, to, speed) {
8118
 
8119
      // if not an animation, just reposition
8120
      if (!speed) {
8121
 
8122
        element.style.left = to + 'px';
8123
        return;
8124
 
8125
      }
8126
 
8127
      var start = +new Date();
8128
 
8129
      var timer = setInterval(function() {
8130
 
8131
        var timeElap = +new Date() - start;
8132
 
8133
        if (timeElap > speed) {
8134
 
8135
          element.style.left = to + 'px';
8136
 
8137
          if (delay) begin();
8138
 
8139
          options.transitionEnd && options.transitionEnd.call(event, index, slides[index]);
8140
 
8141
          clearInterval(timer);
8142
          return;
8143
 
8144
        }
8145
 
8146
        element.style.left = (( (to - from) * (Math.floor((timeElap / speed) * 100) / 100) ) + from) + 'px';
8147
 
8148
      }, 4);
8149
 
8150
    }
8151
 
8152
    // setup auto slideshow
8153
    var delay = options.auto || 0;
8154
    var interval;
8155
 
8156
    function begin() {
8157
 
8158
      interval = setTimeout(next, delay);
8159
 
8160
    }
8161
 
8162
    function stop() {
8163
 
8164
      delay = options.auto || 0;
8165
      clearTimeout(interval);
8166
 
8167
    }
8168
 
8169
 
8170
    // setup initial vars
8171
    var start = {};
8172
    var delta = {};
8173
    var isScrolling;
8174
 
8175
    // setup event capturing
8176
    var events = {
8177
 
8178
      handleEvent: function(event) {
8179
        if(event.type == 'mousedown' || event.type == 'mouseup' || event.type == 'mousemove') {
8180
          event.touches = [{
8181
            pageX: event.pageX,
8182
            pageY: event.pageY
8183
          }];
8184
        }
8185
 
8186
        switch (event.type) {
8187
          case 'mousedown': this.start(event); break;
8188
          case 'touchstart': this.start(event); break;
8189
          case 'touchmove': this.touchmove(event); break;
8190
          case 'mousemove': this.touchmove(event); break;
8191
          case 'touchend': offloadFn(this.end(event)); break;
8192
          case 'mouseup': offloadFn(this.end(event)); break;
8193
          case 'webkitTransitionEnd':
8194
          case 'msTransitionEnd':
8195
          case 'oTransitionEnd':
8196
          case 'otransitionend':
8197
          case 'transitionend': offloadFn(this.transitionEnd(event)); break;
8198
          case 'resize': offloadFn(setup); break;
8199
        }
8200
 
8201
        if (options.stopPropagation) event.stopPropagation();
8202
 
8203
      },
8204
      start: function(event) {
8205
 
8206
        var touches = event.touches[0];
8207
 
8208
        // measure start values
8209
        start = {
8210
 
8211
          // get initial touch coords
8212
          x: touches.pageX,
8213
          y: touches.pageY,
8214
 
8215
          // store time to determine touch duration
8216
          time: +new Date()
8217
 
8218
        };
8219
 
8220
        // used for testing first move event
8221
        isScrolling = undefined;
8222
 
8223
        // reset delta and end measurements
8224
        delta = {};
8225
 
8226
        // attach touchmove and touchend listeners
8227
        if(browser.touch) {
8228
          element.addEventListener('touchmove', this, false);
8229
          element.addEventListener('touchend', this, false);
8230
        } else {
8231
          element.addEventListener('mousemove', this, false);
8232
          element.addEventListener('mouseup', this, false);
8233
          document.addEventListener('mouseup', this, false);
8234
        }
8235
      },
8236
      touchmove: function(event) {
8237
 
8238
        // ensure swiping with one touch and not pinching
8239
        // ensure sliding is enabled
8240
        if (event.touches.length > 1 ||
8241
            event.scale && event.scale !== 1 ||
8242
            slider.slideIsDisabled) {
8243
          return;
8244
        }
8245
 
8246
        if (options.disableScroll) event.preventDefault();
8247
 
8248
        var touches = event.touches[0];
8249
 
8250
        // measure change in x and y
8251
        delta = {
8252
          x: touches.pageX - start.x,
8253
          y: touches.pageY - start.y
8254
        };
8255
 
8256
        // determine if scrolling test has run - one time test
8257
        if ( typeof isScrolling == 'undefined') {
8258
          isScrolling = !!( isScrolling || Math.abs(delta.x) < Math.abs(delta.y) );
8259
        }
8260
 
8261
        // if user is not trying to scroll vertically
8262
        if (!isScrolling) {
8263
 
8264
          // prevent native scrolling
8265
          event.preventDefault();
8266
 
8267
          // stop slideshow
8268
          stop();
8269
 
8270
          // increase resistance if first or last slide
8271
          if (options.continuous) { // we don't add resistance at the end
8272
 
8273
            translate(circle(index - 1), delta.x + slidePos[circle(index - 1)], 0);
8274
            translate(index, delta.x + slidePos[index], 0);
8275
            translate(circle(index + 1), delta.x + slidePos[circle(index + 1)], 0);
8276
 
8277
          } else {
8278
 
8279
            delta.x =
8280
              delta.x /
8281
                ( (!index && delta.x > 0 ||         // if first slide and sliding left
8282
                  index == slides.length - 1 &&     // or if last slide and sliding right
8283
                  delta.x < 0                       // and if sliding at all
8284
                ) ?
8285
                ( Math.abs(delta.x) / width + 1 )      // determine resistance level
8286
                : 1 );                                 // no resistance if false
8287
 
8288
            // translate 1:1
8289
            translate(index - 1, delta.x + slidePos[index - 1], 0);
8290
            translate(index, delta.x + slidePos[index], 0);
8291
            translate(index + 1, delta.x + slidePos[index + 1], 0);
8292
          }
8293
 
8294
          options.onDrag && options.onDrag();
8295
        }
8296
 
8297
      },
8298
      end: function() {
8299
 
8300
        // measure duration
8301
        var duration = +new Date() - start.time;
8302
 
8303
        // determine if slide attempt triggers next/prev slide
8304
        var isValidSlide =
8305
              Number(duration) < 250 &&         // if slide duration is less than 250ms
8306
              Math.abs(delta.x) > 20 ||         // and if slide amt is greater than 20px
8307
              Math.abs(delta.x) > width / 2;      // or if slide amt is greater than half the width
8308
 
8309
        // determine if slide attempt is past start and end
8310
        var isPastBounds = (!index && delta.x > 0) ||      // if first slide and slide amt is greater than 0
8311
              (index == slides.length - 1 && delta.x < 0); // or if last slide and slide amt is less than 0
8312
 
8313
        if (options.continuous) isPastBounds = false;
8314
 
8315
        // determine direction of swipe (true:right, false:left)
8316
        var direction = delta.x < 0;
8317
 
8318
        // if not scrolling vertically
8319
        if (!isScrolling) {
8320
 
8321
          if (isValidSlide && !isPastBounds) {
8322
 
8323
            if (direction) {
8324
 
8325
              if (options.continuous) { // we need to get the next in this direction in place
8326
 
8327
                move(circle(index - 1), -width, 0);
8328
                move(circle(index + 2), width, 0);
8329
 
8330
              } else {
8331
                move(index - 1, -width, 0);
8332
              }
8333
 
8334
              move(index, slidePos[index] - width, speed);
8335
              move(circle(index + 1), slidePos[circle(index + 1)] - width, speed);
8336
              index = circle(index + 1);
8337
 
8338
            } else {
8339
              if (options.continuous) { // we need to get the next in this direction in place
8340
 
8341
                move(circle(index + 1), width, 0);
8342
                move(circle(index - 2), -width, 0);
8343
 
8344
              } else {
8345
                move(index + 1, width, 0);
8346
              }
8347
 
8348
              move(index, slidePos[index] + width, speed);
8349
              move(circle(index - 1), slidePos[circle(index - 1)] + width, speed);
8350
              index = circle(index - 1);
8351
 
8352
            }
8353
 
8354
            options.callback && options.callback(index, slides[index]);
8355
 
8356
          } else {
8357
 
8358
            if (options.continuous) {
8359
 
8360
              move(circle(index - 1), -width, speed);
8361
              move(index, 0, speed);
8362
              move(circle(index + 1), width, speed);
8363
 
8364
            } else {
8365
 
8366
              move(index - 1, -width, speed);
8367
              move(index, 0, speed);
8368
              move(index + 1, width, speed);
8369
            }
8370
 
8371
          }
8372
 
8373
        }
8374
 
8375
        // kill touchmove and touchend event listeners until touchstart called again
8376
        if(browser.touch) {
8377
          element.removeEventListener('touchmove', events, false);
8378
          element.removeEventListener('touchend', events, false);
8379
        } else {
8380
          element.removeEventListener('mousemove', events, false);
8381
          element.removeEventListener('mouseup', events, false);
8382
          document.removeEventListener('mouseup', events, false);
8383
        }
8384
 
8385
        options.onDragEnd && options.onDragEnd();
8386
      },
8387
      transitionEnd: function(event) {
8388
 
8389
        if (parseInt(event.target.getAttribute('data-index'), 10) == index) {
8390
 
8391
          if (delay) begin();
8392
 
8393
          options.transitionEnd && options.transitionEnd.call(event, index, slides[index]);
8394
 
8395
        }
8396
 
8397
      }
8398
 
8399
    };
8400
 
8401
    // Public API
8402
    this.update = function() {
8403
      setTimeout(setup);
8404
    };
8405
    this.setup = function() {
8406
      setup();
8407
    };
8408
 
8409
    this.loop = function(value) {
8410
      if (arguments.length) options.continuous = !!value;
8411
      return options.continuous;
8412
    };
8413
 
8414
    this.enableSlide = function(shouldEnable) {
8415
      if (arguments.length) {
8416
        this.slideIsDisabled = !shouldEnable;
8417
      }
8418
      return !this.slideIsDisabled;
8419
    };
8420
 
8421
    this.slide = this.select = function(to, speed) {
8422
      // cancel slideshow
8423
      stop();
8424
 
8425
      slide(to, speed);
8426
    };
8427
 
8428
    this.prev = this.previous = function() {
8429
      // cancel slideshow
8430
      stop();
8431
 
8432
      prev();
8433
    };
8434
 
8435
    this.next = function() {
8436
      // cancel slideshow
8437
      stop();
8438
 
8439
      next();
8440
    };
8441
 
8442
    this.stop = function() {
8443
      // cancel slideshow
8444
      stop();
8445
    };
8446
 
8447
    this.start = function() {
8448
      begin();
8449
    };
8450
 
8451
    this.autoPlay = function(newDelay) {
8452
      if (!delay || delay < 0) {
8453
        stop();
8454
      } else {
8455
        delay = newDelay;
8456
        begin();
8457
      }
8458
    };
8459
 
8460
    this.currentIndex = this.selected = function() {
8461
      // return current index position
8462
      return index;
8463
    };
8464
 
8465
    this.slidesCount = this.count = function() {
8466
      // return total number of slides
8467
      return length;
8468
    };
8469
 
8470
    this.kill = function() {
8471
      // cancel slideshow
8472
      stop();
8473
 
8474
      // reset element
8475
      element.style.width = '';
8476
      element.style.left = '';
8477
 
8478
      // reset slides so no refs are held on to
8479
      slides && (slides = []);
8480
 
8481
      // removed event listeners
8482
      if (browser.addEventListener) {
8483
 
8484
        // remove current event listeners
8485
        element.removeEventListener('touchstart', events, false);
8486
        element.removeEventListener('webkitTransitionEnd', events, false);
8487
        element.removeEventListener('msTransitionEnd', events, false);
8488
        element.removeEventListener('oTransitionEnd', events, false);
8489
        element.removeEventListener('otransitionend', events, false);
8490
        element.removeEventListener('transitionend', events, false);
8491
        window.removeEventListener('resize', events, false);
8492
 
8493
      }
8494
      else {
8495
 
8496
        window.onresize = null;
8497
 
8498
      }
8499
    };
8500
 
8501
    this.load = function() {
8502
      // trigger setup
8503
      setup();
8504
 
8505
      // start auto slideshow if applicable
8506
      if (delay) begin();
8507
 
8508
 
8509
      // add event listeners
8510
      if (browser.addEventListener) {
8511
 
8512
        // set touchstart event on element
8513
        if (browser.touch) {
8514
          element.addEventListener('touchstart', events, false);
8515
        } else {
8516
          element.addEventListener('mousedown', events, false);
8517
        }
8518
 
8519
        if (browser.transitions) {
8520
          element.addEventListener('webkitTransitionEnd', events, false);
8521
          element.addEventListener('msTransitionEnd', events, false);
8522
          element.addEventListener('oTransitionEnd', events, false);
8523
          element.addEventListener('otransitionend', events, false);
8524
          element.addEventListener('transitionend', events, false);
8525
        }
8526
 
8527
        // set resize event on window
8528
        window.addEventListener('resize', events, false);
8529
 
8530
      } else {
8531
 
8532
        window.onresize = function () { setup(); }; // to play nice with old IE
8533
 
8534
      }
8535
    };
8536
 
8537
  }
8538
});
8539
 
8540
})(ionic);
8541
 
8542
(function(ionic) {
8543
'use strict';
8544
 
8545
  ionic.views.Toggle = ionic.views.View.inherit({
8546
    initialize: function(opts) {
8547
      var self = this;
8548
 
8549
      this.el = opts.el;
8550
      this.checkbox = opts.checkbox;
8551
      this.track = opts.track;
8552
      this.handle = opts.handle;
8553
      this.openPercent = -1;
8554
      this.onChange = opts.onChange || function() {};
8555
 
8556
      this.triggerThreshold = opts.triggerThreshold || 20;
8557
 
8558
      this.dragStartHandler = function(e) {
8559
        self.dragStart(e);
8560
      };
8561
      this.dragHandler = function(e) {
8562
        self.drag(e);
8563
      };
8564
      this.holdHandler = function(e) {
8565
        self.hold(e);
8566
      };
8567
      this.releaseHandler = function(e) {
8568
        self.release(e);
8569
      };
8570
 
8571
      this.dragStartGesture = ionic.onGesture('dragstart', this.dragStartHandler, this.el);
8572
      this.dragGesture = ionic.onGesture('drag', this.dragHandler, this.el);
8573
      this.dragHoldGesture = ionic.onGesture('hold', this.holdHandler, this.el);
8574
      this.dragReleaseGesture = ionic.onGesture('release', this.releaseHandler, this.el);
8575
    },
8576
 
8577
    destroy: function() {
8578
      ionic.offGesture(this.dragStartGesture, 'dragstart', this.dragStartGesture);
8579
      ionic.offGesture(this.dragGesture, 'drag', this.dragGesture);
8580
      ionic.offGesture(this.dragHoldGesture, 'hold', this.holdHandler);
8581
      ionic.offGesture(this.dragReleaseGesture, 'release', this.releaseHandler);
8582
    },
8583
 
8584
    tap: function() {
8585
      if(this.el.getAttribute('disabled') !== 'disabled') {
8586
        this.val( !this.checkbox.checked );
8587
      }
8588
    },
8589
 
8590
    dragStart: function(e) {
8591
      if(this.checkbox.disabled) return;
8592
 
8593
      this._dragInfo = {
8594
        width: this.el.offsetWidth,
8595
        left: this.el.offsetLeft,
8596
        right: this.el.offsetLeft + this.el.offsetWidth,
8597
        triggerX: this.el.offsetWidth / 2,
8598
        initialState: this.checkbox.checked
8599
      };
8600
 
8601
      // Stop any parent dragging
8602
      e.gesture.srcEvent.preventDefault();
8603
 
8604
      // Trigger hold styles
8605
      this.hold(e);
8606
    },
8607
 
8608
    drag: function(e) {
8609
      var self = this;
8610
      if(!this._dragInfo) { return; }
8611
 
8612
      // Stop any parent dragging
8613
      e.gesture.srcEvent.preventDefault();
8614
 
8615
      ionic.requestAnimationFrame(function () {
8616
        if (!self._dragInfo) { return; }
8617
 
8618
        var px = e.gesture.touches[0].pageX - self._dragInfo.left;
8619
        var mx = self._dragInfo.width - self.triggerThreshold;
8620
 
8621
        // The initial state was on, so "tend towards" on
8622
        if(self._dragInfo.initialState) {
8623
          if(px < self.triggerThreshold) {
8624
            self.setOpenPercent(0);
8625
          } else if(px > self._dragInfo.triggerX) {
8626
            self.setOpenPercent(100);
8627
          }
8628
        } else {
8629
          // The initial state was off, so "tend towards" off
8630
          if(px < self._dragInfo.triggerX) {
8631
            self.setOpenPercent(0);
8632
          } else if(px > mx) {
8633
            self.setOpenPercent(100);
8634
          }
8635
        }
8636
      });
8637
    },
8638
 
8639
    endDrag: function() {
8640
      this._dragInfo = null;
8641
    },
8642
 
8643
    hold: function() {
8644
      this.el.classList.add('dragging');
8645
    },
8646
    release: function(e) {
8647
      this.el.classList.remove('dragging');
8648
      this.endDrag(e);
8649
    },
8650
 
8651
 
8652
    setOpenPercent: function(openPercent) {
8653
      // only make a change if the new open percent has changed
8654
      if(this.openPercent < 0 || (openPercent < (this.openPercent - 3) || openPercent > (this.openPercent + 3) ) ) {
8655
        this.openPercent = openPercent;
8656
 
8657
        if(openPercent === 0) {
8658
          this.val(false);
8659
        } else if(openPercent === 100) {
8660
          this.val(true);
8661
        } else {
8662
          var openPixel = Math.round( (openPercent / 100) * this.track.offsetWidth - (this.handle.offsetWidth) );
8663
          openPixel = (openPixel < 1 ? 0 : openPixel);
8664
          this.handle.style[ionic.CSS.TRANSFORM] = 'translate3d(' + openPixel + 'px,0,0)';
8665
        }
8666
      }
8667
    },
8668
 
8669
    val: function(value) {
8670
      if(value === true || value === false) {
8671
        if(this.handle.style[ionic.CSS.TRANSFORM] !== "") {
8672
          this.handle.style[ionic.CSS.TRANSFORM] = "";
8673
        }
8674
        this.checkbox.checked = value;
8675
        this.openPercent = (value ? 100 : 0);
8676
        this.onChange && this.onChange();
8677
      }
8678
      return this.checkbox.checked;
8679
    }
8680
 
8681
  });
8682
 
8683
})(ionic);
8684
 
8685
})();