UNPKG

web-atoms-mvvm

Version:

MVVM, REST Json Service, Message Subscriptions for Web Atoms

455 lines (364 loc) 15.2 kB
/// <reference path="__di.ts" /> namespace WebAtoms { /** * * * @export * @class WindowService */ @WebAtoms.DIGlobal export class WindowService { /** * Reference used by popup opener as an anchor * @type {HTMLElement} * @memberof WindowService */ currentTarget:HTMLElement; popups:AtomControl[] = []; /** * */ constructor() { window.addEventListener("click", (e) => { this.currentTarget = e.target as HTMLElement; this.closePopup(); }); } private closePopup():void { if(!this.popups.length) { return; } var peek:AtomControl = this.popups[this.popups.length-1]; var element:HTMLElement = peek._element; var target:HTMLElement = this.currentTarget; while(target) { if(Core.hasClass(target,"close-popup")) { break; } if(target === element) { // do not close this popup.... return; } target = target.parentElement; } this.close(peek); } private close(c:AtomControl): void { // tslint:disable-next-line:no-string-literal var cp:Function = c["close"]; if(cp) { cp(); } } lastPopupID:number = 0; /** * This method will open a new popup identified by name of the popup or class of popup. * Supplied view model has to be derived from AtomWindowViewModel. * * * @example * * var result = await windowService.openPopup<Task>(NewTaskWindow, new NewTaskWindowViewModel() ); * * class NewTaskWindowViewModel extends AtomWindowViewModel{ * * .... * save(){ * * // close and send result * this.close(task); * * } * .... * * } * * @template T * @param {(string | {new(e)})} windowType * @param {AtomWindowViewModel} [viewModel] * @returns {Promise<T>} * @memberof WindowService */ public async openPopup<T>(p: (HTMLElement | AtomControlType), vm: AtomWindowViewModel): Promise<T> { await Atom.delay(5); return await this._openPopupAsync<T>(p,vm); } private _openPopupAsync<T>(p: (HTMLElement | AtomControlType), vm: AtomWindowViewModel ): Promise<T> { return new Promise((resolve,reject) => { var parent:AtomControl = Core.atomParent(this.currentTarget); var e:HTMLDivElement = document.createElement("div"); // tslint:disable-next-line:no-string-literal e["_logicalParent"] = parent._element; e.id = `atom_popup_${this.lastPopupID++}`; if(vm) { // tslint:disable-next-line:no-string-literal vm["windowName"] = e.id; } var r:Rect = Core.getOffsetRect(this.currentTarget); e.style.position = "absolute"; e.style.left = r.x + "px"; e.style.top = (r.y + r.height) + "px"; e.style.zIndex = 10000 + this.lastPopupID + ""; document.body.appendChild(e); var ct:AtomControl; if(p instanceof HTMLElement) { e.appendChild(p); ct = new AtomControl(e); } else { ct = new p(e); } ct.viewModel = vm; ct.createChildren(); ct.init(); // tslint:disable-next-line:no-string-literal ct["close"] = () => { AtomDevice.instance.broadcast(`atom-window-cancel:${e.id}`,"cancelled"); }; this.popups.push(ct); var d:{ close?: AtomDisposable, cancel?: AtomDisposable } = {}; // tslint:disable-next-line:no-string-literal var closeFunction:Function = () => { ct.dispose(); e.remove(); d.close.dispose(); d.cancel.dispose(); this.popups = this.popups.filter( f => f !== ct); }; d.close = AtomDevice.instance.subscribe(`atom-window-close:${e.id}`, (g,i) => { closeFunction(); resolve(i); }); d.cancel = AtomDevice.instance.subscribe(`atom-window-cancel:${e.id}`, (g,i)=> { closeFunction(); reject(i); }); }); } /** * Resolves current Window Service, you can use this method * to resolve service using DI, internally it calls * DI.resolve(WindowService). * * @readonly * @static * @type {WindowService} * @memberof WindowService */ static get instance(): WindowService { return WebAtoms.DI.resolve(WindowService); } private lastWindowID:number = 1; /** * Display an alert, and method will continue after alert is closed. * * @param {string} msg * @param {string} [title] * @returns {Promise<any>} * @memberof WindowService */ alert(msg:string,title?:string):Promise<any> { return this.showAlert(msg,title || "Message",false); } /** * Display a confirm window with promise that will resolve when yes or no * is clicked. * * @param {string} msg * @param {string} [title] * @returns {Promise<boolean>} * @memberof WindowService */ confirm(msg:string,title?:string):Promise<boolean> { return this.showAlert(msg,title || "Confirm",true); } private showAlert(msg:string ,title:string,confirm:boolean):Promise<boolean> { return new Promise((resolve,reject)=> { // tslint:disable-next-line:no-string-literal var AtomUI:any = window["AtomUI"]; // tslint:disable-next-line:no-string-literal var AtomWindow:any = window["WebAtoms"]["AtomWindow"]; var d:any = { Message: msg, ConfirmValue: false, Confirm: confirm }; var e:any = document.createElement("DIV"); e.style.zIndex = `${this.zIndex++}`; document.body.appendChild(e); var w:any = AtomUI.createControl(e, AtomWindow, d); w.set_windowWidth(380); w.set_windowHeight(120); w.set_windowTemplate(w.getTemplate("alertTemplate")); w.set_title(title); w.set_next(function ():void { this.zIndex = Number.parseInt(e.style.zIndex); w.dispose(); // $(e).remove(); e.remove(); if (d.ConfirmValue) { resolve(true); } else { resolve(false); } }); w.set_cancelNext(()=> { this.zIndex = Number.parseInt(e.style.zIndex); w.dispose(); // $(e).remove(); e.remove(); resolve(false); }); w.refresh(); }); } /** * * * @param {string} frameHostId * @param {((string | {new (e:any)}))} frameType * @param {AtomViewModel} [viewModel] * @returns {Promise<any>} * @memberof WindowService */ async pushPage(frameHostId: string, frameType: (string | {new (e:any)}), viewModel?: AtomPageViewModel): Promise<any> { var host:HTMLElement = window.document.getElementById(frameHostId); if(!host) { throw new Error(`FrameView Host ${frameHostId} not found on the current page`); } // tslint:disable-next-line:no-string-literal var ctrl: AtomPageView = host["atomControl"]; var canClose:boolean = await ctrl.canChange(); if(!canClose) { throw new Error("Cancelled"); } await new Promise((resolve,reject) => { var windowDiv:HTMLDivElement = document.createElement("div"); windowDiv.id = `atom_frame_${frameHostId}_${ctrl.stack.length+1}`; var p:any = null; var windowCtrl:any = AtomUI.createControl(windowDiv,frameType); windowDiv.setAttribute("atom-local-scope","true"); windowCtrl.init(); // tslint:disable-next-line:no-string-literal var dispatcher:any = WebAtoms["dispatcher"]; if(viewModel !== undefined) { viewModel.pageId = windowDiv.id; Atom.set(windowCtrl,"viewModel",viewModel); } ctrl.push(windowCtrl); var d:any = {}; d.disposable = AtomDevice.instance.subscribe(`pop-page:${windowDiv.id}`, () => { ctrl.backCommand(); d.disposable.dispose(); }); resolve(); }); } /** * zIndex of next window * @type {number} * @memberof WindowService */ private zIndex: number = 1001; /** * This method will open a new window identified by name of the window or class of window. * Supplied view model has to be derived from AtomWindowViewModel. * * By default this window has a localScope, so it does not corrupt scope. * * @example * * var result = await windowService.openWindow<Task>(NewTaskWindow, new NewTaskWindowViewModel() ); * * class NewTaskWindowViewModel extends AtomWindowViewModel{ * * .... * save(){ * * // close and send result * this.close(task); * * } * .... * * } * * @template T * @param {(string | {new(e)})} windowType * @param {AtomWindowViewModel} [viewModel] * @returns {Promise<T>} * @memberof WindowService */ async openWindow<T>(windowType: string | {new(e:any)}, viewModel?: AtomWindowViewModel):Promise<T> { return new Promise<T>((resolve,reject)=> { var windowDiv:HTMLDivElement = document.createElement("div"); windowDiv.id = `atom_window_${this.lastWindowID++}`; windowDiv.style.zIndex = `${this.zIndex++}`; // tslint:disable-next-line:no-string-literal var atomApplication:any = window["atomApplication"]; // tslint:disable-next-line:no-string-literal var AtomUI:any = window["AtomUI"]; atomApplication._element.appendChild(windowDiv); if(windowType instanceof String) { windowType = window[windowType as string] as {new (e:any)}; } var windowCtrl:any = AtomUI.createControl(windowDiv,windowType); var closeSubscription:AtomDisposable = AtomDevice.instance.subscribe(`atom-window-close:${windowDiv.id}`, (g,i)=> { if(i!==undefined) { Atom.set(windowCtrl,"value",i); } windowCtrl.closeCommand(); }); var cancelSubscription:AtomDisposable = AtomDevice.instance.subscribe(`atom-window-cancel:${windowDiv.id}`, (g,i)=> { windowCtrl.cancelCommand(); }); windowDiv.setAttribute("atom-local-scope","true"); windowCtrl.init(); // tslint:disable-next-line:no-string-literal var dispatcher:any = WebAtoms["dispatcher"]; if(viewModel !== undefined) { Atom.set(windowCtrl,"viewModel",viewModel); viewModel.windowName = windowDiv.id; viewModel.channelPrefix = windowDiv.id; } windowCtrl.set_next(()=> { cancelSubscription.dispose(); closeSubscription.dispose(); try { resolve(windowCtrl.get_value()); } catch(e) { console.error(e); } dispatcher.callLater(()=> { this.zIndex = Number.parseInt(windowDiv.style.zIndex); windowCtrl.dispose(); windowDiv.remove(); }); }); windowCtrl.set_cancelNext(()=> { cancelSubscription.dispose(); closeSubscription.dispose(); try { reject("cancelled"); } catch(e) { console.error(e); } dispatcher.callLater(()=> { this.zIndex = Number.parseInt(windowDiv.style.zIndex); windowCtrl.dispose(); windowDiv.remove(); }); }); dispatcher.callLater(()=> { var scope:any = windowCtrl.get_scope(); var vm:any = windowCtrl.get_viewModel(); if(vm && !vm.windowName) { vm.windowName = windowDiv.id; } windowCtrl.openWindow(scope,null); }); }); } } } // tslint:disable-next-line:typedef var WindowService = WebAtoms.WindowService;