jsdk-offical
Version:
JSDK is the most comprehensive TypeScript framework, like JDK.
410 lines (346 loc) • 14.6 kB
text/typescript
/**
* @project JSDK
* @license MIT
* @website https://github.com/fengboyue/jsdk
*
* @version 2.0.0
* @author Frank.Feng
*/
/// <reference path="../lang/System.ts"/>
/// <reference path="Model.ts"/>
module JS {
export namespace model {
let J = Jsons;
export type ListModelEvents = 'dataupdating'|'dataupdated'|'rowadded'|'rowremoved'|'validated'|'rowvalidated'|'loading'|'loadsuccess'|'loadfailure'|'loaderror';
export interface ListModelListeners<M> {
/**
* @event (this:ListModel, e:CustomEvent, newData:JsonObject[], oldData:JsonObject[])
*/
dataupdating: EventHandler2<M, JsonObject[], JsonObject[]>
/**
* @event (this:ListModel, e:CustomEvent, newData:JsonObject[], oldData:JsonObject[])
*/
dataupdated: EventHandler2<M, JsonObject[], JsonObject[]>
/**
* @event (this:ListModel, e:CustomEvent, newRows:JsonObject[], index:number)
*/
rowadded: EventHandler2<M, JsonObject[], number>
/**
* @event (this:ListModel, e:CustomEvent, removedRow:JsonObject, index:number)
*/
rowremoved: EventHandler2<M, JsonObject, number>
/**
* @event (this:ListModel, e:CustomEvent, result:ValidateResult, data:JsonObject[])
*/
validated: EventHandler2<M, ValidateResult, JsonObject[]>
/**
* @event (this:ListModel, e:CustomEvent, result:ValidateResult, row:JsonObject, index:number)
*/
rowvalidated: EventHandler3<M, ValidateResult, JsonObject, number>
/**
* @event (this:ListModel, e:CustomEvent, request:HttpRequest)
*/
loading: EventHandler1<M, HttpRequest>
/**
* @event (this:ListModel, e:CustomEvent, result:ResultSet<any>)
*/
loadsuccess: EventHandler1<M, ResultSet<any>>
/**
* @event (this:ListModel, e:CustomEvent, result:ResultSet<any>)
*/
loadfailure: EventHandler1<M, ResultSet<any>>
/**
* @event (this:ListModel, e:CustomEvent, res:HttpResponse|Error)
*/
loaderror: EventHandler1<M, HttpResponse|Error>
};
export type Sorter = {
field: string, //The property of field to sort by. Required unless fn is provided
dir?: 'asc' | 'desc',
sort?: (record1: any, record2: any) => number //used in local sort
}
export class ListModelConfig {
autoLoad?: boolean = false;
readonly listeners?: ListModelListeners<this>;
sorters?: Array<Sorter>;
dataQuery?: string | HttpRequest;
iniData?: JsonObject[];
}
/**
* A model for list data.
*/
export class ListModel {
protected _config: ListModelConfig;
protected _data: JsonObject[] = [];
protected _eventBus = new EventBus(this);
private _isD = false;
constructor(cfg?: ListModelConfig) {
this._config = this._initConfig(cfg);
let listeners = this._config.listeners;
if (listeners) J.forEach(<JsonObject>listeners, (v: EventHandler, key: ListModelEvents) => {
this.on(key, v);
})
if (this._config.iniData) this.setData(this._config.iniData);
if (this._config.autoLoad) this.reload();
}
protected _initConfig(cfg?:ListModelConfig){
return J.union(new ListModelConfig(), cfg)
}
protected _check() {
if (this.isDestroyed()) throw new RefusedError('The model was destroyed!');
}
public addSorter(field: string, dir?: 'asc' | 'desc') {
this._check();
let newSorter: Sorter = {
field: field,
dir: dir ? dir : 'asc'
}, has = false, sorters = this._config.sorters;
if (!sorters) sorters = [];
sorters.some((sorter: Sorter) => {
if (newSorter.field == sorter.field) {
has = true;
if (newSorter.sort) sorter.sort = newSorter.sort;
sorter.dir = newSorter.dir;
return true;
}
return false;
});
if (!has) sorters.push(newSorter);
this._config.sorters = sorters;
}
public removeSorter(field: string) {
this._check();
let sorters = this._config.sorters;
if (!sorters) return;
sorters.remove(item=> {
return item.field == field;
})
}
public clearSorters() {
this._check();
this._config.sorters = [];
}
public sort(field: string, dir?: 'desc' | 'asc'): Promise<any> {
this._check();
this.addSorter(field, dir);
return this.reload();
}
public getSorterBy(fieldName: string): Sorter {
let sorters = this._config.sorters;
if (!sorters) return null;
let sorter: Sorter = null;
sorters.some((srt: Sorter) => {
let is = srt.field === fieldName;
if (is) sorter = srt;
return is;
});
return sorter;
}
private _sortParams(): JsonObject {
let sorters = this._config.sorters;
if (!sorters) return null;
let s = '';
sorters.forEach((sorter: Sorter) => {
s += `${sorter.field} ${sorter.dir ? sorter.dir : 'asc'},`;
});
s = s.slice(0, s.length - 1);
return { sorters: s }
}
public reload() {
return this.load(this._config.dataQuery);
}
private _modelKlass:Klass<Model> = null;
public modelKlass():Klass<Model>
public modelKlass(klass:Klass<Model>):this
public modelKlass(klass?:Klass<Model>){
if(arguments.length==0) return this._modelKlass;
this._modelKlass = klass
return this
}
/**
* load using its configured query.
*/
public load<R=JsonObject[]>(quy: string | HttpRequest, silent?:boolean): Promise<ResultSet<R>> {
this._check();
let me = this,
query = <HttpRequest>J.union(Http.toRequest(this._config.dataQuery),Http.toRequest(quy));
query.data = J.union(<JsonObject>query.data,this._sortParams());
this._fire('loading', [query]);
this._config.dataQuery = query;
return new JsonProxy().execute<R>(query).then(function (result: ResultSet<R>) {
if (result.success()) {
me.setData(<any>result.data(), silent);
me._fire('loadsuccess', [result]);
} else {
me._fire('loadfailure', [result]);
}
return Promise.resolve(<any>result);
}).catch(function (err: HttpResponse|Error) {
if(Types.ofKlass(err, Error)) JSLogger.error('['+(<Error>err).name+']'+(<Error>err).message);
me._fire('loaderror', [err]);
});
}
public getData(): JsonObject[] {
return this.isEmpty()?null:this._data;
}
/**
* Read data into model.
* @param data
* @param silent ignore events
*/
public setData(data: JsonObject[], silent?: boolean) {
this._check();
let newData = data, oldData = J.clone(this._data);
if (!silent) this._fire('dataupdating', [newData, oldData]);
this._data = data||[];
if (!silent) this._fire('dataupdated', [newData, oldData]);
return this;
}
public iniData(): any
public iniData(data: any): this
public iniData(d?: any): any {
let cfg = this._config;
if (arguments.length == 0) return cfg.iniData;
cfg.iniData = d;
return this
}
public reset() {
return this.setData(this.iniData());
}
/**
* Add new data into the tail.
* @param records
* @param silent ignore events
*/
public add(records: JsonObject | JsonObject[], silent?: boolean) {
return this.insert(this._data.length, records, silent);
}
/**
* Insert new data into the index.
* @param index
* @param records
* @param silent ignore events
*/
public insert(index: number, records: JsonObject | JsonObject[], silent?: boolean) {
if (!records) return this;
this._check();
this._data = this._data||[];
let models = Arrays.toArray(records);
this._data.add(models, index);
if (!silent) this._fire('rowadded', [models, index]);
return this;
}
public getRowModel<T extends Model>(index:number, klass?: Klass<T>): T{
if (index < 0 || index >= this.size()) return null;
let d = this._data[index];
if(!d) return null;
let k = klass||this._modelKlass;
if(!k) throw new NotFoundError('The model klass not found!');
return Class.newInstance<T>(k).setData(d,true)
}
public getModels<T extends Model>(klass?: Klass<T>): T[]{
if(this.size()==0) return null;
let k = klass||this._modelKlass;
if(!k) throw new NotFoundError('The model klass not found!');
let mds:T[] = [];
this._data.forEach((d,i)=>{
mds[i] = Class.newInstance<T>(k).setData(d,true)
})
return mds
}
public getRowById(id: number | string): JsonObject {
return this.getRow(this.indexOfId(id));
}
public getRow(index: number): JsonObject {
if (index < 0 || index >= this.size()) return null;
return this._data[index] || null;
}
public indexOfId(id: number | string): number {
if (!id || this.size()==0) return -1;
let idName = 'id';
if(this._modelKlass && Types.subklassOf(this._modelKlass, Model)) {//如果modelKlass是Model的子类,则获取其id名称
let model = Class.newInstance<Model>(this._modelKlass),
field = model.getIdField();
if(field) idName = field.alias();
}
let index = -1;
this._data.some((obj, i) => {
let ret = obj[idName] == id;
if (ret) index = i;
return ret;
})
return index;
}
public removeAt(index: number, silent?: boolean) {
this._check();
if (this.size()==0) return this;
const obj = this._data[index];
if (obj) {
this._data.remove(index);
if (!silent) this._fire('rowremoved', [obj, index]);
}
return this;
}
public clear(silent?: boolean) {
return this.setData(null, silent);
}
public validate(): string | boolean {
if (this.size()==0) return true;
let rst = new ValidateResult(), str = '';
this._data.forEach(m => {
let ret = m.validate(rst);
if (ret !== true) str += (str ? '|' : '') + ret;
});
this._fire('validated', [this._data, rst]);
return str || true;
}
public validateRow(index:number): string | boolean {
let row = this.getRow(index);
if(!row) return null;
let rst = row.validate();
this._fire('rowvalidated', [rst, row, index]);
return rst
}
public size() {
return !this._data?0:this._data.length
}
public isEmpty() {
return this.size() == 0
}
public clone() {
let model = Class.newInstance<this>(this.className, J.clone(this._config));
model.setData(this.getData());
return model
}
protected _fire(type: string, args?: any[]) {
this._eventBus.fire(type, args);
}
public on<H=EventHandler<this>>(type: string, fn: H, once?:boolean) {
this._check();
this._eventBus.on(type, fn, once);
return this
}
public off(type?: string) {
this._check();
this._eventBus.off(<any>type);
return this
}
public destroy() {
if (this._isD) return;
this._eventBus.destroy();
this._eventBus = null;
this._data = null;
this._isD = true;
}
public isDestroyed() {
return this._isD
}
}
}
}
//预定义短类名
import ListModel = JS.model.ListModel;
import ListModelConfig = JS.model.ListModelConfig;
import ListModelEvents = JS.model.ListModelEvents;
import ListModelListeners = JS.model.ListModelListeners;