OLD | NEW |
| (Empty) |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 /** | |
6 * @fileoverview Card slider implementation. Allows you to create interactions | |
7 * that have items that can slide left to right to reveal additional items. | |
8 * Works by adding the necessary event handlers to a specific DOM structure | |
9 * including a frame, container and cards. | |
10 * - The frame defines the boundary of one item. Each card will be expanded to | |
11 * fill the width of the frame. This element is also overflow hidden so that | |
12 * the additional items left / right do not trigger horizontal scrolling. | |
13 * - The container is what all the touch events are attached to. This element | |
14 * will be expanded to be the width of all cards. | |
15 * - The cards are the individual viewable items. There should be one card for | |
16 * each item in the list. Only one card will be visible at a time. Two cards | |
17 * will be visible while you are transitioning between cards. | |
18 * | |
19 * This class is designed to work well on any hardware-accelerated touch device. | |
20 * It should still work on pre-hardware accelerated devices it just won't feel | |
21 * very good. It should also work well with a mouse. | |
22 */ | |
23 | |
24 // Use an anonymous function to enable strict mode just for this file (which | |
25 // will be concatenated with other files when embedded in Chrome | |
26 var Slider = (function() { | |
27 'use strict'; | |
28 | |
29 /** | |
30 * The type of Event sent by Slider | |
31 * @constructor | |
32 * @param {string} type The type of event (one of Slider.EventType). | |
33 * @param {Element!} targetCard The element being dragged. | |
34 */ | |
35 Slider.Event = function(type, activeCardIndex) { | |
36 var event = document.createEvent('Event'); | |
37 event.initEvent(type, true, true); | |
38 event.__proto__ = Slider.Event.prototype; | |
39 | |
40 event.activeCardIndex = activeCardIndex; | |
41 | |
42 return event; | |
43 }; | |
44 | |
45 Slider.Event.prototype = { | |
46 __proto__: Event.prototype | |
47 }; | |
48 | |
49 /** | |
50 * @constructor | |
51 * @param {!Element} frame The bounding rectangle that cards are visible in. | |
52 * @param {!Element} container The surrounding element that will have event | |
53 * listeners attached to it. | |
54 * @param {!Array.<!Element>} cards The individual viewable cards. | |
55 */ | |
56 function Slider(frame, container, cards) { | |
57 /** | |
58 * @type {!Element} | |
59 * @private | |
60 */ | |
61 this.frame_ = frame; | |
62 | |
63 /** | |
64 * @type {!Element} | |
65 * @private | |
66 */ | |
67 this.container_ = container; | |
68 | |
69 /** | |
70 * @type {!Array.<!Element>} | |
71 * @private | |
72 */ | |
73 this.cards_ = cards; | |
74 | |
75 this.lastLeft_ = 0; | |
76 | |
77 this.ignoreClicks_ = false; | |
78 | |
79 /** | |
80 * @type {!TouchHandler} | |
81 * @private | |
82 */ | |
83 this.touchHandler_ = new TouchHandler(this.container_); | |
84 } | |
85 | |
86 /** | |
87 * Events fired by the slider. | |
88 * Events are fired at the container. | |
89 */ | |
90 Slider.EventType = { | |
91 // Fired when the user slides to another card. | |
92 SELECTION_CHANGED: 'slider:selection_changed', | |
93 // Fired at the card element when it is activated. | |
94 ACTIVATE: 'slider:activate', | |
95 // Fired at the card elment when it is deactivated. | |
96 DEACTIVATE: 'slider:deactivate', | |
97 }; | |
98 | |
99 /** | |
100 * The time to transition between cards when animating. Measured in ms. | |
101 * @type {number} | |
102 * @private | |
103 * @const | |
104 */ | |
105 Slider.TRANSITION_TIME_ = 200; | |
106 | |
107 /** | |
108 * The minimum velocity required to transition cards if they did not drag past | |
109 * the halfway point between cards. Measured in pixels / ms. | |
110 * @type {number} | |
111 * @private | |
112 * @const | |
113 */ | |
114 Slider.TRANSITION_VELOCITY_THRESHOLD_ = 0.2; | |
115 | |
116 Slider.prototype = { | |
117 currentCard_: null, | |
118 | |
119 /** | |
120 * Initialize all elements and event handlers. Must call after construction | |
121 * and before usage. | |
122 */ | |
123 initialize: function() { | |
124 var view = this.container_.ownerDocument.defaultView; | |
125 assert(view.getComputedStyle(this.frame_).overflow == 'hidden', | |
126 'Frame should be overflow hidden.'); | |
127 assert(view.getComputedStyle(this.container_).position == 'static', | |
128 'Container should be position static.'); | |
129 for (var i = 0, card; card = this.cards_[i]; i++) { | |
130 assert(view.getComputedStyle(card).position == 'static', | |
131 'Cards should be position static.'); | |
132 } | |
133 | |
134 var self = this; | |
135 var clickSelf = function(e) { | |
136 self.onClick_(self, e); | |
137 }; | |
138 this.container_.addEventListener('click', clickSelf , true); | |
139 this.touchHandler_.enable(/* opt_capture */ false); | |
140 }, | |
141 | |
142 /** | |
143 * Sets the cards used. Can be called more than once to switch card sets. | |
144 * @param {!Array.<!Element>} cards The individual viewable cards. | |
145 * @param {number} index Index of the card to in the new set of cards to | |
146 * navigate to. | |
147 */ | |
148 setCards: function(cards, index) { | |
149 assert(index >= 0 && index < cards.length, | |
150 'Invalid index in Slider#setCards'); | |
151 this.cards_ = cards; | |
152 }, | |
153 | |
154 cardWidth: function() { | |
155 return this.cards_.length == 0 ? 0 : this.cards_[0].offsetWidth; | |
156 }, | |
157 | |
158 cardCount: function() { | |
159 return this.cards_.length; | |
160 }, | |
161 | |
162 get currentCard() { | |
163 if (this.currentCardIndex == null) { | |
164 return null; | |
165 } | |
166 return this.cards_[this.currentCard_]; | |
167 }, | |
168 | |
169 /** | |
170 * Returns the index of the current card. | |
171 * @return {number} index of the current card. | |
172 */ | |
173 get currentCardIndex() { | |
174 return this.currentCard_; | |
175 }, | |
176 | |
177 set currentCardIndex(card) { | |
178 // There's nothing to do if nothing has changed. | |
179 if (card == this.currentCard_) { | |
180 return; | |
181 } | |
182 if (this.currentCard_ != null) { | |
183 this.sendEvent_(Slider.EventType.DEACTIVATE, | |
184 this.cards_[this.currentCard_], null); | |
185 } | |
186 var oldCard = this.currentCard_; | |
187 this.currentCard_ = card; | |
188 // If no cards are selected, restored the last scroll position, | |
189 // and re-enable the cards. | |
190 if (this.currentCard_ == null) { | |
191 this.sendEvent_(Slider.EventType.SELECTION_CHANGED, this.frame_, null); | |
192 } else { | |
193 // Center the newly selected card. | |
194 var cardPosition = card * this.cardWidth(); | |
195 var centerOfContainer = | |
196 (this.frame_.offsetWidth - this.cardWidth()) / 2; | |
197 } | |
198 if (this.currentCard_ != null) { | |
199 this.sendEvent_(Slider.EventType.SELECTION_CHANGED, this.frame_, | |
200 this.currentCard_); | |
201 // TODO(fsamuel): I can't think of a better way to do this currently. | |
202 // We wait a little bit of time to finish the animation and then call | |
203 // onActivateCard to make sure the WebUI doesn't resize while we're | |
204 // animating. | |
205 var self = this; | |
206 setTimeout(function() { | |
207 self.sendEvent_(Slider.EventType.ACTIVATE, | |
208 self.cards_[self.currentCard_], | |
209 self.currentCard_); | |
210 }, 80); | |
211 } // if | |
212 }, | |
213 | |
214 get ignoreClicks() { | |
215 return this.ignoreClicks_; | |
216 }, | |
217 | |
218 onClick_: function(self, e) { | |
219 if (self.ignoreClicks) { | |
220 self.ignoreClicks_ = false; | |
221 e.stopPropagation(); | |
222 } | |
223 }, | |
224 | |
225 /** | |
226 * Cancel any current touch/slide as if we saw a touch end | |
227 */ | |
228 cancelTouch: function() { | |
229 // Stop listening to any current touch | |
230 this.touchHandler_.cancelTouch(); | |
231 this.ignoreClicks_ = false; | |
232 }, | |
233 | |
234 /** | |
235 * Send a Slider event to a specific card element | |
236 * @param {string} eventType The type of event to send. | |
237 * @param {!Element} target The element to send the event to. | |
238 * @private | |
239 */ | |
240 sendEvent_: function(eventType, target, activeCardIndex) { | |
241 var event = new Slider.Event(eventType, activeCardIndex); | |
242 target.dispatchEvent(event); | |
243 }, | |
244 }; | |
245 | |
246 return Slider; | |
247 })(); | |
OLD | NEW |