Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

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