A Better ExtJS Grid and Symfony – Part 1

December 24th, 2009 7 comments

A few months ago I wrote about ExtJS grid search and its integration with the symfony framework.  Well, I’ve had some time to do some more development, and have been working on ExtJS integration with symfony for about a month now, and I’ve learned a few new things.

First off I’ve moved from symfony version 1.2 to the 1.4 version.  That means I’ve switched ORMs from Propel to Doctrine.  This debate parallels the debate between Mac and PC – both offer the same thing, both do things in their own unique way, and both can provide a lot of power.  However, I’m making decision based on the fact that I think Doctrine will have continued steady development (and documentation) as it is now the “default” ORM of symfony. That being said, lets move into some code.

ExtJS + Symfony

Most of us who have worked with ExtJS probably know that the new 3.x version of ExtJS brought about the release of the Ext.Direct methods, which basically allow easy (and somewhat standardized) communication between the client and the server.  One invaluable plugin that I’ve utilized in symfony is the dsExtDirectPlugin.  This plugin allows you to take your symfony actions – add a few comments – that are then parsed into yaml file, and interpreted by an extdirect controller. It also generates then necessary api.js file all ready for you to call from your Ext components.  I won’t go into how to set this up in your project (hint: if using 1.3/1.4 you’ll have to install the plugin from source, as the repository isn’t updated with the latest symfony version – but as far as I can tell – the plugin works as expected in 1.3/1.4 out of the box), and here’s the discussion surrounding it on the ExtJS Forums.

Building up Symfony

Alright lets say we have a need to display a grid of media items from the defined schema.yml file below. Each media item has a speaker, and several categories:

Media:
  actAs:
    Timestampable:
      updated:
        disabled: true
    Sluggable:
      fields: [title]
      canUpdate: true
    SoftDelete:
  columns:
    title:
      type: string(255)
      notnull: true
    speaker_id:
      type: integer
      notnull: true
    sku: string(255)
    best_seller: bit(1)
  relations:
    Speaker:
      foreignAlias: Media
    Categories:
      foreignAlias: Media
      class: Category
      refClass: MediaCategory

Speaker:
  actAs:
    Timestampable:
      updated:
        disabled: true
    Sluggable:
      fields: [name]
      canUpdate: true
  columns:
    name:
      type: string(255)
      notnull: true
    description:
      type: string()
      notnull: true

Category:
  actAs:
    Sluggable:
      fields: [name]
      canUpdate: true
    NestedSet:
      hasManyRoots: true
      rootColumnName: root_id
  columns:
    name:
      type: string(255)
      notnull: true

MediaCategory:
  columns:
    media_id:
      type: integer
      primary: true
    category_id:
      type: integer
      primary: true
  relations:
    Media:
      foreignAlias: MediaCategories
    Category:
      foreignAlias: MediaCategories

Heres some sample data to help populate the database – copy this into your fixtures.yml file:

Speaker:
  Speaker_1:
    id:                         1
    name:                       John Doe
    description:                'John Doe has many good media items avaliable.'
  Speaker_2:
    id:                         2
    name:                       Jane Somebody
    description:                'Jane is just starting out as a speaker, but has a promissing career in media.'
  Speaker_3:
    id:                         3
    name:                       C. Moore Butz
    description:                'A description of this speaker is not avaliable.'

Media:
  Media_1:
    id:                         1
    title:                      'An Awesome CD'
    sku:                        'JP_1_1'
    best_seller:                '1'
    speaker_id:                 1
  Media_2:
    id:                         2
    title:                      'Media about Media'
    sku:                        'JP_1_2'
    best_seller:                '1'
    speaker_id:                 2
  Media_3:
    id:                         3
    title:                      'Another Great Media Title'
    sku:                        'JP_1_3'
    best_seller:                '1'
    speaker_id:                 1
  Media_4:
    id:                         4
    title:                      'Another Great Media Title - Part 2'
    sku:                        'JP_2_1'
    best_seller:                '1'
    speaker_id:                 1
  Media_5:
    id:                         5
    title:                      'Running out of names already'
    sku:                        'JP_2_2'
    best_seller:                '0'
    speaker_id:                 3
  Media_6:
    id:                         6
    title:                      'Sitting Under The Bleachers'
    sku:                        'JP_2_3'
    best_seller:                '1'
    speaker_id:                 3
  Media_7:
    id:                         7
    title:                      'The Areas of My Expertise'
    sku:                        'JP_2_4'
    best_seller:                '1'
    speaker_id:                 1
  Media_8:
    id:                         8
    title:                      'Some form of CD Title'
    sku:                        'JP_3_1'
    best_seller:                '0'
    speaker_id:                 2

