Sencha Touch 2 Models – Loading And Saving Model Data Using a Proxy, PHP Example

Sencha Touch models have the ability to work with a proxy. This feature allows you to save and retrieve model data from the server, memory or local storage, without depending on a Sencha Touch data store.

The model methods you will learn in this article are the following:

  • save
  • erase

Let’s try them with a very simple scenario where the server side is a PHP page. In this example you will create a simple Sencha Touch application with the following files:

In the model/Hotel.js file, you will define the Hotel model like so:

Ext.define('App.model.Hotel', {
    extend: 'Ext.data.Model',
    config: {
        fields: [
            { name: 'id', type: 'int' },
            { name: 'name', type: 'string' },
            { name: 'address', type: 'string' },
            { name: 'status', type: 'int' }
        ],
        proxy: {
            type: 'ajax',
            api: {
                create: '../../services/hotels.php?act=createhotel',
                read: '../../services/hotels.php?act=loadhotel',
                update: '../../services/hotels.php?act=updatehotel',
                destroy: '../../services/hotels.php?act=erasehotel'
            },
            reader: {
                rootProperty:'hotels'
            }
        }
    }
});


Then, you need to create the hotels.php file, which will be the server-side handler for the model’s proxy. For this example we will use PHP as the server-side language. You can check out the C# example if you work with .NET.

class Hotel {
	function __construct($id, $name, $address, $status) {
		$this->id = $id;
		$this->name = $name;
		$this->address = $address;
		$this->status = $status;
	}
}

$action = $_GET["act"];

$result = "{'success':false}";

switch($action) {

	case "loadhotel":

		$hotelId = $_GET["id"];
		$result = "{'success':true,'hotels':[{'id': 1, 'name': 'Siesta by the Ocean', 'address': '1 Ocean Front, Happy Island', 'status': 1}]}";
		break;

	case "createhotel":

		$hotelJson = file_get_contents('php://input');
		$hotel = json_decode($hotelJson);

		// Here you would save the hotel to the database...

        // Returning success for demo purposes.
		$result = "{'success':true,'hotels':[" . json_encode(new Hotel($hotel->id, $hotel->name, $hotel->address, $hotel->status)) . "]}";
		break;

	case "updatehotel":

		$hotelJson = file_get_contents('php://input');
		$hotel = json_decode($hotelJson);

		// Here you would save the hotel to the database...

		// Returning success for demo purposes.
		$result = "{'success':true,'hotels':[" . json_encode(new Hotel($hotel->id, $hotel->name, $hotel->address, $hotel->status)) . "]}";

		break;

	case "erasehotel":

		$hotelJson = file_get_contents('php://input');
		$hotel = json_decode($hotelJson);

		// Here you would delete the hotel from the database...

		// Returning success for demo purposes.
		$result = "{'success':true}";

		break;

}

header('Cache-Control: no-cache, must-revalidate');
header("content-type:application/json");
echo($result);

Once the handler is ready, you can try the proxy by placing the following code in the app.js file:

Ext.application({
    name: 'App',
    requires: ['Ext.util.DelayedTask'],
    models: ['App.model.Hotel'],
    launch: function () {

        App.model.Hotel.load(1, {
            scope: this,
            success: this.proxySuccess,
            failure: this.proxyFailure,
            callback: this.proxyCallback
        });

        var hotel = Ext.create('App.model.Hotel', {
            name: 'La Playa',
            address: '100 Ocean Front, Happy Island',
            status: 1
        });

        hotel.save({
            scope: this,
            success: this.proxySuccess,
            failure: this.proxyFailure,
            callback: this.proxyCallback
        });

        var updateTask = Ext.create('Ext.util.DelayedTask', function () {

            // Saving inside a delayed task, just to wait for the previous operations
            // to execute on the server and invoke the callbacks.
            hotel.set('name', 'Playa Hermosa');
            hotel.save({
                scope: this,
                success: this.proxySuccess,
                failure: this.proxyFailure,
                callback: this.proxyCallback
            });
        }, this);

        updateTask.delay(3000);

        var deleteTask = Ext.create('Ext.util.DelayedTask', function () {

            // Erasing inside a delayed task, just to wait for the previous operations
            // to execute on the server and invoke the callbacks.
            hotel.erase({
                scope: this,
                success: this.proxySuccess,
                failure: this.proxyFailure,
                callback: this.proxyCallback
            });
        }, this);

        deleteTask.delay(6000);

    },

    proxySuccess: function (record, operation) {
        switch (operation.getAction()) {
            case 'create':
                console.log('From the server: Created the ' + record.get('name') + ' hotel.');
                break;
            case 'read':
                console.log('From the server: Loaded the ' + record.get('name') + ' hotel.');
                break;
            case 'update':
                console.log('From the server: Updated the ' + record.get('name') + ' hotel.');
                break;
            case 'destroy':
                console.log('From the server: Erased the ' + record.get('name') + ' hotel.');
                break;
        }
    },

    proxyFailure: function (record, operation) {
        switch (operation.getAction()) {
            case 'create':
                console.log('From the server: Failed to create hotel.');
                break;
            case 'read':
                console.log('From the server: Failed to read hotel.');
                break;
            case 'update':
                console.log('From the server: Failed to update hotel.');
                break;
            case 'destroy':
                console.log('From the server: Failed to erase hotel.');
                break;
        }
    },

    proxyCallback: function (record, operation) {
        console.log('This function is always invoked, regardless of success or failure');
    }
});

