Programiranje

ExtJS i razvoj modernih korisničkih interfejsa

U današnje vreme se sve više i više razvijaju aplikacije koje rade na svim platformama (Windows, Linux, Mac OS). Jedan način na koji je to moguće postići je upotreba Web tehnologije za razvoj aplikacija. Koncept korišćenja Web tehnologija za razvoj aplikacija je osmišljen tako da se jednom napiše a koristi svuda. Kada sa napravi aplikacija na ovakav način, postavi se u kontejner (container) kao što je Electron ili možda sam Chromium i na taj način je dovoljno da aplikacija koja radi na Web pretraživačima može da se koristi i kao desktop aplikacija. Jedan od okvira (framework-a) koji se koriste je ExtJS razvijen od strane kompanije Sencha i služi za pravljenje profesionalnih aplikacija.

ExtJS olakšava razvoj aplikacija za rad sa velikim količinama podataka za desktop, tablet i smart uređaje. Razvijen je u programskom jeziku Javascript i sastoji se iz dva dela:

  • Sencha CMD – aplikacija za upravljanje ExtJS projektima kroz komandnu liniju,
  • ExtJS framework – sam framework koji je potreban Sencha CMD aplikaciji da bi operisala projektima.

Sencha CMD

Sencha CMD se bavi upravljanjem životnog ciklusa projekta, minimizacijom kôda, pretvaranjem kôda iz ES6 u ES koji svaki Web pretraživač podržava, dinamičko učitavanje paketa, generisanje build-ova za progresivne Web aplikacije, itd.

Preuzimanje

Sencha CMD se preuzima sa linka: https://www.sencha.com/products/extjs/cmd-download/.

Postoje dve verzije instalacije:

  • Sa JRE-om – izaberite ovu verziju u slucaju da nemate JRE ili JDK
  • Bez JRE-a – izaberite ovu verziju u slucaju da imate JRE ili JDK

Instaliranje

Instalacija je jednostavna i intuitivna, samo je potrebno pratiti uputstva u toku same instalacije i neće biti nikakvih problema.

Osnovne komande

Neke od osnovnih komandi su:

  • Generisanje ExtJS projekta/aplikacije

sencha -sdk generate app

  • Pokretanje razvojne verzije aplikacije (potrebno je biti u direktorijumu aplikacije)

sencha app watch

  • Kreiranje radne verzije aplikacije (potrebno je biti u direktorijumu aplikacije)

sencha app build

ExtJS framework

ExtJS framework sadrži preko 115 integrisanih komponenti, a neke od komponenti su: kalendar, tabele, grafikoni… Tabele i grafikoni mogu da obrade milione podataka sa lakoćom. Framework sadrži i robustan paket za podatke koji može da pribavi podatke sa bilo kog standardnog izvora. Takođe pruža mogućnost vizualizacije i analize podataka korišćenjem Sencha Pivot tabele i D3 adapter-a.

Preuzimanje

Besplatna verzija ExtJS framework-a (GPLv3) se preuzima sa linka: https://www.sencha.com/legal/gpl/

Instaliranje

Instalacija se vrši tako što se putanja ka raspakovanom ExtJS framework-u navede pri generisanju novog projekta/aplikacije

Struktura projekta

