.. / Шаг 2. Первое «большое» приложение на ExtJS

  1. ExtJS-large-application

Каркас - это viewport (окно растянутое на весь экран) разделенный пополам. В левой части - навигация в виде дерева объектов, в правой - панель табуляции для работы со списком объектов, соответствующих узлу дерева объектов. Больше здесь ничего нет. Потом я соединю этот и предыдущий шаг и получу первое «большое приложение». А пока создам два класса, которе мне будут необходимы для создания каркаса большого приложения. Должно получиться вот это:

extJs-big-application-step3

1. Класс панели с закладками CommonTab

Этот класс будет использоваться в моем «большом» приложении как минимум два раза. Во-первых, для вывода таблицы со списком объектов, соответствующих узлу дерева навигации, и во-вторых внутри каждой закладки с таблицей, где он будет использован для детализации записей таблицы. См. рисунок приложения ниже.


Код для этого класса:

 Ext.define(«App.Common.CommonTab»,{
        ctrl:{},
        extend: 'Ext.tab.Panel',
        alias: «widget.commontab»,
        resizeTabs: true,
        openTab:function(data){
              var itemId = data.id;
              var tab = this.getComponent(itemId); // поиск закладки с itemId = data.id
              if (tab) {tab.show();return;} // если закладка  существует, она открывается
              tab = this; tab.add( // добавляю закладку
                   {
                   itemId:itemId,
                   data:data,
                   iconCls: 'tabs',
                   closable: true})
               .show();
        },
        enableTabScroll: true,
        defaults: {
            autoScroll:true,
            bodyPadding: 0
        }

    });

Для создания закладки и переключения активных закладок я ввожу метод openTab, куда передаю необходимые данные. Пока я не создаю в этой закладке ничего. Просто создаю пустую закладку.
В ExtJS в наборах items (здесь это закладки) можно задавать itemId. В отличие от обычного id он должен быть уникальным только для конкретного набора items. И позже мне это пригодится.
Прежде чем создать новую закладку для объекта c определенным id я смотрю не создана ли она ранее, если она ранее была создана я переключаюсь на нее, если нет - создаю пока пустую закладку.

2. Класс дерево+панель с закладкой MainTree

Код для этого класса:

Ext.define(«App.Common.MainTree»,{
      extend: 'Ext.Panel',
      alias: «widget.maintree»,
      layout: 'border',
      items: [{   title: 'Разделы',
                  region: 'west',
                  xtype: 'treepanel',
                  rootVisible: false,
                  autoScroll: true,
                  split:true,
                  width:200,
                  store:{
                     fields: ['id','text', 'type','url'],
                       proxy: {            // указание типа и  источника данных
                           type: 'ajax',   //  тип данных - ajax
                           url: '/data/mainTree.json' //  урл источника данных
                       }}},{
                  region: 'center',
                  xtype:'commontab'}],
      /*  Конструктор */
       initComponent: function() {
                  /*  вызов конструктора по умолчанию */ App.Common.MainTree.superclass.initComponent.apply(this, arguments);
                  /*   действия после вызова стандартного конструктора ExtJS  */
                   this.tab=this.down('commontab'); /* определю компонент закаладок */
                   this.grid=this.down('treepanel');  /* определю компонент дерева */
                   var that = this;
                  /*  по выделению узла дерева переключаю/создаю закладки  */
                   this.grid.on('selectionchange', function(model,rec){
                                if (rec[0]) {  // на всякий случай проверка наличия выделения
                                            var data=rec[0].data;
                                            data.title=data.text;
                                            this.tab.openTab(data);
                                        }
                   }, this);
                }

});

В этом классе я начал использовать то, что в примерах не применялось - конструктор объекта initComponent. В нем я сначала вызываю станадртный инициализатор компонента ExtJS *.superClass.initComponent. После этого все компоненты имеющиеся в экземпляре класса становятся доступны.
после этого я нахожу в MainTree дерева и закладки:

 App.Common.MainTree.superclass.initComponent.apply(this, arguments);
                  /*   действия после вызова стандартного конструктора ExtJS  */
                   this.tab=this.down('commontab'); /* определю компонент закаладок */
                   this.grid=this.down('treepanel');  /* определю компонент дерева */

И после этого навешиваю на дерево обработчик события, который по выделению узла дерева должен открыть новую закладу или переключиться на существующую

                  /*  по выделению узла дерева переключаю/создаю закладки  */
                   this.grid.on('selectionchange', function(model,rec){
                                if (rec[0]) {  // на всякий случай проверка наличия выделения
                                            var data=rec[0].data;
                                            data.title=data.text;
                                            this.tab.openTab(data);
                                        }
                   }, this);
                }

});

3. Отрабатываю промежуточный результат

Отрабатываю промежуточный результат в index.htm и убеждаюсь, что все получилось как надо.

Ext.Loader.setConfig({enabled: true,disableCaching: false}); Ext.Loader.setPath('App', '/app/'); Ext.require([
      'App.Common.MainTree', 'App.Common.CommonTab', 'App.Common.CommonGrid'
]); Ext.onReady(function(){ Ext.create('Ext.Viewport', {
      layout:'border',
      items:{
        xtype:'maintree',
        layout:'border',
        region: 'center',
      },
        renderTo: Ext.getBody()
    });
});

