Podstawy MVC w Ext JS 4

Podstawy MVC w Ext JS 4

Cześć, w tym artykule postaram się wyjaśnić zastosowane MVC w Ext JS 4. Nowa wersja Ext JS dostarcza całkiem nowy sposób w porównaniu do poprzednika. Jeśli nie wiesz co to MVC, zapraszam przynajmniej tutaj:

Wikipedia architektura MVC

Istnieje wiele definicji MVC, w Ext zastosowano:

a) Model: Kolekcja poszczególnych pól definiowanych poprzez klasę Model, a do prezentowania danych używany jest Store

b) View: To jest Component. Widokiem może być grid (lista rekordów załadowanych ze Store), panel, drzewo, forma i wiele innych

c) Controllers: Tutaj dzieje się cała akcja, np. nasłuchiwanie eventów kliknięcia użytkownika itd.

Każdy projekt Ext JS 4 ma tę samą strukturę, taką strukturę MVC możesz śmiało wygenerować z terminala, tutaj dowiesz się jak to zrobić:

Tworzenie aplikacji Ext JS 4 przy użyciu Sencha Cmd

Projekt wygląda tak:

2-extjs4

W tym artykule skupimy się na tych najważniejszych elementach, niezbędnych do uruchomienia. Składowe to:

a) index.html: To jest strona HTML twojej aplikacji (one page)

b) app.js: Plik, który opakowuje cały kod aplikacji

c) katalog extjs sdk: Biblioteka Ext JS 4 niezbędna do pracy. Znajdują się tutaj m.in. style dla odpowiednich templates oraz pliki javascript dla frameworka Ext JS. Warto zwrócić uwagę na to, że jeśli używamy środowiska developerskiego ładujemy plik ext-debug.js a gdy ustawiamy projekt pod serwer produkcyjny montujemy plik ext.js

d) katalog app (ten w którym najczęściej będziesz pracował): Znajduje się tutaj cała zawartość aplikacji

Zawartość katalogu app:

a) model: Ten katalog zawiera wszystkie klasy modeli

b) store: Ten katalog zawiera wszystkie klasy poszczególnych store (store przechowuje dane opisywane modelami)

c) view: Ten katalog zawiera wszystkie komponenty widoków

//Pamietaj, jeden plik – jeden komponent/widok

d) controller: Ten katalog zawiera wszystkie kontrolery działające w aplikacji

Nazwy klas definiujemy tak: ’DevJS.controller.Main’. Oznacza to, że plik nazywa się Main.js i aplikacja będzie go szukać w katalogu app/controller. I zwróć uwagę na to, że każda nazwa poprzedzana jest “namespace” zdefiniowanym w app.js jako “name”, podajesz je np. podczas tworzenia projektu z terminala.

Model

Przykładowy model reprezentujący użytkownika wygląda następująco:

Ext.define('DevJS.model.User', {
 extend: 'Ext.data.Model',

 fields: [
     { name: 'id', type: 'int' },
     { name: 'login', type: 'string' },
     { name: 'password', type: 'string' },
     { name: 'firstName', type: 'string' },
     { name: 'lastName', type: 'string' },
     { name: 'email', type: 'string' }
 ]
});

Podajesz nazwę klasy dla pliku oraz nazwę klasy, którą ona rozszerza. W tym przypadku rozszerza domyślny model Ext. Kolejno podajesz definicje odpowiednich pól, które później obsłuży sobie Store. Przykładowo robisz to w taki sposób:


Ext.define('DevJS.store.Users', {
 extend: 'Ext.data.Store',
 model: 'DevJS.model.User',

...

Widok

Widok daje wiele możliwości, tutaj konfigurujesz każdy szczegół swojego widoku (pamiętaj, jeden widok – jeden plik js). Dlatego widok, który poniżej używam powinien znajdować się również w nowym pliku. W tym przypadku DevJS.view.users.List. W pliku Main.js powinny być tylko i wyłącznie zdefiniowane “regiony” czyli miejsca w kontenerze. Resztę w realnym świecie ładujesz dynamicznie według menu. Tutaj użyty został lewy region oraz środkowy, gdzie wyświetlimy sobie na potrzeby przykładu np. listę użytkowników systemu.

Skład Pliku:

a) extend - jaką klasę rozszerza dany element. W tym przypadku jest to container (jest dużo lżejszy podczas generowania od klasy Panel, dlatego paneli używaj w mniejszych częściach widoków). Wydajność to podstawa podczas generowania całego UI (szczególnie jak klient wymaga kompatybilności z IE8).

b) requires - tutaj definiujesz jakich klas będziesz używał, aplikacja ładuje zawsze tylko to czego potrzebuje (wydajność przede wszystkim)

