The Art of Gracefully Handling Session Timeouts with Ext JS

A very common solution to gracefully handling session timeouts consists of notifying your user that her session is about to time out, and asking her to take an action in order to prolong the session. In this article I will walk you through one implementation of this important usability and stability pattern utilizing the ExtJS JavaScript library.

The key to this approach is to divide the session duration into two distinct intervals, let’s call them t1 and t2, where t1 is the time the user has been inactive, until we alert her that her session is about to terminate, and t2 is a short grace period we will give the user to perform an action that will extend her session.

If we define Session Duration = t1 + t2, we’re looking at a logical sequence like so:

  • Start a “session timeout” timer
  • When the timer reaches t1, we alert the user and ask her if she wants to extend the session
  • If the user does not extend her session and t2 is reached, we terminate the session
  • If the user extends her session, we reset the session timer and jump back to step 2

As this approach relies on calculating the time the user has been inactive, the manner we define user inactivity in very important. In order to keep the sample code of this article simple, we will assume that the user is inactive if her interaction with the application is not generating requests to the server. For a more solid implementation you might want to consider additional events such as using the mouse, scrolling, etc. But this, of course, depends on the type of system you’re building.

Let’s now look at what it takes to implement this approach.

Starting a session timeout timer upon completing an AJAX request

As we’re assuming that the user is inactive if she is not generating requests to the server, the most important part of our implementaiton is a piece of code we will run upon completing every AJAX request:

Ext.Ajax.on('requestcomplete', function (conn, response, options) {

    if (options.url !== App.SESSION_KILL_URL) {
        // Reset the client-side session timeout timers.
        // Note that you must not reset if the request was to kill the server-side session.
        App.sessionAboutToTimeoutPromptTask.delay(App.toMilliseconds(App.SESSION_ABOUT_TO_TIMEOUT_PROMT_INTERVAL_IN_MIN));
        App.killSessionTask.cancel();
    } else {
        // Notify user her session timed out.
        Ext.Msg.alert(
            'Session Expired',
            'Your session expired. Please login to start a new session.',
            function (btn, text) {

                // TODO: Here you need to make sure you kill the server-side session state.

                if (btn == App.BTN_OK) {

                    // TODO: Show logon form here.
                }
            }
        );
    }
});

This requestcomplete event handler consists of two branches. The first branch will run after we request any resource, except the page that will terminate the server-side session. Inside this branch we utilize a delayed task – App.sessionAboutToTimeoutPromptTask – to keep track of the duration of the session, and a delayed task – App.killSessionTask – that is responsible for triggering the request to terminate the server-side session. I will explain how these delayed tasks work in a minute.

The second branch of the event handler will run after we request the page that will take care of terminating the server-side session. What we need to do in this case, is notify the user that the session was terminated:

Ext.Msg.alert('Session Expired','Your session expired. Please login to start a new session.',function (btn, text) {
 // TODO: Here you need to make sure you kill the server-side session state.
 if (btn == App.BTN_OK) {
 // TODO: Show logon form here.
  }
});

I use an Ext.Msg.alert for this example, but you can modify this area to fit the needs of your application.

Let’s now look at the delayed tasks.

Notifying the user that her session is about to time out

In order to let the user know that her session is about to time out, we will use a delayed task like so:

App.sessionAboutToTimeoutPromptTask = new Ext.util.DelayedTask(function () {

    Ext.Msg.confirm(
        'Your Session is About to Expire',
        String.format('Your session will expire in {0} minute(s). Would you like to continue your session?',
            App.GRACE_PERIOD_BEFORE_EXPIRING_SESSION_IN_MIN),
        function (btn, text) {

            if (btn == App.BTN_YES) {
                // Simulate resetting the server-side session timeout timer
                // by sending an AJAX request.
                App.simulateAjaxRequest();
            } else {
                // Send request to kill server-side session.
                App.simulateAjaxRequestToKillServerSession();
            }
        }
        );

    App.killSessionTask.delay(App.toMilliseconds(
     App.GRACE_PERIOD_BEFORE_EXPIRING_SESSION_IN_MIN));
});

