Работа с данными в Ext JS 6

Поля

Модель определяет поля и опционально: их типы, валидацию, ассоциации и прокси. Модель определяется расширением класса Ext.data.Model. По умолчанию типом поля является 'auto'. Прокси указываются обычно в источнике данных, но можно также указывать их и в модели.

Ext.data.field.Field используется для добавления свойств в модель. Поле может быть предопределенного или кастомного типа. Предопределенные типы:
  • auto
  • boolean
  • date
  • int
  • number
  • string

Конвертация

Если у поля указан тип, то перед сохранением данные этого поля конвертируются в этот тип. Эту конвертацию выполняет встроенный метод convert. У поля с типом auto нет convert метода. Поэтому для поля с типом auto конвертации не происходит. Все другие типы имеют метод convert. Если вы хотите убрать конвертацию для таких полей в целях улучшения производительности, то надо свойство convert соответствующего поля установить значение null.

Валидация

Модели поддерживают валидацию. Предоставляют следующие валидаторы:
  • presence: позволяет убедится в том что значение для поля предоставлено пользователем;
  • format: формат можно проверить через регулярное выражение;
  • length: максимальная и минимальная длина;
  • exclusion and inclusion: можно проверить, что значение попадает или не попадает в заданное множество значений.
Пример:
Ext.define('Employee', {
    extend: 'Ext.data.Model',
    fields: [{
        name: 'id',
        type: 'int',
        convert: null
    }, {
        name: 'firstName',
        type: 'string'
    }, {
        name: 'lastName',
        type: 'string'
    }, {
        name: 'fulltime',
        type: 'boolean',
        defaultValue: true,
        convert: null
    }, {
        name: 'gender',
        type: 'string'
    }, {
        name: 'phoneNumber',
        type: 'string'
    }, ],
    validators: {
        firstName: [{
            type: 'presence'
        }, {
            type: 'length',
            min: 2
        }],
        lastName: [{
            type: 'presence'
        }, {
            type: 'length',
            min: 2
        }],
        phoneNumber: {
            type: 'format',
            matcher: '/^[(+{1})|(00{1})]+([0-9]){7,10}$/'
        },
        gender: {
            type: 'inclusion',
            list: ['Male', 'Female']
        },
    }
});

Для того чтобы создать экземпляр сущности используется метод Ext.create:
var newEmployee = Ext.create('Employee', {
    id: 1,
    firstName: 'Shiva',
    lastName: 'Kumar',
    fulltime: true,
    gender: 'Male',
    phoneNumber: '123-456-7890'
});

Через метод get и set можно читать и записывать значения:
var lastName = newEmployee.get('lastName');
newEmployee.set('gender','Female');

Отношения

Определение отношения один-ко-одному между сущностями:
Ext.define('Address', {
    extend: 'Ext.data.Model',

    fields: ['address', 'city', 'state', 'zipcode']
});
Ext.define('Employee', {
    extend: 'Ext.data.Model',

    fields: [{
        name: 'addressId',
        reference: 'Address'
    }]
});

Отношение один-ко-многим:
Ext.define('Department', {
    extend: 'Ext.data.Model',

    fields: [{
        name: 'employeeId',
        reference: 'Employee'
    }]
});
Ext.define('Division', {
    extend: 'Ext.data.Model',

    fields: [{
        name: 'departmentId',
        reference: 'Department'
    }]
});

Отношение многие-ко-многим:
Ext.define('Employee', {
    extend: 'Ext.data.Model',

    fields: [{
        name: 'empId',
        type: 'int',
        convert: null
    }, {
        name: 'firstName',
        type: 'string'
    }, {
        name: 'lastName',
        type: 'string'
    }, ],

    manyToMany: 'Project'
});
Ext.define('Project', {
    extend: 'Ext.data.Model',

    fields: ['name'],

    manyToMany: 'Employee'
});

Кастомные поля