Здесь самое время поговорить о динамической загрузке классов. Если верить немногочисленным статьям про автозагрузку, например здесь или в самом мануале ExtJs сам знает когда и что грузить. Такой вот умный. А если не знает, то можно подскзать добавив в конфигурацию класса волшебную строчку requires, где в массиве перечислить все что ему надо. На самом деле это работает как-то странно и через жопу. Посмотрим что будет дальше.

4. Соединяю все вместе

Теперь нужно соединить компонент MainTree с компонентами для отображения таблички со списком объектов, которые были получены на предыдущем шаге. Здесь нужно остановиться на структуре данных, которые я закладываю для навигационного дерева объектов в левой части экрана:

{
text:'Root',
expanded: true,
children:[
    {
      text:'First',
      type:«First», /* класс приложения */
      id:'tree-2',
      url:'/data/sql.php?type=first',
      children:[
          {
            text:'First',
            type:«First»,
            id:'tree-21',
            url:'/data/sql.php?type=first',
            leaf:true,
          }
      ]
     },.  ..  ..  ..  ..

Каждый элемент дерева содержит дополнительные неотображаемые поля, необходимые для работы приложения. На текущий момент я ограничился двумя такими полями:

  1. url - адрес по которому выводится список всех объектов, соответстующего узла, для отображения в таблице. Этот же адрес будет использоваться для всех других операция и запросов относящихся к этому узлу - удаление, редактирование, детализация, поиск и т.д.
  2. type - тип объекта. Применительно к CMS это будет очевидные Article, News, Users, Adver, Template и т.п. В моем «большом» абстрактном приложении это будет по введенным соглашениям First,Second и Third

И то и другое необходимо передать в закладку, перед тем как ее создать. И я модифицирую вызов этого метода в MainTree:

       initComponent: function() {. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. … 
                   this.grid.on('selectionchange', function(model,rec){
                                if (rec[0]) {  // на всякий случай проверка наличия выделения
                                            var data=rec[0].data;
                                            data.app='App.'+rec[0].data.type; /* класс приложения */
                                            data.family=data.app; /* тип объекта */
                                            data.title=data.text; 
                                            this.tab.openTab(data);
                                        }
                   }, this);
                }

Здесь в данных я передаю два очень важных поля:

  1. app - класс объекта, который будет создан в закладке (App.First, App.Second…) Напомню, что для отображения разных типов данных я использую разные классы табличек, унаследованных от CommonGrid App.First, App.Second
  2. family - тип объекта, соответствующий узлу дерева. В данном случае app и family совпали, потому что по по введенным соглашениям таблица со списком объектов имеет то же название что и тип данных, соответствующий узлу дерева.
    Все остальные компоенты и виджеты необходимые для работы с данным типом приложения будут получать названия класса исходя из типа данных и назначения, например
    - редактор для App.First будет называться App.FirstEditor
    - просмотрщик для App.Second будет называться App.SecondView и т.д.

Ну и теперь использую эти данные в CommonTab:

 
         openTab:function(data){
              var itemId = data.id;
              var tab = this.getComponent(itemId);
              if (tab) {tab.show();return;} // если закладка  существует, она открывается
              tab = this; Ext.require(  /* динамическая подгрузка класса data.app */
                  data.app,function(){ tab.add(  /*  инициализация необходимой таблицы с данными в закаладке в функции обратного вызова   */ Ext.create(data.app,{
                             bodyPadding:0,
                             itemId:itemId,
                             family:data.app,
                             data:data,
                             title: data.id,
                             iconCls: 'tabs',
                             closable: true})
                        ).show();

                    }
               );
        },

Здесь вместо абстрактной закладки я создаю полноценную закладку с табличкой с необходимым классом. Прежде чем инициализировать таблицу я через Ajax подгружаю необходимый класс ( App.First, App.Second…) и когда он подгрузится инициализирую его в новой закладке. И теперь последний штрих для того чтобы все заработало как надо - изымаю из конфигурации классов кастомных таблиц App.First, App.Second… урл источника данных, потому что он нафиг не нужен. Я получаю эти данные из дерева объектов и передаю при инициализации закладки в CommonGrid: где и использую:

Ext.define(«App.Common.CommonGrid»,{. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. … 
      initComponent: function() {. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. … 
                    this.store= Ext.create('Ext.data.Store',{
                        autoLoad: true,
                        disableCaching: true,
                        fields:fields,
                        pageSize:25,
                        remoteFilter:true,
                        proxy: {
                        url:this.data.url, /* вот здесь я передаю урл источника данных из узла дерева */
                                type: 'ajax',
                            reader: {
                                type: 'json',
                                root: 'data'
                            }
                        }
                      });. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. … 

Все что получилось аккуратно упаковываю и кладу в архив my_big_application_ExtJS4.zip в директорию step2

  1. 2012-03-16
  2. ExtJS-large-application
  1. docs.sencha.com/ext-js/4-0/#/api/Ext.ComponentQuery - Ext.ComponentQuery — навигация по компонентам ExtJS
  2. Шаг3. Шаблон Обозреватель (Observer) в большом ExtJS приложении.
  3. Шаг1. Зарождение собственного компонента в ExtJS (пошаговый пример создания большого приложения на ExtJS 4.0)
Go Index Test