In this task we utilize an Ext.Msg.confirm to notify the user about the timeout and ask her to push the Yes button in order to extend her session. Upon clicking the Yes button, our code will execute an AJAX request that will restart the server-side session. Clicking the No button will execute an AJAX request that will terminate the server-side session immediately.

Notice how while we wait for the user’s response, we also run the App.killSessionTask delayed task, which will terminate the server-side session after the grace period if the user did not respond to our prompt.

It is important that we restart or terminate the session on the server side. As session state includes both client and server variables, we need to make sure that when the session terminates or is extended, these variables are correctly handled on both sides of our system.

The last piece of our timeout engine is App.killSessionTask , the delayed task that will trigger a request to terminate the server-side session after the grace period:

App.killSessionTask = new Ext.util.DelayedTask(function () {
   App.simulateAjaxRequestToKillServerSession();
});

Now that we have the main pieces in place, let’s take a look at the complete solution:

Ext.onReady(function () {

    Ext.ns('App');

    App.BTN_OK = 'ok';
    App.BTN_YES = 'yes';
    // 1 min. before notifying the user her session will expire. Change this to a reasonable interval.
    App.SESSION_ABOUT_TO_TIMEOUT_PROMT_INTERVAL_IN_MIN = .25;
    // 1 min. to kill the session after the user is notified.
    App.GRACE_PERIOD_BEFORE_EXPIRING_SESSION_IN_MIN = 1;
    // The page that kills the server-side session variables.
    App.SESSION_KILL_URL = 'kill-session.html';

    // Helper that converts minutes to milliseconds.
    App.toMilliseconds = function (minutes) {
        return minutes * 60 * 1000;
    }

    // Helper that simulates AJAX request.
    App.simulateAjaxRequest = function () {

        Ext.Ajax.request({
            url: 'foo.html',
            success: Ext.emptyFn,
            failure: Ext.emptyFn
        });
    }

    // Helper that simulates request to kill server-side session variables.
    App.simulateAjaxRequestToKillServerSession = function () {

        Ext.Ajax.request({
            url: App.SESSION_KILL_URL,
            success: Ext.emptyFn,
            failure: Ext.emptyFn
        });
    }

    // Notifies user that her session is about to time out.
    App.sessionAboutToTimeoutPromptTask = new Ext.util.DelayedTask(function () {

        console.log('sessionAboutToTimeoutPromptTask');

        Ext.Msg.confirm(
            'Your Session is About to Expire',
            String.format('Your session will expire in {0} minute(s). Would you like to continue your session?',
                App.GRACE_PERIOD_BEFORE_EXPIRING_SESSION_IN_MIN),
            function (btn, text) {

                if (btn == App.BTN_YES) {
                    // Simulate resetting the server-side session timeout timer
                    // by sending an AJAX request.
                    App.simulateAjaxRequest();
                } else {
                    // Send request to kill server-side session.
                    App.simulateAjaxRequestToKillServerSession();
                }
            }
            );

        App.killSessionTask.delay(App.toMilliseconds(
          App.GRACE_PERIOD_BEFORE_EXPIRING_SESSION_IN_MIN));
    });

    // Schedules a request to kill server-side session.
    App.killSessionTask = new Ext.util.DelayedTask(function () {
        console.log('killSessionTask');
        App.simulateAjaxRequestToKillServerSession();
    });

    // Starts the session timeout workflow after an AJAX request completes.
    Ext.Ajax.on('requestcomplete', function (conn, response, options) {

        if (options.url !== App.SESSION_KILL_URL) {
            // Reset the client-side session timeout timers.
            // Note that you must not reset if the request was to kill the server-side session.
            App.sessionAboutToTimeoutPromptTask.delay(App.toMilliseconds(App.SESSION_ABOUT_TO_TIMEOUT_PROMT_INTERVAL_IN_MIN));
            App.killSessionTask.cancel();
        } else {
            // Notify user her session timed out.
            Ext.Msg.alert(
                'Session Expired',
                'Your session expired. Please login to start a new session.',
                function (btn, text) {

                    if (btn == App.BTN_OK) {

                        // TODO: Show logon form here.
                    }
                }
            );
        }
    });

    // The rest of your app's code would go here. I will just simulate
    // an AJAX request so the session timeout workflow gets started.
    App.simulateAjaxRequest();
});

