Generate MetaData for ExtJs Data Reader
Metadata is one of my most used properties in Ext.data.reader.Reader. It allows me to pass custom data/configuration to Grid Panel, Form, ComboBox and other components without needing extra efforts to change the code. At the following example, I just want to put my column layout configuration to the grid without configuring the data fields in the data store. If the settings of data fields is bound with data columns' information on the database, then we could save a lot of time to match the data field types when updating/retrieving data to/from server.

In this article, we are going to retrieve field types, defaultValues, and allowBlank settings via the meta data which is generated based on the data columns information of the MySQL database.

For example, we want to display a data grid with paging toolbar which shows a list of values of 'items'.

We now just need to create a simple JsonStore and let the proxy retrieves the information by 'MetaDataExample.php'.


// File Name: index.php
// Start loading the page        
Ext.onReady(function(){
 Ext.create('Ext.data.JsonStore', {
  storeId:'itemsStore',
  autoLoad:true,
  proxy: {
    type: 'ajax',
    url: 'MetaDataExample.php', // our data link
    reader: {
     type: 'json'
    }
   }
 });
 
 Ext.create('Ext.grid.Panel', {
  title: 'Items',
  store: Ext.data.StoreManager.lookup('itemsStore'),
  columns: [
   { text: 'ID',  dataIndex: 'id' },
   { text: 'SKU', dataIndex: 'sku', flex: 1 },
   { text: 'Name', dataIndex: 'name', flex: 1 },
   { text: 'QTY', dataIndex: 'qty'}
  ],
  dockedItems: [{
   xtype: 'pagingtoolbar',
   store: Ext.data.StoreManager.lookup('itemsStore'),   // same store GridPanel is using
   dock: 'bottom',
   displayInfo: true
  }],
  height: 600,
  width: 800,
  renderTo: Ext.getBody()
 }); 
});  



The following function feeds the meta data to the client and it indicates that we want to show all records from all fields of items table. The 'db' variable is a custom class to handle the database operation and I will explain the functions later. The 'ret' variable contains output data to feed the JSON reader.


// File Name: MetaDataExample.php
public function getItems(){
 $this->tableName =  "items";
 $start = isset($_GET['start']) ? $_GET['start'] : 0;
 $limit = isset($_GET['limit']) ? $_GET['limit'] : 10;
 
 $query = "SELECT * FROM `{$this->tableName}` LIMIT {$start},{$limit}";
 
 $stmt = $this->db->query($query);
 // get the number of records
 $r = $this->db->query("SELECT COUNT(*) FROM `{$this->tableName}`", PDO::FETCH_NUM);
 // gets the first column as value
 $nbrows = $r->fetchColumn();

 // initialize the default output array
 $data = array();
 // if we have rows
 if($nbrows>0){
   while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
    $data[] = $row;
   }
 }  
 
 // prepare the metadata
 $ret['metaData'] =   $this->makeMetaData($stmt);
 $ret['total']    =   $nbrows;
 $ret['data']     =   $this->Char2Utf8($data);
 $ret['success']  =   true;
 $ret['message']  =   true;
 
 echo json_encode($ret);
}


We want to avoid some unwanted characters to appear on the data, we need to convert some characters to UTF8 format characters.


// File Name: MetaDataExample.php
protected function Char2Utf8($var){
        if (is_array($var)) {
            $out = array();
            foreach ($var as $key => $v) {
                $out[$key] = $this->Char2Utf8($v);
            }
        } else {
            if(gettype($var)=='string'){
                $out = str_replace(chr(194), "", $var);
                $out = utf8_encode($out);
            }
            else
              $out = $var;  
        }       
        return $out;                   
    }


At the following function, we start to generate the metadata for output. The key part is the value of 'fields', since we want to output the field types and default values based by the database's table structure.


// File Name: MetaDataExample.php
protected function makeMetaData($result, $overrides = false){
        /*
        metaData: {
            idProperty: 'id',
            root: 'data',
            totalProperty: 'total',
            successProperty: 'success',
            messageProperty: 'message',
            fields: [
                {name: 'name', allowBlank: false},
                {name: 'job', mapping: 'occupation'}
            ],
            sortInfo: {field: 'name', direction:'ASC'}, // used by store to set its sortInfo
            foo: 'bar' // custom property
        }*/
 
        $metaData['idProperty'] = 'id';
        $metaData['totalProperty'] = 'total';
        $metaData['successProperty'] = 'success';
        $metaData['root'] = 'data';        
        $metaData['fields'] = $this->parseMetaDataFields($result, $overrides);        
        
        return $metaData;
    }


We start to parse the meta data output fields from the data columns of the data table. First of all, we find out the name of the data table from the query result object and gather the columns' information by calling 'getFullColumnsInfo' function which is in the database class. Then we begin to collect the list of database output fields from the query result and match the names to the columns' information we have collected. The columns' information has pretty much enough properties we need in order to generate the settings of field name, field type, default value and allowBlank for meta data fields.


