UNPKG

8.4 kBJavaScriptView Raw
1import $ from './jquery';
2import { defaults, isNumber, isFinite, isNaN } from 'underscore';
3import skate from './internal/skate';
4import { recomputeStyle } from './internal/animation';
5import * as deprecate from './internal/deprecation';
6import globalize from './internal/globalize';
7
8const afterTransitionEvent = 'aui-progress-indicator-after-update';
9const beforeTransitionEvent = 'aui-progress-indicator-before-update';
10const transitionEnd = 'transitionend webkitTransitionEnd';
11
12
13function updateProgress($progressBar, $progressBarContainer, progressValue) {
14 recomputeStyle($progressBar);
15 $progressBar.css('width', progressValue * 100 + '%');
16 $progressBarContainer.attr('data-value', progressValue);
17}
18
19
20function updateProgressElement(element, value) {
21 if (typeof element === 'string') {
22 let el = document.getElementById(element);
23 if (el) {
24 element = el;
25 }
26 }
27 var $progressBarContainer = $(element).first();
28 var $progressBar = $progressBarContainer.children('.aui-progress-indicator-value');
29 var valueAttribute = $progressBarContainer.attr('data-value');
30 var isIndeterminate = !valueAttribute;
31 var currentProgress = parseFloat(valueAttribute) || 0;
32 var isProgressNotChanged = valueAttribute && currentProgress === value;
33
34 // AUI-4771 - check for mis-configured legacy progress bar HTML.
35 if (isProgressNotChanged) {
36 const currentDemonstratedValue = parseFloat($progressBar.get(0).style.width) || 0;
37 isProgressNotChanged = currentProgress === (currentDemonstratedValue * 100);
38 }
39
40 if (isProgressNotChanged) {
41 return;
42 }
43
44 //if the progress bar is indeterminate switch it.
45 if (isIndeterminate) {
46 $progressBar.css('width', 0);
47 }
48
49 transitionProgress($progressBar, $progressBarContainer, { currentProgress, value });
50
51 return $progressBarContainer;
52}
53
54function transitionProgress(progressBar, progressBarContainer, { currentProgress, value }) {
55 const $progressBar = $(progressBar);
56 const $progressBarContainer = $(progressBarContainer);
57
58 if (typeof value === 'number' && value <= 1 && value >= 0) {
59 $progressBarContainer.trigger(beforeTransitionEvent, [currentProgress, value]);
60
61 //trigger the event after transition end if supported, otherwise just trigger it
62 $progressBar.one(transitionEnd, function () {
63 $progressBarContainer.trigger(afterTransitionEvent, [currentProgress, value]);
64 });
65 updateProgress($progressBar, $progressBarContainer, value);
66 }
67}
68
69function setIndeterminateOnProgressElement(element) {
70 var $progressBarContainer = $(element).first();
71 var $progressBar = $progressBarContainer.children('.aui-progress-indicator-value');
72
73 $progressBarContainer.removeAttr('data-value');
74 recomputeStyle($progressBarContainer);
75 $progressBar.css('width', '');
76}
77
78const DEFAULTS = {
79 indeterminate: false,
80 max: 1,
81 val: 0,
82};
83
84function validNumeric(val) {
85 return isNumber(val) && isFinite(val) && !isNaN(val);
86}
87
88function parseNumeric(val, defaultVal = 1) {
89 const num = parseFloat(val);
90 return validNumeric(num) ? num : Number(defaultVal);
91}
92
93function parseDecimal(num, precision = 1) {
94 return Number(parseFloat(num).toFixed(precision));
95}
96
97function safeValue(val, max) {
98 return Math.max(0, Math.min(val, max));
99}
100
101function safeMax(max) {
102 return max > 0 ? max : DEFAULTS.max;
103}
104
105/**
106 * @param data the state
107 * @returns {{max: number, val: number, valAsFraction: number, valAsPercent: number}}
108 */
109function calc(data) {
110 const { val, max } = data;
111 const parsedMax = safeMax(max);
112 const parsedVal = safeValue(val, parsedMax);
113 const valAsFraction = parseDecimal(parsedVal / parsedMax, 6);
114 const valAsPercent = parseDecimal(valAsFraction * 100, 2);
115 return {max: parsedMax, val: parsedVal, valAsFraction, valAsPercent};
116}
117
118function refresh(el) {
119 const {val, valAsFraction, max} = calc(el._data);
120 const bar = el.querySelector('.aui-progress-indicator');
121 const oldVal = bar.getAttribute('data-value');
122
123 if (el.indeterminate) {
124 bar.removeAttribute('aria-valuenow');
125 setIndeterminateOnProgressElement(bar);
126 } else {
127 bar.setAttribute('aria-valuenow', val);
128 bar.setAttribute('aria-valuemax', max);
129 transitionProgress(bar.querySelector('.aui-progress-indicator-value'), bar, {
130 currentProgress: oldVal,
131 value: valAsFraction
132 });
133 }
134}
135
136function setValue(el, data) {
137 el._data.val = parseNumeric(data.newValue, data.oldValue || DEFAULTS.val);
138 refresh(el);
139}
140
141function setMax(el, data) {
142 el._data.max = parseNumeric(data.newValue, data.oldValue || DEFAULTS.max);
143 refresh(el);
144}
145
146const ProgressBarEl = skate('aui-progressbar', {
147 template(el) {
148 // Ensure max is set before value upon element creation and before rendering.
149 // Why is this happening in 'template' and not 'created'? Because it gets called before 'created'!
150 el._data.max = parseNumeric(el.getAttribute('max'), DEFAULTS.max);
151 el._data.val = parseNumeric(el.getAttribute('value'), DEFAULTS.val);
152 el._data.indeterminate = el.hasAttribute('indeterminate');
153 const {val, max, valAsFraction, valAsPercent} = calc(el._data);
154
155 const legacyValue = el._data.indeterminate ? '' : `data-value="${valAsFraction}"`;
156
157 el.innerHTML = `<div class="aui-progress-indicator"
158 ${legacyValue}
159 role="progressbar"
160 aria-valuemin="0"
161 aria-valuenow="${val}"
162 aria-valuemax="${max}"
163 tabindex="0"
164 >
165 <span class="aui-progress-indicator-value" style="width: ${valAsPercent}%"></span>
166 </div>`;
167 },
168
169 attached(el) {
170 refresh(el);
171 },
172
173 attributes: {
174 indeterminate: {
175 created: function(el) {
176 el.indeterminate = true;
177 },
178 removed: function(el) {
179 el.indeterminate = false;
180 }
181 },
182 value: {
183 value: DEFAULTS.val,
184 fallback: function (el, data) {
185 if (el._updating) {return false;}
186 setValue(el, data);
187 }
188 },
189 max: {
190 value: DEFAULTS.max,
191 fallback: function (el, data) {
192 if (el._updating) {return false;}
193 setMax(el, data);
194 }
195 },
196 },
197
198 prototype: {
199 get _data() {
200 return this.__data || (this._data = defaults({}, DEFAULTS));
201 },
202 set _data(d) {
203 return this.__data = d;
204 },
205 get indeterminate() {
206 return this._data.indeterminate;
207 },
208 set indeterminate(val) {
209 this._data.indeterminate = !!val;
210 refresh(this);
211 },
212 get value() {
213 const { val } = calc(this._data);
214 return val;
215 },
216 set value(num) {
217 if (!validNumeric(num)) {return false;}
218 const data = { newValue: parseDecimal(num, 6) };
219 this._updating = true;
220 // Reflect whatever value is set in the attributes.
221 this.setAttribute('value', data.newValue);
222 this._updating = false;
223 setValue(this, data);
224 },
225 get max() {
226 const { max } = calc(this._data);
227 return max;
228 },
229 set max(num) {
230 if (!validNumeric(num)) {return false;}
231 const data = { newValue: parseDecimal(num, 6) };
232 this._updating = true;
233 // Reflect whatever value is set in the attributes.
234 this.setAttribute('max', data.newValue);
235 this._updating = false;
236 setMax(this, data);
237 },
238 }
239});
240
241const progressBars = {
242 update: deprecate.fn(updateProgressElement, 'AJS.progressBars.update', {
243 sinceVersion: '7.7.0',
244 removeInVersion: '10.0.0',
245 extraInfo: 'Use the <aui-progressbar> web component instead'
246 }),
247 setIndeterminate: deprecate.fn(setIndeterminateOnProgressElement, 'AJS.progressBars.setIndeterminate', {
248 sinceVersion: '7.7.0',
249 removeInVersion: '10.0.0',
250 extraInfo: 'Use the <aui-progressbar> web component instead'
251 })
252};
253
254globalize('progressBars', progressBars);
255
256export default progressBars;
257export {
258 ProgressBarEl
259};