c) xtype - alias dla twojej klasy, w tym przypadku skrócona nazwa, została użyta np. w viewporcie aplikacji (view/Viewport.js), nie musisz dzięki temu pisać całych nazw

d) layout - możesz podać string np. “fit” aby rozciągnąć widok u rodzica albo podać obiekt z pełną konfiguracją

e) funkcja initComponent() - funkcja startuje podczas inicjalizacji klasy widoku dzięki temu można ładnie manipulować ustawieniami widoku, w tym przypadku ustawiane są itemy danego widoku, możesz tutaj ustawiać wszystkie zmienne np. this.title = “twój tytuł” lub dać title:”twój tytuł” poza tą funkcją jeśli wiesz, że nie będziesz wykonywał żadnych modyfikacji na tym polu

f) funkcja this.callParent() - musisz ją wywołać i podać przynajmniej obiekt arguments (domyślny) inaczej nie zobaczysz niczego na ekranie ponieważ twój rodzic ma to czego najbardziej potrzebujesz

Ext.define('DevJS.view.Main', {
    extend: 'Ext.container.Container',
    requires: [
        'Ext.tab.Panel',
        'Ext.layout.container.Border',
        'Ext.grid.Panel',
    ],

    xtype: 'app-main',

    layout: {
        type: 'border'
    },

    initComponent: function () {
        var me = this;

        this.items = [
            {
                region: 'west',
                xtype: 'panel',
                title: 'Menu',
                width: 150
            },
            {
                region: 'center',
                xtype: 'tabpanel',
                items: [
                    {
                        xtype: 'users-list'
                    }
                ]
            }
         ]

        //parent
        this.callParent(arguments);
    }
});

Plik listy użytkowników znajduje się w view/users/List.js (wszystko ładnie segregujemy odpowiednio do przeznaczenia). A plik wygląda następująco:

Ext.define('DevJS.view.users.List', {
    extend: 'Ext.grid.Panel',

    xtype: 'users-list',
    title: 'Moduł użytkowników',
    store: 'Users',

    viewConfig: {
        enableTextSelection: true,
        stripeRows: true
    },
    dockedItems: [{
        xtype: 'pagingtoolbar',
        store: 'Users',
        dock: 'bottom',
        displayInfo: true
    }],

    initComponent: function () {
        var me = this;

        this.columns = [
            { text: 'Id', dataIndex: 'id' },
            { text: 'Login', dataIndex: 'login'},
            { text: 'Imię', dataIndex: 'firstName', flex:.5 },
            { text: 'Nazwisko', dataIndex: 'lastName', flex:.5 },
            {
                xtype:'actioncolumn',
                width:50,
                items: [{
                    icon: 'resources/icons/pencil.png',
                    tooltip: 'Edycja',
                    handler: function(grid, rowIndex, colIndex) {
                        this.up('grid').fireEvent('edit', grid, rowIndex, colIndex);
                    }
                },{
                    icon: 'resources/icons/recycleBin.png',
                    tooltip: 'Usuń'
                }]
            }
        ]

        //parent
        this.callParent(arguments);
    }
});

Kontroller

a) eventy

Teraz czas na kontroler, wyłapmy jakieś zachowanie użytkownika! W Ext JS 4 nasłuch eventów znajduje się zawsze w tym samym miejscu, czyli w funkcji init, która podczas inicjalizacji ładnie sobie je binduje. Spójrzmy na kod kontrolera obsługującego użytkowników:

Ext.define('DevJS.controller.Users', {
    extend: 'Ext.app.Controller',

    views:[
        'users.List'
    ],

    stores:[
        'Users'
    ],

    init: function () {
        var me = this;

        this.control({
            'app-main > tabpanel > grid': {
                edit: me.edit
            }
        });
    },
    edit: function(grid, rowIndex, colIndex){
        var rec = grid.getStore().getAt(rowIndex);

        console.log('Użytkownik chce edytować dane..?');
    }
});

Teraz musimy powiadomić kontroler z widoku o takim zdarzeniu. W pliku Main.js znajduje się grid/lista uzytkowików, oraz jedną z kolumn jest actioncolumn, dodajemy do handlera nowy event “edit” aby wiedzieć, że użytkownik właśnie edytuje element:

this.up('grid').fireEvent('edit', grid, rowIndex, colIndex);

//this.up(‘grid’) – oznacza tutaj, dodanie eventu do danego widoku

Ext.define('DevJS.controller.Users', {
    extend: 'Ext.app.Controller',

    views:[
        'users.List'
    ],

    stores:[
        'Users'
    ],

    init: function () {
        var me = this;

        this.control({
            'app-main > tabpanel > grid': {
                edit: me.edit
            }
        });
    },
    edit: function(grid, rowIndex, colIndex){
        var rec = grid.getStore().getAt(rowIndex);

        console.log('Użytkownik chce edytować dane..?');
    }
});

