UNPKG

web-atoms-mvvm

Version:

MVVM, REST Json Service, Message Subscriptions for Web Atoms

539 lines (441 loc) 13.4 kB
/// <reference path="__di.ts" /> /** * Atom helper class * @class Atom */ declare class Atom { static pageQuery: { [key:string]: any }; /** * Set this true to return mock in RestServices * @static * @type {boolean} * @memberof Atom */ static designMode: boolean; /** * Set this true to return mock in test mode * @static * @type {boolean} * @memberof Atom */ static testMode: boolean; /** * Refreshes bindings for specified property of the target * @static * @param {*} target * @param {string} property * @memberof Atom */ static refresh(target:any, property: string):void; /** * Display given error message to user, this must be set by * the app developer * @static * @param {string} msg * @memberof Atom */ static showError(msg: string): void; /** * [Obsolete] do not use, this will retrieve value at given path for target * @static * @param {*} target * @param {string} path * @returns {*} * @memberof Atom */ static get(target: any, path: string): any; /** * [Obsolete] do not use, this will set value at given path for target * @static * @param {*} target * @param {string} path * @param {*} value * @memberof Atom */ static set(target: any, path: string, value: any): void; /** * Schedules given call in next available callLater slot * @static * @param {()=>void} f * @memberof Atom */ static post (f:()=>void): void; /** * Schedules given call in next available callLater slot and also returns * promise that can be awaited, calling `Atom.postAsync` inside `Atom.postAsync` * will create deadlock * @static * @param {()=>Promise<any>} f * @returns {Promise<any>} * @memberof Atom */ static postAsync(f:()=>Promise<any>): Promise<any>; /** * Invokes given function and disposes given object after execution * @static * @param {WebAtoms.AtomDisposable} d * @param {()=>void} f * @memberof Atom */ static using(d:WebAtoms.AtomDisposable, f:()=>void): void; /** * Invokes given function and disposes given object after execution asynchronously * @static * @param {WebAtoms.AtomDisposable} d * @param {()=>Promise<any>} f * @returns {Promise<any>} * @memberof Atom */ static usingAsync(d:WebAtoms.AtomDisposable, f:()=>Promise<any>): Promise<any>; /** * Sets up watch and returns disposable to destroy watch * @static * @param {*} item * @param {string} property * @param {()=>void} f * @returns {WebAtoms.AtomDisposable} * @memberof Atom */ static watch(item:any, property:string, f:()=>void):WebAtoms.AtomDisposable; /** * await for delay for given number of milliseconds * @static * @param {number} n * @param {WebAtoms.CancelToken} [ct] * @returns {Promise<any>} * @memberof Atom */ static delay(n:number, ct?:WebAtoms.CancelToken): Promise<any>; /** * Version * @static * @type {{ * text: string, * major: number, * minor: number, * build: number * }} * @memberof Atom */ static version: { text: string, major: number, minor: number, build: number }; /** * Current time in milliseconds * @static * @returns {number} * @memberof Atom */ static time():number; /** * Combine and prepare given url from fragments * @static * @param {string} url * @param {*} queryString * @param {*} hash * @returns {string} * @memberof Atom */ static url(url: string, queryString:any, hash?:any): string; /** * Creates secure version of the given url with fragments * @static * @param {string} url * @param {...string[]} padding * @returns {string} * @memberof Atom */ static secureUrl(url: string, ... padding: string[]): string; /** * Creates bindable proxy for given object * @static * @param {*} e * @returns {*} * @memberof Atom */ static bindable<T>(e:T):T; /** * Creates an element enclosing given control name or class * * @static * @param {((string | WebAtoms.AtomControlType))} name * @param {string} tagName * @returns {HTMLElement} * @memberof Atom */ static controlToElement(name:(string | WebAtoms.AtomControlType),tagName?:string):HTMLElement; } declare class AtomDate { static zoneOffsetMinutes: number; static zoneOffset: number; static toLocalTime(d:Date): string; static setTime(d:Date, time: string): Date; static toMMDDYY(d:Date): string; static toShortDateString(d:Date | string): string; static toDateTimeString(d:Date | string): string; static toTimeString(d:Date | string): string; static smartDate(d:Date | string): string; static smartDateUTC(d:Date | string): string; static jsonDate(d: Date | string): { Year: number, Month: number, Date: number, Hours: number, Minutes: number, Seconds: number, Offset: number}; static toUTC(d: Date | string): Date; static parse(d:any): Date; static monthList:Array<{ label: string, value: number }>; } declare class AtomPhone { static toSmallPhoneString(v:string): string; static toPhoneString(v:string): string; } declare class AtomUri { constructor(v:string); host: string; protocol: string; port: number; path: string; query: {[s:string]: string}; hash: {[s:string]: string}; } if(location) { Atom.designMode = /file/i.test(location.protocol); } /** * This decorator will mark given property as bindable, it will define * getter and setter, and in the setter, it will refresh the property. * * class Customer{ * * @bindableProperty * firstName:string; * * } * * @param {*} target * @param {string} key */ function bindableProperty(target: any, key: string):void { // property value var _val:any = this[key]; var keyName:string = "_" + key; this[keyName] = _val; // property getter var getter:()=>any = function ():any { // console.log(`Get: ${key} => ${_val}`); return this[keyName]; }; // property setter var setter:(v:any) => void = function (newVal:any):void { // console.log(`Set: ${key} => ${newVal}`); // debugger; var oldValue:any = this[keyName]; // tslint:disable-next-line:triple-equals if(oldValue == newVal) { return; } this[keyName] = newVal; // only if this is not an AtomControl... if(!(this._element && this._element.atomControl === this)) { var c:any = this._$_supressRefresh; if(!(c && c[key])) { Atom.refresh(this, key); } } if(this.onPropertyChanged) { this.onPropertyChanged(key); } }; // delete property if (delete this[key]) { // create new property with getter and setter Object.defineProperty(target, key, { get: getter, set: setter, enumerable: true, configurable: true }); // tslint:disable-next-line:no-string-literal if(target.constructor.prototype["get_atomParent"]) { target["get_" + key] = getter; target["set_" + key] = setter; } } } Atom.bindable = (e:any):any => { if(!e) { return e; } if(e instanceof Array) { throw new TypeError("Invalid object, try to use AtomList instead of Atom.bindable"); } if(typeof e === "string" || e.constructor === String) { return e; } if(typeof e === "number" || e.constructor === Number) { return e; } if(e.constructor === Date) { return e; } var self:any = e; if(e._$_isBindable) { return e; } var keys: string[] = Object.keys(e); e._$_isBindable = true; for(var key of keys) { var k:string = key; var v:any = e[key]; var vk:string = `_${key}`; e[vk] = v; delete e[key]; Object.defineProperty(e, key, { get: function():any { return this[vk]; }, set: function(v:any):void { this[vk] = v; Atom.refresh(this,k); }, enumerable: true }); } return e; }; Atom.controlToElement = (type:(string | WebAtoms.AtomControlType),tagName:string = "div"): HTMLElement => { if(!type) { return undefined; } var name:string = ""; if(!(typeof type === "string")) { // tslint:disable-next-line:no-string-literal name = type["__typeName"]; } else { name = type; } var div:HTMLElement = document.createElement("div"); div.setAttribute("atom-type", name); return div; }; namespace WebAtoms { /** * * * @export * @class CancelToken */ export class CancelToken { listeners:Array<()=>void> = []; private _cancelled:boolean; get cancelled():boolean { return this._cancelled; } cancel():void { this._cancelled = true; for(var fx of this.listeners) { fx(); } } reset():void { this._cancelled = false; this.listeners.length = 0; } registerForCancel(f:()=>void):void { if(this._cancelled) { f(); this.cancel(); return; } this.listeners.push(f); } } export class AtomModel { public refresh(name: string): void { Atom.refresh(this, name); } } /** * Though you can directly call methods of view model in binding expression, * but we recommend using AtomCommand for two reasons. * * First one, it has enabled bindable property, which can be used to enable/disable UI. * AtomButton already has `command` and `commandParameter` property which automatically * binds execution and disabling the UI. * * Second one, it has busy bindable property, which can be used to display a busy indicator * when corresponding action is a promise and it is yet not resolved. * * @export * @class AtomCommand * @extends {AtomModel} * @template T */ export class AtomCommand<T> extends AtomModel { public readonly isMVVMAtomCommand: boolean = true; private _enabled: boolean = true; /** * * * @type {boolean} * @memberof AtomCommand */ get enabled(): boolean { return this._enabled; } set enabled(v: boolean) { this._enabled = v; this.refresh("enabled"); } private _busy: boolean = false; /** * * * @type {boolean} * @memberof AtomCommand */ get busy(): boolean { return this._busy; } set busy(v:boolean) { this._busy = v; this.refresh("busy"); } private action: (p: T) => any; public execute: (p:T) => any; private executeAction(p:T): any { if(this._busy) { return; } this.busy = true; var result:any = this.action(p); if (result) { if(result.catch) { result.catch((error) => { this.busy = false; if(error !== "cancelled") { console.error(error); Atom.showError(error); } }); return; } if(result.then) { result.then(()=> { this.busy = false; }); return; } } this.busy = false; } constructor( action: (p: T) => any) { super(); this.action = action; this.execute = (p:T) => { if (this.enabled) { this.executeAction(p); } }; } } }