/** * An updateable progress bar component. The progress bar supports two different modes: manual * and automatic. * * In manual mode, you are responsible for showing, updating (via {@link #updateProgress}) * and clearing the progress bar as needed from your own code. This method is most appropriate * when you want to show progress throughout an operation that has predictable points of interest * at which you can update the control. * * In automatic mode, you simply call {@link #wait} and let the progress bar run indefinitely, * only clearing it once the operation is complete. You can optionally have the progress bar * wait for a specific amount of time and then clear itself. Automatic mode is most appropriate * for timed operations or asynchronous operations in which you have no need for indicating * intermediate progress. * * @example * var p = Ext.create('Ext.ProgressBar', { * renderTo: Ext.getBody(), * width: 300 * }); * * // Wait for 5 seconds, then update the status el (progress bar will auto-reset) * p.wait({ * interval: 500, //bar will move fast! * duration: 50000, * increment: 15, * text: 'Updating...', * scope: this, * fn: function(){ * p.updateText('Done!'); * } * }); */Ext.define('Ext.ProgressBar', { extend: 'Ext.Component', xtype: 'progressbar', mixins: [ 'Ext.ProgressBase' ], requires: [ 'Ext.Template', 'Ext.CompositeElement', 'Ext.TaskManager', 'Ext.layout.component.ProgressBar' ], uses: ['Ext.fx.Anim'], /** * @cfg {String/HTMLElement/Ext.dom.Element} textEl * The element to render the progress text to (defaults to the progress bar's internal * text element) */ /** * @cfg {String} id * The progress bar element's id (defaults to an auto-generated id) */ /** * @cfg {String} [baseCls='x-progress'] * The base CSS class to apply to the progress bar's wrapper element. */ baseCls: Ext.baseCSSPrefix + 'progress', /** * @cfg {Boolean/Object} animate * True to animate the progress bar during transitions, or an animation configuration * (see the {@link #method-animate} method for details). */ animate: false, /** * @cfg {String} text * The text shown in the progress bar. */ text: '', /** * @private */ waitTimer: null, childEls: [ 'bar' ], defaultBindProperty: 'value', /* eslint-disable indent, max-len */ renderTpl: [ '<tpl if="internalText">', '<div class="{baseCls}-text {baseCls}-text-back" role="presentation">{text}</div>', '</tpl>', '<div id="{id}-bar" data-ref="bar" class="{baseCls}-bar {baseCls}-bar-{ui}" role="presentation" style="width:{percentage}%">', '<tpl if="internalText">', '<div class="{baseCls}-text" role="presentation">', '<div role="presentation">{text}</div>', '</div>', '</tpl>', '</div>' ], /* eslint-enable indent, max-len */ componentLayout: 'progressbar', ariaRole: 'progressbar', focusable: true, tabIndex: 0, autoEl: { 'aria-valuemin': '0', 'aria-valuenow': '0', 'aria-valuemax': '100' }, /** * @event update * Fires after each update interval * @param {Ext.ProgressBar} this * @param {Number} value The current progress value * @param {String} text The current progress text */ initRenderData: function() { var me = this, value = me.value || 0, data; data = me.callParent(); return Ext.apply(data, { internalText: !me.hasOwnProperty('textEl'), text: me.text || Math.round(value * 100) + '%', percentage: value * 100 }); }, onRender: function() { var me = this; me.callParent(arguments); // External text display if (me.textEl) { me.textEl = Ext.get(me.textEl); me.updateText(me.text); } // Inline text display else { // This produces a composite w/2 el's (which is why we cannot use childEls or // renderSelectors): me.textEl = me.el.select('.' + me.baseCls + '-text'); } }, afterRender: function() { var me = this; me.callParent(arguments); if (me.text) { me.ariaEl.dom.setAttribute('aria-valuetext', me.text); } }, updateValue: function(value) { this.updateProgress(value); }, /** * Updates the progress bar value, and optionally its text. * * If the text argument is not specified, then the {@link #textTpl} will be used to generate * the text. If there is no `textTpl`, any existing text value will be unchanged. To blank out * existing text, pass `""`. * * Note that even if the progress bar value exceeds 1, it will never automatically reset -- * you are responsible for determining when the progress is complete and * calling {@link #reset} to clear and/or hide the control. * @param {Number} [value=0] A floating point value between 0 and 1 (e.g., .5) * @param {String} [text=''] The string to display in the progress text element * @param {Boolean} [animate=false] Whether to animate the transition of the progress bar. * If this value is not specified, the default for the class is used * @return {Ext.ProgressBar} this */ updateProgress: function(value, text, animate) { var me = this, oldValue = me.value, textTpl = me.getTextTpl(); value = value || 0; // Ensure value is not undefined. me.value = value || (value = 0); // Empty string (falsy) must blank out the text as per docs. if (text != null) { me.autoText = false; me.updateText(text); } // Generate text using template and progress values. else if (textTpl) { me.autoText = false; me.updateText(textTpl.apply({ value: value, percent: value * 100 })); } else if (!me.text && me.autoText !== false) { me.autoText = true; me.updateText(Math.round(value * 100) + '%'); } // Text was set the previous time but not this time. We can't // deduce what it should be just from the value so need to // reset aria-valuetext because it is no longer valid. else if (me.text && me.ariaEl.dom) { me.ariaEl.dom.removeAttribute('aria-valuetext'); } if (me.rendered && !me.destroyed) { if (animate === true || (animate !== false && me.animate)) { me.bar.stopAnimation(); me.bar.animate(Ext.apply({ from: { width: (oldValue * 100) + '%' }, to: { width: (value * 100) + '%' } }, me.animate)); } else { me.bar.setStyle('width', (value * 100) + '%'); } me.ariaEl.dom.setAttribute('aria-valuenow', Math.round(value * 100)); } me.fireEvent('update', me, value, text); return me; }, /** * Updates the progress bar text. If specified, textEl will be updated, otherwise * the progress bar itself will display the updated text. * @param {String} [text=''] The string to display in the progress text element * @return {Ext.ProgressBar} this */ updateText: function(text) { var me = this; if (!me.autoText) { me.text = text; } if (me.rendered) { me.textEl.setHtml(text); if (!me.autoText) { me.ariaEl.dom.setAttribute('aria-valuetext', text); } else { me.ariaEl.dom.removeAttribute('aria-valuetext'); } } return me; }, applyText: function(text) { this.updateText(text); }, getText: function() { return this.text; }, /** * Initiates an auto-updating progress bar. A duration can be specified, in which case * the progress bar will automatically reset after a fixed amount of time and optionally call * a callback function if specified. If no duration is passed in, then the progress bar will run * indefinitely and must be manually cleared by calling {@link #reset}. * * Example usage: * * var p = new Ext.ProgressBar({ * renderTo: 'my-el' * }); * * // Wait for 5 seconds, then update the status el (progress bar will auto-reset) * var p = Ext.create('Ext.ProgressBar', { * renderTo: Ext.getBody(), * width: 300 * }); * * // Wait for 5 seconds, then update the status el (progress bar will auto-reset) * p.wait({ * interval: 500, // bar will move fast! * duration: 50000, * increment: 15, * text: 'Updating...', * scope: this, * fn: function() { * p.updateText('Done!'); * } * }); * * // Or update indefinitely until some async action completes, then reset manually * p.wait(); * myAction.on('complete', function() { * p.reset(); * p.updateText('Done!'); * }); * * @param {Object} config (optional) Configuration options * @param {Number} config.duration The length of time in milliseconds that the progress bar * should run before resetting itself (defaults to undefined, in which case it will run * indefinitely until reset is called) * @param {Number} config.interval The length of time in milliseconds between each progress * update (defaults to 1000 ms) * @param {Boolean} config.animate Whether to animate the transition of the progress bar. * If this value is not specified, the default for the class is used. * @param {Number} config.increment The number of progress update segments to display within * the progress bar (defaults to 10). If the bar reaches the end and is still updating, it will * automatically wrap back to the beginning. * @param {String} config.text Optional text to display in the progress bar element * (defaults to ''). * @param {Function} config.fn A callback function to execute after the progress bar finishes * auto-updating. The function will be called with no arguments. This function will be ignored * if duration is not specified since in that case the progress bar can only be stopped * programmatically, so any required function should be called by the same code after it resets * the progress bar. * @param {Object} config.scope The scope that is passed to the callback function (only applies * when duration and fn are both passed). * @return {Ext.ProgressBar} this */ wait: function(config) { var me = this, scope; if (!me.waitTimer) { scope = me; config = config || {}; if (config.text != null) { me.autoText = false; } me.updateText(config.text); me.waitTimer = Ext.TaskManager.start({ run: function(i) { var inc = config.increment || 10; i -= 1; me.updateProgress(((((i + inc) % inc) + 1) * (100 / inc)) * 0.01, null, config.animate); }, interval: config.interval || 1000, duration: config.duration, onStop: function() { if (config.fn) { config.fn.apply(config.scope || me); } me.reset(); }, scope: scope }); } return me; }, /** * Returns true if the progress bar is currently in a {@link #wait} operation * @return {Boolean} True if waiting, else false */ isWaiting: function() { return this.waitTimer !== null; }, /** * Resets the progress bar value to 0 and text to empty string. If hide = true, * the progress bar will also be hidden (using the {@link #hideMode} property internally). * @param {Boolean} [hide=false] True to hide the progress bar. * @return {Ext.ProgressBar} this */ reset: function(hide) { var me = this; me.updateProgress(0); me.clearTimer(); if (hide === true) { me.hide(); } if (me.rendered) { me.ariaEl.dom.removeAttribute('aria-valuetext'); } return me; }, /** * @private */ clearTimer: function() { var me = this; if (me.waitTimer) { me.waitTimer.onStop = null; // prevent recursion Ext.TaskManager.stop(me.waitTimer); me.waitTimer = null; } }, doDestroy: function() { var me = this, bar = me.bar, nodes, el, i, len; me.clearTimer(); if (me.rendered) { if (me.textEl.isComposite) { // Clean up Element references created by the layout nodes = me.textEl.slice(); for (i = 0, len = nodes.length; i < len; i++) { el = Ext.get(nodes[i]); el.destroy(); } } Ext.destroyMembers(me, 'textEl', 'progressBar'); if (bar && me.animate) { bar.stopAnimation(); } } me.callParent(); }});