What do you think? Want to give it a try?

Ext JS with ASP.NET MVC: Session Timeout Notifications

In this article I will explain one approach you can take when you need to handle session timeouts in your ExtJS + ASP.NET MVC applications. The goal of this approach is simply to alert the user when her server session has timed out.

These are the basic steps you will follow when implementing this approach:

  1. Before an AJAX request is handled on the server side, determine if the session timed out. If the session timed out, respond with a “session timed out” code. If it didn’t, proceed to handle the AJAX request.
  2. Intercept the AJAX responses on the client side. When the “session timed out” code is detected, inform the user.

Intercepting ExtJS AJAX requests on the server

Suppose you have a FeesBilled and AccountsReceivable ExtJS data stores like so:

App.DetailsStore = function (config) {
    var config = config || {};
    Ext.applyIf(config, {
        fields: ['Id', 'Name', 'Amount'],
        root: 'records',
        totalProperty: 'total',
        remoteSort: true,
        sortInfo: {field:'name',direction:'asc'}
    });
    App.DetailsStore.superclass.constructor.call(this, config);
};
Ext.extend(App.DetailsStore, Ext.data.JsonStore);

var feesBilledStore = new FeesStore ({
    url: 'accounting/feesbilled
});
var accountsReceivableStore = new FeesStore ({
    url: 'accounting/accountsreceivable
});

Requests sent from these stores will be handled by the following ASP.NET MVC action methods:

public ActionResult FeesBilled(int start = 0, int limit = 0, string sort = "name", string dir = "asc")
{
    EmployeeModel empl = Session["empl"] as EmployeeModel;
    return DL.GetFeesBilled(start, limit, sort, dir, empl);
}

public ActionResult AccountsReceivable(int start = 0, int limit = 0, string sort = "name", string dir = "asc")
{
    EmployeeModel empl = Session["empl"] as EmployeeModel;
    return DL.GetAccountsReceivable(start, limit, sort, dir, empl);
}

As you can see, these action methods need an EmployeeModel instance that is stored in a session variable. And you need to guarantee that the session exists and is valid before passing the EmployeeModel instance to the data access layer of the system.

 

Then, how do you determine if the user’s session has timed out before these requests are processed?

You can accomplish this in ASP.NET MVC with a custom action filter. Action filters are custom attributes that allow you to perform logic either before an action method is called, or after an action method runs.

This is an action filter you can use to check for session timeouts:

public class AjaxSessionExpiredFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        HttpContext context = HttpContext.Current;
        // Check if session is supported.
        if (context.Session != null)
        {
            if (context.Session.IsNewSession)
            {
                // If this is a new session and an old session cookie exists, then old session timed out.
                string sessionCookie = context.Request.Headers["Cookie"];
                if ((null != sessionCookie) && (sessionCookie.IndexOf("ASP.NET_SessionId") > -1))
                {
                    context.Response.Write("error=sessiontimeout");
                    context.Response.End();
                }
            }
        }

        base.OnActionExecuting(filterContext);
    }
}

Notice that you need to override the filter’s OnActionExecuting method. This method is fired before the action method executes.

 

Armed with this custom action filter, you can simply decorate the controller methods like so:

[AjaxSessionExpiredFilter]
public ActionResult FeesBilled(int start = 0,
            int limit = 0, string sort = "name", string dir = "asc")
{
    EmployeeModel empl = Session["empl"] as EmployeeModel;

    return DL.GetFeesBilled(start, limit, sort, dir, empl);
}

