• If you are citizen of an European Union member nation, you may not use this service unless you are at least 16 years old.

  • You already know Dokkio is an AI-powered assistant to organize & manage your digital files & messages. Very soon, Dokkio will support Outlook as well as One Drive. Check it out today!

View
 

Todos 07-Hooking Up to the Backend

Page history last edited by Rafi Jacoby 13 years, 1 month ago Saved with comment

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.