Use cases of Ext.Direct

Use cases of Ext.Direct

In previous post I wrote about basic configuration of Ext.Direct and Zend Framework. In this text however I will show you all of the features of this mechanism.

Before we start, all modification are made on files from this repository.

Using Ext.Direct in models and stores

Models and stores to communicate with their backend counterparts use class called proxy. Two type of it exist in Ext JS 4, client and server. First, use local memory to store data, second send it directly to the server. Direct is of course one of the server proxy. What is the difference between them, where are advantages of using direct? With ajax proxy you can send only one set of data from same domain. JsonP allows you to do the same but you are able to request for it to different domain. Rest use restfully methods to communicate with servers. And in the end direct, at first glance, work exactly same way as ajax proxy. Exactly? Really? No. With direct we can send batch of query! This mean that we will need only one request to load all of the stores (if we loading them in the same time).
To check this, open in browser our repository from earlier post. Next, enable developer tools in browser and in the end put code snippet in the console.

Ext.create('MyApp.store.Players', { storeId : 'store-1' });
Ext.create('MyApp.store.Players', { storeId : 'store-2' });
Ext.create('MyApp.store.Players', { storeId : 'store-3' });

As we can notice we create three new stores (to see source open /public/app/stores/Players.js) with property autoLoad set to true. This should trigger requests immediately but I as wrote above, only one, with three separately queries.
Ok, now we will look closer at the model. If we set up it properly we should be able to use static method Load, which will return model populated with data in callback. To test this behavior, add below condition

        // if we have id in params
        // return associative array
        // with one record
        if(array_key_exists($params, 'id')){
            return array(
                'success' => true,
                'Player' => array(
                    'id' => 0,
                    'name' => 'Sebastian',
                    'surname' => 'Widelak',
                    'age' => 24,
                    'nickname' => 'mkalafior'
                )
            );
        }else{
            return array(
                'success'=>true,
                'Player'=>array(
                    ....
                )
            );
        }

to method read in \application\api\Game\Player.php and open developer console pasting this

MyApp.model.Player.load(1, {
    success: function (record) {
        console.log(record);
    }
});

As a response we should get something like that

direct

Now we will test how to create and save data.
Again, before we start we need to improve our backend stuff.
Open \application\api\Game\Player.php and at the end of class add code


    /**
     * @param $name
     * @param array $data
     * @return array
     */
    private function _create($name, array $data)
    {
        // get zend session object
        $namespace = new Zend_Session_Namespace('storage');
        // check if requested namespace already exist
        // if not assign array to it
        isset($namespace->$name) || $namespace->$name = array();
        // create fixed id for new element
        $data['id'] = count($namespace->$name);
        // push data to session
        array_push($namespace->$name, $data);
        // return whole data with new id
        return $data;
    }

    /**
     * @param $name
     * @param $id
     * @param array $data
     * @return array
     */
    private function _update($name, $id, array $data){
        $return = array();
        $namespace = new Zend_Session_Namespace('storage');
        if (is_array($namespace->$name)) {
            // iterate each record
            foreach ($namespace->$name as $key => &$row) {
                // if we find row with id which was sent
                if ($key === (int)$id) {
                    // set this id to the data
                    $data['id'] = $id;
                    // assign data to row
                    $row = $data;
                    // and in the end return it
                    $return = $data;
                }
            }
        }
        return $return;
    }

    /**
     * @param $name
     * @param $id
     * @return array
     */
    private function _load($name, $id = -1)
    {
        $return = array();
        $namespace = new Zend_Session_Namespace('storage');
        if (is_array($namespace->$name)) {
            // check if we loading only one record
            if ($id > -1) {
                // iterate each of them
                foreach ($namespace->$name as $key => $row) {
                    // if key is same as sent id
                    if ($key === (int)$id) {
                        // return row
                        $return = $row;
                    }
                }
            } else {
                // otherwise return all records
                $return = $namespace->$name;
            }
        }
        return $return;
    }

    /**
     *
     * @param $name
     * @param $id
     * @return array
     */
    private function _destroy($name, $id)
    {
        $return = array();
        $namespace = new Zend_Session_Namespace('storage');
        if (is_array($namespace->$name)) {
            unset($namespace->$name[$id]);
        }
        return $return;
    }

