天天看點

Ext JS Data Package

Data package讓我們可以在我們的代碼或者application中加載和儲存資料。最重要的一點是,data package可以讓我們連結或者綁定資料到Ext JS元件。data apckage是由多個類組成,其中最重要的三個類為

  • Ext.data.Model
  • Store
  • Ext.data.proxy.Proxy

    每個application差不多都會使用上面的三個類, 它們受到很多衛星類(圍繞它們)的支援, 如下圖所示

Ext JS Data Package

Ajax

在學習data package 之前,我們需要知道如何向伺服器發送Ajax請求。

Ext JS提供了一個單例的對像Ext.Ajax, 我們可以通過它,向伺服器發送請求

Ext.Ajax.request({
    url:"serverside/myfirstdata.json"
});
console.log("Next lines of code...");
           

以上的請求是異步的,它不會阻止下一行代碼的執行,你也可以運作同步的Ajax請求

Ext.Aajx.async = false

更多Ajax的配置可以檢視http://docs.sencha.com/extjs/6.0.2-classic/Ext.Ajax.html#cfg-async

在上面的代碼中,我沒有處理伺服器響應,我們需要在ajax配置中,配置一個callback函數, 這個函數在伺服器響應時執行,同時還有 success or failure。

Ext.Ajax.request({
    url:"serverside/myfirstdata.json",
    success: function(response,options){
        console.log('success function executed, here we can do some
        stuff !');
    },
    failure: function(response,options){
        Ext.Msg.alert("Message", 'server-side failure with status code
        ' + response.status);
    },
    callback: function( options, success, response ){
        console.log('Callback executed, we can do some stuff !');
    }
});
           

output

success function executed, here we can do some stuff !
Callback executed, we can do some stuff !
           

success會在伺服器響應狀态為200-299時執行,表示請求成功。如果響應為403, 404, 500, 503, 則執行failure方法。

success or failure都會接收兩個參數,第一個參數是伺服器響應對像, 通過它可以獲得響應文本和響應頭。第二個參數是我們對發起的Ajax請求的配置。在我們的例子中,options為URL, success, failure三個屬性組成。

callback 函數将會一直執行,而不管是failure或者success. 并且這個函數接受三個參數: options 是請求時的配置, success是一個布爾值,如果請求成功,則為true, 否則為false. response參數是一個XMLHttpRequest對像,包含了響應的資訊。

假設我們獲得的響應為

{
    "success": true,
    "msg": "This is a success message..!"
}
           

對于success 來說, 響應傳回的是純文字的資料,我們需要将它解碼為JSON資料

success: function(response,options){
    var data = Ext.decode(response.responseText);
    Ext.Msg.alert("Message", data.msg);
},
           

或者

callback: function( options, success, response ){
    if(success){
        var data= Ext.decode(response.responseText);
        Ext.Msg.alert("Message", data.msg);
    }
}
           

如果我們請求的是一個xml資源,而不是JSON

<?xml version="1.0" encoding="UTF-8"?>
<response success="true">
    <msg>This is a success message in XML format</msg>
</response>
           

則代碼為

Ext.Ajax.request({
    url:"myfirstdata.xml",
    success: function(response,options){
       var data = response.responseXML;
        var node = data.getElementsByTagName("msg")[];
        Ext.Msg.alert("Message", node.firstChild.data);
    },
    failure: function(response,options){
        Ext.Msg.alert("Message", 'server-side failure with status code  ' + response.status);
    },
    callback: function( options, success, response ){
        console.log('Callback executed, we can do some stuff !');
    }
});
           

Passing parameters to Ajax request

為了相應的資訊,我們需要向Ajax請求中傳遞需要的參數,我們将使用以下的代碼來傳遞參數

//myfirstparams.php
<?php
    header('Content-Type:application/json');
    $output = array(
        "msg"=> "Message response text using the following params:<br> x=" . $_POST["x"] . ", y=" . $_POST["y"]
    );
echo json_encode($output);

//index.html

Ext.Ajax.request({
    url:"myfirstparams.php",
    method: "POST", //預設為GET
    params: {
        x: ,
        y: ,
    },
    success: function(response,options){
       var data = Ext.decode(response.responseText);
        Ext.Msg.alert("Message", data.msg);
    },
    failure: function(response,options){
        Ext.Msg.alert("Message", 'server-side failure with status code  ' + response.status);
    },
    callback: function( options, success, response ){
        console.log('Callback executed, we can do some stuff !');
    }
});
           

Setting timeout to Ajax request calls

有時,伺服器可能會長時間沒有反應, 而Ext JS 預設等待響應的時間為30秒。根據需要,我們可以設定等待請求的時間

Ext.Ajax.request({
    url: "serverside/myfirstparams.php",
    method: 'POST',
    params: {x:, y:},
    timeout: , 
    success: function(response,options){
        var data = Ext.decode(response.responseText);
        Ext.Msg.alert("Message", data.msg);
    },
    failure: function(response,options){
        Ext.Msg.alert("Message", 'server-side failure with status code
        ' + response.status);
        Ext.Msg.alert("Message", 'server-side failure:' +
                response.status);
    }
});
           

現在我們知道如何擷取資料,但我們還需要一種方法來處理資料。 Ext JS為我們提供了一個簡單的方法來管理資料。

Models

Ext.data.Model是data package的核心。在application中, 模型表示一個實體。比如一個電子商務的app, 可能會有Users, Products, Orders等模型。簡單的講,一個模型就是定義了一組字段,以及相關的業務邏輯。

以下是構成模型的幾個部分

  • Fields 資料的名稱,類型和值
  • Proxies 用來persist data或pull data
  • Validations 檢驗資料的有效性
  • Associations 與其它model的關系
Ext JS Data Package

Creating a Model

在建立一個model時,最好先建立一個公共的基礎類。這個basic class允許我們配置所有model類的公共部分,比如它的id以及id字段的類型,同時,schema也在這個類中配置。schema用來管理所有的model, 相當于mysql中的schema資料庫,而每個model相當于一張表。這個基礎類如下所示

Ext.Ajax.request({
    url: "serverside/myfirstparams.php",
    method: 'POST',
    params: {x:, y:},
    timeout: ,
    success: function(response,options){
        var data = Ext.decode(response.responseText);
        Ext.Msg.alert("Message", data.msg);
    },
    failure: function(response,options){
        Ext.Msg.alert("Message", 'server-side failure with status code
        ' + response.status);
        Ext.Msg.alert("Message", 'server-side failure:' +
                response.status);
    }
});
           

上面我們講解了base model的重要性,接下來看看如何一步步的建立一個model

Ext.define('Myapp.model.Client',{
    extend:'Ext.data.Model', // step 1
    idProperty:'clientId ', // step 2
    fields:[// step 3
        {name: 'clientId', type: 'int'},
        {name: 'name' , type: 'string'},
        {name: 'phone' , type: 'string'},
        {name: 'website' , type: 'string'},
        {name: 'status' , type: 'string'},
        {name: 'clientSince', type: 'date', dateFormat:'Y-m-d H:i'}
    ]
});
           

在第一步,我們讓它繼承于Ext.data.Model. 這類為所有的model提供了所有的功能。

第二步定義的屬性每一條記錄的ID, 在這裡,我們使用的是clientId字段,如果我們在fields中沒有定義clientId, model将會自動為我們預設産生一個id屬性。

在第三步,我們為我們的模型定義了字段,這個屬性是一個數組,數組中的每一個元素是一個對像,它包含了對每一個字段的配置。 在這裡,我們隻是設定了字段的名字和類型,在最後一個字段中,我們設定了一個dateFormat屬性。

字段的更多配置可以檢視Ext.data.field文檔

資料的類型,包含以下幾中

  • String
  • Integer
  • Float
  • Boolean
  • Date (使用此類型時,記得使用dateFormat屬性,以確定解析正确的日期值)
  • Auto (表示對接受到的資料,不進行轉換)

根據定義的model,我們建立一個資料

var myclient = Ext.create('Myapp.model.Client',{
    clientId:,
    name:'Acme corp',
    phone:'+52-01-55-4444-3210',
    website:'www.acmecorp.com',
    status:'Active',
    clientSince:'2010-01-01 14:35'
});
console.log(myclient);
console.log("My client's name is = " + myclient.data.name);
console.log("My client's website is = " + myclient.data.name);
           

建立完一條資料記錄後,我們可以使用get和set方法,讀寫這個記錄每個字段的值

// GET METHODS
var nameClient = myclient.get('name');
var websiteClient = myclient.get('website');
console.log("My client's info= " + nameClient + " - " +
        websiteClient);
// SET Methods
myclient.set('phone','+52-01-55-0001-8888'); // single value
console.log("My client's new phone is = " +
        myclient.get('phone'));
myclient.set({ //Multiple values
    name: 'Acme Corp of AMERICA LTD.',
    website:'www.acmecorp.net'
});
console.log("My client's name changed to = " +  myclient.get("name"));
console.log("My client's website changed to = " +  myclient.get("website") );
           

設定方法可以是單個的值,也是傳遞一個對像,同時設定多個值

其實所有的資料都是儲存存在data屬性上,我們應該使用get 和 set方法來讀寫資料,但因為某些原因,我們需要通路所有的資料時,我們可以使用這個data對像

//READ
console.log("My client's name:" + myclient.data.name);
console.log("My client's website:" + myclient.data.website);
// Write
myclient.data.name = "Acme Corp ASIA LTD.";
myclient.data.website = "www.acmecorp.biz";
           

雖然可以通過這種方法設定,但這不是最佳實踐,因為能過setting方法設定時,會執行更多的任務,比如,辨別我們的模型為髒資料,儲存上一個值,在此之後,我們可以拒絕或者接收改變,還有一個其它的重要步驟。

Mappings

當在model中定義了一個字段,我們可以為這個字段定義一個mapping屬性,告訴字個字段從哪擷取資料。

{
    "success" :"true",
    "id":"id",
    "records":[
        {
        "id": ,
        "name": "Acme corp2",
        "phone": "+52-01-55-4444-3210",
        "x0001":"acme_file.pdf"
        }
    ]
}
           

比如上面有一個”x0001”的字段,可以它在client模型中的名稱為contractFileName, 是以我們需要使用mapping屬性。

Ext.define('Myapp.model.Client',{
    extend: 'Ext.data.Model',
    idProperty: 'clientId ',
    fields:[
        {name: 'clientId', type: 'int' },
        {name: 'name' , type: 'string'},
        {name: 'phone' , type: 'string'},
        {name: 'contractFileName', type: 'string', mapping:'x0001'}
    ]
});
           

Validations

從Ext JS 4開始,可以直接在model中對資料進行驗證。

Ext.define('MyApp.model.User', {
    extend: 'Ext.data.Model',
    fields: ...,
    validators: {
        name: [  //需要校驗的字段
            'presence',
            { type: 'length', min:  },
            { type: 'exclusion', list: ['Bender'] }
        ]
    }
});
           

Validators是一個對像,這個對像的每個鍵對應于要檢驗的字段名。每個字段的檢驗規則,可以為一個對像配置,或者這些配置組成的數組。在這個例子中,我們是驗證name字段,它的長度為7個字元以上,它的值不能為 “Bender”.

有的驗證規則,接收額外的配置 - 比如 length校驗器,可以有min 和 max屬性。 format 可以一個matcher等等。 以下是Ext JS内置的檢驗器,并且還可以自定義規則

  • Presence - 保證這個字段必須有值,空字元串無效
  • Length - 保證字元串的長度在min和max之前。min 和 max這兩個限制條件是可選的
  • Format - 保證字元串比對一個正規表達式format.
  • Inclusion - 保證一個值必須在指定的清單中,比如,gender隻能為 male或者 female
  • Exclusion - 保證一個值不能出現在指定的清單中
更多的檢驗器,可以檢視Ext.data.validator空間下的子類, 了解如何傳遞參數, 比如Presence, http://docs.sencha.com/extjs/6.0.2-classic/Ext.data.validator.Presence.html

接下來,讓我們建立一個違返這些規則的一條記錄

/ now lets try to create a new user with as many validation
// errors as we can
var newUser = new MyApp.model.User({
    id: ,
    name: 'Bender'
});

// run some validation on the new user we just created
console.log('Is User valid?', newUser.isValid());

//returns 'false' as there were validation errors

var errors = newUser.getValidation(),
    error  = errors.get('name');

console.log("Error is: " + error);
           

在上面的代碼中,最關鍵的函數是getValidation(), 它運作所有的檢驗規則,但隻傳回每一個字段,第一條沒有通過的規則。這些檢驗記錄是被懶性建立,隻在需要的時候才會更新。在這裡,錯誤為Length must be greater than 7.

當我們提供的名字超過規定的長度時

newUser.set('name', 'Bender Bending Rodriguez');
errors = newUser.getValidation();
           

這條記錄符合所有的檢驗,它包含7個字元,同時也不跟清單中的Bender比對。

newUser.isValid()将會傳回true, 當我們調用getValidation(), 新的檢驗記錄将被更新,資料不在是髒資料。它所有的字段都将設定為true.

以下是另一個例子

Ext.define('Myapp.model.Client',{
    extend:'Ext.data.Model',
    idProperty:'clientId ',
    fields:[
        {name: 'clientId', type: 'int' },
        {name: 'name' , type: 'string'},
        {name: 'phone' , type: 'string'},
        {name: 'website' , type: 'string'},
        {name: 'status' , type: 'string'},
        {name: 'clientSince' , type: 'date', dateFormat: 'Y-m-d H:i'}
    ],
    validators:{
        name:[
            { type:'presence'}
        ],
        website:[
            { type:'presence', allowEmpty:true},
            { type:'length', min: , max: }
        ]
    }
}); 

//Step 1
var myclient = Ext.create('Myapp.model.Client',{
    clientId : '10001',
    name : 'Acme corp',
    phone: '+52-01-55-4444-3210',
    website: 'www.acmecorp.com',
    status: 'Active',
    clientSince: '2010-01-01 14:35'
});

if (myclient.isValid()){ //Step 2
    console.log("myclient model is correct");
}
console.log(myclient);
console.log("My client's name is = " + myclient.data.name);
console.log("My client's website is = " + myclient.data.website);
// SET methods //Step 3
myclient.set('name','');
myclient.set('website','');
if (myclient.isValid()){//Step 4
    console.log("myclient model is correct");
} else {
//Step 5
    console.log("myclient model has errors");
    var errors = myclient.validate();
    errors.each(function(error){
        console.log(error.field,error.message);
    });
}
           

在第5步,我們使用了validate方法,它将傳回一個驗證失敗的集合。并且輸出字段以及錯誤資訊. 這個集合的類型為Ext.data.ErrorCollection, 它擴充于Ext.util.MixedCollection. 是以我們可以使用each方法來周遊每一條錯誤記錄.

Ext JS Data Package

Custom field types

在我們的application中不同的model之前,經常需要反複使用某個字段類型,在 Ext JS 4在,我們可以通過自動義檢驗器實作。 但在Ext JS 5開始,推薦使用自定義字段類型來代替自定義檢驗器。能過以下代碼,我們将建立一個自定義字段

Ext.define('Myapp.fields.Status',{
    extend: 'Ext.data.field.String', //Step 1
    alias: 'data.field.status',//Step 2
    validators: {//Step 3
    type: 'inclusion',
    list: [ 'Active', 'Inactive'],
    message: 'Is not a valid status value, please select the
        proper options[Active, Inactive]'
    }
});
           
  1. 繼承于Ext.data.field.String
  2. 我們為這個字段類型,定義了一個别名。這個别名不能重複,或者說覆寫一個已經存在于Ext.data.field中的子類
  3. 為這個字段類型設定校驗器
Ext.define('Myapp.model.Client',{
    extend:'Ext.data.Model',
    idProperty:'clientId ',
    fields:[
        {name: 'clientId', type: 'int' },
        {name: 'name' , type: 'string'},
        {name: 'phone' , type: 'string'},
        {name: 'website' , type: 'string'},
        {name: 'status' , type: 'status'}, //Using custom field
        {name: 'clientSince' , type: 'date', dateFormat: 'Y-m-d H:i'}
    ],
    validators:{
            ...
    }
});
           

在這裡,我們使用的是

{name: 'status', type: 'status'}

, 這是因為我們給自定義類型,取了一個别名(alias: ‘data.field.status’). 下面讓我們建立一段代碼來測試

var myclient = Ext.create('Myapp.model.Client',{
    clientId: '10001',
    name: 'Acme corp',
    phone: '+52-01-55-4444-3210',
    website: 'www.acmecorp.com',
    status: 'Active',
    clientSince: '2010-01-01 14:35'
});
if(myclient.isValid()){
    console.log("myclient model is correct");
}
// SET methods
myclient.set('status','No longer client');
if(myclient.isValid()){
    console.log("myclient model is correct");
} else {
    console.log("myclient model has errors");
    var errors = myclient.validate();
    errors.each(function(error){
        console.log(error.field,error.message);
    });
}
           

Relationships

我們可以建立模型之間的關系。比如,一個Client會接觸多名employees, 享受多種Service.

  • Employees for contact(Client聯系的員工, 包含name, title, gender, email, phone, cell phone, 等字段)
  • Services (service ID, service name, service price, branch where service provided)

在Ext JS中支援one-to-many, one-to-one, and many-to-many關聯

關于更多Association Type,可以檢視http://docs.sencha.com/extjs/6.2.0-classic/Ext.data.schema.Association.html

One-to-many associations

Ext.define('Myapp.model.Client',{
    extend:'Ext.data.Model', // step 1
    requires: ['Myapp.model.Employee'],
    idProperty:'id ',
    fields:[.... ],
    hasMany:{
        model:'Myapp.model.Employee',
        name:'employees',
        associationKey: 'employees'
    }
});
           

使用了hasManay屬性,我們定義了一個one-to-many的關系。hasMany屬性可以是一個對像數組,每一個對像都包含一個model屬性. 表明一個Client執行個體,可以有多個 Myapp.model.Employee對像。

此外,我們可以在建立Client類時,定義獲得與它相關聯model資料的函數名稱。在這裡,我們使用employees; 如果我們沒有定義任何的函數名稱, Ext JS将使用子模型的名字,加上”s”, 作為函數名稱

現在我們建立一個Employee類,它位于appcode/model/Employee.js

Ext.define('Myapp.model.Employee',{
    extend:'Ext.data.Model',
    idProperty:'id ',
    fields:[
        {name: 'id', type: 'int' },
        {name: 'clientid' , type: 'int'},
        {name: 'name' , type: 'string'},
        {name: 'phone' , type: 'string'},
        {name: 'email' , type: 'string'},
        {name: 'gender' , type: 'string'}
    ]
});
           

為了測試model之前的關系,我們建立以下代碼

var myclient = Ext.create('Myapp.model.ClientWithContacts',{
    id: ,
    name: 'Acme corp',
    phone: '+52-01-55-4444-3210',
    website: 'www.acmecorp.com',
    status: 'Active',
    clientSince: '2010-01-01 14:35'
});
//Step 2
myclient.employees().add(
    {
        id:, clientId:, name:'Juan Perez', phone:'+52-05-2222-333',
        email:'[email protected]', gender:'male'},
    {
        id:, clientId:, name:'Sonia Sanchez', phone:
            '+52-05-1111-444', email:'[email protected]',gender:'female'}
);
//Step 3
myclient.employees().each(function(record){
    console.log(record.get('name') + ' - ' + record.get('email') );
});
           
  1. 我們建立一個Client的記錄
  2. 我們執行employees方法,這個方法是在定義關聯類時,指定的name屬性。這個方法傳回一個Ext.data.Store執行個體。它是一個集合,用來管理相應的模型。是以我們可以通過它的add方法,添加兩個Employee對像。
  3. 我們周遊myclient中的employees集合。每個記錄表示一個Employee對像,是以我們可以使用get方法,獲得它每個字段的值

One-to-one associations

為了描述這種關系,我們建立了一個合同類,來與 Client相關聯

Ext.define('Myapp.model.Contract',{
    extend:'Ext.data.Model',
    idProperty:'id ',
    fields:[
        {name: 'id', type: 'int' },
        {name: 'contractId', type: 'string'},
        {name: 'documentType', type: 'string'}
    ]
});
           

如果你所看到的,這是一個正常的類。現在我們來定義Customer類

Ext.define('Myapp.model.Customer',{
    extend:'Ext.data.Model',
    requires: ['Myapp.model.Contract'],
    idProperty:'id ',
    fields:[
        {name: 'id', type: 'int'},
        {name: 'name' , type: 'string'},
        {name: 'phone' , type: 'string'},
        {name: 'website' , type: 'string'},
        {name: 'status' , type: 'string'},
        {name: 'clientSince' , type: 'date', dateFormat: 'Y-m-d H:i'},
        {name: 'contractInfo' , reference: 'Contract', unique:true}
    ]
});
           

我們添加了一個字段,稱為contractInfo, 但我們使用reference代替了type屬性。它表示contractInfo的值為一個Contract類的執行個體。

var myclient = Ext.create('Myapp.model.Customer',{
    id: ,
    name: 'Acme corp',
    phone: '+52-01-55-4444-3210',
    website: 'www.acmecorp.com',
    status: 'Active',
    clientSince: '2010-01-01 14:35',
    contractInfo:{
        id:,
        contractId:'ct-001-444',
        documentType:'PDF'
    }
});
           

我們在這裡是直接為contractInfo指派一個對像. 如下所示

Ext JS Data Package

你看到上圖中contractInfo是一個對像,它的字段跟Contract模型中定義的字段相同。如果contractInfo對像中沒有定義Contract中的字段,則整個contractInfo則為undefined.

Ext JS Data Package

Exmaple

我們以一個部落格應用為例,它有User和Post兩個model. 每一個User都可以建立Post. 是以在這種情況下,一個user 可以有多個post. 但一個post隻能屬于建立它的使用者. 這是一個ManyToOne的關系。我們可以通過下面的代碼,表達它們的關系

Ext.define('MyApp.model.User', {
    extend: 'MyApp.model.Base',

    fields: [{
        name: 'name',
        type: 'string'
    }]
});

Ext.define('MyApp.model.Post', {
    extend: 'MyApp.model.Base',

    fields: [{
        name: 'userId',
        reference: 'User', // the entityName for MyApp.model.User
        type: 'int'
    }, {
        name: 'title',
        type: 'string'
    }]
});
           

通過以上面的方式,可以很容易的實作我們應用中不同Model之前的關系。每一個Model可以跟其它的Model産生關聯。同時,模型定義的順序可以是任意的。一旦,你建立了這個Model類型的記錄, 通過這條記錄,就可以周遊與之相關聯的資料。如果你想要獲得一個使用者的所有post. 你可以通過以下的代碼

// Loads User with ID 1 and related posts and comments
// using User's Proxy
MyApp.model.User.load(, {
    callback: function(user) {
        console.log('User: ' + user.get('name'));

        user.posts(function(posts){
            posts.each(function(post) {
                console.log('Post: ' + post.get('title'));
            });
        });
    }
});
           

每一個User model都有多個Posts, 它會被添加一個user.posts()函數,調用user.posts()傳回一個Post model的Store集合.

不僅我們可以加載資料,還可以建立一條新的記錄

user.posts().add({
    userId: ,
    title: 'Post 10'
});

user.posts().sync();
           

上面的代碼會建立一個Post執行個體,并且自動指派userId字段為使用者的id. 調用sync(),通過post的代理,儲存這個新的Post(schema的代理配置決定). 這是一個異步操作,如果你需要獲得操作完成後的通知,可以給它傳遞一個回調函數。

相返的,在Post model中,也會生成一個新的方法

MyApp.model.Post.load(1, {
    callback: function(post) {

        post.getUser(function(user) {
            console.log('Got user from post: ' + user.get('name'));
        });                           
    }
});

MyApp.model.Post.load(2, {
    callback: function(post) {
        post.setUser();                         
    }
});
           

在加載函數中, getUser()是一個異步操作,是以需要一個回調函數,來獲得使用者的執行個體。setUser()方法隻是簡單的更新userId(有時稱為 “外鍵”)為100,并且儲存Post model. 通常也可以傳遞一個回調函數,在儲存操作完成後,觸發回調函數,知道操作是否成功.

加載嵌套的資料

當定義了associations後,可以在單個請求中,加載相關聯系的記錄。比如, 一個伺服器響應如下資料

{
    "success": true,
    "user": [{
        "id": ,
        "name": "Philip J. Fry",
        "posts": [{
            "title": "Post 1"
        },{
            "title": "Post 2"
        },{
            "title": "Post 3"
        }]
    }]
}
           

架構可以自動解析這個當個響應中的嵌套資料。而不用建立兩個請求。

Stores

一個store是某一個模型對像的集合(比如多個users集合,invoices集合),作為一個用戶端緩存來管理我們本地的資料。我們可以使用這個集合執行多種任務,比如排序,分組, filtering. 我們也可以使用一個有效的proxies從伺服器上拉資料,和一個render解析伺服器響應,并且填充這個集合.

一個store經常被添加到widgets/componens,用來顯示資料。比如元件中的grid, tree, combo box, 或者data view,都會使用一個store來管理資料。當我們建立一個自定義 widget,我們也應該使用一個store來管理資料。

為了建立一個store, 我們需要使用Ext.data.Store類。代碼如下

Ext.define('MyApp.store.Customers',{
    extend : 'Ext.data.Store', //Step 1
    model : 'Myapp.model.Customer' //Step 2
});

//Ext JS 6的寫法 http://docs.sencha.com/extjs/6.0.2-classic/guides/core_concepts/data_package.html

var store = new Ext.data.Store ({
    model: 'MyApp.model.User'
});

store.load({
    callback:function(){
        var first_name = this.first().get('name');
         console.log(first_name);
    }
});
           
  1. 為了定義一個store, 我們需要讓它繼承于Ext.data.Store, 這個類負責處理models.
  2. 我們需要為建立的store,關聯一個模型。它必須為一個有效的model類。

當我們建立了一個store類後,可以使用這個store來存取資料

var store = Ext.create("MyApp.store.Customers");
//counting the elements in the store
console.log(store.count());
           

Adding new elements

我們可以建立一個Customer資料,并且通過add 或者insert方法,将這個新的元素添加到store.

//Step 1 (define /create new model instance)
var mynewcustomer = Ext.create('Myapp.model.Customer',{
    id: ,
    name: 'Acme corp',
    phone: '+52-01-55-4444-3210',
    website : 'www.acmecorp.com',
    status: 'Active',
    clientSince: '2010-01-01 14:35',
    contractInfo:{
        id:,
        contractId:'ct-001-444',
        documentType:'PDF'
    }
});
store.add(mynewcustomer); //Step 2
console.log("Records in store:" + store.getCount() );
           

也可以使用以下的方法

//Method 2 for add Records
store.add({
    id: ,
    name: 'Candy Store LTD',
    phone: '+52-01-66-3333-3895',
    website : 'www.candyworld.com',
    status: 'Active',
    clientSince: '2011-01-01 14:35',
    contractInfo:{
        id:,
        contractId:'ct-001-9998',
        documentType:'DOCX'
    }
});
console.log("Records in store:" + store.getCount());
           

如果想一次添加多個,我們可以傳遞一個數組給 add方法。

// Method  for add multiple records
var mynewcustomer = Ext.create('Myapp.model.Customer', { ...});
var mynewcustomerb = Ext.create('Myapp.model.Customer', {
...});
store.add([mynewcustomer, mynewcustomerb]);
console.log("Records in store:" + store.getCount());
           

store使用add方法,會将新的元素添加到集合的最後位置,如果我們想添加一個元素到第一的位置,或才其它位置,則可以使用

insert

方法

Inline data

new Ext.data.Store({
    model: 'MyApp.model.User',
    data: [{
        id: ,
        name: "Philip J. Fry"
    },{
        id: ,
        name: "Hubert Farnsworth"
    },{
        id: ,
        name: "Turanga Leela"
    },{
        id: ,
        name: "Amy Wong"
    }]
});
           

周遊store中的記錄

store.each(function(record, index){
    console.log(index, record.get("name"));
});
           

each方法接受一個函數。這個函數将被store中的每一條記錄執行。這個匿名函數接收兩個參數,每一次周遊時的record和index.

我們也可以給each方法傳遞第二個參數,表明這個匿名函數的作用域。

store中檢索記錄

通過位置索引

如果我們想獲得指定位置的model, 我們可以使用getAt方法

var modelTest = store.getAt(); 
console.log(modelTest.get("name"));
           

First and last records

var first = store.first();
var last = store.last();
console.log(first.get("name"), last.get("name"));
           

* By range*

var list = store.getRange(,); //擷取位置1開始的,3條記錄,包含位1, 2, 3的記錄
Ext.each(list,function(record,index){
    console.log(index,record.get("name"));
});
           

By ID

var record = store.getById();
console.log(modelTest.get("name"));
           

Removing records

我們有三種方法删除記錄

store.remove(record);
store.each(function(record,index){
console.log(index,record.get("name"));
});
           

在上面的代碼中,remove傳遞的參數是一個model引用。

我們也可以一次性删除多個記錄。我們隻需要把要删除的model數組傳遞給它

store.remove([first,last]);
store.each(function(record,index){
console.log(record.get("name"));
});
           

有時,我們沒有model的引用,在這種情況下,我們可以通過記錄的位置進行删除

store.removeAt();
store.each(function(record,index){
    console.log(index,record.get("name"));
});
           

如果我們想要删除所有的記錄, 我們僅需要調用removeAll方法.

store.removeAll();
console.log("Records:",store.count()); // Records: 0
           

Sorting and Grouping

new Ext.data.Store({
    model: 'MyApp.model.User',

    sorters: ['name','id'],
    filters: {
        property: 'name',
        value   : 'Philip J. Fry'
    }
});
           

Retrieving remote data

到目前為此,我們使用的都是本地資料,但在真實的應用中,我們的資料是儲存在資料庫當中的,或者需要通過web服務才能擷取到資料。

Ext JS使用proxies發送和接收資料。proxies通常配置在store或者model。也可以像我們在最開始的base model代碼,在schema中定義.

在Ext JS中Proxies負現處理model的資料,我們可以說proxy是一個處理和操作資料(parsing, organizing等等)的類. 是以store可以通過proxie來讀取和儲存,或者發送資料到伺服器。

一個proxy使用一個reader來解碼接收到的資料,使用一個writer将資料編碼為正确的格式,并且發送到資料源。reader有Array, JSON, XML三種類型,而writer隻有JSON, XML類型。

proxies主要分為兩種類型, Client 和 Server,如果我們想要改變我們資料源,我們僅需要改變proxy類型,而其它的都不需要改變。比如,我們為store或model定義一個Ajax proxy, 然後我們可以将它設定為 local storage proxy.

Client Proxy

  • Memory
  • Local Storage

Server Proxy

  • AJAX
  • JSONP
  • REST

Ajax proxy

Ext.define('Myapp.store.customers.Customers',{
    extend:'Ext.data.Store',
    model: 'Myapp.model.Customer',
    proxy:{
        type:'ajax',
        url: 'serverside/customers.php',
        reader: {
            type:'json',
            rootProperty:'records' //json中的資料字段
        }
    }
});
           

之後我們就可以建立這個store, 并且從遠端加載資料

//Step 1
var store = Ext.create("Myapp.store.customers.Customers");
//Step 2
store.load(function(records, operation, success) {
    console.log('loaded records');//Step 3
    Ext.each(records, function(record, index, records){
        console.log( record.get("name") + ' - ' +
        record.data.contractInfo.contractId );
    });
});
           

在我們執行例子之前,我們應該在伺服器上建立一個serverside/customers.json

{
    "success":true,
    "id":"id",
    "records":[
    {
        "id": ,
        "name": "Acme corp2",
        "phone": "+52-01-55-4444-3210",
        "website": "www.acmecorp.com",
        "status": "Active",
        "clientSince": "2010-01-01 14:35",
        "contractInfo":{
            "id":,
            "contractId":"ct-001-444",
            "documentType":"PDF"
        }
    },{
        "id": ,
        "name": "Candy Store LTD",
        "phone": "+52-01-66-3333-3895",
        "website": "www.candyworld.com",
        "status": "Active",
        "clientSince": "2011-01-01 14:35",
        "contractInfo":{
            "id":,
            "contractId":"ct-001-9998",
            "documentType":"DOCX"
        }
    }
]
}
           

Readers

Readers讓Ext JS知道如何處理響應.

如我們上面的例子

reader: {
    type:'json',
    rootProperty:'records'
}
           

type屬性表示使用什麼格式來解析。它可以為json, xml, array.

rootProperty允許我們定義一個在伺服器響應中的屬性名字,伺服器傳回的資料都是這個字段下。在JSON響應中,它可以為一個數組。 在我們的例子中,我們設為records, 因為我們的JSON使用的是這個名字。如果我們嵌套的是一個對像,可以使用如下的方式

{
    "success" :"true",
    "id":"id",
    "output":{
        "appRecords":[{ our data .... }],
        "customerRecords":[{ our data .... }]
    }
}
           
reader: {
    type:'json',
    rootProperty:'output.customerRecords'
}
           

XML reader

XML reader相對于JSON來說有些變化,是以,我們需要在這個render中,配置其它屬性, 以確定XML可以正常解析

proxy:{
    type:'ajax',
    url: 'serverside/customers.xml',
    reader: {
        type: 'xml',
        rootProperty: 'data',
        record:'customer',
        totalProperty: 'total',
        successProperty: 'success'
    }
}
           
  • rootProperty 定義了 XML檔案中查找records的節點。
  • 我們還添加了一個record屬性,在XML中每一個記錄的名稱
  • totalProperty和successProperty, store有的功能需要使用到這個标簽下的值

現在,讓我們建立一個XML檔案 serverside/customers.xml

<?xml version="1.0" encoding="UTF-8"?>
<data>
    <success>true</success>
    <total>2</total>
    <customer>
        <id>10001</id>
        <name>Acme corp2</name>
        <phone>+52-01-55-4444-3210</phone>
        <website>www.acmecorp.com</website>
        <status>Active</status>
        <clientSince>2010-01-01 14:35</clientSince>
        <contractInfo>
            <id>444</id>
            <contractId>ct-001-444</contractId>
            <documentType>PDF</documentType>
        </contractInfo>
    </customer>
    <customer>
        <id>10002</id>
        <name>Candy Store LTD</name>
        <phone>+52-01-66-3333-3895</phone>
        <website>www.candyworld.com</website>
        <status>Active</status>
        <clientSince>2011-01-01 14:35</clientSince>
        <contractInfo>
            <id>9998</id>
            <contractId>ct-001-9998</contractId>
            <documentType>DOCX</documentType>
        </contractInfo>
</customer>
</data>
           

Sending data

Ext.define('Myapp.store.customers.CustomersSending',{
    extend:'Ext.data.Store',
    model: 'Myapp.model.Customer',
    autoLoad:false,
    autoSync:true,
    proxy:{
        type:'ajax',
        url: 'serverside/customers.json',
        api: {
            read : 'serverside/customers.json',
            create : 'serverside/process.php?action=new',
            update : 'serverside/process.php?action=update',
            destroy : 'serverside/process.php?action=destroy'
        },
        reader: {
            type:'json',
            rootProperty:'records'
         },
        writer:{
            type:'json',
            encode:true,
            rootProperty:'paramProcess',
            allowSingle:false,
            writeAllFields:true,
            root:'records'
        },
        actionMethods:{
            create: 'POST',
            read: 'GET',
            update: 'POST',
            destroy: 'POST'
        }
    }
});
           
  1. 設定了一個api 屬性: 為CRUD分别設定一個url
  2. 設定一個writer屬性
    • type: ‘json’: 以JSON格式發送資料
    • encode: 表示在傳遞資料到伺服器前,是否經過Ext JS進行編碼
    • rootProperty: 包資訊的屬性的名字
    • writeAllFields: 傳遞所有的記錄到伺服器,如果為false, 則隻發送個改的字段。
  3. 對CRUD所對應的方法類型
Ext.Loader.setConfig({
    enabled: true,
    paths:{ Myapp:'appcode' }
});
Ext.require([
    'Ext.data.*',
    'Myapp.model.Contract',
    'Myapp.model.Customer',
    'Myapp.store.customers.CustomersSending'
]);
Ext.onReady(function(){
    var store = Ext.create("Myapp.store.customers.CustomersSending");
//Step 1
    store.load({ // Step 2 load Store in order to get all records
        scope: this,
        callback: function(records, operation, success) {
            console.log('loaded records');
            Ext.each(records, function(record, index, records){
                console.log( record.get("name") + ' - ' +
                        record.data.contractInfo.contractId );
            });
            var test=;
            console.log('Start adding model / record...!');
// step 3 Add a record
            var mynewCustomer = Ext.create('Myapp.model.Customer',{
                clientId : '10003',
                name: 'American Notebooks Corp',
                phone: '+52-01-55-3333-2200',
                website : 'www.notebooksdemo.com',
                status : 'Active',
                clientSince: '2015-06-01 10:35',
                contractInfo:{
                    "id":,
                    "contractId":"ct-00301-99990",
                    "documentType":"DOC"
                }
            });
            //因為我們設定了autoSync屬性為true,是以會發送資料到伺服器
            store.add(mynewCustomer);
// step 4 update a record
            console.log('Updating model / record...!');
            var updateCustomerModel = store.getAt();
            updateCustomerModel.beginEdit();
            updateCustomerModel.set("website","www.acmecorpusa.com");
            updateCustomerModel.set("phone","+52-01-33-9999-3000");
            updateCustomerModel.endEdit();
// step 5 delete a record
            console.log('deleting a model / record ...!');
            var deleteCustomerModel = store.getAt();
            store.remove(deleteCustomerModel);
        }
    });
});
});
           

Schema

介紹

一個Schema是相關Ext.data.Model和之前關系Ext.data.schema.Association的集合。

Schema Instances

預設情況下這個類以單例建立,作為模式服務于所有的實體(model中沒有明确指明schema配置, 也沒有繼承schema配置)

當一個entity指定了一個schema, 相關聯的類,就能夠查找(or create)這個entity類的執行個體,然後繼承這個執行個體。

Importnat: 所有相關的entities 必須屬性單個schema執行個體,這樣才能正确的連接配接它們的關聯性。

Configuring Schemas

控制schema配置的最好方式是定義一個 base model

Ext.define('MyApp.model.Base', {
    extend: 'Ext.data.Model',

    fields: [{
        name: 'id',
        type: 'int'
    }],

    schema: {
        namespace: 'MyApp.model',  // generate auto entityName

        proxy: {     // Ext.util.ObjectTemplate
            type: 'ajax',
            url: '{entityName}.json',
            reader: {
                type: 'json',
                rootProperty: '{entityName:lowercase}'
            }
        }
    }
});
           

通過使用base class, 你所有的model類在聲明建立時,都已要有擁有schema中的預設配置。

Relative Naming

當描述兩個model之前的關系時,需要使用簡單的名稱,它不包含共同的namespace部分. 它稱為entityName, 而不是類名稱。 預設情況下, entityName是整個類的名稱。但是,如果使用了namespace屬性時,共同的部分會被丢棄, 我們可以得到簡單的名稱。

MyApp.model.Foo

entityName

Foo

.

MyApp.model.foo.Thing

foo.Thing

Association Naming

在描述關系時,有不同的術語。最能說明闡明這些術語的一個簡單例子是,User與 Group這種多對多的關系

  • entityName - “User”和”Group”為

    entityName

    , 它們都是從完整的類名中簡寫獲得(App.model.User and App.model.Group)
  • associationName - 當我們談論關系時,特别是many-to-many. 給它們命名就非常重要。Associations(關聯)不被涉級的任何一個實體擁有, 是以這時,這個名稱就類似于一個entityName. 預設的associationName為”GroupUsers”.
  • left and right - Association(關聯)描述了兩個執行個體之前的關系。在談論具體的關系時,我們可以使用兩個參與者的entityName(比如User or Group). 當談論一個抽像的associations

    比如”GroupUsers”這個關系,”User”稱為 left, 而”Group”稱為right. 在一個many-to-many的關聯時, 我們可以任意的選擇left和right. 當涉及到外鍵時,left 則是被包含的外鍵

Custom Naming Conventions

Schema的一個工作就是管理名字的生成(比如entityName). 這個工作它是委派給namer類。 如果你需要以其它的方法生成名字,你可以為你的定提供自定義的namer

Ext.define('MyApp.model.Base', {
     extend: 'Ext.data.Model',

     schema: {
         namespace: 'MyApp.model',
         namer: 'custom'
     }
 });
           

類似于alias建立一個類,如下所示

Ext.define('MyApp.model.CustomNamer', {
     extend: 'Ext.data.schema.Namer',
     alias: 'namer.custom',
     ...
 });
           

繼續閱讀