.. / Шаг 3.  Добавляем функциональность таблицы — delete,edit

  1. ExtJS-large-application

На двух предыдущих шагах я написал функционал, который позволяет просматривать структуру данных размещенных в навигационном дереве и выводить список данных в виде таблицы. Теперь пора добавить возможность манипулирования этими данными.

1. Добавление action - команд в таблицы и их отработка

Для начала я переопределю конфигурацию колонок кастомных классов таблиц:

Ext.define(«App.Second»,{
      extend: 'App.Common.CommonGrid',
      alias: «widget.second»,
      conf:  {
        grid:{
        columns:[
                    {text: 'id', dataIndex: 'id',width:100},
                    {text: 'Имя',      dataIndex: 'name',width:100},
                    {text: 'Фамилия',  dataIndex: 'f', flex:true },
                    {xtype:'actioncolumn',width:22,items: [{icon: '/images/ico/delete.png'}],action:'del'},
                    {xtype:'actioncolumn',width:22,items: [{icon: '/images/ico/edit.png'}],action:'edit'},
                    {xtype:'actioncolumn',width:22,items: [{icon: '/images/ico/view.png'}],action:'view'},
                ]
        }
      },

    });

В конфигурации дополнительных колонок все как в мануале по ExtJS. Кроме поля action, в котором я задаю тип действия - удаление, редактирование и детального просмотра записи. Очевидно что для того чтобы поймать событие нажатия на кнопку с action нужно отрабатывать реакцию события cellclick стандартного компонента Grid. Это я и сделаю и до кучи сделаю заготовку для двух обработчиков - удаления и редактирования.

