UNPKG

jsdk-offical

Version:

JSDK is the most comprehensive TypeScript framework, like JDK.

452 lines (406 loc) 12.3 kB
## 模型 <b>jsmvc</b> 模块有三种模型类:对象模型、列模型、翻页列模型。 * *模型类也是JSFX的表单组件(和Grid组件)的内置对象,帮助JSFX组件完成数据监听与数据绑定功能* ### 对象模型 <b>JS.model.Model</b> 类内部以JSON格式保存一组键值对。 #### 字段 <b>Model</b> 类的每个键称为 <b>Field</b>(字段)。<b>Model</b> 类的所有字段可以类定义时定义: ```javascript @klass('Person') class Person extends Model { static DEFAULT_FIELDS = [ { name: 'no', isId: true }, { name: 'name' }, { name: 'age' }, { name: 'birthday' } ] } ``` * <b>Model</b>类必须使用<b>@klass</b>注解以支持反射 也可以在实例化时动态定义: ```javascript let person = new Person({ [ { name: 'no', isId: true }, { name: 'name' }, { name: 'age' }, { name: 'birthday' } ] }) ``` 还可以在实例化后动态添加: ```javascript person.addFields({ [ { name: 'no', isId: true }, { name: 'name' }, { name: 'age' }, { name: 'birthday' } ] }) ``` 判定某字段是否为ID字段: ```javascript Assert.true(person.isIdField('no')); Assert.false(person.isIdField('age')); ``` ### 数据加载 加载本地JSON数据: ```javascript person.set('no', 1001); person.set('name', 'Bill'); //or person.setData({ no: 1001, name: 'Bill' }); ``` 加载远程JSON数据: ```javascript person.load('bill.json').then(()=>{ Assert.true('Bill', person.get('name')) }) ``` ### 远程JSON格式 模型的远程数据的标准规格定义在 <b>ResultSet</b> 类中: ```javascript /** * A result contains remote json response for model. */ export class ResultSet { public static DEFAULT_FORMAT: ResultSetFormat = { rootProperty: undefined, dataProperty: 'data', totalProperty: 'paging.total', pageProperty: 'paging.page', pageSizeProperty: 'paging.pageSize', messageProperty: 'msg', versionProperty: 'version', langProperty: 'lang', successProperty: 'code', successCode: 'success' } } ``` 即标准JSON格式为: ```javascript { version: string, lang: string, code: string, msg: string, data: PrimitiveType|Array<PrimitiveType>|JsonObject<PrimitiveType>, paging?: { pageSize: number, total: number, page: number } } ``` 假设你项目中的JSON规格为: ```javascript { ver: string, locale: string, status: number, //200 is success errorMsg: string, response: { sizePerPage: number, totalRows: number, pageNo: number, data: any } } ``` 你可以覆盖 <b>ResultSet.DEFAULT_FORMAT</b>,这样Model就能从远程数据源正确取值了: ```javascript ResultSet.DEFAULT_FORMAT: ResultSetFormat = { rootProperty: undefined, dataProperty: 'response.data', totalProperty: 'response.totalRows', pageProperty: 'response.pageNo', pageSizeProperty: 'response.sizePerPage', messageProperty: 'errorMsg', versionProperty: 'ver', langProperty: 'locale', successProperty: 'status', successCode: 200 } ``` ### 字段名映射 很多时候,服务端或本地定义的JSON字段名与<b>Model</b>内部定义的字段名存在差异,这时就需要用到字段名映射功能。 例如,远程JSON文件<code>alias.json</code>的数据格式如下: ```javascript { version: "1.0", lang: "en", code: "success", data: { id: 1001, //mapping "no" alias: 'Bill' //mapping "name" } } ``` 我们可以修改字段映射: ```javascript person.updateFields({ [ { name: 'no', nameMapping: 'id'}, { name: 'name', nameMapping: 'alias' } ] }) ``` 之后我们就可以按照模型自己的字段名正确读取数据了: ```javascript person.load('alias.json').then(()=>{ Assert.true('Bill', person.get('name')); //read from nameMapping }) ``` ### 数据校验 先设置字段的校验器: ```javascript person.updateField({ name: 'no', validators: [ { name: 'custom',//Validator Type Name message: 'The "no" field must be number!', validate: (val: any) => { return typeof val == 'number' } } ] }) ``` * 校验器的更多类型及用法请查阅API文档。 * 可以同时设置多个字段的多种校验器;这里只写最简单的例子。 再设置校验相关的事件监听: ```javascript person.on('fieldvalidated', (e, rst:ValidateResult, val:any, fieldName)=>{ if(rst.hasError()) alert(`The ${fieldName} field's value is wrong!`) }) ``` 最后开始校验字段的值: ```javascript person.validateField('no'); ``` * validate()方法可以校验所有字段的值。 ### 列模型 <b>JS.model.ListModel</b> 类的内部存储格式为JSON数组。 其通常的输出格式为:<b>JsonObject[ ]</b>。你也可以设置其Model类型,再以 <b>Model[ ]</b> 格式输出: ```javascript let persons = new ListModel(); ... person.getData(); //return JsonObject[] person.getModels(Person); //return Person[] person.getRowModel(0, Person); //return the Person of row[0] ``` * <b>ListModel</b> 类并没有直接提供校验方法,你可以先获取其行模型对象,再对其进行校验。 <b>ListModel</b> 类可以添加并提交排序参数:当其加载远程数据时(直接拼接在URL的queryString上)提交至服务器。 ```javascript let persons = new ListModel({ sorters: [{ field:'gmtCreated', dir: 'desc' },{ field: 'name', dir: 'asc' }] }); //or persons.addSorter('gmtCreated', 'desc'); persons.addSorter('name', 'asc'); ``` ### 翻页列模型 <b>JS.model.PageModel</b> 类是 <b>ListModel</b> 类的子类,提供了 <b>ListModel</b> 所没有的提交翻页参数的功能。 ```javascript let persons = new PageModel({ dataQuery: { url: 'test-data/persons-page.json', pageSize: 20 } }); persons.on('loadsuccess', function () { let me = <PageModel>this; Assert.equal(10, me.getCurrentPage()) Assert.equal(3, me.getData().length); Assert.equal('Smith', me.getRowModel<Person>(2, Person).get('name')); }); persons.loadPage(10); ``` ## 视图 View是一组Widget的容器,管理其内部的Widget的创建、渲染、读写、销毁等操作。 <b>JS.view.View</b> 类有三个子类,分别是: > SimpleView 简单视图:适合渲染不带数据的简单组件。 > > TemplateView 模版视图:用本地或远程数据与HTML模版合并后生成HTML片段,基于此片段再渲染组件。 > > FormView 表单视图:适合渲染表单型组件。 * *后面的两个例子讲述简单视图与模版视图的用法,表单视图的用法位于JSFX组件章节* ### 组件标签 View类会自动搜索与其配置的组件同ID的HTML标签并实例化组件对象。一个视图化widget标签是长这样的: ```html <div id="xxx" js-wgt="button"> ``` 上述标签将会被Views搜索到并反射式实例化为一个id为<code>xxx</code>的 <b>JS.fx.Button</b>(其别名为button)实例: ```javascript Class.aliasInstance<T>('button', {id:'xxx'});//等价于: let btn = new Button({id: 'xxx'}) ``` ### 简单视图 我们以 <b>SimpleView</b> 为例,来看看如何使用View。 先在HTML文件写好三个按钮的HTML片段: ```html <div id="btn0" js-wgt="button"> <div id="btn1" js-wgt="button"> <div id="btn2" js-wgt="button"> ``` 再用view对象渲染出所有按钮: ```javascript let view = new SimpleView({ defaultConfig: <ButtonConfig>{ faceMode: ButtonFaceMode.shadow }, widgetConfigs: <JsonObject<ButtonConfig>>{ btn0: { text: 'BUTTON-1' }, btn1: { text: 'BUTTON-2' }, btn2: { text: 'BUTTON-3' } } }); view.render(); ``` 换一种方式,我们也可以定义一个 <b>SimpleView</b> 的子类,用 <b>@compo</b> 标记为IOC组件: ```javascript @compo('JS.sample.ButtonsView') export class ButtonsView extends SimpleView { initialize() { this._config = { defaultConfig: <ButtonConfig>{ faceMode: ButtonFaceMode.shadow }, widgetConfigs: <JsonObject<ButtonConfig>>{ btn0: { text: 'BUTTON-1' }, btn1: { text: 'BUTTON-2' }, btn2: { text: 'BUTTON-3' } } }; super.initialize(); } } ``` * *<b>ButtonsView</b>实例化时就会自动调用render方法* 当被依赖注入时就会被自动实例化: ```javascript @compo('JS.sample.ClassA') export class ClassA { @inject() public view: ButtonsView = null; //必须初始化为null,因为TS在编译时会忽略没有初始化的类属性 } ``` 或者当IOC容器查找时也会被自动实例化: ```javascript let view = Compos.get<ButtonsView>(ButtonsView); ``` ### 模版视图 我们继续用上面的例子,看看如何用模版视图来实现同样的需求。 模版视图会以数据与模版合并生成HTML片段,所以不需要在HTML文件里写按钮标签(仅仅写一个DIV容器即可): ```html <div id="buttons"></div> ``` 实例化一个模版视图对象: ```javascript JS.imports([ '$handlebars', //Must import handlebars as tpl engine '$jsfx' ]).then(()=>{ let view = new TemplateView({ container: '#buttons', defaultConfig: <ButtonConfig>{ faceMode: ButtonFaceMode.shadow }, widgetConfigs: <JsonObject<ButtonConfig>>{ btn0: { text: 'BUTTON-1' }, btn1: { text: 'BUTTON-2' }, btn2: { text: 'BUTTON-3' } }, tpl: `{{#.}} <div id="{{id}}" js-wgt="button"></div> {{/.}}` }); }) ``` * *JSDK使用的模版引擎是handlebars,所以模版语法也是handlebars的语法* 加载数据并自动完成渲染: ```javascript //load local data view.data([{ "id":"btn0" },{ "id":"btn1" },{ "id":"btn2" }]); //or load remote data //view.load('buttons.json'); ``` ## 组件与IOC容器 <b>JS.ioc.Compos</b> 是一个IOC容器,提供对容器中所有IOC组件对象的注册、初始化、销毁等功能,并且能有效节约内存开销。 - *IOC容器目前仅支持单例模式* ### 定义组件类 使用 <b>@compo</b> 注解定义一个IOC组件,则表示此类将被IOC容器管理: ```javascript module JS { export namespace sample { @compo('JS.sample.ClassA') //参数必须为类的正确全名 export class ClassA { } } } ``` ### 组件查找 <b>Compos</b>可以通过<code>get</code>访问查找组件对象: ```javascript let clsA = Compos.get<ClassA>(ClassA); Konsole.print(clsA.a); ``` ### 依赖注入 假设组件类 <b>ClassA</b> 的属性<code>a</code>为组件类<b>ClassB</b>,我们可以用 <b>@inject</b> 注解自动注入属性<code>a</code>: ```javascript module JS { export namespace sample { @compo('JS.sample.ClassB') export class ClassB { } @compo('JS.sample.ClassA') export class ClassA { @inject() public a: ClassB = null; //必须初始化为null,因为TS在编译时会忽略没有初始化的类属性 } } } ``` ### 安全组件 一个类如果有可被修改的属性,那么我们称之为有状态类,反之我们称之为无状态类。<br> 在单例型容器下,每个类都仅有一个实例存在;如果组件实例是有状态的,可能造成内部值被多个调用点不期望的修改,因此是不安全的。 为了组件安全运行,我们提倡定义无状态组件,即<b>组件的属性为以下类型才是安全的</b>: - 常量 - 其他安全的组件类 *备注:如果某个组件类不安全,那么所有装配此组件的其他组件类也会变得不安全*