User Tools

Site Tools


products:stewext

Using Sencha ExtDirect with Phpfox

Overview: http://www.sencha.com/products/extjs/extdirect/

The RPC mechanism has several advantages over regular ajax. In particular, requests are batched to increase performance.

Instead of having an ajax folder for every module, all services are provided within this module (it has separate service folders for each module so that grouping is still mainatined).

Originally based on JBruni. Adapted for fox so that several conventions for back end calls may be easily added.

* CORS (use it from any sub domain without cross-origin scripting hassles)

* Mark any method as a form handler

* All public methods are automatically exposed in the API. Privates remain, um, private.

Javascript calls are namespaced to pfox and of the form module_service.method, eg

alert(pfox.lang_phrase.getPhrase('foobar'));

I have not provided every possible service, I am simply coding what I need as I need it.

In theory, it is a case of calling the appropriate phpfox services and wrapping the data for transport.

In practice, fox services rarely return raw data, but usually something half decorated with markup or jacascript. Or for one reason or another, you want something different.

Great jump start for developers interested in using sencha products for mobile and desktop.

CORE HACK

Requires a plugin hook to be added in Phpfox_Error::trigger().

Generate models and store

The following code just dumps fox tables to javascript.

<?php
// This is just a tool. Given table name, generate some stuff for extJS to save us typing all the columns names

// dsm 27 jun 13 : copy from laravel
// dsm 24 may 14 : updates for latest

defined('PHPFOX') or exit('NO DICE!');


class Stewext_Component_Controller_Crudgen extends Phpfox_Component
{

    public $sModelName,$sStoreName;
    public $model,$store;

    static $err = "Please supply table_NAME/model_NAME, eg table_phpfox2_video/model_video";

    public function process()
    {

        $oReq=Phpfox::getLib('request');

        // No table given
        if ( ! $sTable =  $oReq->get('table'))
        {
            Phpfox_Error::set(self::$err);
            return;
        }

        // PDO for access to db meta
        $dbhost=    Phpfox::getParam(array('db', 'host'));
        $dbuser=    Phpfox::getParam(array('db', 'user'));
        $dbpass=    Phpfox::getParam(array('db', 'pass'));
        $dbname=    Phpfox::getParam(array('db', 'name'));
        $dbport=    Phpfox::getParam(array('db', 'port'));
        $pdo = new PDO("mysql:host=$dbhost;dbname=$dbname", $dbuser, $dbpass);

        // Load list of all tables
        $aTables=array();
        $r = $pdo->query("show tables");
        while ($row = $r->fetch(PDO::FETCH_NUM)) {
            $aTables[]=$row[0];
        }

        // Problem with the table argument
        if(!in_array($sTable,$aTables))
        {
            Phpfox_Error::set(self::$err);
            return;
        }

        // Now have a model. Default model name to table name
        if( $this->sModelName = $oReq->get('model'))
        {
        }
        else
        {
            $this->sModelName = $sTable;
        }
        $this->sModelName = ucfirst(str_replace(array('phpfox2_','phpfox_'),'',$this->sModelName));
        $this->sStoreName = $this->sModelName . 's';

        // What's in this table?
        $r = $pdo->query('show columns from '.$sTable);
        $aCols=$r->fetchAll(PDO::FETCH_CLASS);//ASSOC);

        // Generate additional info based on type, length etc
        $aExtFields = $this->genExtFields($aCols);

        // Generate strings of javascript from the fields
        $buff = '';
        $buff .= "/*\n" . $this->getStore($aCols) . "\n*/\n";
        $buff .= "/*\n" . $this->getModel($aCols) . "\n*/\n";
        $buff .= "/*\n" . $this->getForm($aCols) . "\n*/\n";
        $buff .= "/*\n" . $this->getGrid($aCols) . "\n*/\n";

        // Always overwrite.
        $fn=PHPFOX_DIR_MODULE . 'stewext/gen/' . $this->sModelName . '.js';
        file_put_contents($fn,$buff);

        Phpfox_Error::set(count($aExtFields).' items saved at '.$fn);
    }


