1 // ==========================================================================
  2 // Project:   SproutCore - JavaScript Application Framework
  3 // Copyright: ©2006-2011 Strobe Inc. and contributors.
  4 //            Portions ©2008-2011 Apple Inc. All rights reserved.
  5 // License:   Licensed under MIT license (see license.js)
  6 // ==========================================================================
  7 
  8 SC.BaseTheme.PROGRESS_OFFSET = 0.5;
  9 SC.BaseTheme.PROGRESS_OFFSET_RANGE = 42;
 10 
 11 /**
 12  Renders and updates DOM representations of progress bars.
 13 
 14  Parameters
 15  --------------------------
 16  Expects these properties on the data source:
 17 
 18  - `isIndeterminate`
 19  - `isRunning`
 20  - `isVisibleInWindow`
 21  - `value`
 22 
 23  Theme Constants
 24  -------------------------------------
 25  Ace's `progressRenderDelegate`'s rendering process is not affected by
 26  any theme constants.
 27  */
 28 SC.BaseTheme.progressRenderDelegate = SC.RenderDelegate.create({
 29   className:'progress',
 30 
 31   render:function (dataSource, context) {
 32     this.addSizeClassName(dataSource, context);
 33 
 34     var isIndeterminate = dataSource.get('isIndeterminate'),
 35       theme = dataSource.get('theme'),
 36       valueMax = dataSource.get('maximum'),
 37       valueMin = dataSource.get('minimum'),
 38       valueNow = dataSource.get('ariaValue');
 39 
 40     var value;
 41     if (isIndeterminate) {
 42       value = 1;
 43     } else {
 44       value = dataSource.get('value');
 45     }
 46 
 47     // make accessible
 48     context.setAttr('aria-valuemax', valueMax);
 49     context.setAttr('aria-valuemin', valueMin);
 50     context.setAttr('aria-valuenow', valueNow);
 51     context.setAttr('aria-valuetext', valueNow);
 52 
 53     context.setClass({
 54       indeterminate:isIndeterminate,
 55       running:dataSource.get('isRunning') && isIndeterminate,
 56       'sc-empty':(value <= 0),
 57       'sc-complete':(value >= 1 && !isIndeterminate)
 58     });
 59 
 60     context = context.begin('div').addClass('track');
 61     this.includeSlices(dataSource, context, SC.THREE_SLICE);
 62     context = context.end();
 63 
 64     context = context.begin('div').addClass('content');
 65     context.setStyle('width', (value * 100) + "%");
 66     this.includeSlices(dataSource, context, SC.THREE_SLICE);
 67     context = context.end();
 68   },
 69 
 70   update:function (dataSource, jQuery) {
 71     this.updateSizeClassName(dataSource, jQuery);
 72 
 73     var theme = dataSource.get('theme'),
 74       value,
 75       valueMax = dataSource.get('maximum'),
 76       valueMin = dataSource.get('minimum'),
 77       valueNow = dataSource.get('ariaValue'),
 78       isIndeterminate = dataSource.get('isIndeterminate'),
 79       isRunning = dataSource.get('isRunning'),
 80       isVisibleInWindow = dataSource.get('isVisibleInWindow');
 81 
 82     // make accessible
 83     jQuery.attr('aria-valuemax', valueMax);
 84     jQuery.attr('aria-valuemin', valueMin);
 85     jQuery.attr('aria-valuenow', valueNow);
 86     jQuery.attr('aria-valuetext', valueNow);
 87 
 88     if (isIndeterminate) {
 89       value = 1;
 90     } else {
 91       value = dataSource.get('value');
 92     }
 93 
 94     jQuery.setClass({
 95       indeterminate:isIndeterminate,
 96       running:isRunning && isIndeterminate,
 97       'sc-empty':(value <= 0),
 98       'sc-complete':(value >= 1 && !isIndeterminate)
 99     });
100 
101     jQuery.find('.content').css('width', (value * 100) + "%");
102 
103     // fallback for browsers that don't support css transitions
104     if(!SC.platform.supportsCSSTransitions) {
105       if (!this._queue[jQuery[0].id]) {
106         this._queue[jQuery[0].id] = {
107           offset:0,
108           element:SC.$(jQuery).find('.content .middle'),
109           shouldAnimate:false
110         };
111       }
112 
113       if (isIndeterminate && isRunning && isVisibleInWindow) {
114         // save offset in the queue and request animation
115         this._queue[jQuery[0].id].shouldAnimate = true;
116         this.animate(dataSource);
117       } else if (!isIndeterminate) {
118         // Clear out our custom background-position when isIndeterminate toggles.
119         this._queue[jQuery[0].id].element.css('background-position', '');
120       } else {
121         this._queue[jQuery[0].id].shouldAnimate = false;
122       }
123     }
124   },
125 
126   /** @private Queue of objects to animate: { id, offset, element } */
127   _queue: {},
128 
129   /** @private Catch double calls to _animate */
130   _animating: false,
131 
132   /**
133     Animates the indeterminate progress view's middle background using
134     JavaScript and requestAnimationFrame().
135   */
136   animate: function (dataSource) {
137     var self = this;
138 
139     // avoid invoking the animation code multiple times if more than
140     // one progress bar needs animating *and* one has already started the loop
141     if (this._animating) {
142       return;
143     }
144 
145     function _animate() {
146       var offset,
147         lastOffset,
148         roundedOffset,
149         viewsToAnimate = self._queue,
150         animations = 0,
151         params;
152 
153       var id;
154       for (id in viewsToAnimate) {
155         if (viewsToAnimate.hasOwnProperty(id)) {
156           params=viewsToAnimate[id];
157           if (params.shouldAnimate) {
158             self._animating = true;
159             animations++;
160             lastOffset = params.offset || 0;
161             offset = (lastOffset + SC.BaseTheme.PROGRESS_OFFSET) % SC.BaseTheme.PROGRESS_OFFSET_RANGE;
162 
163             // Only update the style when the offset changes (this avoids making
164             // the browser recalculate style in each frame).
165             roundedOffset = Math.round(offset);
166             if (roundedOffset > Math.round(lastOffset)) {
167               params.element.css('background-position', roundedOffset + "px 0px");
168             }
169 
170             params.offset = offset;
171           }
172         }
173       }
174 
175       if (animations === 0) {
176         self._animating = false;
177       } else {
178         window.requestAnimationFrame(_animate);
179       }
180     }
181 
182     // Start the animation.
183     _animate();
184   }
185 });
186