In this article you will learn how to use the hasMany association, a feature of Sencha Touch models that allows you to connect two models in a one-to-many relationship. The model configs you will learn about in this post are the following:
- hasMany
- belongsTo
The back-end code for this tutorial is in PHP. I also published a version of this Sencha Touch tutorial using C#
Let’s create a simple Sencha Touch application with the following files:
In the model/Hotel.js file, you will define a 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' } ], hasMany: { model: 'App.model.Room', name:'rooms' } } });
The interesting part of this model definition is the hasMany config, consisting of the model and name properties, which reference a Room model. You will create this model in the model/Room.js file:
Ext.define('App.model.Room', { extend: 'Ext.data.Model', requires: ['Ext.data.Store'], config: { fields: [ { name: 'id', type: 'int' }, { name: 'floor', type: 'string' }, { name: 'type', type: 'string' }, { name: 'standardRate', type: 'float' }, { name: 'smoking', type: 'string' }, { name: 'status', type: 'int' }, { name: 'hotelId', type: 'int'} // This works with belongsTo property below. ], belongsTo: { model: 'App.model.Hotel', foreignKey: 'hotelId' }, proxy: { type: 'ajax', api: { create: '../../services/rooms.php?act=createrooms', read: '../../services/rooms.php?act=loadrooms', update: '../../services/rooms.php?act=updaterooms', destroy: '../../services/rooms.php?act=eraserooms' }, reader: { rootProperty: 'hotels' } } } });
This model has a belongsTo config, with the model and foreignKey properties, which reference the Hotel model. It also has a proxy config, which references the services/rooms.php page.
This is the code for the rooms.php page:
// Class to represent a hotel. class Room { function __construct($id, $floor, $type, $standardRate, $smoking, $status, $hotelId) { $this->id = $id; $this->floor = $floor; $this->type = $type; $this->standardRate = $standardRate; $this->smoking = $smoking; $this->status = $status; $this->hotelId = $hotelId; } } $action = $_GET["act"]; $result = "{'success':false}"; switch($action) { case "updaterooms": $roomJson = file_get_contents('php://input'); $rooms = json_decode($roomJson); // Here you would save the rooms to the database... // Returning success for demo purposes. $result = "{'success':true,'rooms':" . json_encode($rooms) . "}"; break; case "loadrooms": $filterJson = $_GET["filter"]; $filter = json_decode($filterJson); // The value of $filter[0]->property should be "hotelId" for rooms associated to a hotel. // The value of $filter[0]->value should be the id of the hotel to which the rooms are associated. // Here you would load the rooms for the given hotelId from the database. // We are returning hard-coded results for demo purposes. $result = "{'success':'true','rooms':[{'id': 1, 'floor': '3', 'type': 'Royal Suite', 'standardRate': '2', 'smoking': 'false', 'status': '1', 'hotelId': '1'}]}"; break; case "createrooms": $roomJson = file_get_contents('php://input'); $rooms = json_decode($roomJson); // Here you would save the rooms to the database... // Returning success for demo purposes. $result = "{'success':true,'rooms':" . json_encode($rooms) . "}"; break; case "eraserooms": $roomJson = file_get_contents('php://input'); $rooms = json_decode($roomJson); // Here you would delete the rooms 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);
Now you can check out the behaviors that the related models have. In the app.js file, you can try the following code:
Ext.application({ name: 'App', models: ['Room', 'Hotel'], launch: function () { var myHotel = Ext.create('App.model.Hotel', { id: 1, name: 'Siesta by the Ocean', address: '1 Ocean Front, Happy Island', status: 1 }); var rooms = myHotel.rooms(); rooms.each(function (item, index, length) { console.log('Room Id: ' + item.id + ', room type: ' + item.type); }); rooms.load(); // Should see request with: act:loadrooms, page: 1, start: 0, limit: 25, filter: [{ "property": "hotelId", "value": 1}] } });
Using Google Chrome, you can browse to the index.html file, open the developer tools and inspect the request to the rooms.php file. The console’s output should look similar to this:
The query string parameters clearly show a filter property that contains the id of the hotel the rooms belong to. This is one of the behaviors that the hasMany association provides for us.
How It Works
As you can see in the code, you can obtain the rooms associated with a hotel by invoking the rooms() method of the Hotel model, which returns an instance of a data store. The method is automatically created based on the hasMany config:
hasMany: { model: 'App.model.Room', name:'rooms' }
In the app.js file, you will now add a couple of rooms to the current hotel:
Ext.application({ name: 'App', models: ['Room', 'Hotel'], launch: function () { var myHotel = Ext.create('App.model.Hotel', { id: 1, name: 'Siesta by the Ocean', address: '1 Ocean Front, Happy Island', status: 1 }); var rooms = myHotel.rooms(); rooms.each(function (item, index, length) { console.log('Room Id: ' + item.id + ', room type: ' + item.type); }); rooms.load(); // Should see request with: act:loadrooms, page: 1, start: 0, limit: 25, filter: [{ "property": "hotelId", "value": 1}] var room1 = Ext.create('App.model.Room', { id: 1, standardRate: 5, floor: 3, type: 'Royal Suite', status: 1, smoking: false }); var room2 = Ext.create('App.model.Room', { id: 2, standardRate: 2, floor: 1, type: 'Single', status: 1, smoking: false }); rooms.add(room1); rooms.add(room2); rooms.sync(); // Rooms have the hotelId property populated. } });
The call to rooms.sync() will upload the rooms to the server. Chrome’s console should reflect the operation:
The hotelId field of these rooms is set to 1, which is the id of their parent hotel. Note that you did not have to take care of this. It happens automatically thanks to the hasMany association you defined.
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.
Stay Tuned
Don’t miss any articles. Get free updates in your inbox.
Hi,
Why when we do a room.add , the “updateroom” parameter is passed to the url !? Why not the “createroom” parameter ?
Tks !
JB
That’s because I have already assigned an id to each model instance. The store interprets as new the records which do not have an id yet (phantom) and are also valid. If you remove the id config from the call to Ext.create, you will see act:createrooms in the request.
Hi Jorge,
thanks for the very nice article, but how if I create a ‘native like app’ using hasMany association on it’s model? what should I write as the value of the create, read, update and destroy property? is it depending to the type of proxy?
thanks,
edie
The proxy essentially defines where the data is saved. You can use server-side or client-side proxies. Not sure what you mean by ‘native like app’.
Hi Jorge,
Thank you for ST2 Book. I’ve recently downloaded this book and the examples are very descriptive. They are of great use.
I was just wondering, if you have an example of hasMany associations using MVC with ST2.1.1. I’m trying to implement a simple example, but this seems to be a nightmare for me.
Example: I have a json response with Orders with Items in it. I could able to get the Orders as list but then on the detail view I would like to display the items.
{
“orders”: [
{
“id”: “1”,
“poNr”: “A123”
“order_items”: [
{
“id”: 10,
“total”: 10.76,
“status”: “invoiced”
},
{
“id”: 11,
“total”: 13.45,
“status”: “shipped”
}
]
},
]
}
Regards,
Geetha
Thank you Geetha. I know the hasMany association usage is confusing. I will post an example as soon as possible.
what if u want to post the Hotel.rooms hierarchy starting at the root of hotel. I tried the following , I have multiple settings per machine but same idea as rooms per hotel. this produced a JSON stream that did not include the settings
var machinesStore = Ext.getStore(‘machines’);
var machine = Ext.create(‘MyApp.model.machine’, {
machineid: “”,
Name: ‘my first machine’,
PictureUrl: ‘test.jpg’
});
var settings = machine.attributes(); //this is a store
var setting1 = Ext.create(‘MyApp.model.machinesetting’, {
Name: ‘setting1’
});
var setting2 = Ext.create(‘MyApp.model.machinesetting’, {
Name: ‘setting2’
});
settings.add( setting1);
settings.add(setting2);
machinesStore.add(machine);
debugger;
machinesStore.sync();
if I run in Chrome to the breakpoint and look at the machineStore I see the Name and PictureUrl set and in the Settings store I see the setting name set. What am I missing
John, please send me your code through email.
Hi Jorge.
Could you please post or email me the solution to John’s problem?
I need the code to add and sync the parent record wherein the framework will save all associated(has many) records too.
Thank you.
hello,
can you please help me on this issue
stackoverflow.com/questions/17590305/cannot-parse-nested-json-fully-sencha
Sur007, send me the code through email.
I have send it sir. Please have a look onto it.