1ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen// Use of this source code is governed by a BSD-style license that can be
3ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen// found in the LICENSE file.
4ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
5ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen/**
6ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * @fileoverview Card slider implementation. Allows you to create interactions
7ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * that have items that can slide left to right to reveal additional items.
8ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * Works by adding the necessary event handlers to a specific DOM structure
9ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * including a frame, container and cards.
10ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * - The frame defines the boundary of one item. Each card will be expanded to
11ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen *   fill the width of the frame. This element is also overflow hidden so that
12ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen *   the additional items left / right do not trigger horizontal scrolling.
13ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * - The container is what all the touch events are attached to. This element
14ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen *   will be expanded to be the width of all cards.
15ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * - The cards are the individual viewable items. There should be one card for
16ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen *   each item in the list. Only one card will be visible at a time. Two cards
17ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen *   will be visible while you are transitioning between cards.
18ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen *
19ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * This class is designed to work well on any hardware-accelerated touch device.
20ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * It should still work on pre-hardware accelerated devices it just won't feel
21ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen * very good. It should also work well with a mouse.
22ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen */
23ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
24ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
25ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen// Use an anonymous function to enable strict mode just for this file (which
26ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen// will be concatenated with other files when embedded in Chrome
27ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsenvar Slider = (function() {
28ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  'use strict';
29ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
30ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  /**
31ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @constructor
32ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @param {!Element} frame The bounding rectangle that cards are visible in.
33ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @param {!Element} container The surrounding element that will have event
34ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   *     listeners attached to it.
35ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @param {!Array.<!Element>} cards The individual viewable cards.
36ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @param {number} currentCard The index of the card that is currently
37ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   *     visible.
38ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @param {number} cardWidth The width of each card should have.
39ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   */
40ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  function Slider(frame, container, cards, currentCard, cardWidth) {
41ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    /**
42ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * @type {!Element}
43ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * @private
44ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     */
45ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    this.frame_ = frame;
46ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
47ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    /**
48ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * @type {!Element}
49ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * @private
50ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     */
51ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    this.container_ = container;
52ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
53ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    /**
54ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * @type {!Array.<!Element>}
55ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * @private
56ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     */
57ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    this.cards_ = cards;
58ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
59ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    /**
60ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * @type {number}
61ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * @private
62ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     */
63ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    this.currentCard_ = currentCard;
64ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
65ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    /**
66ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * @type {number}
67ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * @private
68ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     */
69ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    this.cardWidth_ = cardWidth;
70ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
71ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    /**
72ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * @type {!TouchHandler}
73ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * @private
74ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     */
75ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    this.touchHandler_ = new TouchHandler(this.container_);
76ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  }
77ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
78ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
79ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  /**
80ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * Events fired by the slider.
81ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * Events are fired at the container.
82ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   */
83ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  Slider.EventType = {
84ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    // Fired when the user slides to another card.
85ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    CARD_CHANGED: 'slider:card_changed'
86ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  };
87ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
88ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
89ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  /**
90ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * The time to transition between cards when animating. Measured in ms.
91ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @type {number}
92ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @private
93ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @const
94ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   */
95ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  Slider.TRANSITION_TIME_ = 200;
96ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
97ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
98ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  /**
99ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * The minimum velocity required to transition cards if they did not drag past
100ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * the halfway point between cards. Measured in pixels / ms.
101ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @type {number}
102ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @private
103ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   * @const
104ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen   */
105ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  Slider.TRANSITION_VELOCITY_THRESHOLD_ = 0.2;
106ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
107ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
108ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  Slider.prototype = {
109ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    /**
110ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * The current left offset of the container relative to the frame.
111ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * @type {number}
112ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * @private
113ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     */
114ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    currentLeft_: 0,
115ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
116ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    /**
117ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * Initialize all elements and event handlers. Must call after construction
118ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * and before usage.
119ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     */
120ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    initialize: function() {
121ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      var view = this.container_.ownerDocument.defaultView;
122ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      assert(view.getComputedStyle(this.container_).display == '-webkit-box',
123ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen          'Container should be display -webkit-box.');
124ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      assert(view.getComputedStyle(this.frame_).overflow == 'hidden',
125ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen          'Frame should be overflow hidden.');
126ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      assert(view.getComputedStyle(this.container_).position == 'static',
127ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen          'Container should be position static.');
128ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      for (var i = 0, card; card = this.cards_[i]; i++) {
129ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        assert(view.getComputedStyle(card).position == 'static',
130ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen            'Cards should be position static.');
131ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      }
132ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
133ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      this.updateCardWidths_();
134ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      this.transformToCurrentCard_();
135ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
136ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      this.container_.addEventListener(TouchHandler.EventType.TOUCH_START,
137ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                                       this.onTouchStart_.bind(this));
138ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      this.container_.addEventListener(TouchHandler.EventType.DRAG_START,
139ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                                       this.onDragStart_.bind(this));
140ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      this.container_.addEventListener(TouchHandler.EventType.DRAG_MOVE,
141ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                                       this.onDragMove_.bind(this));
142ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      this.container_.addEventListener(TouchHandler.EventType.DRAG_END,
143ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                                       this.onDragEnd_.bind(this));
144ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
145ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      this.touchHandler_.enable(/* opt_capture */ false);
146ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    },
147ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
148ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    /**
149ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * Use in cases where the width of the frame has changed in order to update
150ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * the width of cards. For example should be used when orientation changes
151ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * in full width sliders.
152ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * @param {number} newCardWidth Width all cards should have, in pixels.
153ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     */
154ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    resize: function(newCardWidth) {
155ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      if (newCardWidth != this.cardWidth_) {
156ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        this.cardWidth_ = newCardWidth;
157ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
158ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        this.updateCardWidths_();
159ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
160ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        // Must upate the transform on the container to show the correct card.
161ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        this.transformToCurrentCard_();
162ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      }
163ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    },
164ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
165ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    /**
166ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * Sets the cards used. Can be called more than once to switch card sets.
167ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * @param {!Array.<!Element>} cards The individual viewable cards.
168ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * @param {number} index Index of the card to in the new set of cards to
169ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     *     navigate to.
170ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     */
171ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    setCards: function(cards, index) {
172ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      assert(index >= 0 && index < cards.length,
173ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen          'Invalid index in Slider#setCards');
174ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      this.cards_ = cards;
175ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
176ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      this.updateCardWidths_();
177ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
178ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      // Jump to the given card index.
179ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      this.selectCard(index);
180ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    },
181ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
182ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    /**
183ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * Updates the width of each card.
184ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * @private
185ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     */
186ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    updateCardWidths_: function() {
187ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      for (var i = 0, card; card = this.cards_[i]; i++)
188ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        card.style.width = this.cardWidth_ + 'px';
189ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    },
190ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
191ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    /**
192ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * Returns the index of the current card.
193ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * @return {number} index of the current card.
194ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     */
195ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    get currentCard() {
196ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      return this.currentCard_;
197ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    },
198ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
199ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    /**
200ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * Clear any transition that is in progress and enable dragging for the
201ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * touch.
202ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * @param {!TouchHandler.Event} e The TouchHandler event.
203ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * @private
204ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     */
205ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    onTouchStart_: function(e) {
206ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      this.container_.style.WebkitTransition = '';
207ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      e.enableDrag = true;
208ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    },
209ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
210ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
211ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    /**
212ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * Tell the TouchHandler that dragging is acceptable when the user begins by
213ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * scrolling horizontally.
214ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * @param {!TouchHandler.Event} e The TouchHandler event.
215ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * @private
216ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     */
217ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    onDragStart_: function(e) {
218ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      e.enableDrag = Math.abs(e.dragDeltaX) > Math.abs(e.dragDeltaY);
219ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    },
220ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
221ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    /**
222ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * On each drag move event reposition the container appropriately so the
223ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * cards look like they are sliding.
224ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * @param {!TouchHandler.Event} e The TouchHandler event.
225ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * @private
226ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     */
227ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    onDragMove_: function(e) {
228ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      var deltaX = e.dragDeltaX;
229ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      // If dragging beyond the first or last card then apply a backoff so the
230ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      // dragging feels stickier than usual.
231ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      if (!this.currentCard && deltaX > 0 ||
232ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen          this.currentCard == (this.cards_.length - 1) && deltaX < 0) {
233ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        deltaX /= 2;
234ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      }
235ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      this.translateTo_(this.currentLeft_ + deltaX);
236ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    },
237ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
238ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    /**
239ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * Moves the view to the specified position.
240ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * @param {number} x Horizontal position to move to.
241ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * @private
242ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     */
243ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    translateTo_: function(x) {
244ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      // We use a webkitTransform to slide because this is GPU accelerated on
245ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      // Chrome and iOS.  Once Chrome does GPU acceleration on the position
246ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      // fixed-layout elements we could simply set the element's position to
247ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      // fixed and modify 'left' instead.
248ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      this.container_.style.WebkitTransform = 'translate3d(' + x + 'px, 0, 0)';
249ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    },
250ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
251ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    /**
252ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * On drag end events we may want to transition to another card, depending
253ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * on the ending position of the drag and the velocity of the drag.
254ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * @param {!TouchHandler.Event} e The TouchHandler event.
255ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * @private
256ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     */
257ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    onDragEnd_: function(e) {
258ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      var deltaX = e.dragDeltaX;
259ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      var velocity = this.touchHandler_.getEndVelocity().x;
260ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      var newX = this.currentLeft_ + deltaX;
261ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      var newCardIndex = Math.round(-newX / this.cardWidth_);
262ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
263ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      if (newCardIndex == this.currentCard && Math.abs(velocity) >
264ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen          Slider.TRANSITION_VELOCITY_THRESHOLD_) {
265ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        // If the drag wasn't far enough to change cards but the velocity was
266ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        // high enough to transition anyways. If the velocity is to the left
267ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        // (negative) then the user wishes to go right (card +1).
268ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        newCardIndex += velocity > 0 ? -1 : 1;
269ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      }
270ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
271ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      this.selectCard(newCardIndex, /* animate */ true);
272ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    },
273ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
274ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    /**
275ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * Cancel any current touch/slide as if we saw a touch end
276ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     */
277ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    cancelTouch: function() {
278ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      // Stop listening to any current touch
279ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      this.touchHandler_.cancelTouch();
280ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
281ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      // Ensure we're at a card bounary
282ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      this.transformToCurrentCard_(true);
283ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    },
284ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
285ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    /**
286ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * Selects a new card, ensuring that it is a valid index, transforming the
287ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * view and possibly calling the change card callback.
288ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * @param {number} newCardIndex Index of card to show.
289ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * @param {boolean=} opt_animate If true will animate transition from
290ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     *     current position to new position.
291ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     */
292ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    selectCard: function(newCardIndex, opt_animate) {
293ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      var isChangingCard = newCardIndex >= 0 &&
294ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen          newCardIndex < this.cards_.length &&
295ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen          newCardIndex != this.currentCard;
296ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      if (isChangingCard) {
297ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        // If we have a new card index and it is valid then update the left
298ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        // position and current card index.
299ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        this.currentCard_ = newCardIndex;
300ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      }
301ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
302ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      this.transformToCurrentCard_(opt_animate);
303ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
304ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      if (isChangingCard) {
305ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        var event = document.createEvent('Event');
306ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        event.initEvent(Slider.EventType.CARD_CHANGED, true, true);
307ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        event.slider = this;
308ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        this.container_.dispatchEvent(event);
309ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      }
310ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    },
311ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
312ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    /**
313ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * Centers the view on the card denoted by this.currentCard. Can either
314ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * animate to that card or snap to it.
315ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * @param {boolean=} opt_animate If true will animate transition from
316ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     *     current position to new position.
317ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * @private
318ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     */
319ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    transformToCurrentCard_: function(opt_animate) {
320ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      this.currentLeft_ = -this.currentCard * this.cardWidth_;
321ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
322ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      // Animate to the current card, which will either transition if the
323ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      // current card is new, or reset the existing card if we didn't drag
324ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      // enough to change cards.
325ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      var transition = '';
326ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      if (opt_animate) {
327ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        transition = '-webkit-transform ' + Slider.TRANSITION_TIME_ +
328ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                     'ms ease-in-out';
329ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      }
330ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      this.container_.style.WebkitTransition = transition;
331ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      this.translateTo_(this.currentLeft_);
332ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    }
333ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  };
334ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
335ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  return Slider;
336ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen})();
337