Ext JS with PHP: How to Create Nodes for a TreePanel

Continuing with my PHP and Ext JS series, this article will show you how to use PHP to create the nodes of an Ext JS TreePanel.

Our goal is to write code that creates a simple Ext JS TreePanel, depicted in the screenshot below, where a PHP page will generate the JSON data needed to create three nodes: Datasources, Datasets and Reports.

Let’s first take care of the Ext JS code for the TreePanel, and then focus on the PHP side.  To generate the Ext TreePanel, we can use the following code:

var tree = new Ext.tree.TreePanel({
    renderTo: Ext.getBody(),
    title: 'Reporting Project',
    width: 200,
    height: 250,
    userArrows: true,
    animate: true,
    autoScroll: true,
    dataUrl: 'tree-nodes.php',
    root: {
        nodeType: 'async',
        text: 'Root Node'
    },
    listeners: {
        render: function() {
            this.getRootNode().expand();
        }
    }
})

As you can tell from the code, after the TreePanel is rendered, a call to the root node’s expand() function will trigger a request to the tree-nodes.php page.  This is the page that will generate the JSON-formatted data needed to create the Datasources, Datasets and Reports nodes.  Let’s work on the PHP script now.

Creating a PHP model of an Ext JS TreeNode

We can represent an Ext JS TreeNode with a PHP Class that can be as simple as the following:

class TreeNode {

    public $text = "";
    public $id = "";
    public $iconCls = "";
    public $leaf = true;
    public $draggable = false;
    public $href = "#";
    public $hrefTarget = "";

    function  __construct($id,$text,$iconCls,$leaf,$draggable,
            $href,$hrefTarget) {

        $this->id = $id;
        $this->text = $text;
        $this->iconCls = $iconCls;
        $this->leaf = $leaf;
        $this->draggable = $draggable;
        $this->href = $href;
        $this->hrefTarget = $hrefTarget;
    }
}

The TreeNode Class contains the main properties of an Ext.tree.TreeNode, and a constructor function that populates these properties.  Creating tree nodes information for our Ext JS TreePanel is as simple as creating TreeNode instances, storing them in an array and adding a JSON representation of this array to the http response stream.  This is how the code would look:

$n1 = new TreeNode("datasources","Datasources","data",true,false,"","");
$n2 = new TreeNode("datasets","Datasets","dataset",true,false,"","");
$n3 = new TreeNode("reports","Reports","report",true,false,"","");

$nodes = array($n1,$n2,$n3);

echo (json_encode($nodes));</pre>
And this is the generated JSON for our tree nodes:
<pre class="brush: js;">[{"text":"Datasources","id":"datasources","iconCls":"data",
"leaf":true,"draggable":false,"href":"","hrefTarget":""},
{"text":"Datasets","id":"datasets","iconCls":"dataset",
"leaf":true,"draggable":false,"href":"","hrefTarget":""},
{"text":"Reports","id":"reports","iconCls":"report",
"leaf":true,"draggable":false,"href":"","hrefTarget":""}]

Encapsulating Ext JS tree nodes information in a PHP Class

Although the approach above satisfies our simple requirements, we can strengthen the design by creating a Class that encapsulates the tree nodes, as shown in the following code:

class TreeNodes {

    protected $nodes = array();

    function add($id,$text,$iconCls,$leaf,$draggable,
        $href,$hrefTarget) {

        $n = new TreeNode($id,$text,$iconCls,$leaf,
                $draggable,$href,$hrefTarget);

        $this-&gt;nodes[] = $n;
    }

    function toJson() {
        return json_encode($this-&gt;nodes);
    }
}

With the TreeNodes Class, we have conveniently hidden the array of tree nodes and created a couple of helper functions, add() and toJson(), that allow us to add nodes and produce the JSON representation of these nodes, respectively.

The code to create our tree nodes would now look like this:

$treeNodes = new TreeNodes();

$treeNodes-&gt;add("datasources","Datasources","data",true,false,"","");
$treeNodes-&gt;add("datasets","Datasets","dataset",true,false,"","");
$treeNodes-&gt;add("reports","Reports","report",true,false,"","");