.sencha/                        # Sencha-specific files (primarily configuration)
         app/                        # Application-specific content
             sencha.cfg              # Application configuration file for Sencha Cmd
             Boot.js                 # Private, low-level dynamic loader for JS and CSS
             Microloader.js          # Loads app based on app.json content
             build-impl.xml          # Standard application build script
             *-impl.xml              # Implementations of various build phases
             defaults.properties     # Default values and docs for build properties
             ext.properties          # Build property values specific to Ext JS
             *.defaults.properties   # Build properties by environment (e.g. "testing")
             plugin.xml              # Application-level plugin for Sencha Cmd
             codegen.json            # Data for merging generated code during upgrade
         workspace/                  # Workspace-specific content (see below)
             sencha.cfg              # Workspace configuration file for Sencha Cmd
             plugin.xml              # Workspace-level plugin for Sencha Cmd`

     ext/                            # A copy of the Ext JS SDK
         cmd/                        # Framework-specific content for Sencha Cmd
             sencha.cfg              # Framework configuration file for Sencha Cmd
         classic/                    # Packages related to the Classic Toolkit
             classic/                # Ext JS Classic Toolkit package
             theme-neptune/          # Classic Toolkit Theme Package for Neptune
             theme-triton/           # Classic Toolkit Theme Package for Triton
             ...
         modern/                     # Packages related to the Modern Toolkit
             modern/                 # Ext JS Modern Toolkit package
             theme-neptune/          # Modern Toolkit Theme Package for Neptune
             theme-triton/           # Modern Toolkit Theme Package for Triton
             ...
         packages/                   # Framework supplied packages
             charts/                 # Charts package
             ux/                     # Contents of "Ext.ux" namespace
         ...
    
     index.html                      # The entry point to your application
     app.json                        # Application manifest
     app.js                          # Launches the Application class
     app/                            # Your application's source code in MVC structure
         model/                      # Folder for application model classes
         store/                      # Folder for application stores
         view/                       # Folder for application view classes
             main/                   # Folder for the classes implementing the Main View
                 Main.js             # The Main View
                 MainModel.js        # The `Ext.app.ViewModel` for the Main View
                 MainController.js   # The `Ext.app.ViewController` for the Main View
         Application.js              # The `Ext.app.Application` class
    
     packages/                       # Sencha Cmd packages
     workspace.json                  # Workspace JSON descriptor
    
     build/                          # The folder where build output is placed

Razvoj aplikacije i razvojni šablon

Sam razvoj je moguć u tri direktorijuma:

  • app/ – razvoj za classic i modern verzije
  • classic/ – razvoj samo za classic verziju
  • modern/ – razvoj samo za modern verziju

Aplikaciju je moguće razvijati koristeći MVC i/ili MVVM razvojni šablon.

Dokumentacija

Dokumentacija se nalazi na linku: https://docs.sencha.com

U okviru dokumentacije su detaljno objašnjeni i Sencha CMD i ExtJS framework. U ExtJS dokumentaciji se takođe nalaze primeri za svaku komponentu.

Primer – Kalkulator

Kao primer korišćenja ovog framework-a ćemo napraviti aplikaciju pod nazivom Kalkulator. U okviru ove aplikacije ćemo iskoristiti Viewport, Window, Button, View, ViewModel, ViewController i Table Layout. Na početku generišemo novu aplikaciju koristeći komandu navedenu u prethodnom poglavlju. Nazvaćemo je Calculator u našem slučaju.

U direktorijumu classic/src/view ćemo napraviti novu klasu Viewport koja proširuje Ext.container.Viewport i u njoj ćemo za početak da imamo samo osnovnu konfiguraciju:

Ext.define('Calculator.view.Viewport', {
        extend: 'Ext.container.Viewport',`

        requires: ['Calculator.view.main.Main'],
    
        items: [{
            xtype: 'main'
        }]
    });

Items konfiguracija služi da se navedu komponente koje će se nalaziti na ovom Viewport-u, a xtype je naziv komponente, u našem slučaju main. Nakon što smo napravili Viewport klasu, potrebno je izmeniti Main klasu u direktorijumu classic/src/view/main tako da izgleda ovako:

Ext.define('Calculator.view.main.Main', {
        extend: 'Ext.window.Window',
        xtype: 'main',`

        requires: [
            'Calculator.view.main.MainModel'
        ],
    
        autoShow: true,
        controller: 'main',
        resizable: false,
        viewModel: {
            type: 'main'
        },
        width: 200,
        height: 200
    });

Ova klasa nam je Window (proširuje Window klasu od ExtJS framework-a) i veličine je 200×200. Da bi ovo sve do što smo do sada uradili zapravo bilo vidljivo, potrebno je dodati Viewport klasu u app.js koji se nalazi u samom direktorijumu aplikacije. Ovaj fajl bi trebalo da izgleda ovako:

/*
     * This file is generated and updated by Sencha Cmd. You can edit this file as
     * needed for your application, but these edits will have to be merged by
     * Sencha Cmd when upgrading.
     */
    Ext.application({
        name: 'Calculator',

        extend: 'Calculator.Application',
    
        requires: [
            'Calculator.view.Viewport'
        ],
    
        // The name of the initial view to create. With the classic toolkit this class
        // will gain a "viewport" plugin if it does not extend Ext.Viewport. With the
        // modern toolkit, the main view will be added to the Viewport.
        //
        mainView: 'Calculator.view.Viewport'
    
        //-------------------------------------------------------------------------
        // Most customizations should be made to Calculator.Application. If you need to
        // customize this file, doing so below this section reduces the likelihood
        // of merge conflicts when upgrading to new versions of Sencha Cmd.
        //-------------------------------------------------------------------------
    });