Расширяя класс Ext.data.field можно определять кастомные поля:
Ext.define('App.field.Email', {
    extend: 'Ext.data.field.Field',
    alias: 'data.field.email',

    validators: {
        type: 'format',
        matcher: /^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/,
        message: 'Wrong Email Format'
    }
});

Сторы

Хранилища (store) представляют собой коллекции экземпляров сущностей и прокси используемых для получения данных. Хранилище определяет некоторые операции типа сортировки и фильтрации. Хранилище определяется расширением класса Ext.data.Store.

Обычно при определении хранилища указывают прокси для того чтобы хранилище знало как читать и записывать данные. Пример хранилища использующего RESTful API запрос для загрузки данных в JSON формате:
var myStore = Ext.create('Ext.data.Store', {
    model: 'Employee',
    storeId: 'mystore',
    proxy: {
        type: 'rest',
        url: '/employee',
        reader: {
            type: 'json',
            rootProperty: 'data'
        }
    },
    autoLoad: true,
    autoSync: true
});
Тут storeId это уникальный идентификатор хранилища. У этого хранилища есть load для загрузки данных через сконфигурированный прокси. Если установить свойству autoLoad значение true, тогда метод load будет вызываться автоматически при определении хранилища. При autoLoad равном false метод load надо дергать вручную.

Аналогично если установить autoSync равным true, то будет происходить автоматическая синхронизация при редактировании, удалении и добавлении записей в хранилище. В вышеприведенном примере это будет происходить через вызовы REST service API. Если autoSync равен false то надо вызывать метод sync вручную:
store.sync({
    callback: function(batch, options) {
        //  upon completion of the sync operations, irrespective of its success or failure
    },
    success: function(batch, options) {
        // when all the sync operations are completed without any exception or failure
    },
    failure: function(batch, options) {
        // if one or more operations in the sync fails
        // check the batch's exception array to see exactly what operations failed and why
    },
    scope: this
});

Если вы не хотите связывать стор с сервером или внешним хранилищем вроде LocalStorage, то можно захардкодить данные прямо в стор:
Ext.create('Ext.data.Store', {
    model: 'Employee',
    data: [{
        firstName: 'Shiva',
        lastName: 'Kumar',
        gender: 'Male',
        fulltime: true,
        phoneNumber: '123-456-7890'
    }, {
        firstName: 'Vishwa',
        lastName: 'Anand',
        gender: 'Male',
        fulltime: true,
        phoneNumber: '123-456-7890'
    }]
});

Стор поддерживает локальную и удаленную фильтрацию. Пример локальной сортировки:
var myStore = Ext.create('Ext.data.Store', {
    model: 'Employee',
    sorters: [{
        property: 'firstName',
        direction: 'ASC'
    }, {
        property: 'fulltime',
        direction: 'DESC'
    }],

    filters: [{
        property: 'firstName',
        value: /an/
    }]
});

Для удаленной сортировки и фильтрации надо установить значение true свойствам remoteSort и remoteFilter.

Доступ к хранилищу

Вам может понадобиться хранить стор в отдельном файле и тогда нужно будет как-то получать к нему доступ из других частей приложения. Через метод lookup класса StoreManager можно получать доступ к хранилищу из любого места приложения. Для этого понадобится storeId. Обратите внимание то что при создании стора контроллером, storeId перезаписывается именем стора. Пример:
Ext.create('Ext.data.Store', {
    model: 'Employee',
    storeId: 'mystore',
    proxy: {
        type: 'rest',
        url: '/employee',
        reader: {
            type: 'json',
            rootProperty: 'data'
        }
    }
});
Доступ к стору:
Ext.data.StoreManager.lookup('myStore'); 
Методы Ext.getStore является сокращением для метода Ext.data.StoreManager.lookup.

Доступ к стору также можно получить через метод getStore класса Ext.app.ViewModel. Этот метод лучше использовать, когда вы внутри ViewController:
var myStore = this.getViewModel().getStore('myStore')
Метод getStore также определен в представлении.

События хранилища

