Option name | Type | Description |
---|---|---|
el | Element | |
params | Object |
ProgressIndicator constructor.
var ProgressIndicator = function(el, params) {
if (!el) {
return;
}
this._setParams(this.defaults, true);
this._cacheElements(el);
this._setParams(params || {});
this._bindEventListenerCallbacks();
this._addEventListeners();
};
ProgressIndicator.prototype = {
Include common functionality.
_setParams: Base.setParams,
_toggleClass: Base.toggleClass,
_hasClass: Base.hasClass,
_round: Base.round,
remove: Base.remove,
Whitelisted parameters which can be set on construction.
_whitelistedParams: ['precision'],
Default values for internal properties we will be setting.
These are set on each construction so we don't leak properties
into the prototype chain.
defaults: {
el: null,
progressEl: null,
statusEl: null,
noteEls: null,
meterEl: null,
fillEl: null,
meterHeight: 0,
meterWidth: 0,
notes: null,
isDeterminate: false,
value: null,
precision: 0,
lastDOMUpdateTime: 0,
_onResizeBound: null
},
Option name | Type | Description |
---|---|---|
val | Number |
Set the value of the indicator.
set: function(val) {
if (val === this.value) {
return;
}
if (val > 1) {
val = 1;
}
this.value = val;
if (this.isDeterminate && this.progressEl) {
this.progressEl.setAttribute('value', Math.round(val * 100) / 100);
}
this._updateDOM();
},
Option name | Type | Description |
---|---|---|
el | Element |
Store a reference to all the needed elements.
_cacheElements: function(el) {
this.el = el;
this.progressEl = this.el.querySelector('progress');
this.statusEl = this.el.querySelector('.spark-progress__value-status, [role="status"]');
this.noteEl = this.el.querySelector('.spark-progress__states');
this.meterEl = this.el.querySelector('.spark-progress__meter');
this.isDeterminate = this.progressEl.getAttribute('value') !== null;
this.size = this._determineSize();
// If this is a determinate value, replace the meter with the SVG.
if (this.isDeterminate) {
var svg = this._buildSVG();
svg.setAttribute('class', this.meterEl.className);
this.meterEl.parentNode.replaceChild(svg, this.meterEl);
this.meterEl = svg;
this.fillEl = this.meterEl.querySelector('.spark-progress__fill');
}
if (this.noteEl) {
this._parseNotes(this.noteEl);
}
if (this.progressEl) {
this.value = this.progressEl.value;
}
this._cacheSize();
this._updateDOM();
},
Cache the size of the meter.
_cacheSize: function() {
this.meterHeight = this.meterEl.clientHeight;
this.meterWidth = this.meterEl.clientWidth;
},
Determine the size of the indicator.
_determineSize: function() {
if (this.el.className.indexOf('progress--sm') !== -1) {
return 'small';
} else if (this.el.className.indexOf('progress--xs') !== -1) {
return 'extraSmall';
}
return 'large';
},
Build the proper SVG element for this size indicator.
_buildSVG: function() {
var size = sizes[this.size];
var template = '<svg viewBox="0 0 ' + size.diameter + ' ' + size.diameter + '" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="' + size.track + '" stroke-width="' + size.stroke + '" class="spark-progress__track"/><path d="' + (size.fill || size.track) + '" stroke-width="' + size.stroke + '" class="spark-progress__fill"/><path d="' + size.border + '" class="spark-progress__border"/></g></g></svg>';
var div = document.createElement('div');
div.innerHTML = template;
return div.children[0];
},
Option name | Type | Description |
---|---|---|
el | Element | |
return | Array |
Take an unordered list of notes and determine the ranges for
when to show a given note.
_parseNotes: function(el) {
this.notes = this.notes || [];
var len = el.children.length;
var i = len - 1;
for (; i >= 0; i--) {
this.notes.push({
min: parseInt(el.children[i].getAttribute('data-value'), 10),
max: el.children[i + 1] ? parseInt(el.children[i + 1].getAttribute('data-value'), 10) - 1 : 100,
el: el.children[i]
});
}
},
Update the text visible based on the value. Also adjust the SVG.
_updateDOM: function() {
if (!this.isDeterminate) {
return;
}
var updateTime = Date.now();
var val = this._round(this.value * 100, this.precision);
// Don't animate if we're animating back to 0 or it's been less than 150ms since our last update.
var noAnimation = val === 0 || this.lastDOMUpdateTime + 150 > updateTime;
this._toggleClass(this.fillEl, 'no-animation', noAnimation);
this.statusEl.innerHTML = val + '%';
var dashArray = (sizes[this.size].diameter - sizes[this.size].stroke) * Math.PI;
var dashOffset = dashArray - (dashArray * (val / 100));
this.fillEl.setAttribute('style', 'stroke-dasharray: ' + dashArray + '; stroke-dashoffset: ' + dashOffset);
this.lastDOMUpdateTime = updateTime;
if (!this.notes) {
return;
}
var i = 0;
var len = this.notes.length;
for (; i < len; i++) {
this._toggleClass(this.notes[i].el, 'active', (this.notes[i].min <= val && this.notes[i].max >= val));
}
},
Create bound versions of event listener callbacks and store them.
Otherwise we can't unbind from these events later because the
function signatures won't match.
_bindEventListenerCallbacks: function() {
this._onResizeBound = this._onResize.bind(this);
},
Add event listeners for DOM events.
_addEventListeners: function() {
window.addEventListener('resize', this._onResizeBound);
},
Remove event listeners for DOM events..
_removeEventListeners: function() {
window.removeEventListener('resize', this._onResizeBound);
},
Option name | Type | Description |
---|---|---|
e | Object |
When the window resizes, cache the dimensions.
_onResize: function() {
this._cacheSize();
}
};
Base.exportjQuery(ProgressIndicator, 'ProgressIndicator');
return ProgressIndicator;
}));