[AjaxSessionExpiredFilter]
public ActionResult AccountsReceivable(int start = 0,
            int limit = 0, string sort = "name", string dir = "asc")
{
    EmployeeModel empl = Session["empl"] as EmployeeModel;

    return DL.GetAccountsReceivable(start, limit, sort, dir, empl);
}

After decorating the methods with the custom action filter, every time the methods are called and the session has timed out, the response generated will be “error:sessiontimeout”.

 

Your ExtJS application needs to catch and handle this response. Let’s see how it’s done.

Handling session timeout in ExtJS

In your ExtJS application, you can check for the session timeout code utilizing a handler function for the requestcomplete event of the Ext.Ajax class. As Ext.Ajax is a singleton, your requestcomplete handler will run for any AJAX requests that completed successfully.

A simplified version of the handler could look like this:

Ext.Ajax.on('requestcomplete', function (conn, response, options) {
    if (response.responseText.indexOf("error=sessiontimeout") > -1) {
        Ext.Msg.alert('Session Timeout', 'Your session has timed out. Please refresh this page to start a new session.');
    }
});

And that’s all it takes. Want to give it a try?

Ext JS with ASP.NET MVC: GridPanel Paging

In this article I describe one of my favorite approaches for implementing gridpanel dataset paging in ExtJS + ASP.NET MVC projects.

Assume you have a “feesBilledStore” JsonStore instance configured like so:

var FeesStore = function (config) {
    var config = config || {};
    Ext.applyIf(config, {
        fields: ['Id', 'Name', 'Amount'],
        root: 'records',
        totalProperty: 'total'
    });
    FeesStore.superclass.constructor.call(this, config);
};
Ext.extend(FeesStore, Ext.data.JsonStore);

var feesBilledStore = new FeesStore ({
	url: 'fees/feesbilled
});

How do you feed the feesBilledStore JsonStore from an ASP.NET MVC controller?

 

When you use paging with your ExtJS gridpanels, along with the paged results, you need to supply your data store the actual size of the recordset. This is the value used to calculate the number of pages available, which, among other things, is displayed in your paging toolbar.

In my ExtJS + ASP.NET MVC projects, I tend to connect the server-side models with the ExtJS gridpanels with the help of a class that I name PagedRecords:

public class PagedRecords
{
	public int TotalRecords { get; set; }
    public dynamic Records { get; set; }
}

PagedRecords nicely maps my data models to the gridpanel ExtJS views I use. The Records property will store the recordset’s page to be rendered by the gridpanel, and the TotalRecords property stores the actual size of the recordset.

 

When converted to Json, an instance of PagedRecords results in a javascript object that will provide the data and the recordset size needed by the ExtJS gridpanel and the pagingtoolbar bound to it.

Using the PagedRecords class, the MVC Contoller method that will feed my hypothetical feesBilledStore would look like this:

public ActionResult FeesBilled(int start = 0, int limit = 15) {

	PagedRecords result = DataRepository.GetFeesBilledPaged(start, limit);

	return Json(new { total = result.TotalRecords, records = result.Records }, JsonRequestBehavior.AllowGet);

}

And my data access routine would need to return an instance of PagedRecords like so:

public static PagedRecords GetFeesBilledPaged(int startRow, int pageSize)
{
	var totalRecords = db.GetFeesBilledCount();
	var fees = from f in db.GetFeesBilled()
		select new FeesBilledModel()
		{
			Id = (int)h.employee_id,
            Name = h.employee_name,
            Amount = (decimal)h.fees_billed
        };
    PagedRecords paged = new PagedRecords() { TotalRecords = totalRecords, Records = hours.Skip(startRow).Take(pageSize).ToList() };

    return paged;
}

What do you think?

More Ext JS with ASP.NET MVC Goodies: Populating a GridPanel

Another simple example of how to use Ext JS with ASP.NET MVC. This time I am going to display a number of fictitious movies using an Ext JS GridPanel. The movies list will be served by an ASP.NET MVC application.

The View

