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