ng-canvas-gauges
Version:
Angular 6+ component wrapper for the canvas-gauges lib written by @Mikhus
353 lines (345 loc) • 11.3 kB
JavaScript
import { DOM } from 'rx-dom-html';
import { ViewChild, Input, ElementRef, Component, NgZone, NgModule } from '@angular/core';
import { DomObserver, LinearGauge, RadialGauge } from 'canvas-gauges';
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
// String utils
/** @type {?} */
const toCamelCase = (/**
* @param {?} str
* @return {?}
*/
(str) => str.replace(/(\-\w)/g, (/**
* @param {?} matches
* @return {?}
*/
(matches) => matches[1].toUpperCase())));
/** @type {?} */
const toKebabCase = (/**
* @param {?} str
* @return {?}
*/
(str) => str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase());
/** @type {?} */
const attributeName2PropertyName = (/**
* @param {?} attrName
* @return {?}
*/
(attrName) => toCamelCase(attrName));
/**
* Base gauge component for the Gauges rendering
* T - Type of the Gauge to be rendered (Currently RadialGauge, LinearGauge from the original library)
* T2 - Type of config options used by the particular gauge (RadialGaugeOptions, LinearGaugeOptions)
* @abstract
* @template T, T2
*/
class BaseGauge {
/**
*
* @param {?} el - reference to the element of the whole component, used to scrape options declared on the component itself
* @param {?} zone - required to redraw gauge outside of Angular, due to animation lags caused by the ovewritten function of the ngZone
*/
constructor(el, zone) {
this.el = el;
this.zone = zone;
/**
* Flag indicating that OnViewInit life-cycle has completed
*/
this.isInited = false;
}
/**
* Returns gauges properties as an options object.
* Option properties consist of the attribute-based properties and those
* explicitly set.
* @return {?} <T2>
*/
get options() {
/** @type {?} */
const options = (/** @type {?} */ ({}));
options.renderTo = this.canvas.nativeElement;
// Map attribute-based options onto options.
// Requries converting kebab style attribute names to camelCase property names
for (const attr of this.el.nativeElement.attributes) {
/** @type {?} */
const prop = attributeName2PropertyName(attr.name);
options[prop] = DomObserver.parse(attr.value);
}
// merge preOptons with attribute-based properties
// tslint:disable-next-line:forin
for (const prop in this.preInitOptions) {
options[prop] = this.preInitOptions[prop];
}
// clear the preInitOptions as they have already been merged
// with the attribute-based properties
if (this.isInited) {
this.preInitOptions = null;
}
return options;
}
/**
* Assign gauge options at anytime in the lifecycle.
* @param {?} newOptions - assign the style and size properties
* @return {?}
*/
set options(newOptions) {
// cache newOptions as preInitOptions until gauge is ready
if (!this.isInited) {
this.preInitOptions = newOptions;
return;
}
this.update(newOptions);
}
/**
* Assign the value of the gauge visual indicator such as a needle or pointer
* @param {?} newValue the guage new value
* @return {?}
*/
set value(newValue) {
// case new gauge value as preInitValue until the gauge is ready
if (!this.isInited) {
this.preInitValue = newValue;
return;
}
this.zone.runOutsideAngular((/**
* @return {?}
*/
() => {
this.gauge.value = newValue;
}));
}
/**
* Update the gauge options. Do not use until after OnViewInit() before using.
*
* Special implementation note - options.properties are maintained as
* attribute name->value on this component's elementRef. Thus this method
* maps each newOptions property onto the property's corresponding attribute.
* The attribute update triggers a DOM mutation event which "this" listens for.
* See #listenForDOMEvents()
*
* @param {?} newOptions - the options to update the gauge
* @return {?}
*/
update(newOptions) {
// map all options onto this element's attributes
// Then attribute changes will be detected and pushed to the gauge.update()
if (!newOptions) {
return;
}
// tslint:disable-next-line:forin
for (const prop in newOptions) {
/** @type {?} */
const val = newOptions[prop].toString();
if (prop === 'value') {
// short circuit the value property update by calling
// the gauge.value api directly for efficient animated update
this.value = DomObserver.parse(val);
}
else {
/** @type {?} */
const attrName = toKebabCase(prop);
this.el.nativeElement.setAttribute(attrName, val);
}
}
}
/**
* Perform gauge initialization.
* Subclasses that override this method must this super version
* for proper operation.
* @return {?}
*/
ngAfterViewInit() {
// initial update of gauge properties
this.initGauge();
this.listenForDOMEvents();
this.isInited = true;
}
/**
* Listen for attribute-change events that are created when updating
* the options of this gauge.
* @protected
* @return {?}
*/
listenForDOMEvents() {
// Listen to gauge element for attribute changes
// Convert all changed attribtues into a GenericOptions or subclass
// Update the gauge with the new options.
this.domListener =
DOM.fromMutationObserver(this.el.nativeElement, { attributes: true }).
subscribe((/**
* @param {?} changes
* @return {?}
*/
changes => {
/** @type {?} */
const newOptions = (/** @type {?} */ ({}));
changes.forEach((/**
* @param {?} change
* @return {?}
*/
change => {
if ('attributes' === change.type) {
// console.log('DOM, change', change.attributeName);
newOptions[attributeName2PropertyName(change.attributeName)] =
DomObserver.parse(this.el.nativeElement.getAttribute(change.attributeName));
}
}));
this.basicUpdate(newOptions);
}));
}
/**
* Discontinue listening for attribute change events.
* @protected
* @return {?}
*/
stopListeningForDOMEvents() {
if (this.domListener) {
this.domListener.disconnect();
this.domListener = null;
}
}
/**
* Initalize the gauge with all options defined by attributes and
* parent component options.
* @protected
* @return {?}
*/
initGauge() {
/** @type {?} */
const options = this.options;
if (this.preInitValue) {
options.value = this.preInitValue;
}
// init options.renderTo if needed
if (!options.hasOwnProperty('renderTo') || !options.renderTo) {
options.renderTo = this.canvas.nativeElement;
}
this.basicUpdate(options);
}
/**
* Performs the gauge update using the current options
* @protected
* @param {?} options The options for the guage
* @return {?}
*/
basicUpdate(options) {
// treat the value property special and update it through the
// value getter.
if (typeof options.value === 'number') {
// use gauge api directly for most efficient update method
this.value = options.value;
// filter value property from options to avoid redundant
// processing by gauge
delete options.value;
}
// do nothing if no option properties to update
if (Object.keys(options).length) {
this.gauge.update(options);
}
}
}
BaseGauge.propDecorators = {
canvas: [{ type: ViewChild, args: ['gauge',] }],
options: [{ type: Input }],
value: [{ type: Input }]
};
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* Implements Linear Gauge from the original library
*/
// tslint:disable-next-line:component-class-suffix
class LinearGauge$1 extends BaseGauge {
/**
* @param {?} el
* @param {?} zone
*/
constructor(el, zone) {
super(el, zone);
}
/**
* @return {?}
*/
ngOnInit() {
this.gauge = new LinearGauge(this.options).draw();
}
}
LinearGauge$1.decorators = [
{ type: Component, args: [{
// tslint:disable-next-line:component-selector
selector: 'linear-gauge',
template: '<canvas #gauge></canvas>'
}] }
];
/** @nocollapse */
LinearGauge$1.ctorParameters = () => [
{ type: ElementRef },
{ type: NgZone }
];
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* Implements Radial Gauge from the original library
*/
// tslint:disable-next-line:component-class-suffix
class RadialGauge$1 extends BaseGauge {
/**
* @param {?} el
* @param {?} zone
*/
constructor(el, zone) {
super(el, zone);
}
/**
* @return {?}
*/
ngOnInit() {
this.gauge = new RadialGauge(this.options).draw();
}
}
RadialGauge$1.decorators = [
{ type: Component, args: [{
// tslint:disable-next-line:component-selector
selector: 'radial-gauge',
template: '<canvas #gauge></canvas>'
}] }
];
/** @nocollapse */
RadialGauge$1.ctorParameters = () => [
{ type: ElementRef },
{ type: NgZone }
];
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class GaugesModule {
}
GaugesModule.decorators = [
{ type: NgModule, args: [{
declarations: [
LinearGauge$1,
RadialGauge$1
],
imports: [],
exports: [
LinearGauge$1,
RadialGauge$1
]
},] }
];
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
export { GaugesModule, LinearGauge$1 as LinearGauge, RadialGauge$1 as RadialGauge, BaseGauge as ɵa };
//# sourceMappingURL=ng-canvas-gauges.js.map