echo $treeNodes-&gt;toJson();

Downloads

Grab the code: ExtJS-TreePanel-with-PHP.zip

Want to learn more?

Ext-JS-Cookbook My Ext JS 3.0 Cookbook has more than a hundred step-by-step recipes that you can use to build your Ext JS applications.  Download a sample chapter and see for yourself.

Writing an Ext JS Plugin

In this article I will show you how to write an Ext JS plugin that displays how many characters you can type on a textarea before the a maximum number of characters is reached.

Our plugin will subscribe to the keyup event of the textarea, and will use the textarea’s maxLength property and getValue() function to calculate how many characters can still be typed before the maxLength value is reached. The current and maximun number of characters will be displayed using a div element that will be rendered below the textarea’s html input field.

The basic code template of an Ext JS plugin

An Ext JS plugin gains access to its host component via a its init() function, that takes a reference to the component, and is called after the component is initialized but before it is rendered.

Any code template for a plugin in Ext JS must include the init() function.  Our plugin will use the following template:

Ext.ns('Plugins');

Plugins.CharsLeftReminder = (function() {

    return {

        init: function(field) {

        }
    };
});1
<h3>Implementing the Ext JS plugin</h3>
<p>In the <span style="font-family: Courier New;">init()</span> function, we will first make sure the plugin&rsquo;s host is a textarea:</p>
1init: function(field) {

    var thisXType = field.getXType();

    if (false == (field instanceof Ext.form.TextArea)) return; // We only want to modify textarea fields.

}

We also define the function that will return the characters left, as well as the handler for the keyup event:

init: function(field) {

    ...

    var maxChars = field.maxLength;
    var charsLeft;
    var suffix = ' of ' + maxChars + ' characters left';
    var instanceId = field.id + '-charsLeft';

    var getCharsLeft = function(field) {
        var charsLeft = maxChars - field.getValue().length;
        return charsLeft;
    };

    var keyupHandler = function(f, evt) {
        charsLeft = getCharsLeft(f);
        Ext.getDom(instanceId).innerHTML = charsLeft + suffix;
    };

}

Finally, we use the createSequence() function to define our own onRender() and onDestroy() methods, to be called after the textarea’s onRender() and onDestroy():

init: function(field) {

    ...

    Ext.apply(field, {
        enableKeyEvents: true,
        onRender: field.onRender.createSequence(function(ct, position) {
            if (Ext.getDom(instanceId)) return;
		charsLeft = getCharsLeft(field);
            var reminder = {
                tag: 'div',
                id: instanceId,
                html: charsLeft + suffix,
                style: 'padding:1px 1px 5px 1px;'
            }
            Ext.DomHelper.insertAfter(this.el, reminder);
            field.on('keyup', keyupHandler);
        }),
        onDestroy: field.onDestroy.createSequence(function() {
            var el = Ext.getDom(instanceId);
            if (el) {
                Ext.removeNode(el);
            }
        })
    });

}

Besides connecting the keyupHandler function to the keyup event, our onRender() implementation defines a div element that will display the “[N] of [maxLength] characters left” message.  We use DomHelper.inserAfter() to add this div to the DOM immediately after the textarea’s html input field.

Notice that  we use enableKeyEvents to enable the broadcast of key events from the textarea’s html input field.

To be on the safe side, we clean after ourselves by using onDestroy() to remove our plugin’s DOM node from the DOM.

This completes the plugin implementation.  Now we can use it, as in the following example:

Plugins.updateForm = new Ext.FormPanel({
    id: 'updateForm',
    frame: true,
    renderTo: Ext.getBody(),
    title: 'What\'s happening?',
    bodyStyle: 'padding:5px',
    width: 550,
    layout: 'form',
    items: [
        {
            xtype: 'textarea',
            hideLabel: true,
            name: 'comments',
            anchor: '100%',
            height: 100,
            maxLength: 140,
            plugins: [new Plugins.CharsLeftReminder()]
        }
    ],
    buttons: [{
        text: 'Send'
    }, {
        text: 'Cancel',
        handler: function() {
            Ext.getCmp('updateForm').destroy();
        }
    }]
});