Category:
  Category_1:
    id:                         1
    name:                       Self Help
  Category_2:
    id:                         2
    name:                       Social Media
  Category_3:
    id:                         3
    name:                       Do It Yourself
  Category_4:
    id:                         4
    name:                       Audiobooks

MediaCategory:
  MediaCategory_1:
    media_id:                   4
    category_id:                1
  MediaCategory_2:
    media_id:                   1
    category_id:                2
  MediaCategory_3:
    media_id:                   2
    category_id:                3
  MediaCategory_4:
    media_id:                   3
    category_id:                4
  MediaCategory_5:
    media_id:                   5
    category_id:                2
  MediaCategory_6:
    media_id:                   6
    category_id:                3
  MediaCategory_7:
    media_id:                   7
    category_id:                1
  MediaCategory_8:
    media_id:                   8
    category_id:                3
  MediaCategory_9:
    media_id:                   3
    category_id:                1
  MediaCategory_10:
    media_id:                   8
    category_id:                1

Alright – now lets build our model/forms/db by running [cci_bash]symfony doctrine:build –all –and-load –no-confirmation[/cci_bash].  This of course will destroy all the data in your defined database, so make sure this is a fresh app, or you don’t value what data you’ve got this app connected too.

Creating the Grid

Lets start by creating a page for us to view the grid on. Run the following in your cli or terminal:

symfony generate:app main
symfony generate:module main media

Next lets create a .js file that will build and display our grid:

Ext.ns('Media.main');

Ext.BLANK_IMAGE_URL = '/js/ext-3.0.0/resources/images/default/s.gif';
// Register provider
Ext.app.JP_API.enableBuffer = 0;
Ext.Direct.addProvider(Ext.app.JP_API);

Ext.onReady(function() {

    Ext.QuickTips.init();

		var view = new Ext.Viewport({
			items:[{
          id: 'content-panel',
          region: 'center', // this is what makes this panel into a region within the containing layout
          margins: '0 0 0 3',
          layout: 'card',
          border: false,
          split:true,
					autoScroll:true,
          activeItem: 0,
          items: [
							{xtype:"media_grid", id:"mediaGrid",hideMode:'offsets'}
          ]
      }]
		});
});