As I explained in my previous Ext JS with ASP.NET MVC example, in a model-view-controller scenario the View renders entities from the Model into a form suitable for interaction.

The View in this example is the html page containing the Movies grid.  The ExtJS components I use in this page are a JsonStore and a GridPanel:

Ext.onReady(function () {

    var filmsStore = new Ext.data.JsonStore({
        url: 'http://localhost/MvcApp/Films',
        root: 'Films',
        idProperty: 'Id',
        totalProperty: 'count',
        fields: ['Id', 'Title', 'ReleaseYear', 'Rating'],
        remoteSort: true,
        autoDestroy:true,
        autoLoad:true
    });

    var filmsGrid = new Ext.grid.GridPanel({
        title: 'Movies',
        store: filmsStore,
        columns: [
            { id: 'title-col', header: "Title", width: 180, dataIndex: 'Title', sortable: true },
            { header: "Rating", width: 65, dataIndex: 'Rating', sortable: true, align: 'right' },
            { header: "Year", width: 75, dataIndex: 'ReleaseYear', sortable: true, align: 'right' }
        ],
        autoExpandColumn: 'title-col',
        renderTo: Ext.getBody(),
        width: 400,
        height: 250,
        loadMask: true,
        columnLines: true
    });

});

The Controller

Requests to the /Films route in the view are sent to FilmsController.  This controller will ask the Model to produce the needed data.  When a request is made to the /Films route, the Index method will be executed:

public class FilmsController : Controller
{
    //
    // GET: /Films/

    public JsonResult Index()
    {

        FilmsContainer container = FilmsDataContext.GetFilms();

        return Json(container,JsonRequestBehavior.AllowGet);
    }

}

Note that Index returns Json-encoded data in a JsonResult instance.  This is the data the movies grid needs.

Building the Model

The model is the objects that represent the data and domain logic of the application.  A movie will be represented by an instance of the Movie Class:

public class Film
{
    public int Id { get; set; }
    public string Title { get; set; }
    public int ReleaseYear { get; set; }
    public string Rating { get; set; }
}

FilmsContainer is  a Class that exposes an array of Film objects.  An instance of this Class will be Json-serialized by the Index method of the FilmsController Class:

public class FilmsContainer
{
    public Film[] Films { get; set; }
    public int Count { get; set; }
}

FilmsDataContext creates the movies data. To keep the example simple, I am hard-coding a few films:

public class FilmsDataContext
{
    public static FilmsContainer GetFilms()
    {
        Film[] filmsArray = new Film[5];

        filmsArray[0] = new Film()
        {
            Id = 1,
            Title = "ACADEMY DINOSAUR",
            ReleaseYear = 2006,
            Rating = "PG"
        };
        filmsArray[1] = new Film()
        {
            Id = 1,
            Title = "ACE GOLDFINGER",
            ReleaseYear = 2006,
            Rating = "G"
        };
        filmsArray[2] = new Film()
        {
            Id = 1,
            Title = "AFFAIR PREJUDICE",
            ReleaseYear = 2006,
            Rating = "G"
        };

        filmsArray[3] = new Film()
        {
            Id = 1,
            Title = "AGENT TRUMAN",
            ReleaseYear = 2006,
            Rating = "PG"
        };
        filmsArray[4] = new Film()
        {
            Id = 1,
            Title = "ALICE FANTASIA",
            ReleaseYear = 2006,
            Rating = "NC-17"
        };

        FilmsContainer container = new FilmsContainer()
        {
            Films = filmsArray,
            Count = filmsArray.Length
        };

        return container;
    }
}

Downloads

Grab the code for this article: ExtJS_With_ASP_MVC_2.zip

Want to learn more?

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.

Ext JS Data Store Dependency Injection

A feature I really like in the Ext JS framework is that components that use extjs data stores do not necessarily need to be supplied actual references to the stores at configuration time.  These components can also be supplied a string key – the identifier of the store – that will be used to obtain the data store reference from an external repository.

Besides reducing lines of code, this dependency injection approach relieves us, to a great extent, of having to track what data store references we supply to what components.