Downloads

Grab the code for the sample from my downloads page.

Want to learn more?

Ext-JS-Cookbook My Ext JS 3.0 Cookbook has more than a hundred step-by-step recipes that you can use to build your Ext JS applications.  Download a sample chapter and see for yourself.

What Is An ExtJS Plugin?

Here are some facts that will help you understand Ext JS plugins.

  • An ExtJS plugin is a class that defines and encapsulates behavior to be added to one or more ExtJS components.
  • Plugins are attached to their target component(s) via the plugins configuration option
  • Instead of exposing services that the target component can use, a plugin gains access to the component via a required function, init(), that takes a reference to the component
  • A plugin’s init() function is called at a specific point of its target component’s lifecycle: after the component is initialized and before it is rendered
  • A plugin can be lazy-instantiated if its target is lazy-instantiated

Can you spot the differences and similarities with other ways of modifying component behavior in ExtJS, for example, subclassing and configuration options?

Using the Ext Scheduler, Part 1

Fellow developer Mats Bryntse has kindly asked me to review his Ext Scheduler component, and this is the first of a series of articles where I talk about the Ext Scheduler while I use it to build a hypothetical conference room reservations interface.

The goal for this first article is to build a scheduler that displays the reservations that exist for a number of conference rooms.  In future articles I will be adding the ability to create, edit and remove reservations.

At the end of this phase, we will have a scheduler that looks like the following screenshot:

The business domain model the scheduler is based on

The scheduler component is based on a business domain model consisting of resources and events. You can think of a resource as an entity that can be assigned domain events.  People, places, tasks, classes and cars are real-life examples of resources.  Meetings, appointments and other activities are examples of events.

In this model, there is a multiplicity relationship where a resource can be assigned one or more business domain events:

Visually, the scheduler can be thought of as two views:

  • A resources view. This view displays a list of business domain entities or “resources” to which business domain events can be assigned.  It can include any number of properties of the resources, shown in one or more columns of the scheduler.
  • A time view. This view consists of a number of columns representing a time interval, and displays the business domain events occurring within this interval for the given resources.  The time columns adhere to one of various precision levels: hour, day, week, month, year, or your own.

These are the views in our example:

The scheduler’s API

As a system, the scheduler can be roughly divided into the following discrete parts:

  • A grid panel that serves as the container where the information is displayed.
  • A “resource” data store, which provides the information consumed by the resources view, for example, people, tasks, classes or places.
  • An “event” data store, which provides the data for the time view, for example, events’ start date, duration, name and description.
  • An autoViews array containing one or more objects, each defining the configuration of a time view that the scheduler will use (Yes, you can interchange time views) .  A time view’s configuration includes the precision used (hour, day, week or month), the number of days to display, a renderer function used to display the event information within the view,  and an optional “behavior” object where you can put code specific to the view type.
  • A number of templates and plugins that provide features such as showing event tooltips, drag and drop, and event edition.

The scheduler API’s is currently a moving target, but this is something to be expected of a very young component under active development.

Now that we know the main concepts, let’s see what we can do with the scheduler.

Setting up the Ext Scheduler

Setting up the scheduler involves adding references to its JavaScript and css sources. You can obtain the Ext Scheduler at http://www.ext-scheduler.com.  While the JavaScript source currently resides in a single file, the styles come in files that are named after the piece of functionality they address. You will only need to include the styles related to the scheduler features you will use in your application. In my example, I use the following:

<!-- Scheduler includes -->
<link href="ext-sch/css/sch.schedulerpanel.css" rel="stylesheet" type="text/css" />
<link href="ext-sch/css/sch.plugins.resize.css" rel="stylesheet" type="text/css" />
<link href="ext-sch/css/sch.plugins.common.css" rel="stylesheet" type="text/css" />
<link href="ext-sch/css/sch.event.css" rel="stylesheet" type="text/css" />
<script src="ext-sch/js/Scheduler/sch-all-eval.js" type="text/javascript" ></script>

