/** @module deliteful/ProgressBar */
define([
"dcl/dcl",
"requirejs-dplugins/jquery!attributes/classes",
"ecma402/IntlShim",
"delite/register",
"delite/Widget",
"delite/handlebars!./ProgressBar/ProgressBar.html",
"delite/theme!./ProgressBar/themes/{{theme}}/ProgressBar.css"
], function (dcl, $, Intl, register, Widget, template) {
/**
* A widget that displays the completion progress of a task.
*
* The progress is either indeterminate, indicating that progress is being made but that it is not clear how
* much more work remains to be done before the task is complete, or the progress is a number in the range
* zero to a maximum, giving the fraction of work that has so far been completed.
*
* There are two properties that determine the current task completion represented by the element. The value
* property specifies how much of the task has been completed, and the max property specifies how much work
* the task requires in total. The units are arbitrary and not specified.
*
* When the progress bar is determinate, a default message displays the percentage of progression.
* The property fractionDigits allows to specify the number of fraction digits to display. You can set a custom
* message using the message property, or override the method formatMessage to generate a dynamic custom message.
*
* When the progress bar is indeterminate, use the message property to display a message.
*
* @class module:deliteful/ProgressBar
* @augments module:delite/Widget
*/
return register("d-progress-bar", [HTMLElement, Widget], /** @lends module:deliteful/ProgressBar# */{
/**
* A number indicating the amount of completion of a task.
* The value property must be a valid floating-point number or NaN. Set the value to NaN to make the
* progress bar indeterminate. Set a value comprised between 0 and the max property to make
* the progress bar determinate. A value greater than max is converted to the max value. An invalid or
* negative value is converted to 0.
* @member {number}
* @default NaN
*/
value: NaN,
/**
* A number which express the task as completed.
* The max property must be a valid positive floating-point number, otherwise it is converted to 1.0.
* @member {number}
* @default 1.0
*/
max: 1.0,
/**
* Indicates the relative position of the current value relative to the maximum value (max).
* If the progress bar is an indeterminate progress bar, then the position is −1. Otherwise, it is the result
* of dividing the current value by the maximum value.
* @member {number}
* @default -1
* @readonly
*/
position: -1,
/**
* Allow to specify/override the message on the progress bar whether it's determinate or indeterminate.
* The default behavior of the ProgressBar is to displays the percentage of completion when the state
* is determinate, and to display no message when state is indeterminate. You can override this with the
* message property. Set an empty string to restore the default behavior.
* @member {string}
* @default ""
*/
message: "",
/**
* Allow to set an additional message.
* Depending on the theme it may not be displayed. For themes that display
* both messages, typically message is on one side and the additional message is on the other side. By default
* the additional message is in the form value/max. Ex: [65%........379/583] or [Loading......379/583]. You may
* customize it by overriding the method formatExtMsg.
* @member {boolean}
* @default false
*/
displayExtMsg: false,
/**
* Number of places to show on the default message displayed when the progress bar is determinate.
* @member {number}
* @default 0
*/
fractionDigits: 0,
/**
* The name of the CSS class of this widget.
* @member {string}
* @default "d-progress-bar"
*/
baseClass: "d-progress-bar",
template: template,
postRender: function () {
// TODO: move this code to the template
this.setAttribute("aria-valuemin", 0);
},
computeProperties: function (props) {
if ("max" in props) {
var newMax = this._convert2Float(this.max, 1.0);
if (newMax <= 0) {
newMax = 1.0;
}
if (newMax !== this.max) {
this.max = newMax;
}
}
if ("value" in props && !isNaN(this.value)) {
var newValue = this._convert2Float(this.value, 0);
newValue = Math.max(0, Math.min(this.max, newValue));
if (newValue !== this.value) {
this.value = newValue;
}
}
this.position = isNaN(this.value) ? -1 : this.value / this.max;
},
refreshRendering: function (props) {
//update widget to reflect value/max changes
if ("max" in props) {
// TODO: move to template
this.setAttribute("aria-valuemax", this.max);
}
if ("value" in props || "max" in props) {
if (this.position === -1) { //indeterminate state
this.indicatorNode.style.removeProperty("width");
this.removeAttribute("aria-valuenow");
} else {
this.indicatorNode.style.width = (this.position * 100) + "%";
this.msgInvertNode.style.width =
window.getComputedStyle(this.msgNode).getPropertyValue("width");
this.setAttribute("aria-valuenow", this.value);
}
}
//update widget message
this.msgNode.innerHTML = this.msgInvertNode.innerHTML =
this.formatMessage(this.position, this.value, this.max);
var hasExtMsg = this.displayExtMsg && this.position !== -1;
$(this.msgNode).toggleClass(this.baseClass + "-msg-ext", hasExtMsg);
if (hasExtMsg) {
//set content value to be used by pseudo element d-progress-bar-msg-ext::after
this.msgNode.setAttribute("msg-ext", this.formatExtMsg(this.position, this.value, this.max));
} else {
this.msgNode.removeAttribute("msg-ext");
}
//aria text only on indeterminate state with custom message
if (this.message && this.position === -1) {
this.setAttribute("aria-valuetext", this.message);
} else {
this.removeAttribute("aria-valuetext");
}
$(this).toggleClass(this.baseClass + "-indeterminate", (this.position === -1));
},
/**
* Formats and returns a message to display inside/beside the progress bar (depends on theme settings).
* If a custom message is specified with the message property, it is returned as-is. Otherwise if the
* progress bar is determined (value is not NaN), it returns the percentage of progression formatted in
* respect with fractionDigits. This method is called when the value and/or the max property changes. May be
* overridden to customize the message.
*
* @param {number} position - Position of the current value relative to the maximum value (from 0.0 to 1.0).
* @param {number} value - The amount of completion of the task.
* @param {number} max - The value that express the task is completed (maximum value).
* @returns {string} The message to display.
*/
formatMessage: function (position, value, /*jshint unused: vars */max) {
if (!this._numberFormat || this._prevLang !== this.lang ||
this._numberFormat.resolvedOptions().minimumFractionDigits !== this.fractionDigits) {
var options = {
style: "percent",
minimumFractionDigits: this.fractionDigits,
maximumFractionDigits: this.fractionDigits
};
this._numberFormat = new Intl.NumberFormat(this.lang || undefined, options);
this._prevLang = this.lang;
}
return this.message ? this.message : (isNaN(value) ? "" : this._numberFormat.format(position));
},
/**
* Formats and returns the extra message to display when the property displayExtMsg is enabled.
* Returns a string formatted with "value/max". May be overridden to customize the extra message.
*
* @param {number} position Position of the current value relative to the maximum value (from 0.0 to 1.0).
* @param {number} value The amount of completion of the task.
* @param {number} max The value that express the task is completed (maximum value).
* @returns {string} The extra message to display.
*/
formatExtMsg: function (position, value, max) {
return value + "/" + max;
},
/*
* Converts a value to a valid floating-point numbers.
* The Infinity and Not-a-Number (NaN) values are not valid floating-point numbers.
*
* @param value - The value to convert
* @param fallbackValue - The value to assign if the conversion fails.
* @returns {Number}
* @private
*/
_convert2Float: function (value, fallbackValue) {
var v = parseFloat(value);
if (isNaN(v) || v === Infinity) {
v = fallbackValue;
}
return v;
}
});
});