Mastering Ext.Direct, Part 2

7. September 2009 – 22:50

<<< Mastering Ext.Direct, Part 1

Adding server-side classes

After completing part 1, we have the basic setup running. Although Ext.Direct PHP stack example classes are fine for demonstrating the functionality we will need our own PHP classes in the real life. I’m not going to explain basics of PHP object oriented programming, anyway, there is a couple of things to point out.

Let’s create (useless) class Car from part1. For that, create file classes/Car.php with the following content:

<?php
// vim: sw=4:ts=4:fdc=4:nospell
    class Car {
        /**
          * @remotable
          */
        public function start() {
            return "Started";
        } // eo function start
 
        /**
          * @remotable
          */
        public function go($speed) {
            return "The speed is $speed";
        } // eo function go
 
        /**
          * @remotable
          */
        public function stop() {
            return "Stopped";
        } // eo function stop
 
        public function repair() {
            return "Done";
        } // eo function repair
 
    } // eo class car
// eof

Then edit api.php and add Car to $api->add() call so that it reads:

// ...
$api->add(
    array(
        'Echo' => array('prefix' => 'Class_'),
        'Exception' => array('prefix' => 'Class_'),
        'Time',
        'File',
        'Car'
    )
);
// ...

Reload index.php and type in Firebug console:

Example.Car.start();

You should get the following response:

{"type":"rpc","tid":2,"action":"Car","method":"start","result":"Started"}

Now type:

Example.Car.repair();

Error, yes? Why? Take a deeper look at Car source code. Some methods have comments

        /**
          * @remotable
          */

but repair does not have it. And that is the single reason why it is not exported to API so it is not known to the client application. You can verify it by typing:

Example.API.actions.Car;

in Firebug console.

If you want to know how is it done study ExtDirect_API PHP class in classes/API.php file and ReflectionClass documentation. In any case, it makes our life very easy as we only need to:

  • write server side classes
  • add the above comment to the methods that can be called remotely

Can it be simpler?

Returning values

Type in Firebug console:

console.log(Example.Car.start());

Now you see that the return value of the call is undefined despite of the fact that server side method returns string "Started". This leads us to a very important note:

Ext.Direct calls are ASYNCHRONOUS.

That means that the code execution does NOT wait until the call is finished but it immediately continues. So never try to use the return value, neither assume that you have data available at the line that follows the call.

The value returned from the server side method call is wrapped in the request response and it arrives when it arrives. It can take milliseconds or seconds depending on the connection speed, server execution time and other factors.

I will discuss response processing in the next part

Exceptions

Edit go method of our Car class so that it reads:

        /**
          * @remotable
          */
        public function go($speed) {
            if(0 >= $speed || 200 < $speed) {
                throw new Exception("Speed must be between 0 and 200");
            }
            return "The speed is $speed $unit";
        } // eo function go

and execute:

Example.Car.go(300);

You get the following response:

{"type":"exception","tid":3,
"message":"Speed must be between than 0 and 200",
"where":
"#0 [internal function]: Car->go(300)\n
#1 \/ddata1\/devel\/extjs.eu\/direct\/ExtDirect\/Router.php(176): call_user_func_array(Array, Array)\n
#2 \/ddata1\/devel\/extjs.eu\/direct\/ExtDirect\/Router.php(62): ExtDirect_Router->rpc(Object(stdClass))\n
#3 \/ddata1\/devel\/extjs.eu\/direct\/router.php(23): ExtDirect_Router->dispatch()\n
#4 {main}"}

You see that the response type changed from rpc (remote procedure call) to exception and the response contains member message that is the text of the Exception we’ve thrown at server side. So far so good, however, where member contains debugging data that, while useful for debugging, shouldn’t be disclosed in a production environment.

You can freely throw exceptions while debugging your Ext.Direct application but you should consider another method(s) of handling errors for production systems. I will tackle it again later in this series.

Combining requests

Type the following:

Example.Car.start();Example.Car.go(80);Example.Car.stop();

The request is:

[
{"action":"Car","method":"start","data":null,"type":"rpc","tid":4},
{"action":"Car","method":"go","data":[80],"type":"rpc","tid":5},
{"action":"Car","method":"stop","data":null,"type":"rpc","tid":6}
]

and the response is:

[
{"type":"rpc","tid":4,"action":"Car","method":"start","result":"Started"},
{"type":"rpc","tid":5,"action":"Car","method":"go","result":"The speed is 80"},
{"type":"rpc","tid":6,"action":"Car","method":"stop","result":"Stopped"}
]

Ext.Direct combines requests that come within a configurable time frame into one to minimize the server round trips count.

Conclusion

Now we know a couple of basics:

  • we need to export API from server to client – Ext.Direct stack does a lot of dirty work for us
  • we can call server side methods directly at client prefixed with a configurable namespace (Example in our case)
  • we cannot use return values of client calls because calls are asynchronous
  • to add classes we need to add them to $api->add() call and add /** @remotable */ comments to methods
  • multiple requests and responses are combined in one if they come fast enough

Mastering Ext.Direct, Part 3 >>>

Mastering Ext.Direct, Part 1

5. September 2009 – 20:07

Preface

My first idea how to the name this article was “Ext.Direct for Dummies” just because I feel as a one as long as Ext.Direct is concerned. I’ve first heard about it during Ext Conference in April and I think that it is one of the brightest ideas of Ext 3.x release. Nevertheless, I had no time do dig into into it and to understand the concepts fully.

Now I’ve decided to take a journey of discovering what is under hood, how to setup client and server side and how to use Ext.Direct effectively in applications. If you want, I invite you to travel with me.

Who is this article for

It is for developers who are familiar (at least) with basic javascript and Ext object oriented programming, who are able to setup a web page and have it running from a http server and who can code in a server-side programming language. (I will use PHP in this article so PHP developers will have a slight advantage.)

What is Ext.Direct anyway

Rich Internet Applications (RIA) consist of two parts: client side and server side. Client cannot call server functions directly but sends requests, server processes them calling the appropriate functions and returns results back to client.

Let’s say we have a server side class Car that has methods start, go and stop. From the client viewpoint, we need to ask server: Please, start the Car, then go with it and then stop it.

Now, imagine that we could directly call

Car.start();
Car.go();
Car.stop();

and these would call server side methods of server side class Car. Nice, isn’t it? You need to remember only one set of class names and their methods, code is neat and less bug prone.

And that is what Ext.Direct does. You export list of server side classes and their methods that should be made available for client to call and Ext.Direct takes care of the rest so that you can really use Car.start() in your code.

What we need

  1. a working http server we have an access to. It can be installed on the local computer but it must be present. file:///something links will not work
  2. a server side (scripting) language enabled in the above server. If you will use PHP, you need 5+ version to take advantage of ReflectionClass
  3. Ext JS 3.0.0 library or latest Ext version built from SVN, if you have purchased Ext Premium Membership
  4. Ext.Direct Pack
  5. Firefox with Firebug installed
  6. If you use PHP, FirePHP is strongly recommended

Initial setup

  1. create directory direct under your http server document root. Name does not matter in fact but I will use direct as the root for testing in this article.
  2. extract ExtJS into ext subdirectory under direct
  3. extract content of php subdirectory from Ext.Direct Pack under direct
  4. extract content of FirePHPCore-x.x.x into firephp subdirectory under direct

At this point, your direct directory listing should read the following:

+ cache/
+ classes/
+ data/
+ ext/
+ ExtDirect/
+ firephp/
  api.php
  router.php

The first test

We still need to do some work before we can see it running. First, create config.php file with the following content:

<?
// vim: sw=4:ts=4:fdc=4:nospell
 
// authentication would come here in the real world
 
// switch it to false if you do not want to use FirePHP
// $useFirePHP would be false also for a production system
$useFirePHP = true;
 
// require FirePHP files
if($useFirePHP) {
	require_once("firephp/lib/FirePHPCore/FirePHP.class.php");
	require_once("firephp/lib/FirePHPCore/fb.php");
}
 
// define empty fb() function so code does not break 
// on any forgotten fb() calls later
else {
	function fb() {};
}
 
// eof
?>

We also need index.php so we have something to run:

<?require_once("config.php");?>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <link rel="stylesheet" type="text/css" href="ext/resources/css/ext-all.css">
  <script type="text/javascript" src="ext/adapter/ext/ext-base.js"></script>
  <script type="text/javascript" src="ext/ext-all-debug.js"></script>
  <title id="page-title">Mastering Ext.Direct by Saki</title>
  <script type="text/javascript" src="api.php"></script>
  <script type="text/javascript">
  	Ext.Direct.addProvider(Example.API);
  </script>
</head>
<body>
</body>
</html>

Ext.Direct Pack comes with example PHP classes (Echo, Exception, File and Time) and with api.php file. You can leave classes untouched for the moment but edit api.php to read the following:

<?php
require_once("config.php");
 
session_start();
 
// Include ExtDirect PHP Helpers
require_once('ExtDirect/API.php');
require_once('ExtDirect/CacheProvider.php');
 
// disable caching for development, enable for production
//$cache = new ExtDirect_CacheProvider('cache/api_cache.txt');
$api = new ExtDirect_API();
 
$api->setRouterUrl('router.php'); // default
 
// disable caching for development, enable for production
//$api->setCacheProvider($cache);
 
$api->setNamespace('Example');
$api->setDescriptor('Example.API');
$api->setDefaults(array(
    'autoInclude' => true,
    'basePath' => 'classes'
));
 
$api->add(
    // these are example classes from Ext.Direct PHP stack
    array(
        // real class name is Class_Echo, therefore prefix
        'Echo' => array('prefix' => 'Class_'),
 
        // real class name is Class_Exception, therefore prefix
        'Exception' => array('prefix' => 'Class_'),
        'Time',
        'File'
    )
);
$api->output();
 
$_SESSION['ext-direct-state'] = $api->getState();
 
// eof
?>

So far, so good… Now you can navigate to http://yourserver/direct/index.php and if everything went right you will see the blank page and no errors in Firebug.

Open Firebug console and type the following:

Example.Time.get();

You can see that request is sent to server:

{"action":"Time","method":"get","data":null,"type":"rpc","tid":2}

and that response comes back:

{"type":"rpc","tid":2,"action":"Time","method":"get","result":"09-05-2009 19:46:38"}

You can try other classes and methods:

Example.Echo.send("Test to be echoed");
Example.File.list(".");
Example.Time.get();

and you can also put fb($someVariable) statement in various places of php code if you want to know what’s going on here and there.

Conclusion

We have set up very minimum of Ext.Direct server and client side. The main purpose of this part is to get acquainted with basic components of this technology.

Mastering Ext.Direct, Part 2 >>>

New example – Drag from Grid to Tree

23. June 2009 – 22:26

Hi all,

I’ve just posted new example on how to drag rows from grid to tree.

Enjoy!

http://examples.extjs.eu/?ex=grid2treedrag

New Example – Downloading File

3. June 2009 – 10:19

Hi all,

I’ve just uploaded new example of how to invoke “Save As..” dialog when downloading files from the server.

http://examples.extjs.eu

Enjoy!

New Example – Asynchronous Tree State

5. April 2009 – 10:59

Hi,

I’ve uploaded example of keeping state of asynchronously loaded tree at http://examples.extjs.eu.

The core state-keeping code is written in the form of plugin so it can be sticked in any existing tree.

Enjoy!

New Example – Combo with Remote Store

3. April 2009 – 13:58

Hi,

I’ve just uploaded a full-fledged example of combo with remote store at http://examples.extjs.eu.

Enjoy!

Factory Function File Pattern

17. March 2009 – 15:50

You know, I’m not very big fan of factory functions, nevertheless, I’m aware of the fact that they may be necessary in some situations. Here is the file pattern that works (briefly tested).

Keep each factory function in a separate file name of which should be Namespace.Factory.functionName.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// vim: ts=4:sw=4:nu:fdc=4:nospell
/*global Ext, MyNamespace */
/**
 * @class MyNamespace.Factory
 *
 * A Factory function pattern
 *
 * @author    Ing. Jozef Sakáloš
 * @copyright (c) 2009, by Ing. Jozef Sakáloš
 * @date      17. March 2009
 * @version   0.1
 * @revision  $Id$
 *
 * @license MyNamespace.Factory.myPanel.js is licensed under the terms of
 * the Open Source LGPL 3.0 license. Commercial use is permitted to the extent
 * that the code/component(s) do NOT become part of another Open Source or 
 * Commercially licensed development library or toolkit 
 * without explicit permission.
 * 
 * <p>License details: <a href="http://www.gnu.org/licenses/lgpl.html"
 * target="_blank">http://www.gnu.org/licenses/lgpl.html</a></p>
 */
 
// create namespace
Ext.ns('MyNamespace.Factory');
 
/**
 * @method myPanel
 * @param {Object} config
 * A config object
 * @return {Ext.Panel}
 */
MyNamespace.Factory.myPanel = function(config) {
 
	// pre-instantiation code
	var defaults = {
		// put your defaults here
		// but avoid id, el, contentEl, renderTo, applyTo, or similar
	}; // eo defaults object
 
	// create config object
	var cfg = Ext.apply({}, config, defaults);
 
	// instantiate
	var cmp = new Ext.Panel(cfg);
 
	// post-instantiation code
 
	// return the created component
	return cmp;
 
} // eo function MyNamespace.Factory.myPanel
 
// eof

Writing a Big Application in Ext (Part 3)

14. March 2009 – 14:53

Important

If you have not already done so, study Writing a Big Application in Ext (Part 1) and Writing a Big Application in Ext (Part 2)before you read this article. It would be very hard, if not impossible, to understand concepts explained here before you fully understand the first and second part.

Introduction

Helping on the forum and reading code of others that failed to extend Ext classes, revealed more errors that users, especially beginners, commonly make. Therefore, I’ve decided to start this article that will collect these errors and will explain why the errors are errors. I mean it as loosely ended as I may discover more errors and ways of avoiding them so I plan just to add them to this article, not endlessly create parts 4, 5, etc…

… continued: Most Common Sources of Troubles

Here we go:

  1. Unnecessary Extending
  2. Adding Objects to Prototype
  3. Hard Coding ids

 

Unnecessary Extending

The main reasons for extending are:

  • re-usability
  • adding functionality
  • combination of them

so we extend if we need a re-usable component or we need to add a functionality (new methods) or both. If we are after re-usability the extension can be as simple as:

1
2
3
4
5
MyPortlet = Ext.extend(Ext.Panel, {
     anchor:'100%'
    ,draggable:true
    ,defaultType:'mygraph'
});

You see what happens? We are going to use MyPortlet many times so instead of scatter the above configuration in 10,000 lines application code 100 times, we create this simple extension and we save 297 lines of code.

The other aspect is that if we upgrade our ‘mygraph’ to ‘mygraph_new’ the only place where to change it is our extension saving us searching out code for all occurrences of ‘mygraph’ (100 times) and replacing it with ‘mygraph_new’ 100 times.

(Well, 100 is exaggerated, but you get the point, right?)

If we are after adding functionality, which can be also simple, we add some “logic”:

1
2
3
4
5
6
MyPanel = Ext.extend(Ext.Panel, {
    onRender:function() {
        MyPanel.superclass.onRender.apply(this, arguments);
        alert('Rendered');
    }
});

Here we add some logic to Panel, it does more that it did before.

There is no need to extend in all other cases.

 

Adding Objects to Prototype

Run this code first:

<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <link rel="stylesheet" type="text/css" href="ext/resources/css/ext-all.css">
  <script type="text/javascript" src="ext/adapter/ext/ext-base.js"></script>
  <script type="text/javascript" src="ext/ext-all-debug.js"></script>
  <title id="page-title">Extending Error: Object in prototype</title>
  <script type="text/javascript">
  Ext.BLANK_IMAGE_URL = 'ext/resources/images/default/s.gif';
  Ext.onReady(function() {
    MyPanel = Ext.extend(Ext.Panel, {
         layout:'fit'
        ,panelConfig: {
            bodyBg:'red'
        }
 
        ,initComponent:function() {
            var config = {
                bodyStyle:'background-color:' + this.panelConfig.bodyBg
            }; // eo config object
 
            // apply config
            Ext.apply(this, Ext.apply(this.initialConfig, config));
 
            MyPanel.superclass.initComponent.apply(this, arguments);
        } // eo function initComponent
 
        ,applyBackground:function(color) {
            this.panelConfig.bodyBg = color;
            this.body.applyStyles({'background-color':color});
        } // eo function applyBackground
 
    }); // eo extend
 
    var p1 = new MyPanel({
         title:'Panel with Blue Background'
        ,renderTo:Ext.getBody()
        ,width:240
        ,height:160
    });
 
    p1.applyBackground('blue');
 
    var p2 = new MyPanel({
         title:'Panel with Red Background'
        ,renderTo:Ext.getBody()
        ,width:240
        ,height:160
    });
 
  });
  </script>
</head>
<body></body>
</html>

What do we expect? It is written in titles of panels: Top panel (p1) should have blue body background because we set it to it after it is created. And bottom panel (p2) should have red because we just create default MyPanel.

But it is blue too!!! Why? The reason is simple: panelConfig is object that is created during class definition and it is added to MyPanel prototype. Objects (arrays too) are accessed by reference so p1 and p2 share the same instance of panelConfig. Setting bodyBg property in applyBackground method changes this single instance of panelConfig object. So we create p2 with blue background too.

You see, here it is clearly and immediately visible that something went wrong but making this error can lead to weeks of wasted debugging time in real applications. Imagine you have a store in prototype…

 

Hard Coding ids

Very simple, but deadly mistake is to set ids in the extension either to the main extension object or on its items, toolbars, buttons, etc. If a hard coded ids are set we cannot create two or more instances of our extension, can we?

Loose End

That’s all for now but if I discover more errors I will add them above.

Stay tuned!

Do not forget to read Part 1 and Part 2 of this article.

Follow up: Factory Functions in Ext Extensions (Abstract Classes)

New Example – How To Dynamically Add Tabs To TabPanel

13. March 2009 – 14:55

This example shows how to easily add tabs to a TabPanel without any layout issues of the newly added tab.

http://examples.extjs.eu

Enjoy!

How to remove all children of a tree node

4. March 2009 – 23:34

Suppose we want to remove all children of the node node:

while(node.firstChild) {
    node.removeChild(node.firstChild);
}

That’s all there is to it