Ext JS Data Package

Data package讓我們可以在我們的代碼或者application中加載和保存數據。最重要的一點是,data package可以讓我們鏈接或者綁定數據到Ext JS組件。data apckage是由多個類組成,其中最重要的三個類爲

  • Ext.data.Model
  • Store
  • Ext.data.proxy.Proxy
    每個application差不多都會使用上面的三個類, 它們受到很多衛星類(圍繞它們)的支持, 如下圖所示

這裏寫圖片描述

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")[0];
        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: 200,
        y: 300,
    },
    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:200, y:300},
    timeout: 50000, 
    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的關係

這裏寫圖片描述

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:200, y:300},
    timeout: 50000,
    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:10001,
    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": 10001,
        "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: 7 },
            { 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: 10,
    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: 5, max:250 }
        ]
    }
}); 

//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方法來遍歷每一條錯誤記錄.

這裏寫圖片描述

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: 10001,
    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:101, clientId:10001, name:'Juan Perez', phone:'+52-05-2222-333',
        email:'[email protected]', gender:'male'},
    {
        id:102, clientId:10001, 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: 10001,
    name: 'Acme corp',
    phone: '+52-01-55-4444-3210',
    website: 'www.acmecorp.com',
    status: 'Active',
    clientSince: '2010-01-01 14:35',
    contractInfo:{
        id:444,
        contractId:'ct-001-444',
        documentType:'PDF'
    }
});

我們在這裏是直接爲contractInfo賦值一個對像. 如下所示

這裏寫圖片描述

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

這裏寫圖片描述

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(1, {
    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: 1,
    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(100);                         
    }
});

在加載函數中, getUser()是一個異步操作,所以需要一個回調函數,來獲得用戶的實例。setUser()方法只是簡單的更新userId(有時稱爲 “外鍵”)爲100,並且保存Post model. 通常也可以傳遞一個回調函數,在保存操作完成後,觸發回調函數,知道操作是否成功.

加載嵌套的數據

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

{
    "success": true,
    "user": [{
        "id": 1,
        "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: 10001,
    name: 'Acme corp',
    phone: '+52-01-55-4444-3210',
    website : 'www.acmecorp.com',
    status: 'Active',
    clientSince: '2010-01-01 14:35',
    contractInfo:{
        id:444,
        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: 10002,
    name: 'Candy Store LTD',
    phone: '+52-01-66-3333-3895',
    website : 'www.candyworld.com',
    status: 'Active',
    clientSince: '2011-01-01 14:35',
    contractInfo:{
        id:9998,
        contractId:'ct-001-9998',
        documentType:'DOCX'
    }
});
console.log("Records in store:" + store.getCount());

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

// Method 3 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: 1,
        name: "Philip J. Fry"
    },{
        id: 2,
        name: "Hubert Farnsworth"
    },{
        id: 3,
        name: "Turanga Leela"
    },{
        id: 4,
        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(2); 
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開始的,3條記錄,包含位1, 2, 3的記錄
Ext.each(list,function(record,index){
    console.log(index,record.get("name"));
});

By ID

var record = store.getById(10001);
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(2);
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

Server Proxy

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": 10001,
        "name": "Acme corp2",
        "phone": "+52-01-55-4444-3210",
        "website": "www.acmecorp.com",
        "status": "Active",
        "clientSince": "2010-01-01 14:35",
        "contractInfo":{
            "id":444,
            "contractId":"ct-001-444",
            "documentType":"PDF"
        }
    },{
        "id": 10002,
        "name": "Candy Store LTD",
        "phone": "+52-01-66-3333-3895",
        "website": "www.candyworld.com",
        "status": "Active",
        "clientSince": "2011-01-01 14:35",
        "contractInfo":{
            "id":9998,
            "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=11;
            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":99990,
                    "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(0);
            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(1);
            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.FooentityNameFoo. MyApp.model.foo.Thingfoo.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',
     ...
 });
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章