Aplikacija trenutno izgleda ovako:

Ovo kaže našoj aplikaciji koji je početni View koji se učitava. Dalje je potrebno da dovršimo izgled Kalkulatora. U našoj Main klasi ćemo dodati komponente koje su nam potrebne (Button-i, Header) i da postavimo odgovarajući Layout (raspored). Pored komponenti, imamo Defaults konfiguraciju, koja označava vrednosti konfiguracija za ostale komponente koje koristimo. U našem slučaju smo rekli da su sve komponente dimenzija 50×50, da imaju CSS klasu btn i da imaju onClickNumber handler (funkcija koja se poziva kada se klikne na Button). konfiguracije koje su postavljene kao default (podrazumevane) mogu da se izmene u okviru same komponente (na primer button-i za operacije imaju drugi handler i drugu CSS klasu). Nakon svih izmena naša klasa bi trebalo da izgleda ovako:

Ext.define('Calculator.view.main.Main', {
        extend: 'Ext.window.Window',
        xtype: 'main',`

        requires: [
            'Calculator.view.main.MainModel'
        ],
    
        autoShow: true,
        controller: 'main',
        defaults: {
            width: 50,
            height: 50,
            cls: 'btn',
            handler: 'onClickNumber'
        },
        defaultType: 'button',
        header: {
            items: [{
                xtype: 'displayfield',
                bind: {
                    value: '{display}'
                },
                cls: 'display',
                colspan: 4,
                height: 60,
                padding: 0,
                width: 200
            }]
        },
        layout: {
            type: 'table',
            columns: 4
        },
        resizable: false,
        viewModel: {
            type: 'main'
        },
        items: [{
            cls: 'btn-green',
            colspan: 2,
            handler: 'onClickClear',
            text: 'C',
            width: 100
    
        }, {
    
            cls: 'btn-green',
            handler: 'onClickChangeSign',
            text: '+/-'
        }, {
            cls: 'btn-orange',
            handler: 'onClickOp',
            text: '÷'
        }, {
            text: '7'
        }, {
            text: '8'
        }, {
            text: '9'
        }, {
            cls: 'btn-orange',
            handler: 'onClickOp',
            text: '×'
        }, {
            text: '4'
        }, {
            text: '5'
        }, {
            text: '6'
        }, {
            cls: 'btn-orange',
            handler: 'onClickOp',
            text: '-'
        }, {
            text: '1'
        }, {
            text: '2'
        }, {
            text: '3'
        }, {
            cls: 'btn-orange',
            handler: 'onClickOp',
            text: '+'
        }, {
            colspan: 2,
            text: '0',
            width: 100
        }, {
            handler: 'onClickDot',
            text: '.'
        }, {
            cls: 'btn-orange',
            handler: 'onClickOp',
            text: '='
        }]
    });

Nakon Main View-a, prelazimo na ViewModel koji nam služi za dvosmerno povezivanje podataka koji se prikazuju korisniku sa View-om. ViewModel se nalazi u direktorijumu app/view/main, pod nazivom MainModel. U njemu ćemo da definišemo samo promenljivu za rezultat koja će da se prikaže korisniku. On bi trebalo ovako da izgleda ovako:

Ext.define('Calculator.view.main.MainModel', {
        extend: 'Ext.app.ViewModel',

        alias: 'viewmodel.main',
    
        data: {
            display: 0.0
        }
    });

Najzad nam je ostao još samo ViewController, odnosno kreiranje svih onih handler funkcija koje smo naveli u Main View-u. ViewController se nalazi u direktorijumu app/view/main pod nazivom MainController. U okviru njega ćemo da definišemo našu konfiguraciju State, koja će pamtiti trenutno stanje (trenutna operacija, trenutni rezultat, operande…) i funkcije. On bi trebao da ovako da izgleda:

Ext.define('Calculator.view.main.MainController', {
        extend: 'Ext.app.ViewController',

        alias: 'controller.main',
    
        state: {
            operatorClicked: false,
            selectedOperator: null,
            dotClicked: false,
            op1: 0,
            numberClicked: false,
            sign: true,
            decimal: false
        },
    
        onClickClear: function(){
            var vm = this.getViewModel();
            vm.set('display', '0');
            this.state.selectedOperator = null;
            this.state.op1 = 0;
            this.state.isPositive = true;
            this.state.decimal = false;
            this.state.sign = true;
        },
    
        onClickChangeSign: function(){
            var vm = this.getViewModel();
            var cur = vm.get('display');
            if (cur != '0') {
                if (this.state.sign === true) {
                    vm.set('display', '-' + cur);
                }
                else {
                    vm.set('display', cur.toString().substring(1));
                }
            }
            this.state.sign = !this.state.sign;
        },
    
        onClickOp: function(btn){
            if (this.state.selectedOperator != null &&
                this.state.numberClicked === true) {
                var vm = this.getViewModel();
                var op2 = parseFloat(vm.get('display'));
                var op1 = parseFloat(this.state.op1);
                var result = 0;
                switch (this.state.selectedOperator) {
                    case '+':
                        result = op1 + op2;
                        break;
                    case '-':
                        result = op1 - op2;
                        break;
                    case '×':
                        result = op1 * op2;
                        break;
                    case '÷':
                        result = op1 / op2;
                        break;
                }
                vm.set('display', Math.round(result * 100) / 100);
                this.state.selectedOperator = null;
            }
            if (btn.text != '=') {
                this.state.operatorClicked = true;
            }
            this.state.selectedOperator = btn.text;
            this.state.numberClicked = false;
        },
    
        onClickDot: function(btn){
            if (this.state.decimal === false) {
                var vm = this.getViewModel();
                vm.set('display', vm.get('display') + '.');
            }
        },
    
        onClickNumber: function(btn){
            this.state.numberClicked = true;
            if (this.state.selectedOperator === '=') {
                this.onClickClear();
            }
            var vm = this.getViewModel();
            if (this.state.operatorClicked === true) {
                this.state.op1 = vm.get('display');
                vm.set('display', btn.text);
                this.state.operatorClicked = false;
            }
            else {
                var cur = vm.get('display');
                if (cur == '0') {
                    cur = '';
                }
                vm.set('display', cur + btn.text);
            }
        }
    });

Aplikacija trenutno izgleda ovako:

Nakon svega ovoga smo napravili Kalkulator koji ne izgleda baš najbolje (trenutno je potpuno plav ako ga pokrenete sa Sencha CMD). Da bismo poboljšali izgled, dodaćemo CSS (u slučaju ExtJS framework-a, koristi se SASS). SASS fajl koji nama treba za aplikaciju se nalazi u direktorijumu classic/sass/src/view/main pod nazivom Main. Pošto cilj ovog članka nije CSS/SASS, neće biti dato objašnjenja za ovaj deo. Ovaj fajl bi trebao da izgleda ovako:

.btn:hover {
        background-color: #C7C7C7 !important;
    }

    .btn-orange:hover {
        background-color: #D07B32 !important;
    }
    
    .btn-green:hover {
        background-color: #A1C9AC !important;
    }
    
    .btn,
    .btn.x-btn-focus.x-btn-default-small {
        background-color: #E0E0E0;
    }
    
    .btn-orange,
    .btn-orange.x-btn-focus.x-btn-default-small {
        background-color: #F5923E;
    }
    
    .btn-green,
    .btn-green.x-btn-focus.x-btn-default-small {
        background-color: #AFDCBB;
    }
    
    .x-btn-focus.x-btn-default-small,
    .x-btn-focus.x-btn-over.x-btn-default-small {
        box-shadow: none;
    }
    
    .btn-orange span,
    .display div {
        color: #FFFFFF !important;
    }
    
    .display {
        background-color: #69787F;
        padding: 0px !important;
    }
    
    .btn span,
    .btn-orange span,
    .btn-green span {
        font-size: 18px;
        line-height: 22px;
        color: #000000;
    }
    
    .btn-orange,
    .btn,
    .display,
    .btn-green {
        border-radius: 0;
        border-color: #89898C;
        border-width: 0.5px;
    }
    
    .display div {
        font-size: 24px;
        line-height: 32px;
        text-align: right;
        padding-right: 5px;
        padding-top: 10px;
    }
    
    td div {
        margin-bottom: 0px !important;
    }
    
    .x-window-header-default-top {
        padding: 0px !important;
        top: 0px !important;
        left: 0px !important;
    }

Preuzimanje

Urađenu aplikaciju možete da preuzmete sa linka Calculator.tar.gz