Краснов Александр ExtJS Classic Создание классических приложений Вспомнить все • ExtJS - библиотека JavaScript для разработки веб-приложений и пользовательских интерфейсов • Classic – классические приложения (desktop) • Modern – планшеты, телефоны • Sencha CMD – приложение для создания и последующей работой с проектом • app build – построение проекта Создание первого приложения «Галопом по Европе» • Загружаем GPL файлы • Генерация проекта с помощью Sencha CMD • Знакомство с MVVM • Создание собственного раздела • Посторонние проекта • Публикация проекта Поехали! • Скачиваем GPL лицензию https://www.sencha.com/legal/GPL/ • Устанавливаем Sencha CMD https://www.sencha.com/products/extjs/cmd-download/ • Выполняем команду sencha -sdk "D:\ext-6.0.0-gpl\" generate app MyApp classic “D:\MyApp“ В каталоге с приложением выполняем: app build Мой проект • Открываем проект в Visual Studio как Web Site • Добавляем файл web.config и дописываем следующий код <system.webServer> <staticContent> <remove fileExtension=".json"/> <mimeMap fileExtension=".json" mimeType="application/json"/> <remove fileExtension=".appcache"/> <mimeMap fileExtension=".appcache" mimeType="xml/text" /> <remove fileExtension=".woff"/> <mimeMap fileExtension=".woff" mimeType="font/x-woff" /> <remove fileExtension=".woff2"/> <mimeMap fileExtension=".woff2" mimeType="application/font-woff2"/> </staticContent> </system.webServer> • Запускаем приложение F5(CTRL+F5) Внимание, бывают «обломы» Даже разработчики sencha ошибаются Иногда после выполнения команды app build выходит ошибка Sencha CMD не смогла сгенерировать css стили. Что делать? • удалить данные из папки build • изменить тему в файле app.json Знакомство «Тернистый путь к знаниям» • app – папка файлами приложения • model – модели • store – хранилища • view - представления • build - папка с построенным приложением для публикации • ext – папка с файлами extjs’a • resources – хранение статичных файлов (jpg, json, xml и т.д.) • sass - стили • app.json – настройка приложения Далее подробно Все создаваемые(используемые) объект в приложении требуется хранить в папке app. И дополнительно прописать их в файле app.json Данное действие позволит приложению sencha cmd скомпилировать все js – файлы в один Модели Ext.define('MyApp.model.User', { extend: 'Ext.data.Model', // наследование fields: [ {name: 'name', type: 'string'}, {name: 'age', type: 'int'} ] }); На уровень модели можно накладывать валидацию validators: { age: 'presence', name: { type: 'length', min: 2 }, gender: { type: 'inclusion', list: ['Male', 'Female'] }, username: [ { type: 'exclusion', list: ['Admin', 'Operator'] }, { type: 'format', matcher: /([a-z]+)[0-9]{2,3}/i } ] } Представление Ext.define('MyApp.view.main.List', { // соответствует расположению в папке extend: 'Ext.grid.Panel', xtype: 'mainlist', requires: [ 'MyApp.store.Personnel' ], // обязательные элементы controller: 'main', // ссылка на контроллер viewModel: 'main', // ссылка на view model title: 'Personnel', store: { type: 'personnel' }, // ссылка на хранилище columns: [ { text: 'Name', dataIndex: 'name' }, { text: 'Email', dataIndex: 'email', flex: 1 }, { text: 'Phone', dataIndex: 'phone', flex: 1 } ], listeners: { // события select: 'onItemSelected' } }); MyApp.view.main.List Контроллер Ext.define('MyApp.view.main.MainController', { extend: 'Ext.app.ViewController', // базовый класс alias: 'controller.main', // псевдоним для дальнейшего использования onItemSelected: function (sender, record) { // метод определенный во view Ext.Msg.confirm('Confirm', 'Are you sure?', 'onConfirm', this); }, onConfirm: function (choice) { if (choice === 'yes') { // } } }); Примечание: Один контроллер может быть привязан к нескольким представлениям ViewModel Ext.define('MyApp.view.main.MainModel', { extend: 'Ext.app.ViewModel', alias: 'viewmodel.main', // псевдоним data: { // данные для привязки name: 'MyApp', loremIpsum: 'Lorem ipsum dolor sit amet' } //TODO - add data, formulas and/or methods to support your view }); Есть возможность использовать «формулы» formulas: { name: { get: function (get) { var fn = get('firstName'), ln = get('lastName'); return (fn && ln) ? (fn + ' ' + ln) : (fn || ln || ''); }, set: function (value) { var space = value.indexOf(' '), split = (space < 0) ? value.length : space; this.set({ firstName: value.substring(0, split), lastName: value.substring(split + 1) }); } } } Примечание: В наших проектах мы используем ViewModel для хранения бизнес-логики Хранилище (Store) Ext.define('MyApp.store.Users', { extend: 'Ext.data.Store', alias: 'store.users', model: 'MyApp.model.User', // модель data : [ {firstName: 'Seth', age: '34'}, {firstName: 'Scott', age: '72'}, {firstName: 'Gary', age: '19'}, {firstName: 'Capybara', age: '208'} ] }); Совместно с моделью Ext.define('MyApp.store.Users', { extend: 'Ext.data.Store', alias: 'store.users', fields:[‘firstName’, ‘age’] data : [ {firstName: 'Seth', age: '34'}, {firstName: 'Scott', age: '72'}, {firstName: 'Gary', age: '19'}, {firstName: 'Capybara', age: '208'} ] }); Без модели Взаимодействие с сервером AJAX (proxy) или Store REST (proxy) или DIRECT (proxy) Server Наш метод Расслабляемся и получаем удовольствие censored • Поднимаем Direct Ext.Direct.addProvider(myProvider) • Указываем в хранилище что используем proxy: direct Ext.define('ImportSubstitution.store.NavigationMenu', { extend: 'Ext.data.TreeStore', alias: 'store.navigationMenu', proxy: { type: 'direct', // указываем Direct directFn: 'PN.Util.It.Module.Extjs.ExtjsDirectRpc.GetNavigation‘ // имя функции на сервере } }); censored Вкусности • Роутинг • Плагин • Миксин (mixin) • Наследование (expand) • Bind’инг (связано с viewmodel) Роутинг Ext.define('MyApp.view.main.MainController', { extend : 'Ext.app.ViewController', routes : { 'users' : 'onUsers‘, ‘user/:id’: ‘onUser’ }, onUsers : function() { //... }, onUser: function(id){ //... } }); Ext.define('MyApp.Application', { extend : 'Ext.app.Application', //... Инициализация перехода на другую «страницу» this.redirectTo('user/1234'); defaultToken : 'home' }); Настройка «страницы» по умолчанию Плагины Можно «встроить» дополнительный функционал Ext.define('It.prototype.plugin.OpenForm', { extend: 'Ext.AbstractPlugin', alias: 'plugin.proto-openform', init: function (component) { var me = this; var results = component.getDockedItems().filter(function (dock) { return dock.dock == 'top' && dock.xtype == 'toolbar' }); var toolbar; if (results.length == 0) { // строим toolbar toolbar = component.addDocked({ xtype: 'toolbar', dock: 'top' })[0]; } else { // выбираем найденный toolbar = results[0]; } toolbar.insert(0, { // добавляем кнопку на форму xtype: 'button', text: 'Открыть форму', handler: function (btn) { me.onClick(); } }); ... } if (results.length != 0) toolbar.insert(1, '|'); применение Ext.define('It.grid.Panel', { extend: 'Ext.grid.Panel', alias: ['widget.itgridpanel', 'widget.itgrid'], …. initComponent: function () { var me = this; … me.plugins.push({ ptype: 'proto-openform', parentContainerByListView: 'tabpanel' }); … this.callParent(arguments); }, … MIXIN Наделение дополнительными функциями Ext.define('It.data.queryable.GridQueryable', { // обычный класс ExtJS extend: 'It.data.queryable.BaseQueryable', writeSelectQuery: function () { // этот метод вызывается в базовом классе var me = this; Ext.each(me.columns, function (column) { me.pushColumn(column); }); }, }); pushColumn: function (column) { var me = this; var items = this.getSelectQueryables(); if (column.hidden !== true) { if (column.columns) { Ext.each(column.columns, function (item) { me.pushColumn(item); }); } else { items.push({ dataIndex: column.dataIndex, alias: column.columnAlias }); Применение } } } Ext.define('It.grid.Panel', { extend: 'Ext.grid.Panel', alias: ['widget.itgridpanel', 'widget.itgrid'], mixins: ['It.data.queryable.GridQueryable', 'It.data.IncludeToRPC'], … changeProxy: function () { if (this.updateSelectParams && this.store.getProxy().type != 'memory') { this.updateSelectParams(); this.store.load(); } }, … Наследование Все как у всех Ext.define('It.data.queryable.BaseQueryable', { selectQueryables: null, getSelectQueryables: function () { return this.selectQueryables; }, selectQueryableToString: function () { var me = this; var result = ''; …. return result.substr(0, result.length - 1); }, updateSelectParams: function () { var me = this; me.writeSelectQuery(); // вызов дочерней функции me.store.proxy.extraParams['select'] = me.selectQueryableToString(); } }); Ext.define('It.data.queryable.GridQueryable', { extend: 'It.data.queryable.BaseQueryable', // наследование writeSelectQuery: function () { // переопределенная функция var me = this; Ext.each(me.columns, function (column) { me.pushColumn(column); }); }, pushColumn: function (column) { … } }); Bind’инг Простой пример Ext.define('MyApp.view.home.HomeModel', { extend: 'Ext.app.ViewModel', alias: 'viewmodel.home', data: { firstName: 'Александр', lastName: 'Краснов' }, formulas: { name: function (get) { return get('firstName') + ' ' + get('lastName'); }, } }); Ext.define('MyApp.view.home.Home', { extend: 'Ext.Container', viewModel: 'home', items: [ { bind:{ html: 'Hello {name}' } } ] }); Пример • Создание приложение • Настройка роутинга • Простейшая форма • ViewModel (Bind’инг) • Публикация Всем спасибо! Доклад окончен Полезные ссылки • http://it.chuvashia.com • http://docs.sencha.com • http://www.google.ru 2015 © Краснов Александр