Four functions above will be our adapters for storing data in session.

Private functions are not visible for Ext.Direct

To use them we need to edit public CRUD functions as below:

    /**
     * @param array $params
     * @return array
     */
    public function read($params)
    {
        if ($params['id']) {
            return array(
                'success' => true,
                'Player' => $this->_load('Player', $params['id'])
            );
        } else {
            return array(
                'success' => true,
                'Player' => $this->_load('Player')
            );
        }
    }

    /**
     * @param array $params
     * @return array
     */
    public function create($params)
    {
        return array(
            'success' => true,
            'Player' => $this->_create('Player', $params)
        );
    }

    /**
     * @param array $params
     * @return array
     */
    public function update($params)
    {
        return array(
            'success' => true,
            'Player' => $this->_update('Player', $params['id'], $params)
        );
    }

    /**
     * @param array $params
     * @return array
     */
    public function destroy($params)
    {
        $return = $this->_destroy('Player', $params['id']);
        return array(
            'success' => true,
            'return' => $return
        );
    }

Ok, now we are ready to play. Go to console and put

var player = MyApp.model.Player.create();
player.set({
    name : 'Sebastian',
    surname : 'Widelak',
    age : 24,
    nickname : 'mkalafior'
});
player.save();

in it and after request refresh application. As we see we got new record in our grid. Nice, but it will be more useful if we don’t have to refresh entire app after each record was added. So, we need to do that via store:

var store = Ext.StoreManager.lookup('Players');
store.add({
   name : 'Name',
   surname : 'Surname',
   age : 20,
   nickname: 'nick'
});

We should notice that row was immediately added to the grid and after request was finished Ext JS automatically update id of record to this which was sent from the server.

Forms

Next component where we can use Ext.Direct is form. If we assign proper method from server to it, we will gain ability to fill and save all fields in ease way.
Firstly, open \public\app\view\Viewport.js and modify part of code as is shown below:

{
    region: 'center',
    xtype: 'tabpanel',
    layout: 'fit',
    items: [
        {
            title: 'Details',
            layout: 'fit',
            items: [
                {
                    xtype: 'form',
                    model: 'Player',
                    // we need to add line which will
                    // define in what order parameters
                    // will be sent
                    paramOrder: ['id', 'name', 'surname', 'nickname', 'age'],
                    padding: 5,
                    border: false,
                    // next we add configuration for api
                    api: {
                        // this two functions below will be used only for form
                        // currently they not exist but we will add them later
                        load: 'Game.Player.load',
                        submit: 'Game.Player.submit'
                    },
                    items: [
                        {
                            xtype: 'hiddenfield',
                            name: 'id'
                        },
                        {
                            xtype: 'textfield',
                            name: 'name'
                        },
                        {
                            xtype: 'textfield',
                            name: 'surname'
                        },
                        {
                            xtype: 'textfield',
                            name: 'nickname'
                        },
                        {
                            xtype: 'numberfield',
                            name: 'age'
                        }
                    ],
                    // also we need to add some buttons
                    // for sending and reseting form
                    buttons: [
                        {
                            text: 'Reset Form',
                            handler: function (btn) {
                                var form = btn.up('form').getForm();
                                form.reset();
                            }
                        },
                        {
                            text: 'Submit Form',
                            handler: function (btn) {
                                var form = btn.up('form').getForm();
                                // here we call direct submit action
                                form.submit({
                                    success : function () {
                                        // and after we have result
                                        // we reloading store
                                        store.load();
                                    }
                                });
                            }
                        }
                    ]
                }
            ]
        }
    ]
}

