1 /* 2 * Copyright © 2009 Apple Inc. All rights reserved. 3 */ 4 5 const TKSlideshowControllerContainerCSSClass = 'tk-slideshow-controller-view'; 6 7 TKSlideshowController.inherits = TKController; 8 TKSlideshowController.synthetizes = ['slidingViewData', 'togglePlaybackButton', 'playing', 'currentSlideIndex', 'numberOfSlides', 'previousSlideButton', 'nextSlideButton']; 9 10 /** 11 * @class 12 * 13 * <p>A slideshow controller plays through a collection of slides, also allowing to control the playback state and position of the slideshow. Control 14 * buttons are easily wired and remote-based navigation is completely automated.</p> 15 * 16 * @extends TKController 17 * @since TuneKit 1.0 18 * 19 * @param {Object} data A hash of properties to use as this object is initialized. 20 */ 21 function TKSlideshowController (data) { 22 // public properties 23 this._slidingViewData = null; 24 this._togglePlaybackButton = null; 25 this._previousSlideButton = null; 26 this._nextSlideButton = null; 27 this._playing = null; 28 /** 29 * Indicates whether the slideshow loops through for constant playback. Defaults to <code>true</code>. 30 * @type bool 31 */ 32 this.loops = true; 33 /** 34 * Indicates the duration in milliseconds each slide remains on screen. 35 * @type int 36 */ 37 this.interval = 3000; 38 this.timer = null; 39 this.willNeedToResume = true; 40 // set up the sliding view 41 this.slidingView = new TKSlidingView(); 42 this.slidingView.ready = false; 43 this.slidingView.delegate = this; 44 // 45 this.callSuper(data); 46 }; 47 48 /* ==================== View Processing ==================== */ 49 50 TKSlideshowController.prototype.processView = function () { 51 this.callSuper(); 52 // restore properties that have not been set yet since construction 53 this.restoreProperty('slidingViewData'); 54 this.restoreProperty('previousSlideButton'); 55 this.restoreProperty('nextSlideButton'); 56 this.restoreProperty('togglePlaybackButton'); 57 this.restoreProperty('currentSlideIndex'); 58 this.restoreProperty('playing'); 59 // add the sliding view and slide control containers 60 this.container = this._view.appendChild(document.createElement('div')); 61 this.container.addClassName(TKSlideshowControllerContainerCSSClass); 62 this.container.appendChild(this.slidingView.element); 63 // ensure our first slide gets told about its being highlighted 64 this.slideDidChange(this.currentSlideIndex); 65 // start playing by default 66 if (this._playing === null) { 67 this.willNeedToResume = true; 68 } 69 }; 70 71 TKSlideshowController.prototype._viewDidAppear = function () { 72 if (this.willNeedToResume) { 73 this.playing = true; 74 } 75 }; 76 77 TKSlideshowController.prototype._viewWillDisappear = function () { 78 this.willNeedToResume = this.playing; 79 this.playing = false; 80 }; 81 82 /* ==================== Synthetized Properties ==================== */ 83 84 /** 85 * @name TKSlideshowController.prototype 86 * @property {bool} playing Indicates the current playback state of the slideshow, defaults to <code>true</code>. 87 */ 88 TKSlideshowController.prototype.setPlaying = function (playing) { 89 if (this._playing == playing) { 90 return; 91 } 92 // pause 93 if (!playing) { 94 window.clearTimeout(this.timer); 95 } 96 // resume 97 else { 98 this.rewindTimer(); 99 } 100 // remember ivar 101 this._playing = playing; 102 // inform the playback state changed 103 this.playbackStateDidChange(); 104 }; 105 106 /** 107 * @name TKSlideshowController.prototype 108 * @property {int} numberOfSlides Indicates how many slides total are in the slideshow. 109 */ 110 TKSlideshowController.prototype.getNumberOfSlides = function () { 111 return (!this.slidingView.ready) ? 0 : this.slidingView.numberOfElements; 112 }; 113 114 /** 115 * @name TKSlideshowController.prototype 116 * @property {int} currentSlideIndex Indicates the index of the current slide. 117 */ 118 TKSlideshowController.prototype.getCurrentSlideIndex = function () { 119 return this.slidingView.activeElementIndex; 120 }; 121 122 TKSlideshowController.prototype.setCurrentSlideIndex = function (index) { 123 if (index === null || !this.slidingView.ready) { 124 return; 125 } 126 // out of range 127 if (index < 0 || index >= this.numberOfSlides) { 128 return; 129 } 130 // update the slideshow index 131 this.slidingView.activeElementIndex = index; 132 // update timers if we're running 133 if (this.playing) { 134 // cancel the previous timer in case we still had one running 135 window.clearTimeout(this.timer); 136 // rewind it, and set playback to false in case we can't rewind any further 137 if (!this.rewindTimer()) { 138 this.playing = false; 139 } 140 } 141 }; 142 143 /** 144 * @name TKSlideshowController.prototype 145 * @property {String} togglePlaybackButton A CSS selector matching a button to be used as the button to control the playback state. 146 */ 147 TKSlideshowController.prototype.setTogglePlaybackButton = function (togglePlaybackButton) { 148 if (togglePlaybackButton === null) { 149 return; 150 } 151 // forget old button 152 if (this._togglePlaybackButton) { 153 this._togglePlaybackButton.removeEventListener('click', this, false); 154 } 155 // process new one 156 this._togglePlaybackButton = this.view.querySelector(togglePlaybackButton); 157 if (this._togglePlaybackButton !== null) { 158 if (IS_APPLE_TV) { 159 this._togglePlaybackButton.style.display = 'none'; 160 } 161 else { 162 this._togglePlaybackButton.addEventListener('click', this, false); 163 } 164 } 165 }; 166 167 /** 168 * @name TKSlideshowController.prototype 169 * @property {String} previousSlideButton A CSS selector matching a button to be used as the button to decrement the {@link #currentSlideIndex}. 170 */ 171 TKSlideshowController.prototype.setPreviousSlideButton = function (previousSlideButton) { 172 if (previousSlideButton === null) { 173 return; 174 } 175 // forget old button 176 if (this._previousSlideButton) { 177 this._previousSlideButton.removeEventListener('click', this, false); 178 } 179 // process new one 180 this._previousSlideButton = this.view.querySelector(previousSlideButton); 181 if (this._previousSlideButton !== null) { 182 if (IS_APPLE_TV) { 183 this._previousSlideButton.style.display = 'none'; 184 } 185 else { 186 this._previousSlideButton.addEventListener('click', this, false); 187 } 188 } 189 }; 190 191 /** 192 * @name TKSlideshowController.prototype 193 * @property {String} nextSlideButton A CSS selector matching a button to be used as the button to increment the {@link #currentSlideIndex}. 194 */ 195 TKSlideshowController.prototype.setNextSlideButton = function (nextSlideButton) { 196 if (nextSlideButton === null) { 197 return; 198 } 199 // forget old button 200 if (this._nextSlideButton) { 201 this._nextSlideButton.removeEventListener('click', this, false); 202 } 203 // process new one 204 this._nextSlideButton = this.view.querySelector(nextSlideButton); 205 if (this._nextSlideButton !== null) { 206 if (IS_APPLE_TV) { 207 this._nextSlideButton.style.display = 'none'; 208 } 209 else { 210 this._nextSlideButton.addEventListener('click', this, false); 211 } 212 } 213 }; 214 215 /** 216 * @name TKSlideshowController.prototype 217 * @property {TKSlidingViewData} slidingViewData The set of properties used to set up the contents of the page slider. 218 */ 219 TKSlideshowController.prototype.setSlidingViewData = function (data) { 220 if (data === null) { 221 return; 222 } 223 // set up the data source if we have .elements on the data object 224 if (!TKUtils.objectIsUndefined(data.elements)) { 225 this.slidingView.dataSource = new TKSlidingViewDataSourceHelper(data.elements); 226 delete data.element; 227 } 228 // see if we have some intersting bits to pass through 229 var archived_slide_index = this.getArchivedProperty('currentSlideIndex'); 230 if (archived_slide_index !== undefined) { 231 data.activeElementIndex = archived_slide_index; 232 } 233 // copy properties 234 TKUtils.copyPropertiesFromSourceToTarget(data, this.slidingView); 235 // init our view 236 this.slidingView.init(); 237 // 238 this.slidingView.ready = true; 239 }; 240 241 /* ==================== Previous / Next Slide ==================== */ 242 243 TKSlideshowController.prototype.attemptToGoToPreviousSlide = function () { 244 if (!this.loops && this.currentSlideIndex <= 0) { 245 TKSpatialNavigationManager.soundToPlay = SOUND_LIMIT; 246 } 247 else { 248 this.goToPreviousSlide(); 249 } 250 }; 251 252 TKSlideshowController.prototype.attemptToGoToNextSlide = function () { 253 if (!this.loops && this.currentSlideIndex >= this.numberOfSlides - 1) { 254 TKSpatialNavigationManager.soundToPlay = SOUND_LIMIT; 255 } 256 else { 257 this.goToNextSlide(); 258 } 259 }; 260 261 TKSlideshowController.prototype.goToPreviousSlide = function () { 262 this.currentSlideIndex = ((this.currentSlideIndex + this.numberOfSlides) - 1) % this.numberOfSlides; 263 }; 264 265 TKSlideshowController.prototype.goToNextSlide = function () { 266 this.currentSlideIndex = (this.currentSlideIndex + 1) % this.numberOfSlides; 267 }; 268 269 TKSlideshowController.prototype.rewindTimer = function () { 270 if (this.loops || this.currentSlideIndex < this.numberOfSlides - 1) { 271 var _this = this; 272 this.timer = window.setTimeout(function () { 273 _this.goToNextSlide(); 274 }, this.interval); 275 return true; 276 } 277 else { 278 return false; 279 } 280 }; 281 282 /* ==================== Keyboard Navigation ==================== */ 283 284 TKSlideshowController.prototype.wantsToHandleKey = function (key) { 285 return (key == KEYBOARD_LEFT || key == KEYBOARD_RIGHT || key == KEYBOARD_RETURN) ? true : this.callSuper(key); 286 }; 287 288 TKSlideshowController.prototype.keyWasPressed = function (key) { 289 // default action is move, so wire that up 290 TKSpatialNavigationManager.soundToPlay = SOUND_MOVED; 291 // left should go to the previous slide 292 if (key == KEYBOARD_LEFT) { 293 this.attemptToGoToPreviousSlide(); 294 } 295 // right should go to the next slide 296 else if (key == KEYBOARD_RIGHT) { 297 this.attemptToGoToNextSlide(); 298 } 299 // return key should toggle playback 300 else if (key == KEYBOARD_RETURN) { 301 TKSpatialNavigationManager.soundToPlay = SOUND_ACTIVATED; 302 this.playing = !this.playing; 303 } 304 // let the default behavior happen too 305 this.callSuper(key); 306 }; 307 308 TKSlideshowController.prototype.elementWasActivated = function (element) { 309 // toggle playback button pressed 310 if (element === this._togglePlaybackButton) { 311 this.playing = !this.playing; 312 } 313 // previous slide button pressed 314 else if (element === this._previousSlideButton) { 315 this.attemptToGoToPreviousSlide(); 316 } 317 // next slide button pressed 318 else if (element === this._nextSlideButton) { 319 this.attemptToGoToNextSlide(); 320 } 321 // fall back to default behavior 322 else { 323 this.callSuper(element); 324 } 325 }; 326 327 /* ==================== TKSlidingView Protocol ==================== */ 328 329 TKSlideshowController.prototype.slidingViewDidFocusElementAtIndex = function (view, index) { 330 this.slideDidChange(index); 331 }; 332 333 TKSlideshowController.prototype.slidingViewStyleForItemAtIndex = function (view, index) { 334 return this.styleForSlideAtIndex(index); 335 }; 336 337 /* ==================== Placeholder Methods ==================== */ 338 339 /** 340 * Triggered when the playback state has changed. 341 */ 342 TKSlideshowController.prototype.playbackStateDidChange = function () {}; 343 344 /** 345 * Triggered when the {@link #currentSlideIndex} property has changed. 346 * 347 * @param {int} index The index of the current slide. 348 */ 349 TKSlideshowController.prototype.slideDidChange = function (index) {}; 350 351 /** 352 * This method allows to provide custom style rules for a slide programatically any time the {@link #currentSlideIndex} property changes. The values in this 353 * array are expected to be individual two-value arrays, where the first index holds the CSS property name, and the second index its value. 354 * 355 * @param {Array} index The index of the slide for which we are trying to obtain custom styles. 356 */ 357 TKSlideshowController.prototype.styleForSlideAtIndex = function (index) { 358 return []; 359 }; 360 361 /* ==================== Archival ==================== */ 362 363 TKSlideshowController.prototype.archive = function () { 364 var archive = this.callSuper(); 365 archive.currentSlideIndex = this.currentSlideIndex; 366 return archive; 367 }; 368 369 TKClass(TKSlideshowController); 370