1 /*
  2  *  Copyright © 2009 Apple Inc. All rights reserved.
  3  */
  4 
  5 // ---------------------------------------------------
  6 // A crossfading slideshow view
  7 // ---------------------------------------------------
  8 
  9 // data source method names
 10 const TKSlideshowViewNumberOfElements = 'slideshowViewNumberOfElements';
 11 const TKSlideshowViewElementAtIndex = 'slideshowViewElementAtIndex';
 12 
 13 const TKSlideshowViewDidShowElementAtIndex = 'slideshowViewDidFocusElementAtIndex';
 14 const TKSlideshowViewDidHideElementAtIndex = 'slideshowViewDidHideElementAtIndex'; // TODO: XXX
 15 
 16 // css protocol
 17 const TKSlideshowViewCSSContainerClass = 'slideshow-view';
 18 const TKSlideshowViewCSSElementClass = 'slideshow-view-element';
 19 
 20 TKSlideshowView.synthetizes = ['dataSource',
 21                                'delegate',
 22                                'activeElementIndex', // the index of the currently displayed element
 23                                'duration', // time between automatic advance, in ms
 24                                'fadeDuration', // the time each crossfade will take, in ms
 25                                'numberOfElements'];
 26 
 27 function TKSlideshowView (element) {
 28   this.callSuper();
 29   this._activeElementIndex = null;
 30   this._duration = 3000;
 31   this._fadeDuration = 1000;
 32   this._playing = false;
 33   
 34   if (element) {
 35     this.container = element;
 36   } else {
 37     // create the element we'll use as a container
 38     this.container = document.createElement("div");
 39   }
 40   this.container.addClassName(TKSlideshowViewCSSContainerClass);
 41   
 42   this.currentElement = null;
 43 }
 44 
 45 TKSlideshowView.prototype.init = function () {
 46 
 47   if (!this.dataSource ||
 48       !TKUtils.objectHasMethod(this.dataSource, TKSlideshowViewNumberOfElements) ||
 49       !TKUtils.objectHasMethod(this.dataSource, TKSlideshowViewElementAtIndex)) {
 50     return;
 51   }
 52   
 53   this.showElement();
 54 };
 55 
 56 TKSlideshowView.prototype.setActiveElementIndex = function (newActiveElementIndex) {
 57   
 58   if (newActiveElementIndex >= 0 &&
 59       newActiveElementIndex < this.numberOfElements &&
 60       newActiveElementIndex != this._activeElementIndex) {
 61 
 62     // return early if this is the first time we've been set/initialised
 63     if (this._activeElementIndex === null) {
 64       this._activeElementIndex = newActiveElementIndex;
 65       return;
 66     }
 67 
 68     // call delegate to inform hide of current active element
 69     if (TKUtils.objectHasMethod(this.delegate, TKSlideshowViewDidHideElementAtIndex)) {
 70       this.delegate[TKSlideshowViewDidHideElementAtIndex](this, this._activeElementIndex);
 71     }
 72     
 73     this._activeElementIndex = newActiveElementIndex;
 74 
 75     this.showElement();
 76   }
 77 };
 78 
 79 TKSlideshowView.prototype.getNumberOfElements = function () {
 80   this._numberOfElements = this.dataSource[TKSlideshowViewNumberOfElements](this);
 81   return this._numberOfElements;
 82 };
 83 
 84 TKSlideshowView.prototype.advance = function () {
 85   if (this._playing) {
 86     if (this.activeElementIndex < this.numberOfElements - 1) {
 87       this.activeElementIndex++;
 88     } else {
 89       this.activeElementIndex = 0;
 90     }
 91     var _this = this;
 92     setTimeout(function() {
 93       _this.advance();
 94     }, this._duration);
 95   }
 96 };
 97 
 98 TKSlideshowView.prototype.play = function () {
 99   if (!this._playing) {
100     this._playing = true;
101     var _this = this;
102     setTimeout(function() {
103       _this.advance();
104     }, this._duration);
105   }
106 };
107 
108 TKSlideshowView.prototype.pause = function () {
109   this._playing = false;
110 };
111 
112 TKSlideshowView.prototype.reset = function () {
113   this._playing = false;
114   this.currentElement = null;
115 };
116 
117 TKSlideshowView.prototype.showElement = function () {
118 
119   var oldElement = this.currentElement;
120   
121   var el = this.dataSource[TKSlideshowViewElementAtIndex](this, this._activeElementIndex);
122   el.addClassName(TKSlideshowViewCSSElementClass);
123   el.style.webkitTransitionProperty = "opacity";
124   el.style.webkitTransitionDuration = this._fadeDuration + "ms";
125   el.style.opacity = 0;
126   
127   if (oldElement) {
128     this.container.insertBefore(el, oldElement);
129   } else {
130     this.container.appendChild(el);
131   }
132 
133   // call delegate to inform show of new active element
134   if (TKUtils.objectHasMethod(this.delegate, TKSlideshowViewDidShowElementAtIndex)) {
135     this.delegate[TKSlideshowViewDidShowElementAtIndex](this, this._activeElementIndex);
136   }
137 
138   this.currentElement = el;
139 
140   var _this = this;
141   setTimeout(function() {
142     el.style.opacity = 1;
143     if (oldElement) {
144       // make sure the old element becomes inactive to clicks (it is on top)
145       // FIXME: maybe the user wants it to be active - this should be done via a css class
146       oldElement.style.pointerEvents = "none";
147       oldElement.style.opacity = 0;
148       setTimeout(function() {
149         // An external controller might have removed the child by now. Check.
150         if (_this.container && _this.container.hasChild(oldElement)) {
151           _this.container.removeChild(oldElement);
152         }
153       }, (_this.fadeDuration + 10));
154     }
155   }, 0);
156 };
157 
158 
159 TKClass(TKSlideshowView);
160 
161 /* ====================== Datasource helper ====================== */
162 
163 function TKSlideshowViewDataSourceHelper(data) {
164   this.data = data;
165 };
166 
167 TKSlideshowViewDataSourceHelper.prototype.slideshowViewNumberOfElements = function(view) {
168   if (this.data) {
169     return this.data.length;
170   } else {
171     return 0;
172   }
173 };
174 
175 TKSlideshowViewDataSourceHelper.prototype.slideshowViewElementAtIndex = function(view, index) {
176   if (!this.data || index >= this.data.length) {
177     return null;
178   }
179   var source = this.data[index];
180   var element = TKUtils.buildElement(source);
181   return element;
182 };
183 
184 /* ====================== Declarative helper ====================== */
185 
186 TKSlideshowView.buildSlideshowView = function(element, data) {
187   if (TKUtils.objectIsUndefined(data) || !data || data.type != "TKSlideshowView") {
188     return null;
189   }
190 
191   var slideshowView = new TKSlideshowView(element);
192 
193   if (!TKUtils.objectIsUndefined(data.elements)) {
194     slideshowView.dataSource = new TKSlideshowViewDataSourceHelper(data.elements);
195   }
196 
197   TKSlideshowView.synthetizes.forEach(function(prop) {
198     if (prop != "dataSource" && prop != "delegate") {
199       if (!TKUtils.objectIsUndefined(data[prop])) {
200         slideshowView[prop] = data[prop];
201       }
202     }
203   });
204 
205   return slideshowView;
206 };
207 
208