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