Ext.define(«App.Common.CommonGrid»,{

. . . . . . . . . . . . . . . . . . . . . . 
      edit:function(){
        alert('edit');
      },
      del:function(){
        alert('del');
      },
      listeners: { // события
            /* клик мышкой по ячейке таблицы */
            cellclick: function(
                grid,       // таблица
                cell,
                columnIndex,// индекс колонки
                record,    // запись в хранилище, соотв. строке
                node,
                rowIndex, // индекс столбвца
                evt){
                col = this.conf.grid.columns[columnIndex];
                var data = {
                    grid:grid,
                    rec:record.data,
                    url: grid.store.proxy.url,
                    conf:this.conf,
                    itemId:this.itemId,
                    family:this.family
                }
                if (col.action) {

                   if (col.action == 'del') this.del(data)
                   if (col.action == 'edit') this.edit(data)
                }
            }}

2. Удаление

В обработчике cellclick, я проверяю не заданно ли в конфигурации кликнутой ячейки какое-нибудь действие - action. Если задано я вызываю метод CommonGrid, который соответствует этому методу. Сразу хочу сказать, что это только предварительный код. Он крайне поганый как минимум потому, что я заранее определяю в CommonGrid которые может быть и не понадобятся для какого-то конкретного типа данных. И как максимум потому что я не знаю какие именно обработчики вообще понадобятся. И если появится новый тип обработчика, которого я не предусмотрел это вызовет переработку класса CommonGrid. Следующий шаг и очень важный шаг для разработки больших приложений (тут я не беру слово «больших» в кавычки ) посвящу как раз исправлению этого и других недостатков. А теперь займусь обработчиком удаления.

      del:function(data){
                  var store=data.grid.getStore(),
                        id = data.rec.id,
                        recName=data.rec.name || data.rec.title || id,
                        fireParams={id:id,family:this.family},
                        url = store.proxy.url+'&del='+id; Ext.MessageBox.show({
                          title:'Удаление',
                          buttons: Ext.MessageBox.YESNO,
                          icon: Ext.MessageBox.QUESTION,    // иконка мб {ERROR,INFO,QUESTION,WARNING}
                          msg: 'Удалить запись «'+recName+'»?',
                          fn: function(btn) {
                                 if (btn == 'yes') { Ext.Ajax.request({
                                                    method: 'GET',
                                                        url: url,
                                                        success: function(response){
                                                            var res = Ext.JSON.decode(response.responseText);
                                                  if (res.success) {
                                                      //App.Event.fire('deleted',fireParams); Ext.MessageBox.alert(
                                                            {    title:'OK!',
                                                                 icon: Ext.MessageBox.INFO,    // иконка мб {ERROR,INFO,QUESTION,WARNING}
                                                                 msg: 'Запись «'+recName+'»  удалена',
                                                                 buttons: Ext.Msg.OK
                                                            }
                                                      );
                                                      return;
                                                  }
                                                        }
                                                 });
                                  }
                          }

                  });

В этом фрагменте я запрашиваю необходимость удаления и отправляю запрос на сервер. После получения ответа я вывожу сообщение об удалении. Удаление строки с записью в таблице я сделаю позже.

3. Редактирование

Общая логика создания окна с редактором точно такая же как и для создания вкладки с таблицей

  1. каждая вкладка с таблицей при создании получает тип данных с которым она должна работать. В своем примере я их обозвал First,Second и Third
  2. в самом начале я ввел соглашения что все самостоятельные виджеты для просмотра и редактирования наследуются от классов с именами типа FirstEditor, FirstViewer, SecondEditor, SecondViewer, где первая часть обозначает исходный класс (тип данных - First,Second), а вторая - назначение класса (редактирование - Editor, просмотр - viewer)
  3. Зная свой тип данных, каждый виджет (например таблица) загружает необходимый класс и передает туда необходимые данные.

Код который создает редактор для какой-то строки таблицы CommonGrid, будет следующим:

Ext.define(«App.Common.CommonGrid»,{

. . . . . . . . . . . . . . . . . . . . . . 

      edit:function(data){
                var id='editor-'+data.rec.id,       // задаю уникалльный id редактора
                    editor = Ext.getCmp(id);    // определяю не открыт ли он ранее
                if (editor) {editor.focus();return;} // фокусирую если он уж существует
                var app=data.family+'Editor',    // определяю класс редактора
                    params = {id:id,
                             family:data.family,
                             data: {
                                    url:data.url, // определяю url для загрузки данных,
                                    id:data.rec.id}
                            }; Ext.require(         // загружаю класс редактора, если он не загружен
                      app,function(){
                            var editor = // создаю новый редактор Ext.create(app,params);
                            editor.show();
                        }
                   );
      },

Код практически тот же что был в закладке и это даже навевает мысль о создании какого нибудь общего класса для них. Но я гоню эту мысль, чтобы не запутаться. Единственная разница заключается в том, что может существовать две закладки с одним и тем же itemId (забегая вперед скажу, что такое возможно для детализации записей). Но такого не должно быть для отдельных окон редактирования. И дальше без разбивки приведу фрагмент кастомного класса FirstEditor для редактирования класса First и базовый класс CommonEditor, от которого наследуются все кастомные редакторы.
В кастомных классах присутствует только описание элементов форм, а базовый класс обеспечивает все остальное от построения формы до взаимодействия с сервером и другими компонентами «большого» приложения. Код FirstEditor:

Ext.define(«App.FirstEditor»,{
      extend: 'App.Common.CommonEditor',
      form:
             [{   fieldLabel: 'person',
                    name: 'person',
                    width:400,
                    allowBlank: false
                },
                {   fieldLabel: 'name',
                    name: 'name',
                    width:400,
                    allowBlank: false
                },
              ],.
. . . . . . . . . . . . . . . . . . . . . . 
  
       });

Код для CommonEditor с сокращениями:

Ext.define("App.Common.CommonEditor",{
        extend: 'Ext.window.Window',
        alias           : "widget.commoneditor",
        title: 'CommonEditor',
        items : [{
           xtype: 'form',
           buttons: [

                    {
                        text: 'Сохранить',
                        formBind: true, //only enabled once the form is valid
                        disabled: true, // запретить submit если есть ошибки заполнения
                        handler: function() {
                            var form = this.up('form').getForm(); // нахожу родительский контейнер с формой
                                var p=this.up('commoneditor'); // опредляю родительский commoneditor
                                var url=p.data.url;  // вытаскиваю из него url сабмита
                                form.submit({
                                    url:url,
                                    params:{save:1,id:p.data.id}, // дополнительные данные
                                    success: function(form, action) { // нормальное сохранение
                                       var ok=Ext.Msg.alert('Ok!', 'Внесенные изменения сохранены');
                                    }
                                });
                    }}]
            }],
        initComponent: function() {
                     // передаю данные формы из конфигурац. кастомного класса
                    this.items[0].items=this.form;
                     App.Common.CommonEditor.superclass.initComponent.apply(this, arguments);
        },
        listeners:{
                afterrender:function(){
                  /* после отрисовки окна редактора загружаю в форму данные с сервера */ 
                  var url= this.data.url+'&one='+this.data.id;
                  this.down('form').getForm().load({url: url,win: this});
                }
        }
    });

Полностью код класса можно посмотреть в исходниках в архиве. Здесь только минимальный функционал необходимый для понимания принципа действия.

Резюме

Все что я написал - полуфабрикат. Причем очень низкого качества. Три основных недостатка или недоработки:

  1. Ни при удалении, ни при редактировании не происходит взаимодействия мое "большое" приложение никак не отрабатывает результаты выполнения операций, хотя при удалении записи эта запись должна быть удалена из таблицы, а окно с удаляемой записбю должно быть закрыто. То же самое и с редактированием. Результат редактирования будет виден только если вручную срефрешить страницу. И я сознательно перенес эти действия на следующий шаг.
  2. Компонент CommonGrid у меня получился избыточно "умным". Пока он знает как вызвать редактор, как удалить строку, потом он будет знать как вызвать детализацию записей, потом что-то еще, еще и еще
  3. Третий недостаток напрямую вытекает из предыдущего. Класс CommonGrid никогда не будет завершен до конца. Как только понадобится новый тип action, не предусмотренный заранее мне придется впиндюривать его в этот класс и я никогда не получу стабильной версии библиотеки.

Другими словами я получил говнокод, который срочно нужно исправлять. И этим я займусь на следующем шаге, а пока все что получилось аккуратно упаковываю и кладу в архив my_big_application_ExtJS4.zip в директорию step3

  1. 2012-03-16
  2. ExtJS-large-application
  1. Шаг 2. Каркас большого приложения
  2. Шаг 1. Зарождение собственного компонента в ExtJS
Go Index Test