    /*
        Convert from array of DBColumn to array of ExtField

            class [array]  Sat 24 May 2014 04:57pm 09:000000
            Array
            (
                [0] => stdClass Object
                    (
                        [Field] => blog_id
                        [Type] => int(10) unsigned
                        [Null] => NO
                        [Key] => PRI
                        [Default] =>
                        [Extra] => auto_increment
                    )
     */
    public function genExtFields($aCols)
    {
        //hkd($aCols,'begin genExtFields ');

        foreach($aCols AS &$aCol)
        {
            // Invent a label from the field name. Remove underscores and change to CamelCase
            $aWords=explode('_',$aCol->Field);
            if(count($aWords)==0)
            {
               $aWords=(array)$aWords;
            }
            foreach($aWords AS &$word)
            {
                $word=ucfirst($word);
            }
            $aCol->extLabel = implode('',$aWords);

            // How tedious. Whatever driver we have today is not parsing the field type. Here we go:

            // Extract first word. This will be the field name
            $rc=preg_match('/([\w]+)/',$aCol->Type,$aParts);
            if($rc==1)
            {
                $aCol->fldType=$aParts[0];
            }

            // Most fields are of the form "int(10) unsigned" a field length an optional modifier
            // Let us pick those up now if available, we will call them len and modifier
            $aCol->fldLen = false;
            $aCol->fldModifier = false;
            $rc=preg_match('/(.*)\(([\d]+)\)[\s]*(.*)/',$aCol->Type,$aParts);
            if($rc==1)
            {
                $aCol->fldLen = isset($aParts[2]) ? $aParts[2] : false;
                $aCol->fldModifier = isset($aParts[3]) ? $aParts[3] : false;
            }
            
            // Leaves us with decimal,real, enum and who knows what else. 
            // Let us deal with them as they come up.

            // Now set these variables for extJS
            $aCol->extType = $aCol->extLen = $aCol->extDec = $aCol->extFormat = $aCol->extList = false;

            //hkd($aCol);


            // Pre processing
            switch($aCol->fldType)
            {
                // Fox does not use boolean types. We can only guess here.
                case 'tinyint':

                    if($aCol->fldLen == 1 AND substr($aCol->Field,0,3)=='is_')
                    {
                        $aCol->fldType = 'boolean';
                    }
                    break;
            }

            switch($aCol->fldType)
            {
                case 'timestamp':
                    $aCol->extType='date';
                    $aCol->extFormat= 'timestamp';
                    break;


                case 'mediumtext'   :
                case 'text'   :
                case 'varchar':
                case 'char'   :
                    $aCol->extType='string';
                    if ($aCol->fldLen>0)        {$aCol->extLen=$aCol->fldLen;}
                    if (isset($aCol->opt))   {$f['store']=$aCol->opt;}
                    break;

                case 'enum':
                    $aCol->extType='string';
                    //if ($aCol->fldLen>0)        {$aCol->extLen=$aCol->fldLen;}
                    //if (isset($aCol->opt))   {$f['store']=$aCol->opt;}
                    break;

                case 'smallint':
                case 'tinyint':
                case 'mediumint':
                case 'int':
                    $aCol->extType='int';
                    if ($aCol->fldLen>0) {$aCol->extLen=$aCol->fldLen;}
                    break;

                case 'decimal':

                    $rc = preg_match('/(.*)\(([\d]+),([\d]+)\).*/', $aCol->Type,$aParts);
                    if($rc)
                    {
                        $aCol->fldLen = isset($aParts[2]) ? $aParts[2] : false;
                        $aCol->fldDec = isset($aParts[3]) ? $aParts[3] : false;

                    }
                    $aCol->extType='float';
                    if ($aCol->fldLen) {$aCol->extLen=$aCol->fldLen;}
                    if ($aCol->fldDec) {$aCol->extDec=$aCol->fldDec;}
                    break;


                case 'boolean':
                    $aCol->extType='boolean';
                    break;

                case 'datetime':
                    $aCol->extType='date';
                    break;

                default:
                    Phpfox_Error::set('What kind of ExtField to make for dbcol type  : '.$aCol->type);
                    break;

            }
        }

        return $aCols;
    }