Let’s see how the feature works by stepping through a hypothetical example.  Assume we have an “Employees”  extjs data store, defined with the code that follows:

EmployeesStore = Ext.extend(Ext.data.JsonStore, {
    storeId: "employeesStore",
    constructor: function() {
        EmployeesStore.superclass.constructor.call(this, {
            fields : [ // Fields ]
        });
    }
});

The StoreMgr class as a global registry of data stores

With the incorporation of the StoreMgr class, Ext JS provides a container that can be used as a global registry for the data stores we have created in our applications.  Although data stores can be added to this registry manually, they are automatically added by the framework if we use the storeId option in the store configuration, as we did above.

This makes it possible to configure an “Employees” grid panel with the store’s id, as shown in the following code:

EmployeedGrid = Ext.extend(Ext.grid.GridPanel, {
    title: "Employees List",
    store: "employeesStore",
    width: 400,
    height: 250,
    initComponent: function() {
        this.columns = [ // Columns ];
        EmployeedGridUi.superclass.initComponent.call(this);
    }
});

See the difference?  Where we could have used store:EmployeesStore, we used store:”employeesStore”, and made our grid responsible for obtaining the reference to the store whose id is “employeesStore”.

We can do this because the initComponent method of the GridPanel class contains the following call:

this.store = Ext.StoreMgr.lookup(this.store);

Looking up data stores

The Store manager’s lookup method gets a registered store by id.  The current implementation looks like this:

lookup : function(id){
    if(Ext.isArray(id)){
        var fields = ['field1'], expand = !Ext.isArray(id[0]);
        if(!expand){
            for(var i = 2, len = id[0].length; i <= len; ++i){
                fields.push('field' + i);
            }
        }
        return new Ext.data.ArrayStore({
            fields: fields,
            data: id,
            expandData: expand,
            autoDestroy: true,
            autoCreated: true
        });
    }
    return Ext.isObject(id) ? (id.events ? id : Ext.create(id, 'store')) : this.get(id);
}

This look-up is what allows the component to locate its data store in the StoreMgr’s registry.   Note that lookup() also functions as a factory method, returning a new data store if the supplied id does not map to an existing one.

Want to learn more?

Check out my Ext JS 3.0 Cookbook.  It contains more than a hundred step-by-step recipes that explain useful techniques to build great Ext JS applications.

Ext JS Charts: Creating Line Charts Without Markers

We have previously worked on changing data series style and creating custom markers for our Ext JS charts.  In this article we will look at a simple configuration that can be used with the Ext JS line charts in order to hide the chart markers.

This configuration is useful, for example, in scenarios like the one shown below, where the Average series of a web page performance chart does not need to have markers:

Hiding Ext JS chart markers using transparency

The approach consists of using the alpha and fillAlpha properties of a series’ style to make the markers invisible, as shown in the following code:

series: [{
    type: 'line',
    displayName: 'Average',
    yField: 'average',
    style: {
        color: 0xCCCCCC,
        alpha:0,
        fillColor: 0xCCCCCC,
        fillAlpha:0
    }
}, {
    type: 'line',
    displayName: 'Load Time',
    yField: 'loadtime',
    style: {
        color: 0xcc6600
    }
}]

According to the YUI 2 Charts documentation, the alpha property is a numeric value, 0.0 to 1.0, that represents the alpha of the markers in the series.  The fillAlpha property represents the fill alpha of the markers in the series.

Setting both alpha and fillAlpha to 0 makes the markers transparent and the series appears as a solid line with no markers.

Want to learn more?

Check out my Ext JS 3.0 Cookbook.  It contains more than a hundred step-by-step recipes that explain useful techniques to build great Ext JS applications.

Ext JS with PHP: How to Add Children to Async Tree Nodes

After reading Ext JS with PHP: How to Create Nodes for a TreePanel, some readers have asked how the aproach discussed in the article can be extended to add children to any async node, as shown in the following screenshot:

In our Ext JS TreePanel, expanding any parent node (My Project, Datasources and Reports) will generate an http request for the node’s children.  What we need to do then is monitor the request parameters, determine which parent node should be populated, and generate the child nodes.  Let’s modify our original code so we can accomplish this.

A PHP model for Ext JS tree nodes

First, remember that we’re using the following PHP classes to model the Ext JS tree nodes:

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;
    }
}

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->nodes[] = $n;
    }

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