// File Name: MetaDataExample.php
protected function parseMetaDataFields($stmt, $overrides = false){
        // gets the table of the $result
        $table = $this->tableName;
        // gets the table descriptions for future use
        $columnsInfo = $this->db->getFullColumnsInfo($table);
        // gets the number of fields
        $nbFields = $stmt->columnCount();
        // starts a empty array
        $fields = array();
        // loop through the fields
        for ($i=0; $i < $nbFields; $i++){
          $meta = $stmt->getColumnMeta($i);
          $name  = $meta['name'];
          $fields[$i]['name']  = $name; 
            
          if(array_key_exists($name, $columnsInfo)){
            $type  = $columnsInfo[$name]['Type'];
                  
            if($type == 'date')
               $fields[$i]['dateFormat'] = 'Y-m-d';
            else if($type == 'datetime')
               $fields[$i]['dateFormat'] = 'c';   
               
            $fields[$i]['type']  = $this->convertType($type);
            
            if($name != 'id'){
                $fields[$i]['allowBlank'] = ($columnsInfo[$name]['Null'] == 'YES') ? true : false;
                // if we have default value in table column
                if(!is_null($columnsInfo[$name]['Default'])){
                 if($columnsInfo[$name]['Default']=='CURRENT_TIMESTAMP'){
                  $fields[$i]['defaultValue'] ='new Date()';
                 }else{
                  $fields[$i]['defaultValue'] = $columnsInfo[$name]['Default'];
                 }                   
                }
            }
          } else {
              $fields[$i]['type'] = 'auto';
          }
          
          if(!empty($overrides) && is_array($overrides)){
            if(array_key_exists($name, $overrides)){
                foreach($overrides[$name] as $key=>$value){
                    $fields[$i][$key]=$value;
                }
            }
          }
        }
        return $fields;
    }


The convertType function plays an important role. We want to convert MySQL's data type to ExtJs data type. This function contains my most used types. The rest of less used types, I leave them to be 'auto'. If you have further types need to be converted, feel free to add them in.

Please note that MySQL does not have 'Boolean' type, but it uses 'tinyint' instead.


// File Name: MetaDataExample.php
public function convertType($type){
        if(strpos($type,'(') !== false){
            $type = substr($type, 0, strpos($type,'('));
        }

        switch($type){
            case 'varchar':
            case 'char':
            case 'text':
            case 'tinytext':
            case 'mediumtext':
            case 'longtext':
                return 'string';
            case 'int':
            case 'smallint':
            case 'mediumint':
            case 'bigint':
                return 'int';
            case 'tinyint':
                return 'boolean';
            case 'real':
            case 'float':
            case 'double':
            case 'decimal':
            case 'numeric':
                return 'float';
            case 'date':
            case 'datetime':
                return 'date';
            default:
                return 'auto';        
        }
    }


The following simple database class wraps functions we need together. In this example, we use  PDO class to connect to our MySQL database. You might want to use mysqli extension for MySQL functions instead. However I don't recommend using mysql extension since mysql extension is deprecated since PHP version 5.5.0.


// File Name: MySqlDataAdapter.php
class MySqlDataAdapter
{
    protected $server, $user, $password;    
    /**
    * Db name
    */
    public $dbName;
    
    /**
    * MySQL pdo object
    */
    public $pdo;
    
    /**
    * Result of statement
    */
    protected $stmt;
    
    /**
     * Constructor
     */              
    public function __construct($server, $user, $password, $dbName){
        $this->server   = $server;      // Host address
        $this->user     = $user;        // User
        $this->password = $password;    // Password
        $this->dbName   = $dbName;      // Database         
        $this->connect();
    }
 
    /**
     * Destructor
     *
     * @return void
     */
    public function __destruct(){
     $this->stmt = null;
        $this->pdo = null;
    }
 
    /**
     * Connect to the database
     */
    public function connect(){
       // create the pdo object     
    $this->pdo = new PDO('mysql:host='.$this->server.';dbname='.$this->dbName, $this->user, $this->password);       
       // if connection is failed, then trigger user error
       if ($this->pdo === false){
            trigger_error('Could not connect to the database.', E_USER_ERROR);
    }else{
      $this->pdo->exec("set names utf8");
    }
       // return the connection object
       return $this->pdo;
    }    
    
    /**
     * Query the database
     *
     * @param string $queryStr SQL query string
     * @return resource MySQL result set
     */
    public function query($queryStr){
     try{
      //calling beginTransaction() turns off auto commit automatically
      $this->pdo->beginTransaction();
   // prepare the statement
   $this->stmt = $this->pdo->prepare($queryStr);
   // execute the statement
   $this->stmt->execute();
   // commit the statement if everything is alright
   $this->pdo->commit();
  }
  catch(PDOException $ex){
   //Something went wrong rollback!
      $this->pdo->rollBack();
   // trigger an sql error
   trigger_error($ex->getMessage(), E_USER_ERROR);
  }
  // return the statement object
  return $this->stmt;
    } 

 /**
  * Get column info from table name
  */
    function getFullColumnsInfo($table){
        $stmt = $this->query("SHOW FULL COLUMNS FROM `{$table}`");
        $data = array();
        // gets the information
        while($row = $stmt->fetch(PDO::FETCH_ASSOC)){
            $data[$row['Field']] = $row;
        }
        // return the data array
        return $data;        
    }
}


You can also use the same concept to configure the Grid Panel column settings via the meta data. Have a look at 'Dynamic GridPanel for ExtJs 4' and it may give you some ideas to apply column settings via meta data to the Grid Panel.

You can download the source code via here.