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