niedziela, 14 grudnia 2014

Wyłączenie wybranych wierwszy kolumny Checkcolumn w drzewie (ExtJS TreeGrid)

Niedawno spotkałem się z ciekawym problem. Potrzebowałem komponent TreeGird z kolumną typu checkcolumn. Prosta sprawa - wystarczy w polu column dla drzewa dodać definicję kolumny i po sprawie.

Schody zaczęły się, gdy dla wybranych wierszy checkbox miał być wyłączony (disabled = true). Biblioteka ExtJS nie przewiduje takiej sytuacji. Można wyłączyć tylko całą kolumnę (wszystkie checkboxy). Potrzebowałem rozwiązania, które sprawi, że dla części wierszy nie będzie można kliknąć na checkbox w kolumnie oraz nie popsuje to kolumny checkcolumn w innych gridach i drzewach.

Konfiguracja własnego renderera dla kolumny nie wchodziła w grę - psuje to wyświetlanie całej kolumny. Nie jest też to zalecane przez programistów Sencha.
W sieci można odszukać mnóstwo tego typu problemów. Żadne z przedstawionych rozwiązań nie spełniało jednak moich potrzeb. Pominę tutaj wszystkie próby, który podjąłem i przedstawię działające rozwiązanie (na razie dla ExtJS 5.1 - wkrótce też dla wersji 4.2).

Rozwiązaniem okazało się przygotowanie overrida dla klasy Ext.grid.column.CheckColumn, dokładnie do dwóch metod: processEvent (odpowiada ona za obsługę zdarzeń dla checkboxa) oraz defaultRenderer (odpowiada za tworzenie elementu w kolumnie - konkretnego checkboxa).

Ext.grid.column.CheckColumn.override({  
           processEvent: function(type, view, cell, recordIndex, cellIndex, e, record, row) {  
                var me = this,  
                  key = type === 'keydown' && e.getKey(),  
                  mousedown = type == 'mousedown';  
                if (!me.disabled && (mousedown || (key == e.ENTER || key == e.SPACE))) {  
                  var checked = !me.isRecordChecked(record);  
                  if(record.get(me.dataIndex) === null){  
                      return false;  
                  }  
                  if (me.fireEvent('beforecheckchange', me, recordIndex, checked) !== false) {  
                     me.setRecordCheck(record, checked, cell, row, e);  
                     me.fireEvent('checkchange', me, recordIndex, checked);  
                     if (mousedown) {  
                       e.stopEvent();  
                     }  
                     if (!me.stopSelection) {  
                       view.selModel.selectByPosition({  
                          row: recordIndex,  
                          column: cellIndex  
                       });  
                     }  
                     return false;  
                  } else {  
                     return !me.stopSelection;  
                  }  
                } else {  
                  return me.callParent(arguments);  
                }  
           },  
           defaultRenderer : function(value, cellValues) {  
                var cssPrefix = Ext.baseCSSPrefix,  
                cls = cssPrefix + 'grid-checkcolumn';  
                if (this.disabled) {  
                     cellValues.tdCls += ' ' + this.disabledCls;  
                }  
                if (value === null) {  
                    cellValues.tdCls += ' ' + this.disabledCls;                     
                }  
                if (value) {  
                     cls += ' ' + cssPrefix + 'grid-checkcolumn-checked';  
                }  
                return '<img class="' + cls + '" src="' + Ext.BLANK_IMAGE_URL + '"/>';  
           }  
      });  

Na czerwono oznaczyłam kod, który został dodany. W metodzie processEvent został dodany warunek, który sprawdza, czy pole powiązane z kolumną ma wartość null. Dzięki temu po kliknięciu w komórkę z checkboxem, nic się nie dzieje. Zmiana w metodzie defaultRenderer odpowiada wyłączenie konkretnego elementu. Jeżeli wartość zmiennej value jest nullem, to do stylu CSS komórki zostaje dodana klasa CSS, która wyszarza element.

Ostatnim etapem jest utworzenie stora i komponentu Tree.

 var store = Ext.create('Ext.data.TreeStore', {  
           root: {  
                expanded: true,  
                children: [  
                     { text: "Office", leaf: true, isWorking: true },  
                     { text: "Finances", expanded: true, isWorking: false, children: [  
                          { text: "Personal", leaf: true, isWorking: null},  
                          { text: "Corporate", leaf: true, isWorking: false}  
                          ] },  
                     { text: "Legal Department", leaf: true , isWorking: null}  
                ]  
           }  
      });  
      Ext.create('Ext.tree.Panel', {  
           title: 'CheckColumn Tree',  
           store: store,  
           height: 400,  
           rootVisible: false,  
           renderTo: Ext.getBody(),  
           columns: [{  
                xtype: 'treecolumn',  
                text: 'Name',  
                flex: 2,  
                sortable: true,  
                dataIndex: 'text'  
           },{  
                xtype: 'checkcolumn',  
                text: 'Working',  
                flex: 1,  
                sortable: true,  
                dataIndex: 'isWorking',  
                align: 'center',  
                listeners: {  
                     checkchange: {  
                       fn: function(checkcolumn, rowIndex, checked, eOpts ){ console.log('Row %s checked change', rowIndex); }  
                     }  
                }  
           }]  
      });  

W konfiguracji kolumny "checkolumn" w polu dataIndex wpisujemy 'isWorking' (lub inną nazwę pola z konfiguracji modelu dla TreePanel).
Teraz wystarczy, aby serwer w metodzie do wypełniania drzewa zwracał null w wartości konkretnego pola.

Załączonym przykładzie odbywa się to przez ustawienie pola root dla stora.
W produkcyjnej aplikacji oczywiście do stora przypisany byłby konkretny model.
Ważne jest, aby model został skonfigurowany, tak aby przyjmował wartość null (domyślnie, jeżeli serwer zwróci w polu wartość null, zostanie ona zastąpiona przez false). W tym celu w modelu, w konfiguracji pola ustawiamy useNull na true.


 Ext.define('MyExampleApp.model.tree.exampleTreeModel', {  
   extend: 'Ext.data.Model',  
   requires: [  
     'Ext.data.Field'  
   ],  
   fields: [  
     {  
       name: 'text',  
       type: 'string'  
     },  
     {  
       name: 'leaf',  
       type: 'boolean'  
     },  
     {  
       name: 'expanded',  
       type: 'boolean'  
     },  
     {  
       name: 'isWorking',  
       type: 'boolean',  
       useNull: true  
     },  
   ]  
 });  


Od teraz wybrane checkboxy w drzewie lub gridzie będą nieaktywne (tak, jak na załączonym zrzucie). 


















Link do pobrania plików z działającym przykładem: disable-tree-checkcolumn.zip

Brak komentarzy:

Prześlij komentarz