after that put in \application\api\Game\Player.php code:


    /**
     * @param $id
     * @return array
     */
    public function load($id)
    {
        return array(
            'success' => true,
            'data' => $this->_load('Player', $id)
        );
    }

    /**
     * @param $id
     * @param $name
     * @param $surname
     * @param $nickname
     * @param $age
     * // for direct submit we need to
     * // put special annotation in docblock
     * @formHandler
     * @return array
     */
    // parameters in form handler are sent separately.
    public function submit(
        $id,
        $name,
        $surname,
        $nickname,
        $age
    )
    {
        if(ctype_digit($id) || is_int($id)){
            $return = $this->_update('Player', (int)$id, array(
                'name' => $name,
                'surname' => $surname,
                'age' => $age,
                'nickname' => $nickname,
            ));
        }else{
            $return = $this->_create('Player', array(
                'name' => $name,
                'surname' => $surname,
                'age' => $age,
                'nickname' => $nickname,
            ));
        }

        return array(
            'success' => true,
            'data' => $return
        );
    }

Last step, open \public\app\controller\Main.js, find line 14 and modify code to something like this:

               'itemclick' : function (grid, record, el, index){
                    var form = Ext.ComponentQuery.query('form')[0]; //line 14
                    // we get form by component query
                    // and after that we load form using
                    // direct
                    form.getForm().load({
                        // we need to pass object
                        // with id of clicked row
                        params : {
                            id : record.get('id')
                        }
                    });
                }

Refresh app and add few records via form. As you see grid is automatically reloaded. Clear form (by reset button) and after that click in chosen row in the table. Record should be loaded to form. Nice, now change value in one of fields and save it. After that try with different record and back again to first one to check if data was really saved, should be.

Direct call

Last way to use Ext.Direct is to call it directly from any part of application. Yes, really, this tool gives you that possibility. Let’s say that we want to prevent from adding user with duplicate nick to the database, thanks to direct, it’s pretty simple! Firstly we need to add another method to our backend class (\application\api\Game\Player.php)

    /**
     * @param $nick
     * @return array
     */
    public function isNickUnique($nick)
    {
        $return = array('success' => true);
        $namespace = new Zend_Session_Namespace('storage');
        if (is_array($namespace->Player)) {
            foreach ($namespace->Player as $key => $row) {
                if ($row['nickname'] === $nick) {
                    $return = array('success' => false);
                }
            }
        }
        return $return;
    }

which will return true when nick is unique and false when not.
We need to apply some changes on our main controller too, so open \public\app\controller\Main.js and in the listener for grid replace function for itemclick with this code

function (grid, record, el, index){
    var form = Ext.ComponentQuery.query('form')[0],
        basic = form.getForm();
    // we need to add small hack to prevent validate
    // field (especially nickname) when we load record from db
    basic.getFields().each(function(item, index, length){
        item.suspendCheckChange++;
    });
    basic.load({
        params : {
            id : record.get('id')
        },
        success : function (form, object) {
            // we cache loaded data
            // to use it in validation
            basic.rawData = object.result.data;
            // in the end when record was loaded
            // we turn on validation again
            basic.getFields().each(function(item, index, length){
                item.suspendCheckChange--;
            });
        }
    });
}

Ok, we modify backend and frontend controllers, now we just need to upgrade our view. In \public\app\view\Viewport.js search for nickname field declaration and add the following code (after line name: ‘nickname’,)

{
    validator: function (value) {
        var me = this,
            form = me.up('form'),
            basic = form.getForm();
        // if value was exactly the same
        // as loaded from db do not trigger validation
        if (
            me.originalValue == value ||
                me.newValue == value ||
                (basic.rawData && basic.rawData[me.name] == value)
            )
            return true;
        // Ok and here is punch line of our task
        // we call direct method directly from
        // validator body.
        // As it's asynchronous request we have to assume
        // that nick will not be unique
        Game.Player.isNickUnique(value, function (response) {
            // but if response was successful
            if (response.success == true) {
                // we clear errors and unlock
                // possibility to send form
                me.clearInvalid();
                me.newValue = value;
            }
            else
                return 'Sorry, nickname exists.';
        });
        // and return error message.
        return 'Sorry, nickname exists.';
    }
}

Now, we only need to check that everything is ok. Add record and at second try put same nickname, field should be highlighted on red and if you click submit button form still shouldn’t be send (check it via network tab in dev tools).

Comments:

Leave a Reply

Please fill all required fields


Drag circle to the rectangle on the right!

Send