-
Notifications
You must be signed in to change notification settings - Fork 3
Step 7: Hooking Up to the Backend
NOTE: NOT UPDATED FOR SPROUTCORE 1.0 API. THE REST IS OUT OF DATE AND A WORK IN PROGRESS
Now that you have a working backend (using your favorite server-side technology), and it is proxied trough sc-server, we can go back to your SproutCore app and get it talking to the backend. Start your backend server so that it is available and make sure sc-server is running also. Then get back into your SproutCore code and get ready to start work there again.
Sproutcore 1.0 has implemented a new Store API that helps you maintain the state of your data. To support all backend flavors, we created a class to bridge SC.Store and your backend, this bridge is called a DataSource and you are already using one for your fixtures. What you will do in this part of the tutorial is to create a custom data source to connect to your backend and switch your to load from fixtures a backend server.
Create a new directory called data_source and inside it create a new file called merb_data_source.js. Then insert the following code.
/** @class
TODO: Describe Class
@extends SC.DataSource
@since SproutCore 1.0
*/
SC.MerbDataSource = SC.DataSource.extend( {
// ..........................................................
// STANDARD DATA SOURCE METHODS
//
fetch: function(store, fetchKey, params) {
return null;
},
retrieveRecord: function(store, storeKey) {
return NO;
},
createRecord: function(store, storeKey) {
return NO;
},
updateRecord: function(store, storeKey) {
return NO;
},
destroyRecord: function(store, storeKey) {
return NO;
},
cancel: function(store, storeKeys) {
return NO;
}
});
This template includes all the basic methods you are required to implement to get your dataSource working. You can take a look at the SC.DataSource documentation for more details.
We’ll start by implementing fetch(). This function is called by the store every time you call findAll().
Create a request for fetch instead of creating one every time fetch runs. We recommend using SC.Request as it wraps your XHR request and enables you to handle the connections with the guarantee that they will work cross-browser and it has the big advantage that objects are reusable reducing your memory footprint and performance.
Add the following code to the data source:
fetchRequest: SC.Request.getUrl("tasks").set('isJSON', YES),
Replace the fetch function with the following code :
fetch: function(store, fetchKey, params) {
var ret = [], url;
var action={};
this.fetchRequest.notify(this, this.fetchDidComplete,
{
store: store,
fetchKey: fetchKey ,
storeKeyArray: ret
}
).send();
return ret;
},
As you can tell the code is really simple, we call notify() on the fetchRequest to set the callback function used once the request completes and some parameters used later by the callback function, then it calls send() to add the request to the request queue. Notice that at the end the function returns an empty array and that the same array is passed as a reference in the hash to be used by fetchDidComplete, this array will be observed and whenever the contents of that array are replaced the collectionView wired to the store will automatically update.
Now we need to implement the callback function fetchDidComplete(). Copy the following code:
fetchDidComplete: function(r,params) {
var storeKeys= [], store, fetchKey, response;
response = r.response();
if(response.kindOf ? response.kindOf(SC.Error) : false){
this.requestDidError(r);
}else{
fetchKey = params.fetchKey;
storeKeys = params.store.loadRecords(fetchKey, response.content);
params.storeKeyArray.replace(0,0,storeKeys);
}
return YES;
},
It starts by checking if the request was successful or not. If it was successful we get the content that is already converted into objects as we set JSON as the request type. Finally, it loads the records into the store and replaces the storeKeyArray with the storeKeys just loaded. Once the storeKeyArray content is replaced the listView will be automatically updated so you don’t have to deal updating the UI, and this is where you start to understand why SC is so useful.
We still need to add a function to handle the errors. Just add the following:
requestDidError: function(r){
var pane = SC.AlertPane.error("There has been an error with your request",
r.get('rawResponse').toString(), '', "OK", "Cancel", this);
return YES;
},
That code will popup an alert box with the error. Of course you can be much more sophisticated and have some code to retry failed requests or recover however you like.
Lets complete our data retrieval code by completing the retrieveRecord function. Copy and paste the following:
retrieveRequest: SC.Request.getUrl("").set('isJSON', YES),
retrieveRecord: function(store, storeKey) {
var ret = [], url, action={},
recordType = SC.Store.recordTypeFor(storeKey),
id = recordType.idFor(storeKey);
url='tasks/'+id;
this.retrieveRequest.set('address', url);
this.retrieveRequest.notify(this, this.retrieveRecordDidComplete,
{ store: store, storeKey: storeKey,id:id }
).send();
return ret;
},
At the end of your code add:
retrieveRecordDidComplete: function(r,params) {
var response, results, dataHash, storeKeys = [], hashes = [];
response = r.response();
if(response.kindOf ? response.kindOf(SC.Error) : false){
this.requestDidError(r);
}else{
results = response.content;
dataHash = results;
hashes.push(dataHash);
storeKeys.push(params.storeKey);
params.store.dataSourceDidComplete(params.storeKey, dataHash, params.id);
}
return YES;
},
RetrieveRecord() is similar to fetch but there is one fundamental difference, fetch() is used to get a big set of bulk records and retrieveRecord only retrieves specific records to either update them or load one by one.
Now that we have completed all of the retrieval code , we can test if our app load but first you have to switch your app from using fixtures.
To confirm our progress we first have to switch our source from fixtures to the new merbDataSource. Open core.js and change the source passed in the ‘from’ function.
store: SC.Store.create().from(SC.MerbDataSource.create())
And then add a require() at the top of the file:
require('data_source/merb_data_source');
Now, reload your app. Voila, now you should be seeing your records load. :D
Let’s start with Create. Copy the following code.
createRequest: SC.Request.postUrl("tasks").set('isJSON', YES),
createRecord: function(store, storeKey) {
var dataHash = store.readDataHash(storeKey),
obj={"content":dataHash};
this.createRequest.notify(this, this.createRecordDidComplete,
{
store: store, storeKey: storeKey
}
).send(SC.json.encode(obj));
return YES ;
},
This is pretty similar code to retrieveRecord. Create, Update and Destroy are called after a commit in your store, the store identifies the state of your records and calls the appropriate functions. For the create case the function starts by retrieving the new record hash from the store and puts the hash as the content parameter of another object that just serves as a wrapper. After that it calls notify on the createRequest to set the callback function and the parameters, to finally send the request with the object as the body of the request.
Now the callback function:
createRecordDidComplete: function(r, params){
var response, results, guid;
response = r.response();
if(response.kindOf ? response.kindOf(SC.Error) : false){
this.requestDidError(r);
}else{
results = response.content;
guid=results.guid;
params.store.dataSourceDidComplete(params.storeKey, results, guid);
}
return YES;
},
Same structure as the previous callback methods, checks for and error if succesful gets the response content that contains the hash inserted in the backend with the guid generated by the backend. Then it notifies the store that the new record was created and updates it with the new backend generated guid.
It’s time to check is our create function is working, so we have to hook the new task button to create a new record.
!!!!!!!!!!!!! this has to be changed once inline editing is working once again.
To finalize the tutorial just paste the following code that completes your datasource and doesn’t need any further explanation.
updateRequest: SC.Request.putUrl("tasks").set('isJSON', YES),
updateRecord: function(store, storeKey) {
var id = store.idFor(storeKey),
dataHash = store.readDataHash(storeKey);
this.updateRequest.notify(this, this.updateRecordDidComplete,
{
store: store, storeKey: storeKey, id:id
}
).send(SC.json.encode(dataHash));
return YES ;
},
destroyRequest: SC.Request.deleteUrl("").set('isJSON', YES),
destroyRecord: function(store, storeKey) {
var id = store.idFor(storeKey);
if(!id) return YES;
this.destroyRequest.set('address',id) ;
this.destroyRequest.notify(this, this.destroyRecordDidComplete,
{
store: store, storeKey: storeKey
}
).send();
return YES ;
},
updateRecordDidComplete: function(r, params){
var dataHash, response, results;
response = r.response();
if(response.kindOf ? response.kindOf(SC.Error) : false){
this.requestDidError(r);
}else{
dataHash = params.store.readDataHash(params.storeKey);
results = response.content;
params.store.dataSourceDidComplete(params.storeKey, results, params.id);
this.cancelStoreKeys[params.storeKey]=null;
}
return YES;
},
destroyRecordDidComplete: function(r, params){
var response = r.response();
if(response.kindOf ? response.kindOf(SC.Error) : false){
this.requestDidError(r);
}else{
params.store.dataSourceDidDestroy(params.storeKey);
this.cancelStoreKeys[params.storeKey]=null;
}
return YES;
},