UNPKG

angular2-promise-buttons

Version:
262 lines (255 loc) 9.87 kB
import * as i0 from '@angular/core'; import { InjectionToken, Directive, Inject, Input, HostListener, NgModule } from '@angular/core'; import { Observable, Subscription } from 'rxjs'; const DEFAULT_CFG = { spinnerTpl: '<span class="btn-spinner"></span>', disableBtn: true, btnLoadingClass: 'is-loading', handleCurrentBtnOnly: false, minDuration: null, }; const userCfg = new InjectionToken('cfg'); class PromiseBtnDirective { constructor(el, cfg) { // provide configuration this.cfg = Object.assign({}, DEFAULT_CFG, cfg); // save element this.btnEl = el.nativeElement; } // this is added to fix the overriding of the disabled state by the loading indicator button. // https://github.com/johannesjo/angular2-promise-buttons/issues/34 set isDisabledFromTheOutsideSetter(v) { this.isDisabledFromTheOutside = v; if (v) { // disabled means always disabled this.btnEl.setAttribute('disabled', 'disabled'); } else if (this.isPromiseDone || this.isPromiseDone === undefined) { this.btnEl.removeAttribute('disabled'); } // else the button is loading, so do not change the disabled loading state. } set promiseBtn(passedValue) { const isObservable = passedValue instanceof Observable; const isSubscription = passedValue instanceof Subscription; const isBoolean = typeof passedValue === 'boolean'; const isPromise = passedValue instanceof Promise || (passedValue !== null && typeof passedValue === 'object' && typeof passedValue.then === 'function' && typeof passedValue.catch === 'function'); if (isObservable) { throw new TypeError('promiseBtn must be an instance of Subscription, instance of Observable given'); } else if (isSubscription) { const sub = passedValue; if (!sub.closed) { this.promise = new Promise((resolve) => { sub.add(resolve); }); } } else if (isPromise) { this.promise = passedValue; } else if (isBoolean) { this.promise = this.createPromiseFromBoolean(passedValue); } this.checkAndInitPromiseHandler(this.btnEl); } ngAfterContentInit() { this.prepareBtnEl(this.btnEl); // trigger changes once to handle initial promises this.checkAndInitPromiseHandler(this.btnEl); } ngOnDestroy() { // cleanup if (this.minDurationTimeout) { clearTimeout(this.minDurationTimeout); } } createPromiseFromBoolean(val) { if (val) { return new Promise((resolve) => { this._fakePromiseResolve = resolve; }); } else { if (this._fakePromiseResolve) { this._fakePromiseResolve(); } return this.promise; } } /** * Initializes all html and event handlers */ prepareBtnEl(btnEl) { // handle promises passed via promiseBtn attribute this.appendSpinnerTpl(btnEl); } /** * Checks if all required parameters are there and inits the promise handler */ checkAndInitPromiseHandler(btnEl) { // check if element and promise is set if (btnEl && this.promise) { this.initPromiseHandler(btnEl); } } /** * Helper FN to add class */ addLoadingClass(el) { if (typeof this.cfg.btnLoadingClass === 'string') { el.classList.add(this.cfg.btnLoadingClass); } } /** * Helper FN to remove classes */ removeLoadingClass(el) { if (typeof this.cfg.btnLoadingClass === 'string') { el.classList.remove(this.cfg.btnLoadingClass); } } /** * Handles everything to be triggered when the button is set * to loading state. */ initLoadingState(btnEl) { this.addLoadingClass(btnEl); this.disableBtn(btnEl); } /** * Handles everything to be triggered when loading is finished */ cancelLoadingStateIfPromiseAndMinDurationDone(btnEl) { if ((!this.cfg.minDuration || this.isMinDurationTimeoutDone) && this.isPromiseDone) { this.removeLoadingClass(btnEl); this.enableBtn(btnEl); } } disableBtn(btnEl) { if (this.cfg.disableBtn) { btnEl.setAttribute('disabled', 'disabled'); } } enableBtn(btnEl) { if (this.cfg.disableBtn) { if (this.isDisabledFromTheOutside) { btnEl.setAttribute('disabled', 'disabled'); } else { btnEl.removeAttribute('disabled'); } } } /** * Initializes a watcher for the promise. Also takes * this.cfg.minDuration into account if given. */ initPromiseHandler(btnEl) { const promise = this.promise; // watch promise to resolve or fail this.isMinDurationTimeoutDone = false; this.isPromiseDone = false; // create timeout if option is set if (this.cfg.minDuration) { this.minDurationTimeout = window.setTimeout(() => { this.isMinDurationTimeoutDone = true; this.cancelLoadingStateIfPromiseAndMinDurationDone(btnEl); }, this.cfg.minDuration); } const resolveLoadingState = () => { this.isPromiseDone = true; this.cancelLoadingStateIfPromiseAndMinDurationDone(btnEl); }; if (!this.cfg.handleCurrentBtnOnly) { this.initLoadingState(btnEl); } // native Promise doesn't have finally if (promise.finally) { promise.finally(resolveLoadingState); } else { promise .then(resolveLoadingState) .catch(resolveLoadingState); } } /** * $compile and append the spinner template to the button. */ appendSpinnerTpl(btnEl) { // TODO add some kind of compilation later on btnEl.insertAdjacentHTML('beforeend', this.cfg.spinnerTpl); } /** * Limit loading state to show only for the currently clicked button. * Executed only if this.cfg.handleCurrentBtnOnly is set */ handleCurrentBtnOnly() { if (!this.cfg.handleCurrentBtnOnly) { return true; // return true for testing } // Click triggers @Input update // We need to use timeout to wait for @Input to update window.setTimeout(() => { // return if something else than a promise is passed if (!this.promise) { return; } this.initLoadingState(this.btnEl); }, 0); } } PromiseBtnDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.17", ngImport: i0, type: PromiseBtnDirective, deps: [{ token: i0.ElementRef }, { token: userCfg }], target: i0.ɵɵFactoryTarget.Directive }); PromiseBtnDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "12.0.0", version: "12.2.17", type: PromiseBtnDirective, selector: "[promiseBtn]", inputs: { isDisabledFromTheOutsideSetter: ["disabled", "isDisabledFromTheOutsideSetter"], promiseBtn: "promiseBtn" }, host: { listeners: { "click": "handleCurrentBtnOnly()" } }, ngImport: i0 }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.17", ngImport: i0, type: PromiseBtnDirective, decorators: [{ type: Directive, args: [{ selector: '[promiseBtn]' }] }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: undefined, decorators: [{ type: Inject, args: [userCfg] }] }]; }, propDecorators: { isDisabledFromTheOutsideSetter: [{ type: Input, args: ['disabled'] }], promiseBtn: [{ type: Input }], handleCurrentBtnOnly: [{ type: HostListener, args: ['click'] }] } }); class Angular2PromiseButtonModule { // add forRoot to make it configurable static forRoot(config) { // NOTE: this is never allowed to contain any conditional logic return { ngModule: Angular2PromiseButtonModule, providers: [{ provide: userCfg, useValue: config }] }; } } Angular2PromiseButtonModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.17", ngImport: i0, type: Angular2PromiseButtonModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); Angular2PromiseButtonModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "12.2.17", ngImport: i0, type: Angular2PromiseButtonModule, declarations: [PromiseBtnDirective], exports: [PromiseBtnDirective] }); Angular2PromiseButtonModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "12.2.17", ngImport: i0, type: Angular2PromiseButtonModule, providers: [], imports: [[]] }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.17", ngImport: i0, type: Angular2PromiseButtonModule, decorators: [{ type: NgModule, args: [{ declarations: [ PromiseBtnDirective, ], imports: [], exports: [ PromiseBtnDirective, ], providers: [] }] }] }); /** * Generated bundle index. Do not edit. */ export { Angular2PromiseButtonModule, PromiseBtnDirective }; //# sourceMappingURL=angular2-promise-buttons.js.map