Now that you have a working backend (using your favorite server-side technology), and it is proxied through sc-server, we can go back to your SproutCore app and get it communicating with the backend. Start your backend server so that it is available and make sure sc-server is also running. 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.
In fact, you are already using a data source in your application. It is called the FixturesDataSource and you set it up back when you first configured your model with this line in your core.js:
store: SC.Store.create().from(SC.Record.fixtures);
In this step of the tutorial, you will replace the fixtures data source with a version that talks to your backend server.
Creating a DataSource
To create a new data source, you can use the sc-gen tool. In your terminal, from the todos project, type the following:
sc-gen data-source Todos.TaskDataSource
You can add as many data sources as you want to your application, though normally you will only have one per backend that you need to communicate with. In this case, we'll only be talking to the server we wrote, so Todo's will only need one data source.
If you look inside your Todos app, you should find a new file at apps/todos/data_sources/task.js. This is where you will create your data source. If you open this file, you will see placeholders for five different methods:
- fetch() - will be called whenever you try to list all of the tasks in the system.
- retrieveRecord() - will be called to retrieve individual tasks if needed
- createRecord() - will be called to commit a new record to the server
- updateRecord() - will be called to commit a modified record to the server
- destroyRecord() - will be called to commit a destroyed record to the server
We are going to fill in code for each of these methods. The code we write for each one will be largely similar. Each method will call the server to either load data or commit changes and then notify the store when it has finished with the data.
Whenever the store calls your data source, it will first lock any records in the store you might need to work with so that they can't be changed from underneath you. This is why it is very important you always end your DataSource code with a notification to the store to tell it you are finished working.
OK, enough background. Let's get started writing the data source.
Fetching Records
The first method you will implement in almost every data source is fetch(). This method is called by the store whenever you try to find records using a query. The purpose of this call is to give your data source a chance to load data from the server.
In the case of Todos, fetch() will be called when we find the Tasks in our main() function. We want to design our data source so that it will load the initial set of tasks from the server at that time.
Creating TASKS_QUERY
Before we write this method, however, we need to do a little maintenance on the main() function. When fetch() is called, the store will pass the query object it is trying to find. fetch() will need to detect when we are searching for all Tasks and do the right thing.
You could write fetch() to inspect the query object, decide what it is trying to do and get from the server that way. For the sake of simplicity though, we are just going to look for an actual Query object.
At the top of your task.js data_source, add the following two lines:
in apps/todos/data_sources/task.js:
sc_require('models/task');
Todos.TASKS_QUERY = SC.Query.local(Todos.Task, {
orderBy: 'isDone,description'
});
This code defines the query object we will use in main(). The sc_require() function before instructs the build system to make sure the models/task.js is loaded before this file so that Todos.Task is defined.
Next, we need to make sure that main() is using this same query object. Replace the tasks line in your main with the following:
in apps/todos/main.js (replacing lines 24-25):
var tasks = Todos.store.find(Todos.TASKS_QUERY);
The fetch() Method
OK, now we are ready to write the fetch() method. For this code we are going to use the new SC.Request provided by SproutCore, which is a wrapper around XMLHttpRequest. Open your data source and replace the fetch method with the following code:
in apps/todos/data_sources/task.js (replacing lines 25-31):
fetch: function(store, query) {
if (query === Todos.TASKS_QUERY) {
SC.Request.getUrl('/tasks').header({'Accept': 'application/json'}).json()
.notify(this, 'didFetchTasks', store, query)
.send();
return YES;
}
return NO;
},
didFetchTasks: function(response, store, query) {
if (SC.ok(response)) {
store.loadRecords(Todos.Task, response.get('body').content);
store.dataSourceDidFetchQuery(query);
} else store.dataSourceDidErrorQuery(query, response);
},
The fetch() method simply checks that the passed query object is the same TASKS_QUERY we defined earlier. If it is, then it creates a new request to get /tasks, configures a callback for completion, and sends it.
Note that fetch() will only load records when you use store.find(Todos.TASKS_QUERY) only. Any other queries will be ignored by this data source. This is how you can implement several data sources handling different types of queries.
Note that Firefox won't send 'application/json' in the accept header without the .header({'Accept': 'application/json'}) call
didFetchTasks() is called by the SC.Request object when a response arrives from the server. The first parameter passed is the response object. The other parameters are anything we passed to notify() (in this case 'store' and 'query') up above.
This method simply loads any returned data into the store and then notifies the store that we have finished loading the query results. This will update a status property on the query results you get from find() on the store.
If the server returns an error for some reason, this code doesn't do much interesting. It simply informs the store and exits. In a shipping application you would probably want to make this more developed.
Switch on the DataSource
Finally, before we can try out this data source, we need to tell the store we want to use it. You do this in your core.js file. Instead of passing 'SC.Record.fixtures' when you configure the store, you can pass the name of your new store instead. Since your store may not be defined just yet, we will simply pass the class name as a string:
in apps/todos/core.js (replacing line 23):
store: SC.Store.create({
commitRecordsAutomatically: YES
}).from('Todos.TaskDataSource')
While we are at it, the code above also turns on a useful option on the store called commitRecordsAutomatically. This will cause the store to notice any changes to our data and automatically notify the data source. Without this option, you would have to manually tell the store when you want modified records to be sent to the server. In a complex app, you will usually want to control this yourself but auto-commit is a great way to get started so we'll use it here.
OK. Your data source is all set. Reload your app and make sure no errors are thrown. If you created any todos manually in your server while testing, you should now see them displayed. If not, your list might be empty. But never fear, we are about to make the store handle the rest of our setup as well.
NOTE: To get remote queries working you have to switch the following code, (See "Local vs. Remote Queries" in DataStore About for a definition. In following this tutorial there is no need to use a remote query and therefore no need to make the changes outlined below and doing so without other changes will cause the Todos app to break)
Todos.TASKS_QUERY = SC.Query.local(Todos.Task, {
orderBy: 'isDone,description'
});
to
Todos.TASKS_QUERY = SC.Query.remote(Todos.Task, {
orderBy: 'isDone,description'
});
and
didFetchTasks: function(response, store, query) {
if (SC.ok(response)) {
store.loadRecords(Todos.Task, response.get('body').content);
store.dataSourceDidFetchQuery(query);
} else store.dataSourceDidErrorQuery(query, response);
},
to
didFetchTasks: function(response, store, query) {
if (SC.ok(response)) {
var storeKeys = store.loadRecords(Todos.Task, response.get('body').content);
store.loadQueryResults(query, storeKeys);
} else store.dataSourceDidErrorQuery(query, response);
},
Retrieving Records
Now that we can fetch all our tasks, we need to handle the other basic operations for records, including retrieving individual items. This code is almost identical to the fetch() method except the store will pass a single storeKey instead of a Query.
The storeKey is a number assigned to each record when it loads into memory. It is temporal - that is it is regenerated each time you reload your app. You should never save storeKeys to your server, but you will need to use them to lookup the data you actually want to fetch or update.
Here is what the implementation of retrieveRecord() should look like:
in apps/todos/data_sources/task.js (replacing lines 49-55):
retrieveRecord: function(store, storeKey) {
if (SC.kindOf(store.recordTypeFor(storeKey), Todos.Task)) {
var url = store.idFor(storeKey);
SC.Request.getUrl(url).header({
'Accept': 'application/json'
}).json()
.notify(this, 'didRetrieveTask', store, storeKey)
.send();
return YES;
} else return NO;
},
didRetrieveTask: function(response, store, storeKey) {
if (SC.ok(response)) {
var dataHash = response.get('body').content;
store.dataSourceDidComplete(storeKey, dataHash);
} else store.dataSourceDidError(storeKey, response);
},
This code work's much like fetch. First, retrieveRecord() makes sure the storeKey we are asked to retrieve is actually a Task. If it is, the method looks up the ID (which is the URL in our data model) and then issues a retrieve request. On response, didRetrieveTask() will notify the store that we have finished handling the storeKey, passing any dataHash. In the case of an error, we notify of an error as well.
This is pretty simple. It also won't be used often in our particular app because we load all the tasks up front, so let's implement the next one that is important: creating records.
Creating Records
Creating records is almost exactly like retrieving a record except that we need to send some data and deal with the 'Location' header the server will include in the response. Here's what the code looks like:
in apps/todos/data_sources/task.js (replaces lines 69-75):
createRecord: function(store, storeKey) {
if (SC.kindOf(store.recordTypeFor(storeKey), Todos.Task)) {
SC.Request.postUrl('/tasks').header({
'Accept': 'application/json'
}).json()
.notify(this, this.didCreateTask, store, storeKey)
.send(store.readDataHash(storeKey));
return YES;
} else return NO;
},
didCreateTask: function(response, store, storeKey) {
if (SC.ok(response)) {
// Adapted from parseUri 1.2.2
// (c) Steven Levithan <stevenlevithan.com>
// MIT License
var parser = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/;
var url = parser.exec(response.header('Location'))[8];
store.dataSourceDidComplete(storeKey, null, url); // update url
} else store.dataSourceDidError(storeKey, response);
},
By now, the structure of these methods should look pretty familiar to you. Verify you can handle the storeKey, then create a new request to send to the server. In this case, we will POST to the /tasks URL to create the new task. We send the Task data hash as the body of the POST when we pass it to SC.Request#send().
Later, on response, we check the parsed location and get the relative part of the location in case it was an absolute URL. We then call dataSourceDidComplete(). Passing null as the second parameter tells the Store we don't have any new data to provide. passing the URL as the third parameter causes the store to remap the storeKey to the new ID.
You can actually test this code now. Reload your app and try creating some new tasks. As soon as you add a new task, it should update on the server. Of course, changes won't stick. Let's add the last few methods.
Updating and Destroying Records
You can probably predict what these will look like:
in apps/todos/data_sources/task.js (replaces lines 88-102):
// ..........................................................
// UPDATE RECORDS
//
updateRecord: function(store, storeKey) {
if (SC.kindOf(store.recordTypeFor(storeKey), Todos.Task)) {
SC.Request.putUrl(store.idFor(storeKey)).header({
'Accept': 'application/json'
}).json()
.notify(this, this.didUpdateTask, store, storeKey)
.send(store.readDataHash(storeKey));
return YES;
} else return NO ;
},
didUpdateTask: function(response, store, storeKey) {
if (SC.ok(response)) {
var data = response.get('body');
if (data) data = data.content; // if hash is returned; use it.
store.dataSourceDidComplete(storeKey, data) ;
} else store.dataSourceDidError(storeKey);
},
// ..........................................................
// DESTROY RECORDS
//
destroyRecord: function(store, storeKey) {
if (SC.kindOf(store.recordTypeFor(storeKey), Todos.Task)) {
SC.Request.deleteUrl(store.idFor(storeKey)).header({
'Accept': 'application/json'
}).json()
.notify(this, this.didDestroyTask, store, storeKey)
.send();
return YES;
} else return NO;
},
didDestroyTask: function(response, store, storeKey) {
if (SC.ok(response)) {
store.dataSourceDidDestroy(storeKey);
} else store.dataSourceDidError(response);
}
These methods post updates and destroy methods. They always, always, callback to the store. With these items in place, your app should be fully functional with the server now.
Put It All Together
That's it! You are done! You should be able to create a task by clicking the "Add task" button, remove a task by hitting the delete key, and update a task by double-clicking its description to enable the inline editor.
Congratulations. You've just built your first app on SproutCore!
Now you're ready to write your own app. Here's some things you can do next:
Happy Sprouting!
Comments (Show all 50)
Juan Pinzon said
at 2:39 pm on Sep 21, 2009
I have to updte this Majd. The idea was to keep track of the records to be able to rollback changes to a previous clean state in case of an error.
Mike said
at 10:29 am on Sep 22, 2009
I cannot move over this part of tutorial, getting error when clicking Add Task button:
>this.cancelStoreKeys is undefined
I tried to comment the line but that doesn't work for me.
I also downloaded todos sample version from github with SC and merb app. I can't make that work too. The error in that is:
> StoreKeys required
Records don't even load and cannot be created using the button.
Please help me out.
Louis Chen said
at 4:52 am on Sep 27, 2009
@kaung Yam: have you solved your problem yet? If so, how? I believe I am experiencing the same problem.
I checked what Juan Pinzon has suggested.
kaung Yam said
at 1:05 pm on Sep 30, 2009
I'm at a dead end. The delete event is not working for me. I spent a week trying to fix it. I like to learn sproutcore, but it has been tough.
kaung Yam said
at 1:07 pm on Sep 30, 2009
Hi Juan,
Can I download the complete working demo?
Guy said
at 2:33 pm on Oct 5, 2009
I built with Sinatra and DataMapper and I can't get past the "Switching from fixtures to your backend" part. I'm assuming I don't need to use merb_data_source.js and that somehow data_source.js is what I need, but I can't seem to hook them up. Any help would be appreciated.
bonndan said
at 6:14 am on Oct 6, 2009
The tutorial has not been updated completely!
@Guy
In core.js, require data_source instead of the merb stuff. Plus, rename Todos.Datasource to Datasource and then in core.js write:
store: SC.Store.create().from('DataSource')
In my version Todos is not defined even if I require the data source AFTER application.create(), so Todos.Datasource does not compute...
Robert Feldt said
at 12:12 am on Oct 30, 2009
Error after I switch on the data source:
SC.Store#find() must pass recordType or query
[Break on this error] if (!recordType) throw new Error("...find() must pass recordType or query");\nstore.js...256636806 (line 832)
I tried some of the idea about renaming Todos.Datasource to Datasource with no change. I can't understand why the Todos.TASKS_QUERY is not a query, it should be. Maybe it is another find() that gives the error? OTOH, no other find should run when I launch.
Richard Das said
at 8:03 am on Dec 2, 2009
Check your error console. I had exactly the same error, pointing to in data_sources/task.js — turned out it was some javascript syntactical errors (missing commas, sc_require in the wrong place, etc.)
(I think @bonndan's comment about renaming DataSource was related to a separate issue that @Guy was having.)
mudphone said
at 5:02 pm on Jun 5, 2010
I had the same problem as Robert Feldt. In order to get past it, I had to add the hack of redefining the TASK_QUERY in the Todos.main function (in main.js).
Frédéric GRASSET said
at 5:12 am on Nov 28, 2009
I’ve got the whole todos application working (without help of the working source on github) thus it help me to understand how it work. I find out that the REST protocol used by some functions described here is not in sync with the one described on part 6. For instance
in the retrieveRecord function, the line
var url = store.idFor(storeKey);
is supposed to return something like /<tablename>/<keyindex> according to what is expected in the server side described in part 6.
However, on my config, when storeKey is 1, then then url value is "1".
I’m not sure if I do something wrong in setting up the model or if my version of sproutcore is bogus (v1.0.1037) thus I fix the issue by replacing the line by:
var url = "/tasks/" + store.idFor(storeKey);
Can someone do confirm or explain this issue?
Frédéric GRASSET said
at 3:12 am on Dec 2, 2009
It’s seems that my issue with the line
var url = store.idFor(storeKey);
comes from my config. Can someone have some idea where it comes from?
Wojtek Augustynski said
at 7:24 pm on Dec 10, 2009
Greetings.
So, I love SproutCore.
Got through the first part and then hooked up Merb and went through this, last part, hooking the store up (in stead of fixtures). Firebug is showing me all my JSON coming back from Merb (on sc-server:4020) though I don't see any controls any more... none of the MainPane is getting rendered (top, middle, or bottom)..
Not much info to go on, I'm sure though any clue why?
Juan Pinzon said
at 3:39 pm on Jan 15, 2010
I just updated part of the tutorial. There was a part of the code that was out dated in the section "The fetch() method". Instead of using dataSourceDidFetchQuery() you have to use loadQueryResults() to load the records in the store. This changed when the concept of local and remote queries got introduced.
Juan Pinzon said
at 3:48 pm on Jan 15, 2010
Also you have to switch from SC.Query.local to SC.Query.remote.
I apologize for the outdated code.
alex_g said
at 9:43 pm on Jan 16, 2010
To be specific, you must use remote query as below
Todos.TASKS_QUERY = SC.Query.remote(...
And use the store method loadQueryResults like so
storeKeys = store.loadRecords(Todos.Task, response.get('body').content);
store.loadQueryResults(query, storeKeys);
Alex said
at 3:45 pm on Jan 28, 2010
Help! Once I hook up to the back-end, my UI stops refreshing automatically. For example, when I click the "Add Task" button, the UI doesn't refresh. If I force the page to refresh, then I see the "New Task" item. Another example is if I delete an item, the text disappears but the checkbox remains (but dimmed) - until I refresh the page, then all is OK.
Using Safari 4.0.4.
Everything worked fine until I followed the steps on this page.
If I change back to a local store by going back to:
Todos.TASKS_QUERY = SC.Query.local ...
and
store: SC.Store.create().from(SC.Record.fixtures)
the UI starts working again.
Any ideas what could be causing this?
Johnathan Loggie said
at 11:52 am on Feb 15, 2010
This happens if you use a remote query rather than a local one. A local query can still do whatever you do in your Datasource, including going of and get data from a server as the tutorial shows by specifically looking for a query and handling it the way it does, though this is counter intuitive to say the least.
If you ignore the section starting 'NOTE: To get remote queries working' then the tutorial will work.
Consequently I tried faffing about to see what it was about remote queries that wasn't working, and it looks like for the createRecord and didDestroyTask methods they are not updating the tasks SC.RecordArray. I managed to write some code that would make using remote queries work but I'm pretty sure this is not the correct way. It makes sure that the tasks SC.RecordArray only include the non-deleted items in the store ...
refreshQuery: function(store)
{
// For remote queries we need to manually update the result
store.loadQueryResults(Todos.TASKS_QUERY,
store.storeKeys().filter(function(item,index,enumerable) {
return store.peekStatus(item) != SC.Record.DESTROYED_CLEAN;
})
);
},
createRecord: function(store, storeKey,params) {
if (SC.kindOf(store.recordTypeFor(storeKey), Todos.Task)) {
SC.Request.postUrl('/tasks').json()
.notify(this, this.didCreateTask, store, storeKey)
.send(store.readDataHash(storeKey));
this.refreshQuery(store);
return YES;
didDestroyTask: function(response, store, storeKey) {
if (SC.ok(response)) {
store.dataSourceDidDestroy(storeKey);
this.refreshQuery(store);
} else store.dataSourceDidError(response);
}
} else return NO;
},
elrochoco said
at 5:00 pm on Feb 7, 2010
Hi! I followed your tutorial's steps until the "Retrieving Records" part. Everything was working great 'til there, my Ruby on Rails back-end was answering me json files as expected in the 6th part of the tutorial, but now that it is connected, I can only have my list filled with the tasks in my database if I put the following code in my fetch: method instead of the one written in the tutorial:
SC.Request.getUrl('/tasks?format=json').json()
Notice the GET parameter "format=json" I added... without it, my RoR back-end keeps answering an HTML content... Do somebody have an idea?
Lhowon said
at 2:47 am on Feb 24, 2010
Hi!
I have the same issue only on Firefox, but Safari.
I tried to add "?format=json", and got a good result.
Thanks.
Rafael Vega said
at 3:02 pm on Mar 24, 2010
Same here, only in firefox. Maybe it has to do with Firefox not sending application/json in the accept header? It's sending the "Content-Type application/json" header but the accept header does not include application/json
Joel Greutman said
at 10:28 am on Apr 23, 2010
@elrochoco @Lhowon @Rafael
Are you all using Rails servers? I got around this by forcing SC.request to send the correct accept header. I'm suspecting that the json() function is somehow broken or not working how it should in FF. Anyway, I changed the code to look like this:
SC.Request.getUrl('/tasks')
.header({'Accept' : 'application/json'})
.json()
And now it works great. I'm going to do this for my create method also, since this is expecting json back, too. Could someone look into why this is happening in FF? Thanks for the awesome tutorial!
rndm said
at 1:04 pm on Feb 11, 2010
Hi, Thanks for the tutorial, great resources!
Only I have an issue with proxying /tasks path to the rails backend
I added this line to the Buildfile:
proxy '/tasks', :to => 'localhost:3000'
But SC insists on generating the /tasks path based on the SC server path:
XHR finished loading: "http://localhost:4020/tasks"
rndm said
at 1:18 pm on Feb 11, 2010
Oh sorry, I forgot to restart the server.
Anyway I've just implemented the fetch method and switched the datasource in core.js so I should be able to GET the listing of tasks but instead I am having this error.
TypeError: Result of expression 'dataHashes' [undefined] is not an object.
BTW I am using Rails.
rndm said
at 1:34 pm on Feb 11, 2010
Right, ROR was returning a 500 and SC couldn't parse it, but having same issue as elrochoco.
Pierre said
at 4:24 am on Feb 23, 2010
Hello,
I have a problem with the "create" part. Here is the result of a Post with curl:
curl -i -H "Content-Type: application/json" -X POST -d '{"description": "first test task", "done": true }' http://localhost:8080/HelloWorld/resources/tasks
HTTP/1.1 204 No Content
X-Powered-By: Servlet/3.0
Server: GlassFish v3
Location: http://localhost:8080/HelloWorld/resources/tasks/301
When Webrick sends and receives the reply, here is what happens :
PROXY: POST 204 /HelloWorld/resources/tasks -> http://localhost:8080/HelloWorld/resources/tasks
x-powered-by: Servlet/3.0
server: GlassFish v3
location: http://localhost/HelloWorld/resources/tasks/302
[2010-02-23 12:01:21] ERROR NoMethodError: undefined method `bytesize' for nil:NilClass
/usr/lib/ruby/gems/1.9.1/gems/rack-1.1.0/lib/rack/utils.rb:228:in `bytesize'
/usr/lib/ruby/gems/1.9.1/gems/rack-1.1.0/lib/rack/content_length.rb:22:in `block in call'
/usr/lib/ruby/gems/1.9.1/gems/rack-1.1.0/lib/rack/handler/webrick.rb:48:in `service'
/usr/lib/ruby/1.9.1/webrick/httpserver.rb:111:in `service'
/usr/lib/ruby/1.9.1/webrick/httpserver.rb:70:in `run'
/usr/lib/ruby/1.9.1/webrick/server.rb:183:in `block in start_thread'
localhost.localdomain - - [23/Feb/2010:12:01:21 CET] "POST /HelloWorld/resources/tasks HTTP/1.1" 500 324
Referer -> /HelloWorld/resources/tasks
It does not work better if I try with the proxy 'todos.demo.sproutcore.com'.
I am using ruby 1.9.1p378 (2010-01-10 revision 26273) [i686-linux] and WEBrick 1.3.1
Any idea what could be wrong ?
Thanks !
Pierre said
at 5:24 am on Feb 23, 2010
Ok this problem happens when you send a status code 204 instead of 201. I am not sure why the tutorial mentions 204 in step 6. Probably a mistake.
Now I have a problem with the location header field that needs to be return by the response to a creation post. To make it work, this field apparently needs to be just "/tasks/id" but according to the HTML spec, the location header field is an absolute url . And of course I am using a framework (JAX-RS) that complies with these standard so it is a bit of a shame to hack it to return the guid instead ...
Pierre said
at 9:05 am on Feb 23, 2010
Well Status 204 is probably the best response code for the delete (and a correct return status for the create). Unfortunately I still get the same Webrick error every time I issue a response 204 with an empty content. Any help appreciated.
curl -i -X DELETE http://localhost:8080/HelloWorld/resources/tasks/851
HTTP/1.1 204 No Content
X-Powered-By: Servlet/3.0
Server: GlassFish v3
Date: Tue, 23 Feb 2010 15:35:58 GMT
But Webrick replies : undefined method `bytesize' for nil:NilClass
A part from this little problem, everything works as expected.
Pierre said
at 4:21 am on Feb 24, 2010
I wish I could edit my previous post ;-)
Just want to add "the obvious". The problem only occurs in a development environment. Everything looks ok after building. I guess it might be related to a Webrick bug.
Steve B. said
at 2:00 pm on Mar 4, 2010
Overall, a great introduction to SproutCore. I had a few rough moments but I eventually figured some things out. I posted my solutions in the relevant steps and hopefully they might help others.
I have just one comment.
The whole "NOTE: To get remote queries working you have..." section is confusing. It says you have to do it, then it says don't do it or your application will break, then there are a few comments that say you have to do it. I didn't do it, and everything appears to work right...but I wonder if that section could be made clearer.
Ahmad Alhashemi said
at 10:57 am on Mar 12, 2010
According to RFC 2161, the Location header must contain an absolute URL. However, the todos app used to fail with absolute URLs. I just fixed this by editing the code for didCreateTask (which is the only affected function) to convert absolute URLs to relative ones and will continue to work with relative URLs like before:
didCreateTask: function(response, store, storeKey) {
if (SC.ok(response)) {
// Adapted from parseUri 1.2.2
// (c) Steven Levithan <stevenlevithan.com>
// MIT License
var parser = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/;
var url = parser.exec(response.header('Location'))[8];
store.dataSourceDidComplete(storeKey, null, url);
} else store.dataSourceDidError(storeKey, response);
},
I guess by changing this, the code in this wiki is now out of sync with the code in the repository. If this is unacceptable, please excuse my ignorance and revert my edits.
Oliver said
at 5:59 pm on Apr 12, 2010
I like this so far except there is one issue I'm having. When I do
SC.Request.getUrl('/tasks').json()
.notify(this, 'didFetchTasks', store, query)
.send();
It does not return a json format, it returns HTML, I have circumvented this by changing the getUrl param to '/tasks?format=json'
Is this acceptable or is it not good to do that?
Michael Dubakov said
at 12:47 pm on Apr 19, 2010
I got it working easily following the tutorial. Great job!
But I think this method is not correct.
When you double click to edit task, it automatically marked as Done
toggleDone: function() {
var sel = this.get('selection');
sel.setEach('isDone', !sel.everyProperty('isDone'));
return YES;
}
HecHu said
at 1:51 pm on Jun 7, 2010
Hola, tengo algunos problemas, estoy usando PHP como BackEnd, el problema es que cuando envio datos en formato JSON por los metodos POST o PUT el servidor se traba, no los recibe y el tiempo de respuesta es 1 minuto, un ejemplo de lo que aparece en la consola de Firebug como datos enviados es JSON --> {"guid":"1","description":"new tasks","isDone":true}, pero si cambio el código y hago un .send()
vacio el servidor si responde obiamente así no me sirve de nada pero eso me llevo a la conclusión de que el problema es cuando envio los datos.
Los metodos GET y DELETE funcionan correctamente.
Estoy usando MAMP en Mac Os X.
--------------
Hello, i have some problems, i use PHP like backend, when i send data in POST/PUT method to the server for example ({"guid":"1","description":"new tasks","isDone":true}) the server don't do it anything and never responce and no errors in the firebug console.
I'm using MAMP for Mac Os X.
Works fine GET and DELETE methods. The problem is sending Json data to the server
HecHu said
at 7:57 am on Jun 10, 2010
The solution for now is start the server this: sc-server with --filesystem=false
Daniel said
at 11:06 am on Jul 9, 2010
When I add a new todo or try to edit one it won't let me change or uncheck. What is wrong here?
Daniel said
at 11:22 am on Jul 9, 2010
Also, after trying to edit, I noticed that in terminal SproutCore says it sent the POST with "content-type: text/html" where it should be "application/json". In apps/todos/data_soures/task.js all of the types say "application/json". How do I make it use json?
Daniel said
at 11:25 am on Jul 9, 2010
Ok I figured it out. Turns out re-copy and pasting does the trick.
Jeffrey Richardson said
at 8:52 am on Jul 11, 2010
My fetch function wouldn't fire. I had to change:
if (query === Todos.TASKS_QUERY)
to:
if (query == Todos.TASKS_QUERY)
Jeffrey Richardson said
at 8:55 am on Jul 11, 2010
Correction: fetch did fire, but the SC.REquest.getUrl function inside fetch wouldn't fire until I changed "===" to "==".
You don't have permission to comment on this page.