    public function getStore($aCols)
    {
        $buff="
Ext.define('hank.store.fox.".$this->sStoreName."',{
    extend:'Ext.data.Store',
    requires:['hank.model.fox.".$this->sModelName."'],
    storeId:'".$this->sStoreName."',
    model:'hank.model.fox.".$this->sModelName."',
    remoteSort:true,
    //autoLoad:false,
    //autoSync:false
})

";
        
        
        $buff .="
Ext.create('hank.store.fox.".$this->sStoreName."',{        
    proxy: Ext.create('HankDirect',{
            api: {
                read    : 'pfox.".$this->sModelName.".browse',
                create  : 'pfox.".$this->sModelName.".create',
                update  : 'pfox.".$this->sModelName.".update',
                destroy : 'pfox.".$this->sModelName.".destroy'
            }
        })
        })

        ";
        
        return $buff;
    }


    //
    public function getModel($aCols)
    {
        $idProperty = $aCols[0]->Field;
        $buff="
Ext.define('hank.model.fox.".$this->sModelName."',{
extend: 'Ext.data.Model',

    idProperty:'$idProperty',
";

        // Model fields
        $aFields=array();
        foreach($aCols AS $aCol)
        {
            $item=array(
                'name'=>$aCol->Field,           // column name aka data index aka field name
                'type'=>$aCol->extType
            );
            $aFields[]=$item;
        }

        $buff .= "\tfields:" . $this->json($aFields) . ',';
        return $buff;
    }

    //
    public function getForm($aCols)
    {
        $buff="
Ext.define('crud.view.".$this->sModelName."Edit', {
extend: 'crud.view.Edit',
alias: 'widget.crud".$this->sModelName."EditForm',


    initComponent: function () {
        this.callParent(arguments);
    },


    getItemsForEdit:function(){
        return
";


        // Form fields
        $aFields=array();
        foreach($aCols AS $aCol)
        {
            switch($aCol->extType)
            {
                case 'boolean':
                    $xtype='checkbox';
                    break;

                default:
                    $xtype='textfield';
                    break;
            }

            $item=array(
                'xtype'=>$xtype,
                'name'=>$aCol->Field,
                'fieldLabel'=>$aCol->extLabel,
                'allowBlank'=>true
            );
            $aFields[]=$item;
        }

        $buff .= $this->json($aFields) . ",\n}\n";;



        return $buff;
    }


    public function getGrid($aCols)
    {
        $buff="
getColumns:function(){
    return";

        // Grid columns
        $aFields=array();
        foreach($aCols AS $aCol)
        {
            $item=array(
                'dataIndex'=>$aCol->Field,
                'text'=>$aCol->extLabel,
                '//filter'=>true,
                '//flex'=>1,
                '//width'=>'100',
            );
            $aFields[]=$item;
        }

        $buff .= $this->json($aFields) . "\n}\n";;
        return $buff;
    }


    /*
     * json_encode PRETTY_PRINT surrounds property names with double quotes.
     * We prefer unquoted property names for sencha classes
     */
    public function json($a)
    {
        $buff = json_encode($a,JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
        $aLines=explode("\n",$buff);
        $aParts=array();
        foreach($aLines AS &$sLine)
        {
            // IN:   \t\t"foo": "bar"\n
            // OUT:  \t\tfoo: "bar"\n

            //$rc = preg_match('/([\s]+)"([\w]+)":(.*)/',$sLine,$aParts);
            // Added "//blah" to some grid columns (output but commented). Deal with that also now.
            $rc = preg_match('/([\s]+)"(\/?\/?[\w]+)":(.*)/',$sLine,$aParts);


            if($rc)
            {
                $sLine = $aParts[1]  . $aParts[2] . ':' . $aParts[3];;
            }
        }
        return implode("\n", $aLines);
    }
}

That produces output like the following.

It's nothing to sing about, but it saves some typing, and can be customized..

/*

Ext.define('hank.store.fox.Products',{
    extend:'Ext.data.Store',
    requires:['hank.model.fox.Product'],
    storeId:'Products',
    model:'hank.model.fox.Product',
    remoteSort:true,
    //autoLoad:false,
    //autoSync:false
})


Ext.create('hank.store.fox.Products',{        
    proxy: Ext.create('HankDirect',{
            api: {
                read    : 'pfox.Product.browse',
                create  : 'pfox.Product.create',
                update  : 'pfox.Product.update',
                destroy : 'pfox.Product.destroy'
            }
        })
        })

        
*/
/*

Ext.define('hank.model.fox.Product',{
extend: 'Ext.data.Model',

    idProperty:'product_id',
	fields:[
    {
        name: "product_id",
        type: "string"
    },
    {
        name: "is_core",
        type: "boolean"
    },
    {
        name: "title",
        type: "string"
    },
    {
        name: "description",
        type: "string"
    },
    {
        name: "version",
        type: "string"
    },
    {
        name: "latest_version",
        type: "string"
    },
    {
        name: "last_check",
        type: "int"
    },
    {
        name: "is_active",
        type: "boolean"
    },
    {
        name: "url",
        type: "string"
    },
    {
        name: "url_version_check",
        type: "string"
    }
],
*/
/*

Ext.define('crud.view.ProductEdit', {
extend: 'crud.view.Edit',
alias: 'widget.crudProductEditForm',


    initComponent: function () {
        this.callParent(arguments);
    },


    getItemsForEdit:function(){
        return
[
    {
        xtype: "textfield",
        name: "product_id",
        fieldLabel: "ProductId",
        allowBlank: true
    },
    {
        xtype: "checkbox",
        name: "is_core",
        fieldLabel: "IsCore",
        allowBlank: true
    },
    {
        xtype: "textfield",
        name: "title",
        fieldLabel: "Title",
        allowBlank: true
    },
    {
        xtype: "textfield",
        name: "description",
        fieldLabel: "Description",
        allowBlank: true
    },
    {
        xtype: "textfield",
        name: "version",
        fieldLabel: "Version",
        allowBlank: true
    },
    {
        xtype: "textfield",
        name: "latest_version",
        fieldLabel: "LatestVersion",
        allowBlank: true
    },
    {
        xtype: "textfield",
        name: "last_check",
        fieldLabel: "LastCheck",
        allowBlank: true
    },
    {
        xtype: "checkbox",
        name: "is_active",
        fieldLabel: "IsActive",
        allowBlank: true
    },
    {
        xtype: "textfield",
        name: "url",
        fieldLabel: "Url",
        allowBlank: true
    },
    {
        xtype: "textfield",
        name: "url_version_check",
        fieldLabel: "UrlVersionCheck",
        allowBlank: true
    }
],
}

*/
/*

getColumns:function(){
    return[
    {
        dataIndex: "product_id",
        text: "ProductId",
        //filter: true,
        //flex: 1,
        //width: "100"
    },
    {
        dataIndex: "is_core",
        text: "IsCore",
        //filter: true,
        //flex: 1,
        //width: "100"
    },
    {
        dataIndex: "title",
        text: "Title",
        //filter: true,
        //flex: 1,
        //width: "100"
    },
    {
        dataIndex: "description",
        text: "Description",
        //filter: true,
        //flex: 1,
        //width: "100"
    },
    {
        dataIndex: "version",
        text: "Version",
        //filter: true,
        //flex: 1,
        //width: "100"
    },
    {
        dataIndex: "latest_version",
        text: "LatestVersion",
        //filter: true,
        //flex: 1,
        //width: "100"
    },
    {
        dataIndex: "last_check",
        text: "LastCheck",
        //filter: true,
        //flex: 1,
        //width: "100"
    },
    {
        dataIndex: "is_active",
        text: "IsActive",
        //filter: true,
        //flex: 1,
        //width: "100"
    },
    {
        dataIndex: "url",
        text: "Url",
        //filter: true,
        //flex: 1,
        //width: "100"
    },
    {
        dataIndex: "url_version_check",
        text: "UrlVersionCheck",
        //filter: true,
        //flex: 1,
        //width: "100"
    }
]
}

*/

Download and demo

Not available. Inquire within.

products/stewext.txt · Last modified: 2014/06/15 09:32 by steward