I will also use a few extra classes to change the look of the rendered events:


<style type="text/css">
 .sch-event     {
 background:url(ext3/images/default/grid/grid-blue-hd.gif) repeat-x;

border-color:#6699cc;

border-style:solid;

color:#1C417C;

cursor:pointer;

text-align: center;

top:0px;

}

.sch-event-inner    {        padding:1px;                   }

.sch-event-selected     {        border-color:#3366cc !important;        border-width:1px !important;    }

div.sch-event:hover     {        border-color:#3366cc;    }

</style>

These classes will give me a nice gradient for the event background, as well as different border colors for the hover and selected states.  This is how an event looks when rendered:

Configuring the resource and event data stores

The resource store contains the conference rooms id’s and names. I’m using data from a local array to keep my example simple:

// Resource store
ConfRooms.confRoomStore = new Ext.data.JsonStore({
fields: ['Id', 'name'],
data: [        { Id: '1', name: 'Conference Room A' },
        { Id: '2', name: 'Conference Room B' },
        { Id: '3', name: 'Conference Room C' },
       { Id: '4', name: 'Conference Room D' },
       { Id: '5', name: 'Conference Room E' }
]});

The event store in turn contains the reservations that exist for the available conference rooms:

// Event store
ConfRooms.reservationsStore = new Ext.data.JsonStore({
	url: 'reservations.aspx',
	autoLoad: true,
	root: 'records',
	idProperty: 'Id',
	fields: [         ?
                 // Mandatory
		 {name: 'Id' },
		 { name: 'StartDate', type: 'date', dateFormat: 'Y-m-d G:i:s' },
		 { name: 'EndDate', type: 'date', dateFormat: 'Y-m-d G:i:s' },
		 { name: 'ResourceId', type: 'string', mapping: 'RoomId' },
		// Application-specific
		 {name: 'ReservedTo', type: 'string', mapping: 'ReservedTo' },
		 { name: 'Description', type: 'string', mapping: 'Description' }    ],
	writer: new Ext.data.JsonWriter({autoSave: false,encode: true    })
});

Some fields are mandatory in the event store: Id, StartDate, EndDate and ResourceId. The scheduler needs these fields in order to properly render the events. In particular, the relationship between resources and events is expressed in the scheduler’s API via the ResourceId property of an event, which maps to the Id of a resource. (A conference room in this example) The rest of the fields are application-specific. For this example I use a couple of application-specific fields, but you can use any fields your business domain requires.

Creating the scheduler panel and configuring the time view

To define the start and end date of my time view, I will use hard-coded values:

ConfRooms.start = new Date(2009, 11, 16, 8, 00, 00);
ConfRooms.end = new Date(2009, 11, 16, 18, 00, 00);

Now I can go ahead and create the scheduler panel:

ConfRooms.sch = new Sch.SchedulerPanel({
	height: 300,  width: 800,
	renderTo: 'conf-rooms-1',
	title: 'Conference Room Reservations',
	trackMouseOver: false,
	stripeRows: true,
	store: ConfRooms.confRoomStore,
	eventStore: ConfRooms.reservationsStore,
	startParamName: 'StartDate',
	endParamName: 'EndDate',
	viewConfig: {forceFit: true },
	columns: [{ header: 'Room Name', sortable: true, width: 150, dataIndex: 'name' }],
	eventTemplate: new Ext.Template( '&lt;div id="{id}" style="width:{width}px;left:{leftOffset}px" class="sch-event {cls}"&gt;',  '&lt;div class="sch-event-inner"&gt;{text}&lt;/div&gt;',  '&lt;/div&gt;').compile(),
	tooltipTpl: new Ext.Template('&lt;div class="eventTip"&gt;',  '&lt;div&gt;&lt;b&gt;{Description}&lt;/b&gt;&lt;/div&gt;',  '&lt;div&gt;Reserved by: {ReservedTo}&lt;/div&gt;',  '&lt;/div&gt;').compile()  });  

In this source code, mixed with configuration options inherited from the GridPanel class, you will find options like the eventTemplate and tooltipTpl templates, which define the html markup for the events and event tooltips respectively. The startParamName and endParamName options, although not used in the example, map the start and end dates of the time view to event store parameters. Finally, I will configure the view I want to use:

ConfRooms.sch.setView(ConfRooms.start, ConfRooms.end, 'hour', Sch.ViewBehaviour.HourView, function(item, m, r, row, col, ds, index) {    return {        text: item.get('Description')    };});
 

An alternative to using the autoViews array, the setView function allows you to configure the scheduler’s view.  Here I use the start and end date variables created previously, define the precision to be used (hour) and a behavior for the view, and create a renderer function for the events.  The renderer function will simply provide the reservation’s description as the text to be rendered for the event. At this point we can check out the panel and confirm that the resources and events are displaying correctly:

The only detail I’m not happy with is that the column headers are displaying the time using the 24-hr format. We can change this format defining a new header renderer function before the scheduler panel is instantiated: Sch.ColumnFactory.headerRenderers.hour = function(a) { return a.format(“g:i A”) }; This function will produce the familiar 12-hr format:

Conclusion

In this first Ext Scheduler article I talked about the concepts behind the Ext Scheduler, and showed you how to use this component to put together a simple UI that displays conference room reservations.  In the next article of this series we will resume development and add the ability to create, edit and remove reservations. I encourage you to try and experiment with the source code for this article, as well as check out the scheduler’s examples and support materials.

Downloads

Grab the code for the sample: ExtScheduler1.zip. You can obtain the Ext Scheduler at http://www.ext-scheduler.com.

Ext JS Tips: Binding an Array of Objects to a GridPanel

How can you bind an array of objects to a GridPanel?  What kind of data store should you use?

Assume you have an array of employee objects that you need to display using a GridPanel:

var employeeArray = [];

for( i=1; i&lt;100; i++ ){
    var anEmployee = {};
    anEmployee.name = 'nm'+i;
    anEmployee.surname = 'snm'+i;
    anEmployee.no = i;
    employeeArray.push(anEmployee);
}

You can display this array of employee objects using an ArrayStore.  Here’s how to do it.

First, create a data record constructor for the employee object.  You can build this constructor using the Ext.dataRecord.create() function.

When using create(), you need to pass an array of field definitions that will help the data store understand how your employee objects built:

var employeeRecord = Ext.data.Record.create([
    {name:'name',mapping:'name'},
    {name:'surname',mapping:'surname'},
    {name:'no',mapping:'no'}
]);

Next, instantiate an ArrayStore:

var employeesStore = new Ext.data.ArrayStore({
    idIndex: 2,
    fields:employeeRecord,
    data:employeeArray
})

Use the fields and idIndex config options to specify both, the fields definitions, and what element of the row array is the ID for a given record.

If you inspect the ArrayStore config options in the Ext JS documentation, you will not find these options listed.  However, you will find a note stating that ArrayStore accepts all the configuration options of the ArrayReader class, fields and idIndex among them.

Finally, create the grid panel:

var g = new Ext.grid.GridPanel({
    id:'inProgressGrid',
    title:'Employees',
    width:600,
    height:600,
    renderTo:Ext.getBody(),
    columns:[{header:'Name',id:name}],
    autoExpandColumn:name,
    store:employeesStore,
    viewConfig:{
        forceFit:true,
        emptyText:'No employees to display'
    }
});

Want to learn more?

Ext-JS-Cookbook My Ext JS 3.0 Cookbook has more than a hundred step-by-step recipes that you can use to build your Ext JS applications.  Check out a sample chapter and see for yourself.

Learn To Customize the Ext JS Charts

Following up on Custom Markers for Your Ext JS Charts, here’s another example of how you can change the look of the Ext JS charts.  In  this case, I will show you how to change a data series style so, as shown in the image below, the markers have a different look and are not connected by a line.

How to do it

As usual, it’s very important to correctly configure the CHART_URL constant.  Observe that I’m using a folder called ext3 for my Ext JS installation.  You might need to modify the folder name to match your setup.

Ext.chart.Chart.CHART_URL = 'ext3/resources/charts.swf';

As a second step, let’s create a data store to hold some dummy records.

var store = new Ext.data.JsonStore({
    fields:['name', 'games', 'movies','music'],
    data: [
        {name:'Jul 07', games: 245, movies: 300, music:700},
        {name:'Aug 07', games: 240, movies: 350, music:550},
        {name:'Sep 07', games: 355, movies: 400, music:615},
        {name:'Oct 07', games: 375, movies: 420, music:460},
        {name:'Nov 07', games: 490, movies: 450, music:625}
    ]
});

And now we can create our line chart.

var c = new Ext.chart.LineChart({
    renderTo: Ext.getBody(),
    width:300,
    height:275,
    id:'chart',
    store: store,
    xField: 'name',
    yAxis: new Ext.chart.NumericAxis({
        displayName: 'games',
        labelRenderer : Ext.util.Format.numberRenderer('0,0')
    }),
    tipRenderer : function(chart, record, index, series){
        if(series.yField == 'games'){
            return Ext.util.Format.number(record.data.games, '0,0') + ' games in ' + record.data.name;
        }if(series.yField == 'music'){
            return Ext.util.Format.number(record.data.music, '0,0') + ' CD\'s in ' + record.data.name;
        }
        else{
            return Ext.util.Format.number(record.data.movies, '0,0') + ' movies in ' + record.data.name;
        }
    },
    extraStyle: {
        padding: 10,
        animationEnabled: true,
        legend:{
            display:'bottom'
        },
        xAxis: {
            color: 0x3366cc,
            majorGridLines: {size: 1, color: 0xdddddd}
        },
        yAxis: {
            color: 0x3366cc,
            majorTicks: {color: 0x3366cc, length: 4},
            minorTicks: {color: 0x3366cc, length: 2},
            majorGridLines: {size: 1, color: 0xdddddd}
        }
    },
    series: [{
        type: 'line',
        displayName: 'Movies',
        yField: 'movies',
        style: {
            size: 7,
            borderColor: 0xCC0000,
            fillColor:0xffffff,
            connectPoints:false
        }
    },{
        type:'line',
        displayName: 'Games',
        yField: 'games',
        style: {
            size: 7,
            borderColor: 0x00CC00,
            fillColor: 0xffffff,
            connectPoints:false
        }
    },{
        type:'line',
        displayName: 'Cd\'s',
        yField: 'music',
        style: {
            size:7,
            borderColor: 0x0000CC,
            fillColor: 0xffffff,
            connectPoints:false
        }
    }]
});

How it works

You can stylize the look of a data series using the style object.  The size property  represents the size of the markers in the series.  You can also use borderColor and fillColor to change the border and fill colors of the markers.  These two properties accept hex-formatted string or number values, for example: “#ff0000″, “ff0000″ or 0xff0000.

Setting connectPoints to false allows you to display the line series with no lines connecting the markers.

Where to find more information about Ext JS charts

The Ext JS charts are not well documented yet, but you can find more information at the YUI charts page.

Downloads

Grab the code: Learn-To-Customize-the-Ext-JS-Charts.zip

Want to learn more?

Ext-JS-Cookbook My Ext JS 3.0 Cookbook has more than a hundred step-by-step recipes that you can use to build your Ext JS applications.  Download a sample chapter and see for yourself.

Custom Markers for Your Ext JS Charts

If you’re looking for a quick way to customize your Ext JS charts, one of the things you can do is use images to change the look of a series’ data point markers.  Let’s put together a simple example that will create a chart similar to the one below.

How to do it

First, it’s very important to correctly configure your CHART_URL constant.

Ext.chart.Chart.CHART_URL = 'ext3/resources/charts.swf';

Next, let’s create data store to hold some dummy records.

var store = new Ext.data.JsonStore({
    fields:['name', 'games', 'movies','music'],
    data: [
        {name:'Jul 07', games: 245, movies: 300, music:700},
        {name:'Aug 07', games: 240, movies: 350, music:550},
        {name:'Sep 07', games: 355, movies: 400, music:615},
        {name:'Oct 07', games: 375, movies: 420, music:460},
        {name:'Nov 07', games: 490, movies: 450, music:625}
    ]
});

And now we can create our line chart.

var chart = new Ext.chart.LineChart({
    renderTo: Ext.getBody(),
    width: 300,
    height: 300,
    id: 'chart',
    store: store,
    xField: 'name',
    yAxis: new Ext.chart.NumericAxis({
        displayName: 'games',
        labelRenderer: Ext.util.Format.numberRenderer('0,0')
    }),
    tipRenderer: function(chart, record, index, series) {
        if (series.yField == 'games') {
            return Ext.util.Format.number(record.data.games, '0,0') + ' games in ' + record.data.name;
        } if (series.yField == 'music') {
            return Ext.util.Format.number(record.data.music, '0,0') + ' CD\'s in ' + record.data.name;
        }
        else {
            return Ext.util.Format.number(record.data.movies, '0,0') + ' movies in ' + record.data.name;
        }
    },
    extraStyle: {
        padding: 10,
        animationEnabled: true,
        font: {
            name: 'Tahoma',
            color: 0x444444,
            size: 11
        },
        legend: {
            display: 'bottom'
        },
        dataTip: {
            padding: 5,
            border: {
                color: 0x99bbe8,
                size: 1
            },
            background: {
                color: 0xDAE7F6,
                alpha: .9
            },
            font: {
                name: 'Tahoma',
                color: 0x15428B,
                size: 10,
                bold: true
            }
        },
        xAxis: {
            color: 0x69aBc8,
            majorTicks: { color: 0x69aBc8, length: 4 },
            minorTicks: { color: 0x69aBc8, length: 2 },
            majorGridLines: { size: 1, color: 0xeeeeee }
        },
        yAxis: {
            color: 0x69aBc8,
            majorTicks: { color: 0x69aBc8, length: 4 },
            minorTicks: { color: 0x69aBc8, length: 2 },
            majorGridLines: { size: 1, color: 0xdfe8f6 }
        }
    },
    series: [{
        type: 'line',
        displayName: 'Movies',
        yField: 'movies',
        style: {
            color: 0xCCCCCC,
            image: 'img/star_red.png',
            mode: 'stretch'
        }
    }, {
        type: 'line',
        displayName: 'Games',
        yField: 'games',
        style: {
            color: 0xCCCCCC,
            image: 'img/star_green.png',
            mode: 'stretch'
        }
    }, {
        type: 'line',
        displayName: 'Cd\'s',
        yField: 'music',
        style: {
            color: 0xCCCCCC,
            image: 'img/star_blue.png',
            mode: 'stretch'
        }
    }]

});

How it works

You can stylize the look of a data series using the style object.  In this case, the image property defines the image that will be used as data point marker.

Using mode:‘stretch’ will resize the image to the dimensions of the data point marker.  And color is the color of the lines and markers in the series.  (Not relevant for our markers because we are using images.)

In future articles I will cover other customization options for the Ext JS charts.  What about you?  Care to share what you’ve done with them?

Downloads

Grab the code: Custom-Markers-for-Your-Ext-JS-Charts.zip

Want to learn more?

Ext-JS-Cookbook My Ext JS 3.0 Cookbook has more than a hundred step-by-step recipes that you can use to build your Ext JS applications.  Download a sample chapter and see for yourself.

Display an Image Inside an Ext JS GridPanel’s Cell, Part 3

Are you stuck figuring out how to display an image inside an Ext GridPanel cell?  In this article we will examine how this can be done using a template column.  (In part 1 and part 2 of this series we explored how it can be done using a renderer function.) As in previous articles, the example I will use is a GridPanel that displays movie rentals information.  Along with movie titles and number of rentals, the grid will display rental trends using a small icon, as shown in the following screenshot: Ext-template-column

How to do it

First, we need to define the styles used to insert the trend icons in the gridd’s cells:

&lt;style type="text/css"&gt;
   .trend-down
   {
       background:url(img/trend-down.png) right no-repeat !important;
   }
   .trend-neutral
   {
       background:url(img/trend-neutral.png) right no-repeat !important;
   }
   .trend-up
   {
       background:url(img/trend-up.png) right no-repeat !important;
   }
   .padding-img
   {
       width:18px;height:0px;
   }
&lt;/style&gt;

An ArrayStore will feed the grid a few dummy records:

var store = new Ext.data.ArrayStore({
    fields: ['title', 'rentals', 'trend'],
    data: [['ACADEMY DINOSAUR', 305, 'up'],
    ['DRAGONFLY STRANGERS', 240, 'neutral'],
    ['FAMILY SWEET', 188, 'down'],
    ['FREAKY POCUS', 205, 'up'],
    ['GABLES METROPOLIS', 265, 'up']]
});

As we will use a TemplateColumn, we need an instance of the XTemplate class to provide the formatting for the column’s cells:

var template = new Ext.XTemplate(
   '&lt;span class="{[this.getTrendClass(values.trend)]}"&gt;{rentals}' +
   '&lt;img class="padding-img" src="' + Ext.BLANK_IMAGE_URL + '"/&gt;&lt;/span&gt;',
   {
        getTrendClass : function(trend) {
            var retValue = '';
            switch (trend) {
                 case 'down':
                     retValue = 'trend-down';
                     break;
                 case 'neutral':
                     retValue = 'trend-neutral';
                     break;
                 case 'up':
                     retValue = 'trend-up';
                     break;
                 default:
                    retValue = 'trend-neutral';
                     break;
             }
             return retValue;
        }
   }
);

template.compile();

Next comes the GridPanel definition:

var grid = new Ext.grid.GridPanel({
    title: 'Movie rentals this month',
    store: store,
    columns: [
     { id: 'title-col', header: "Title", width: 275, dataIndex: 'title', sortable: true },
     { header: "Rentals", width: 75, dataIndex: 'rentals', sortable: true, align: 'right',
        xtype:'templatecolumn', tpl:template}
],
    autoExpandColumn: 'title-col',
    renderTo: Ext.getBody(),
    width: 350,
    height: 175,
    loadMask: true
});

How it works

A TemplateColumn is a column definition class that renders a value by processing a record’s data using a configured XTemplate. By using the xtype:’templatecolumn’ and tpl:template configuration options on the rentals column, we are specifying that the data type of this column is TemplateColumn, and its XTemplate instance is the previously defined template object. Note that template will render the cell’s contents as a span element that contains the value of the record’s rentals field:

'<span class="{[this.getTrendClass(values.trend)]}">{rentals}' +
'<img class="padding-img" src="' + Ext.BLANK_IMAGE_URL + '"/></span>'

To understand how the icon that represents the trend value is inserted, observe that inside the template we use the {[ ... ]} tags to call the template’s member function getTrendClass:

getTrendClass : function(trend) {
    var retValue = '';
    switch (trend) {
         case 'down':
             retValue = 'trend-down';
             break;
         case 'neutral':
             retValue = 'trend-neutral';
             break;
         case 'up':
             retValue = 'trend-up';
             break;
         default:
            retValue = 'trend-neutral';
             break;
     }
     return retValue;
}

getTrendClass changes the style of the span element using the css classes defined earlier.  The style simply assigns a background image to the span element, and this image is one of the three icons that represent the three trends: neutral, up, and down. Also in the template, the transparent image (Ext.BLANK_IMAGE_URL) serves to add some padding between the cell’s  value and the right border of the cell.  This prevents the value from displaying right over the trend icon, which is positioned on the background and right-aligned.

What about you?

Do you have a favorite approach to accomplish this task?  Share your thoughts in the comments section. :-)

Downloads

Download the sample: How-to-Display-an-Image-Inside-an-Ext-JS-GridPanel-3.zip