In Google Chrome, when you open the index.html, you will see the following output in the JavaScript console:

How It Works

You connect the Hotel model to a proxy through the model’s proxy config:

 proxy: {
    type: 'ajax',
    api: {
        create: '../../services/hotels.ashx?act=createhotel',
        read: '../../services/hotels.ashx?act=loadhotel',
        update: '../../services/hotels.ashx?act=updatehotel',
        destroy: '../../services/hotels.ashx?act=erasehotel'
    },
    reader: {
        rootProperty:'hotels'
    }
}

The proxy we are using is of type ajax. Its api config allows you to specify which server-side end points will handle the create, read, update and destroy (CRUD) operations that can occur on the data.

The rootProperty config of the proxy’s reader defines which property of the JSON-encoded server response contains the data for the model.

On the server side, the Hotels.php handler inspects that act (short for action) query string parameter, defined in the proxy’s api config, to determine what operation will be performed.

Instead of executing database access code, for this example the handler will return canned responses that demonstrate the results of each operation:

switch($action) {

	case "loadhotel":

		$hotelId = $_GET["id"];
		$result = "{'success':true,'hotels':[{'id': 1, 'name': 'Siesta by the Ocean', 'address': '1 Ocean Front, Happy Island', 'status': 1}]}";
		break;

	case "createhotel":

		$hotelJson = file_get_contents('php://input');
		$hotel = json_decode($hotelJson);

		// Here you would save the hotel to the database...

        // Returning success for demo purposes.
		$result = "{'success':true,'hotels':[" . json_encode(new Hotel($hotel->id, $hotel->name, $hotel->address, $hotel->status)) . "]}";
		break;

	case "updatehotel":

		$hotelJson = file_get_contents('php://input');
		$hotel = json_decode($hotelJson);

		// Here you would save the hotel to the database...

		// Returning success for demo purposes.
		$result = "{'success':true,'hotels':[" . json_encode(new Hotel($hotel->id, $hotel->name, $hotel->address, $hotel->status)) . "]}";

		break;

	case "erasehotel":

		$hotelJson = file_get_contents('php://input');
		$hotel = json_decode($hotelJson);

		// Here you would delete the hotel from the database...

		// Returning success for demo purposes.
		$result = "{'success':true}";

		break;

}

header('Cache-Control: no-cache, must-revalidate');
header("content-type:application/json");
echo($result);

Switching back to the client-side, in the app.js file you will place code that executes each of the CRUD operations. First, a read:

App.model.Hotel.load(1, {
    scope: this,
    success: this.proxySuccess,
    failure: this.proxyFailure,
    callback: this.proxyCallback
});

Second, a create:

var hotel = Ext.create('App.model.Hotel', {
    name: 'La Playa',
    address: '100 Ocean Front, Happy Island',
    status: 1
});

hotel.save({
    scope: this,
    success: this.proxySuccess,
    failure: this.proxyFailure,
    callback: this.proxyCallback
});

Third, an update:

var updateTask = Ext.create('Ext.util.DelayedTask', function () {

    // Saving inside a delayed task, just to wait for the previous operations
    // to execute on the server and invoke the callbacks.
    hotel.set('name', 'Playa Hermosa');
    hotel.save({
        scope: this,
        success: this.proxySuccess,
        failure: this.proxyFailure,
        callback: this.proxyCallback
    });
}, this);