/********************
/  A function to turn returned mysql bit() chars into '1' or '0'
*********************/
function ord (string) {
    // Returns the codepoint value of a character
    //
    // version: 909.322
    // discuss at: http://phpjs.org/functions/ord
    // +   original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // +   bugfixed by: Onno Marsman
    // +   improved by: Brett Zamir (http://brett-zamir.me)
    // *     example 1: ord('K');
    // *     returns 1: 75
    // *     example 2: ord('\uD800\uDC00'); // surrogate pair to create a single Unicode character
    // *     returns 2: 65536
    var str = string + '';

    var code = str.charCodeAt(0);
    if (0xD800 <= code && code <= 0xDBFF) { // High surrogate (could change last hex to 0xDB7F to treat high private surrogates as single characters)
        var hi = code;
        if (str.length === 1) {
            return code; // This is just a high surrogate with no following low surrogate, so we return its value;
                                    // we could also throw an error as it is not a complete character, but someone may want to know
        }
        var low = str.charCodeAt(1);
        if (!low) {

        }
        return ((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000;
    }
    if (0xDC00 <= code && code <= 0xDFFF) { // Low surrogate
        return code; // This is just a low surrogate with no preceding high surrogate, so we return its value;
                                // we could also throw an error as it is not a complete character, but someone may want to know
    }
    return code;
}

And now the actual grid code itself:

Ext.ns('Media.main');

Media.main.MediaGrid = Ext.extend(Ext.grid.GridPanel , {
    initComponent: function() {

				this.expander = new Ext.grid.RowExpander({
            tpl: new Ext.XTemplate(
                '<h2>Media Categories</h2>',
                '<tpl if="Categories">',
                '<tpl for="Categories">',
                '{name}<br />',
                '</tpl>',
                '</tpl>')
        });

				this.store = new Ext.data.DirectStore({
					  storeId:'mediaList',
            directFn: jpActions.media.getMediaList,
						paramsAsHash:true,
						fields:['id','title','best_seller','sku',{name:'speaker',mapping:'Speaker.name'},'Categories','created_at'],
						idProperty:'id',
						root:'media',
						totalProperty:'total_count',
						sortInfo:{
							field:'title',
							direction:'ASC'
						},
						remoteSort:true,
						paramOrder: ['sort','dir','start','limit','fields','query'],
						paramNames:{
						    start : 'start',  // The parameter name which specifies the start row
						    limit : 'limit',  // The parameter name which specifies number of rows to return
						    sort : 'sort',    // The parameter name which specifies the column to sort on
						    dir : 'dir',       // The parameter name which specifies the sort direction
						    fields: 'fields',
								query: 'query'
						},
						listeners: {
							load:function(){
								//console.info('load',this,arguments);
							}
						}
        });

        this.store.setDefaultSort("title", "ASC");

        this.rowActions = new Ext.ux.grid.RowActions({
            actions:[{
                iconCls:'icon-best-seller',
                qtip:'Media is a Best Seller',
                style:'margin:0 0 0 3px'
            }]
        });

        this.rowActions.on('action', this.onRowAction, this);

        /*  Ext 3.0 Feature
        this.editor = new Ext.ux.RowEditor({
            saveText: 'Update'
        });
        */

        this.pagingBar = new Ext.PagingToolbar({
           pageSize: 150,
           store: this.store,
           displayInfo: true,
           dispalyMsg: 'Displaying {0} - {1} of {2}',
           emptyMsg: 'No Media Found'
        });

				this.sm = new Ext.grid.CheckboxSelectionModel();

        Ext.apply(this, {
						loadMask:true,
						columns:[this.sm,this.expander,{
						        id:'title',
						        header:'Media Title',
						        sortable:true,
						        width:80,
						        dataIndex:'title'
						    },{
						        id:'speaker_id',
						        header:'Speaker',
						        sortable:true,
						        width:80,
						        dataIndex:'speaker'
						    },{
						        id:'sku',
						        header:'SKU',
						        sortable:true,
						        width:120,
						        dataIndex:'sku'
						    },{
						        id:'best_seller',
						        header:'Best Seller',
						        sortable:true,
						        width:80,
						        dataIndex:'best_seller'
						    },{
						        id:'created_at',
						        header:'Created At',
						        sortable:true,
						        width:80,
						        dataIndex:'created_at'
						    },
						    this.rowActions
						],
						sm:this.sm,
						stripeRows:true,
						title:"Jeff Pipas - Doctrine Media Grid",
						viewConfig:{forceFit:true},
						plugins:[new Ext.ux.grid.Search({
						        iconCls:'icon-zoom',
						        minChars:3,
						        width:240,
						        autoFocus:true
						}),this.rowActions,this.expander],
						bbar:this.pagingBar
        });

        this.on({
					beforeshow:{
						scope:this,
						single:true,
						fn:function(){
							this.store.load({params:{sort:'title',dir:'ASC',start:0,limit:150}});
						}
					}
				});
        Media.main.MediaGrid.superclass.initComponent.apply(this, arguments);
    },
    onRowAction:function(grid, record, action, row, col) {

    }

});

Ext.reg("media_grid", Media.main.MediaGrid);

So I’m assuming you’re pretty familiar with what I’m doing here. I’m basically creating a small application. The first file (I’ve named it app.js) sets up my Ext.Direct api calls – as well as initializes my grid using my defined xtype, which I built in the second file (named media_grid.js). The grid is pretty straight forward. I’ve got an expander row to show all the media’s categories, and a few columns to display the data, along with a paging bar, and the search box.

To Be Continued

Thats all for now – this should at least get you started – we’ve got to add our ExtJS library files, and a few plugin files and define them in our view.yml config next. Then, the fun begins. Stay tuned!

Categories: ExtJs, Symfony Tags: , , ,

MBA 564 – Personal Leadership Plan

August 31st, 2009 No comments

Academic Integrity Statement

“This assignment is my own work. Any assistance I received in its preparation is acknowledged within the assignment in accordance with academic practice. If I used data, ideas, words, diagrams, pictures, or other information from any source, I have cited the source(s). I understand that copying text word for word from other sources without placing it in quotation marks is considered plagiarism and not acceptable even if I cite the source where the material was copied from. I certify that this assignment was prepared specifically for this class and has not been submitted, in whole or in part, to any other class at Walsh College or elsewhere.”

Jeff Pipas

Below is my podcast for MBA 564 – Project Leadership and Management

MBA564.mp3

Sources:

Alessandra, Dr. Tony (2009). The Platinum Rule Behavioral Style Assessment. Retrieved on August 29, 2009 from http://www.assessmentsnow.com/

Ferraro, Jack (2008). The Strategic Project Leader: Mastering Service-Based Project Leadership. Boca Raton, FL: Auerbach Publications, Taylor & Francis Group.

Mersino, Anthony (2007). Emotional Intelligence for Project Managers: The People Skills You Need to Achieve Outstanding Results. New York City, NY: AMACOM.

Music By Subject 2 Change – Copyright 2001. Used with permission.

Categories: MBA, Project Management Tags:

Where Are They Now?

August 23rd, 2009 No comments

Found this gem of an album in iTunes on an old machine of mine.  I’m posting it here for archival purposes so when the time comes to either humiliate, or boast, I’ve got somewhere to direct people.  Either way, it’s good to remember where this came from, and the fun we had making it. Kinda miss it.  Anyways, for your listening or comedic pleasure:

Subject 2 Change: Beyond Our Control

01 All This Time.mp3 02 Saved Me Trying.mp3 03 Help Me Through.mp3 04 Hold On.mp3 05 Waste.mp3 06 Stay With Me.mp3 07 Storybook Tales.mp3 08 Dream.mp3 09 In The End.mp3 10 Boonoover Stoory.mp3

Notable times during this album:

1 – All This Time: Opening track of our very first album as a band – and Tom swears twice in the first 3 minutes.

2 – The Gregorian Chant at the end of Hold On – followed by Tom’s wonderful introduction to Waste

3 – The superior drumming skills of Mark on a Boonoover fill (around 3:43)

If you find more – leave some comments.

Symfony and ExtJS Grid Search

August 4th, 2009 1 comment

I’ve been working on several projects that have utilized an ExtJS grid using the search plugin, with a Symfony backend.  One of the features of the grid search plugin is that it allows the user to search across several fields (columns) all at once.  Below is a snippet of code I found very useful in my actions when working with the grid search plugin, and symfony.

$column = AdvertisementPeer::translateFieldName(sfInflector::camelize(strtolower($field)), BasePeer::TYPE_PHPNAME, BasePeer::TYPE_COLNAME);
if($request->getParameter('query') != ""){
    array_push($crit_array, $c->getNewCriterion($column,"%".$request->getParameter('query')."%",Criteria::LIKE));
}

The code in the case takes a field string (given to us by a POST to the server from the search plugin), maps it to the peer class using [cci_php] sfInflector::camelize()[/cci_php] for the domain object we’re searching on, and then adds a new criteria search for that item to a query.  The work horse of this snippet is the sfInflector class.  Pretty useful class.  That, and it saves you from having to write [cci_php]Peer::COLUMN_NAMES[/cci_php] out all the time.

Categories: ExtJs, Symfony Tags: , ,