8-extjs4

Rezultat po kliknięciu w konsoli:

7-extjs4

Opis elementów kontrolera:

- extend - jaką klasę rozszerzamy

- views - widoki, których używa kontroler

- stores - story, których używa kontroler

- funkcja init() - funkcja automatycznie się wykonuje podczas inicjalizacji

    – funkcja this.control() - tutaj binduje wszystkie eventy, przykład:

'app-main > tabpanel > grid': {
    edit: me.edit
}

W tym miejscu jest używany Ext.ComponentQuery (opiszę w jednym z postów wkrótce). Podobnie jak w CSS idziemy po dzieciach w dół pliku Main.js. W tym przypadku nasłuchujemy grida, wiec podajemy wszystkie eventy jakie chcemy. Tutaj jest tylko edit, który uruchamia funkcję edit i tam dzieje się cała logika, oczywiście podaje ładnie prosto z grida wszystkie argumenty potrzebne do działania.

//Używaj zawsze funkcji. Ponieważ kod jest wtedy bardziej czytelny i staraj się zawsze pisać krótkie funkcje 

- edit() - customowa funkcja, którą dodałem aby zajęła się edycją zaznaczonego użytkownika

b) referencje

Kontroler daje również możliwość kontrolowania widoków poprzez zdefiniowane na początku referencje do obiektów, przydatne przy częstym ich wywoływaniu.

Przykład, zmodyfikujmy nasz kontroler użytkowników:

Ext.define('DevJS.controller.Users', {
    extend: 'Ext.app.Controller',

    views:[
        'users.List'
    ],

    stores:[
        'Users'
    ],

    refs: [
        {
            ref: 'UsersList',
            selector: 'app-main > tabpanel > grid'
        }
    ],

    init: function () {
        var me = this;

        this.control({
            'app-main > tabpanel > grid': {
                edit: me.edit
            }
        });
    },
    edit: function(grid, rowIndex, colIndex){
        var rec = grid.getStore().getAt(rowIndex);

        console.log('Użytkownik chce edytować dane..?');

        //pobierz listę użytkowników (obiekt grid) zamiast pobierać z argumentów
        console.log(this.getUsersList())
    }
});

Rezultat w konsoli:

9-extjs4

Całą magię wykonuje ta tablica obiektów :

refs: [
        {
            ref: 'UsersList',
            selector: 'app-main > tabpanel > grid'
        }
    ]

Podobnie jak w funkcji control, ustawiasz selektor do danej klasy i później w zasięgu twojego kontrolera wywołujesz jak zwykłą funkcję dodając przed ref “get” czyli getUsersList.

Możesz dać z małej litery:


refs: [
       {
           ref: 'usersList',
           selector: 'app-main > tabpanel > grid'
       }
 ]

Interpreter zadba o to żeby zmienić na dużą podczas wywoływania getUsersList.

c) getter methods

Kontroler ma również pomocne magiczne metody (getters methods). Wymienię kilka np.:

a) getModel(‘nazwa klasy modelu’): Zwraca referencję modelu

b) getStore(‘nazwa klasy store’): Zwraca referencję store

c) getView(‘nazwa klasy view’): Zwraca referencję view.

//Pamiętaj, że metody zadziałają tylko jeśli klasy będą zadeklarowane w plikach JavaScript.

Dobrze, teraz zbieramy wszystko razem i działamy. Aby odpalić swoje customowe klasy dopisujesz je w application.js, możesz tutaj również dodać widoki, story jeśli wiesz, że będą używane w całej aplikacji. Jeśli nie, podpinasz je w kontrolerach. Dlaczego? Ponieważ np. gdy wyłączysz dany kontroler, jego widoki też sie dezaktywują aby się nie ładować niepotrzebnie.

Ext.define('DevJS.Application', {
name: 'DevJS',

extend: 'Ext.app.Application',

views: [
    // TODO: add views here
],

controllers: [
    'Main',
    'Users'
],

stores: [
    // TODO: add stores here
]
});

Informacji jest tak dużo, że można o tym napisać książkę. Starałem się tutaj opisać najważniejsze aspekty bez których ciężko jest nawet ruszyć, oczywiście możesz śmiało pisać/pytań a ja postaram się pomóc. Możliwe, że naniosę jakieś korekty lub napiszę rozszerzoną wersję. Ale na razie udanej zabawy. Pozdrawiam.

Komentarze:

Zostaw odpowiedź

Proszę wypełnij wymagane pola aby dodać komentarz.


Drag circle to the rectangle on the right!

Wyślij