updateTask.delay(3000);

And fourth, an erase:

var deleteTask = Ext.create('Ext.util.DelayedTask', function () {

    // Erasing inside a delayed task, just to wait for the previous operations
    // to execute on the server and invoke the callbacks.
    hotel.erase({
        scope: this,
        success: this.proxySuccess,
        failure: this.proxyFailure,
        callback: this.proxyCallback
    });
}, this);

deleteTask.delay(6000);

As the CRUD executes asynchronously, we are placing some of the operations inside a DelayedTask instance so we give the prior operation time to complete on the server. This is fine for demo purposes, but in your applications you don’t have to do this.

You can pass callback functions to the the load, save and erase methods of the model. In the example, you use the proxySuccess, proxyFailure and proxyCallback methods defined inside of the application function.

You can use the record and operation arguments of these callbacks to take action based on the results returned by the server:

proxySuccess: function (record, operation) {
    switch (operation.getAction()) {
        case 'create':
            console.log('From the server: Created the ' + record.get('name') + ' hotel.');
            break;
        case 'read':
            console.log('From the server: Loaded the ' + record.get('name') + ' hotel.');
            break;
        case 'update':
            console.log('From the server: Updated the ' + record.get('name') + ' hotel.');
            break;
        case 'destroy':
            console.log('From the server: Erased the ' + record.get('name') + ' hotel.');
            break;
    }
},

Want To Learn More?

My Sencha Touch books will teach you how to create a Sencha Touch application, step by step, from mockups to production build.

About Jorge

Jorge is the author of Building a Sencha Touch Application, How to Build a jQuery Mobile Application, and the Ext JS 3.0 Cookbook. He runs a software development and developer education shop that focuses on mobile, web and desktop technologies.
If you'd like to work with Jorge, contact him at ramonj[AT]miamicoder.com.

Comments

  1. Hey Jorge thanx for this serie again i’ve been following yo blog posts from the Notes App..man u rock…..And how do i access the API actions at the server side in the controller…..mayb if i have a delete button,how can i access the delete script by pursing the id of the item i want to delete…thx

    • If you have a delete button that triggers the erase method on the model, the proxy will invoke the erasehotel action on the server side.

  2. hi i’m getting this error: Error: You’re trying to decode an invalid JSON String. How to fix it? thanks

  3. Jorge,

    I am getting the same error as Anderson: ” Error: You’re trying to decode an invalid JSON String.”. I posted this on Stack Overflow yesterday: http://stackoverflow.com/questions/12591087/sencha-touch-calling-a-php-function-from-sencha-touch-example-results-in-invali

    I am using the Sencha Touch Getting Started Tutorial – in which their Contact Form calls a php function. In the tutorial it is left undefined. When I add a file I get the Invalid JSON string error – even if the file is blank.

    Do you have any idea what may be causing this error?

    And thank you for the tutorials – they are a ton of help.

    • The error says the received json is invalid. Make sure the php page creates valid json. You can start by simply outputting a hard-coded string that you know is valid json. Also, make sure the page is sending the correct content type to the browser:

      header(‘Cache-Control: no-cache, must-revalidate’);
      header(“content-type:application/json”);

  4. Thank you – that worked. I am coming from a real time embedded C++ background(like SW to boot a cell phone or guide a missile) w/ some experience in php and mySQL. Very light experience in javascript & DOM so this is a steep learning curve for me. Your tutorials have been very helpful – I am starting to catch on. The Sencha Touch documentation on Sencha’s site is less than stellar.

    So it is expecting JSON as output from the php function. Where does the JSON go? Back to the calling process?

    My first thought was that I could use php to handle the call from the contact form and store the data in a mySQL database. Surely this is possible right? Are there any examples out there for doing something like this?

  5. Hi Jorge,

    This is great tutorial. How about a cross-domain example?

    Thank you

  6. Hi Jorge,

    can you provice the source of this example as download please ;-)

    thanks a lot

  7. Thank you Jorge for providing a sample that works and that I can finally understand.

  8. Jorge,

    Your PHP code is missing the beginning and ending PHP code block indicators.
    They were probably eaten by this commenting system.

  9. Nice work,

    But i could not run this when implemrnting it a native application and even in Blackberry simulator.
    There is no error displayed only white screen.

    Any help will be highly appreciated

    Regards

Speak Your Mind

*