У стора есть следующие события, которые можно слушать:
  • add: добавление записи;
  • beforeload: вызывается перед загрузкой данных;
  • beforesync: вызывается перед синхронизацией;
  • datachanged: вызывается при удалении или добавлении записи;
  • load: вызывается при чтении удаленного источника данных;
  • remove: удаление записи;
  • update: обновление записи.
Ext.create('Ext.data.Store', {
    model: 'Employee ',
    storeId: 'mystore',
    proxy: {
        type: 'rest',
        url: '/employee',
        reader: {
            type: 'json',
            rootProperty: 'data'
        }
    },
    listeners: {
        load: function(store, records, options) {
            //Do something
        }
    }
});

Подписать на события стора из контроллера можно следующим образом:
init: function() {
    this.getViewModel().getStore('myStore').on('load', this.onStoreLoad, this);
}

Стор и ViewModel можно определять вместе или раздельно. Пример определения стора во ViewModel:
Ext.define('MyApp.view.employee.EmployeeModel', {
    extend: 'Ext.app.ViewModel',
    alias: 'viewmodel.employee',
    stores: {
        employee: {
            fields: [{
                name: 'id',
                type: 'string'
            }, {
                name: 'firstname',
                type: 'string'
            }, {
                name: 'lastname',
                type: 'string'
            }],
            autoLoad: false,
            sorters: [{
                property: 'firstname',
                direction: 'ASC'
            }],
            proxy: {
                type: 'rest',
                url: 'employee',
                reader: {
                    type: 'json',
                },
                writer: {
                    type: 'json'
                }
            }
        }
    }
});

Прокси

Сторы использую прокси для загрузки и сохранения данных. Есть два типа прокси: клиентские и серверные.

Клиентские прокси

Клиентский прокси используется для управления загрузкой данных и сохранения данных на клиенте. Клиентские прокси: memory, LocalStorage и SessionStorage.

Пример memory proxy:
var data = {
    data: [{
        firstName: 'Shiva',
        lastName: 'Kumar',
        gender: 'Male',
        fulltime: true,
        phoneNumber: '123-456-7890'
    }, {
        firstName: 'Vishwa',
        lastName: 'Anand',
        gender: 'Male',
        fulltime: true,
        phoneNumber: '123-456-7890'
    },
    };
    var myStore = Ext.create('Ext.data.Store', {
        model: 'Employee',
        data: data,
        proxy: {
            type: 'memory',
            reader: {
                type: 'json',
                rootProperty: 'Employee'
            }
        }
    });

LocalStorage - это key-value-pair хранилище добавленное в HTML5. Пример  LocalStorage proxy:
var myStore = Ext.create('Ext.data.Store', {
    model: 'Benefits',
    autoLoad: true,
    proxy: {
        type: 'localstorage',
        id: 'benefits'
    }
});

SessionStorage - это браузерное хранилище добавленное в HTML5. Оно хранит данные до тех пор пока существует сессия, после истечения срока сессии данные удаляются. Пример SessionStorage proxy:
var myStore = Ext.create('Ext.data.Store', {
    model: 'Benefits',
    autoLoad: true,
    proxy: {
        type: 'localstorage',
        id: 'benefits'
    }
});

Серверные прокси

Серверный прокси взаимодействует с сервером для чтения и сохранения данных.
  • Ajax: используется для асинхронных запросов.
  • Direct: использует Ext.Direct для взаимодействия с сервером.
  • JSONP (JSON with padding): полезно при необходимости посылать запрос в другой домен. Ajax может использоваться только для запросов в одном домене.
  • REST: используется для Ajax-запросов к серверу через RESTful HTTP действия вроде GET, POST, PUT и DELETE.
Выше уже приводился пример REST proxy. А вот пример JSONP:
var myStore = Ext.create('Ext.data.Store', {
    model: 'Products',
    proxy: {
        type: 'jsonp',
        url: 'http://domain.com/products',
        callbackKey: 'productsCallback'
    }
});