UNPKG

ng-canvas-gauges

Version:

Angular 6+ component wrapper for the canvas-gauges lib written by @Mikhus

353 lines (345 loc) 11.3 kB
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