Creating children for Ext JS async tree nodes

We now need to capture the parent node that is making the request:

$requestedNode = "";

if (isset($_REQUEST["node"])) {
    $requestedNode = $_REQUEST["node"];
}</pre>
With the parent node captured, we can build its child nodes, and send them to the Ext JS client:
<pre class="brush: php;">$treeNodes = new TreeNodes();

if ('node-root' == $requestedNode) {
    $treeNodes-&gt;add("node-datasources","Datasources","",false,false,"","");
    $treeNodes-&gt;add("node-reports","Reports","",false,false,"","");
} else if ('node-datasources' == $requestedNode) {
    $treeNodes-&gt;add("employees-system-node","Employee Management System","datasource",true,false,"","");
    $treeNodes-&gt;add("customers-system-node","Customer Management System","datasource",true,false,"","");
    $treeNodes-&gt;add("order-system-node","Order Processing System","datasource",true,false,"","");
} else if ('node-reports' == $requestedNode) {
    $treeNodes-&gt;add("time-report-node","Time and Attendance","report",true,false,"","");
    $treeNodes-&gt;add("orders-by-quarter-report-node","Orders By Quarter","report",true,false,"","");
    $treeNodes-&gt;add("customers-trends-report-node","Customer Trends","report",true,false,"","");
}

echo $treeNodes-&gt;toJson();</pre>
And this is the TreePanel definition:
<pre class="brush: js;">var tree = new Ext.tree.TreePanel({
    renderTo: Ext.getBody(),
    title: 'Reporting Project',
    width: 250,
    height: 250,
    userArrows: true,
    animate: true,
    autoScroll: true,
    dataUrl: 'tree-nodes-2.php',
    root: {
        nodeType: 'async',
        text: 'My Project',
        id:'node-root'
    },
    listeners: {
        render: function() {
            this.getRootNode().expand();
        }
    }
})

This is, my friends, all it takes to add children to Ext JS async tree nodes using PHP.  Want to share your preferred approach?

Downloads

Grab the code for this article: ExtJS-TreePanel-with-PHP-2.zip

Want to learn more?

Ext-JS-Cookbook Check out my Ext JS 3.0 Cookbook.  It contains more than a hundred step-by-step recipes that explain useful techniques to build great Ext JS applications.

Using the Ext Scheduler, Part 2

This is another article where we will explore the Ext Scheduler while we use it to build a hypothetical conference room reservations interface.  In Using the Ext Scheduler, Part 1, we set out to to build a scheduler that displays the reservations that exist for a number of conference rooms.  This time the goal is to add the the ability to create, edit and remove reservations.

We will allow for creation and edition of reservations by using the scheduler’s EventEditor plugin.  For deletions, we will use a simple context menu.  Let’s see how it is done.

Editing events in the Ext Scheduler

The first change we are going to make in order to support event edition is use a specialized version of the scheduler – the Sch.EditorSchedulerPanel class – as the data type for the scheduler. (In Using the Ext Scheduler, Part 1 we employed the Sch.SchedulerPanel class.)

ConfRooms.sch = new Sch.EditorSchedulerPanel({

// Configuration...

});

The scheduler’s event editor is a plugin (Sch.plugins.EventEditor class) that defines a number of UI elements that display next to the event being edited.  This is how our reservation editor will look:

In the editor, a built-in header region contains the input fields for the start date and duration of the event.  A separate region contains fields for any other event properties that we might want to include in the editor.  This region is configurable through the fieldsPanelConfig option of the EventEditor class.  Let’s write the code that defines our editor:

plugins: [
    this.editor = new Sch.plugins.EventEditor({
        height: 180,
        width: 270,
        buttonAlign: 'center',
        saveHandler: ConfRooms.onSave,
        saveHandlerScope: this,
        fieldsPanelConfig: {
            layout: 'form',
            border: false,
            cls: 'editorpanel',
            labelAlign: 'top',
            items: [
                descriptionField = new Ext.form.TextArea({
                    name: 'Description',
                    fieldLabel: 'Description',
                    anchor: '100%'
                })
            ]
        },
        listeners: {
            expand: function() {
                descriptionField.focus(true);
            }
        }
    })
]

Note how we use the fieldsPanelConfig.items property to add a text area so our users can change the description of the edited event, in this case a conference room reservation.

Another important configuration option of the event editor is saveHandler.  This option specifies the function that will take care of saving the changes made to the reservation.  This is the implementation of our saveHandler:

ConfRooms.onSave = function(formPanel, newStart, newEnd, record) {
     record.beginEdit();
     formPanel.getForm().updateRecord(record);
     record.set('StartDate', newStart);
     record.set('EndDate', newEnd);
     record.endEdit();

     if (!record.get('Id')) {
         this.grid.eventStore.add([event]);
     }
     formPanel.collapse();
}

Our handler first updates the reservation’s description through a call to the underlying form’s updateRecord() function, it then updates the start and end dates, and finally, it inspects the record’s id in order to determine if it is a new or an existing reservation.

Creating events in the Ext Scheduler

As depicted below, we will allow our users to create a reservation with a drag and drop gesture over the desired time period:

While the editor’s UI is automatically shown upon double-cliking on an existing reservation, we need to take care of showing the UI when the user creates a new reservation. This is why we will add a handler for the scheduler’s dragcreateend event:

dragcreateend: {
 fn: function(p, data, e) {
     var reservation = new ConfRooms.reservationsStore.recordType({
         Id: ConfRooms.nextId(),
         Description: 'Enter a description for this event',
         ReservedTo: 'Jorge Ramon',
         ResourceId: data.record.get('Id'),
         StartDate: data.startDate,
         EndDate: data.endDate
     });

     ConfRooms.reservationsStore.add(reservation);
     this.editor.show(reservation);
 },
 scope: this
}

The handler for dragcreateend first creates a new reservation record, capturing the start and end dates passed by the scheduler via the data parameter.  It then adds the new record to the reservations store, and displays the editor’s user interface by calling editor.show().

It really doesn’t take much effort to implement event edition in the Ext Scheduler.  Let’s now take a look at deleting events.

Deleting events in the Ext Scheduler

We already gave the users of our conference room reservation system the ability to create and edit reservations, but we are missing an easy way to delete reservations.  To accomplish this we will attach a “Delete” context menu to each reservation displayed on the scheduler.  Here’s a depiction of the menu:

We can attach this menu to the each reservation by taking advantage of the eventcontextmenu configuration option of the scheduler:

eventcontextmenu: {
 fn: function(g, rec, e) {
     e.stopEvent();
     if (!this.gCtx) {
         this.gCtx = new Ext.menu.Menu({
             items: [
                {
                    id: 'context-delete',
                    text: 'Delete event',
                    iconCls: 'icon-delete'
                }
            ]
         });

         this.gCtx.on('itemclick', function(item, e) {
             switch (item.id) {
                 case 'context-delete':
                     ConfRooms.sch.eventStore.remove(this.gCtx.rec);
                     break;

                 default:
                     throw item.id + ' is not a valid menu action';
                     break;
             }
         }, this);
     }
     this.gCtx.rec = rec;
     this.gCtx.showAt(e.getXY());
 }

The interesting part of the context menu definition is the itemclick handler, which is where we remove the reservation from its data store.

Conclusion

This brings to completion the features we set out to work on in this article, and we now have a simple conference room reservation system based on the Ext Scheduler.

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: Ext-Scheduler2.zip.

You can obtain the Ext Scheduler at http://www.ext-scheduler.com.