I have an ember action that submits a form using jquery post like this,
submit : function(){ //submitting new story
this.set('onsubmit' , true);
var self = this;
$.post( "/api/post", { some post data })
.done(function(data) {
self.transitionTo('post' + data);
});
}
The urls are like this, domain example.com. Post is located at example.com/api/post. After posting the user must be redirected to example.com/post/3432232 (some value returned by the post data).
However after posting this transition to "example.com/api/post/5000203" not example.com/post/234234234.
Which doesnt exists and gives a 404. How can I make the user go back to example.com/post/234234234 using transition-to function in ember?
Edit - Routes are as follows
App.Router.map(function() {
this.resource('index', {path : '/'} , function() {
this.resource('p', function(){
this.resource('post', { path: ':post_id' });
});
});
});
Thanks
In Ember routing system transitionTo takes as arguments route's name and route's model (the same model, that route returns when Ember.Route.model hook is invoked). There are two type of router's transitions in Ember's glossary: URL transition (happens when client handles new URL, fo ex., when you open a page from scratch) and "named transition" (which happens when this.tranisionTo is called).
Ember.Route tries to load model based on params list (:post_id, for example) while handling "URL transition". In other hand, while it handling "named transition" it doesn't call model hook, but uses already provided model (this.tranisition('post', PostModel)`). Docs.
So, if you have post_id on hands, just call this.store.find('post', postId) to get PostModel and provide requested model to PostRoute: this.transitionTo('post', this.store.find('post', data).
P.S. Please, consider reading this question and an answer to get your routing structure actually nested: Nested URLs: issues with route transition.
P.P.S. I looked at the Ember's documentation and found new (at least for me) signatures for transitionTo method: http://emberjs.com/api/classes/Ember.Route.html#method_transitionTo. According to this doc, you can rewrite your example to get it work: this.tranisitionTo('post', data), where data is post_id.
Related
I need to make a POST to a server-side API. I must send an id key into the request body to the server.
I use a Backbone model. But when I do:
myModel.set("id", somevalue)
myModel.save()
The network request that is fired is : URL/someValue [PUT]
Backbones doesn't do a POST but a PUT and appends the id to the url.
So I just want to pass an id key to the server without Backbone noticing.
From Backbone's doc:
Backbone is pre-configured to sync with a RESTful API.
[...]
The default sync handler maps CRUD to REST like so:
create → POST /collection
read → GET /collection[/id]
update → PUT /collection/id
patch → PATCH /collection/id
delete → DELETE /collection/id
A new entry doesn't have an ID, so if you give an ID to the model before saving it, Backbone defaults to a PUT request because it thinks you want to save an existing entry.
How to make a POST request with an id?
Choose one of the following solutions.
Stick to a RESTful API
This one is the obvious one. If you can, stick to the standard.
Change the API to handle PUT/PATCH requests and only use POST on creation. Make the API endpoint take the ID from the URL.
RESTful API best practices
Pass the type option1
Simple and works really well for a one-off situation.
Every options passed to save (or fetch) overrides the options the sync function defines by default and passes to jQuery.ajax function.
Backbone sync source
// Make the request, allowing the user to override any Ajax options.
var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
var url = model.url(); // get the url before setting the `id`
model.save({
id: somevalue
}, {
url: url, // fix the url
type: 'POST' // choose the HTTP verb
});
Fixing the url that the model uses is simple, you have also some choices:
pass the url option (like above)
override the url function of the model
Overriding the url function (source) works well for situation where every call should use a specific url, without the default id appended to it.
var MyModel = Backbone.Model.extend({
url: function() {
return _.result(this, 'urlRoot') ||
_.result(this.collection, 'url') ||
urlError();
}
});
Set the idAttribute on the model
This depends on what the id you're trying to pass means in the data.
Backbone Model uses "id" has the default id attribute name. You can specify a different name by overriding the idAttribute property of the model. Whatever the name, it is always automatically made available through the model.id property.
Now, assuming the id attribute isn't related to this model and this model's real id attribute name is something like UID, you could change the idAttribute of the model to reflect the real name of the attribute (or it could even be a string that's never going to be an attribute).
var MyModel = Backbone.Model.extend({
idAttribute: 'UID',
});
Now, the id attribute is not considered an id for the current model, and model.isNew() will return true, sending a POST request to create it on save.
Change the sync/save function behavior
If the API you're using is not RESTful, you can adjust the behaviors by overriding the sync function. This can be done on the model or collection, or on the Backbone.sync function which is used by default by the collections and models.
For example, if you wanted to make every request use POST by default for MyModel class:
var MyModel = Backbone.Model.extend({
sync: function(method, model, options) {
return Backbone.sync.call(this, method, model,
_.extend({ type: 'POST' }, options));
}
});
You could do something similar with only the save function to let the fetch do its GET request as usual.
Use the emulateHTTP setting2
If you want to work with a legacy web server that doesn't support
Backbone's default REST/HTTP approach, you may choose to turn on
Backbone.emulateHTTP. Setting this option will fake PUT, PATCH and
DELETE requests with a HTTP POST, setting the X-HTTP-Method-Override
header with the true method.
[...]
Backbone.emulateHTTP = true;
model.save(); // POST to "/collection/id", with "_method=PUT" + header.
Do not override isNew
Has this model been saved to the server yet? If the model does not yet
have an id, it is considered to be new.
Some other answers on this site suggest overriding the isNew function. Don't. The function has its purpose and overriding it to force a POST request is a poor hack, not a solution.
isNew is used internally but can also be used by your code or other libraries and Backbone plugins.
1 While I did not take this from stack overflow, it was already an answer by Andrés Torres Marroquín on a similar question.
2 Taken from Maanas Royy's answer.
Is it possible to have a Router implementation similar to the following?
var Router = Backbone.Router.extend({
routes: {
'' : 'search',
'*querystring' : 'results'
},
search: function() {
// load search view
},
results: function(querystring) {
// load search view
// make ajax request using querystring
}
});
The search view has a form that when submitted should go to the results view which will parse the url for the query, submit an ajax request and then display the response.
Obviously something like this would make more sense
'results?*querystring' : 'results'
But I can't get my form to submit the URL in that format.
When put my form action as <form action="index.html/results"> I get http://localhost:8000/index.html/results?c=foo&a=bar as my URL.
This is close, but I really need http://localhost:8000/index.html#/results?c=foo&a=bar and when I try to do this with <form action="index.html#/results"> it gives me http://localhost:8000/index.html?c=foo&a=bar#/results which is not what I want :(
This is why I would rather just have no form action and instead have a route that can will parse the query if one exists.
Ok thanks for reading. Hopefully someone understands some of that and can help me out.
don't put pushstate to true, set it to false
Backbone.js PushStates: Fallback for Internet Explorer not working
Remove form or prevent the submission
Simply get the params and trigger a route
Handle the params appropriately in the triggered route.
Router
routes:{
'search':'search' //queryString is automatically passed as last param in backbone 1.1
},
search: function(queryString){
//Write your logic to do the search
}
View:
events:{
'submit form':'preventAndNavigate'
},
preventAndNavigate: function(e){
e.preventDefault();
var query = $(e.currentTarget).serialize();
Backbone.history.navigate('search?'+query,{trigger:true});
}
Docs :
Backbone Routers now handle query params in route fragments, passing them into the handler as the last argument. Routes specified as strings should no longer include the query string ('foo?:query' should be 'foo').
References :
http://backbonejs.org/#changelog
I'm having an issue getting Ember.js with Ember Data to hit a nested resource API endpoint. Here is my code:
https://gist.github.com/feliksg/7470254
Here is what i'm using:
DEBUG: ------------------------------- ember.js:3224
DEBUG: Ember : 1.2.0-beta.3 ember.js:3224
DEBUG: Ember Data : 1.0.0-beta.2 ember.js:3224
DEBUG: Handlebars : 1.0.0 ember.js:3224
DEBUG: jQuery : 2.0.3 ember.js:3224
DEBUG: -------------------------------
I'm also using Ember Appkit as the base for this project.
Basically the issue is when I try to submit a new post, ember data does the following:
POST request to /user/posts
instead of a
POST request to /users/1/posts
In addition, for some reason the request payload as shown by chrome inspector shows the form data being passed to the API looks like this:
{ "user/post": { "published":false, "created_at":null, "user":"1" } }
However, I would expect the data to be passed in like this:
{"post": { "body":"some text...", "published":false, "created_at":null, "user_id":"1" } }
So for some reason, it doesn't even pass in the 'body' field even though I have it in the form.
Any help is greatly appreciated!
UPDATE 1
When I visit http://localhost:8000/#/users/1/posts, it sends a GET API request to /users.json. There must be something wrong with the way I set up the PostsRoute but i'm not sure how to fix it.
UPDATE 2
I've updated my PostsRoute to fetch the JSON without using Ember Data which returns the records, but now the posts template does not render. My PostsRoute now looks like this:
PostsRoute = Ember.Route.extend
model: (params) ->
user = #modelFor('user')
userId = user.get('id')
return $.getJSON('http://localhost:5000/api/v1/users/' + userId + '/posts.json')
I also get the following error:
Error while loading route: TypeError: Object # has no method 'slice'
So when you create/use 'users/post' you are defining a namespace where the post lives, not that it's underneath a specific model. AKA, it isn't going to use the associated user model to build up your url, it's just going to make requests using users as part of the post.
I'm not totally positive why the body isn't being attached to the post, are you sure the model includes the body? Are you sure the body property exists on the model you are sending into the createRecord?
BTW, needs doesn't do anything on a route, it only applies to controllers.
The tutorials & guides that I've found suggest that Ember.js models are very data centric, in that you have data in the browser that is persisted to the server and/or a model is filled with data from the server.
How about something that is more verb centric? For example, my case is that, so far, I have a "Search" model, where a search has a query, a state ("beforesearch","duringsearch", etc...), and, hopefully, some results. I want for the search to then "runQuery", which fires off an ajax request to the server, which returns and fills the model with the results, and changes its state to "aftersearch".
What's the best way of handling such verbs on models? Should the "runQuery" go via ember-data, or just manually fired off using $.ajax or similar? Am I maybe thinking about models in the wrong way, and this should actually go via a controller?
Edit: After reading up a bit on REST, I think what I'm wanting is to POST to a "controller" resource. So, for example:
POST: /searches (to create a search)
POST: /searches/1/run (to execute search 1's "run" controller
Does Ember.js / ember-data have a recommended way of calling controller resources like this?
Ember-data is very oriented around using model objects that contain various information fields and relationships and are defined by a unique id. Half of my API is like what ember-data expects and half is like you described, it is more about data processing or performing a calculation than creating/retrieving/updating/deleting a data object that has an id. It doesn't make sense to treat these calculations the same and assign it an id and persist it in the database.
In my case, since I have both ember-data style data objects and calculation functionality I use a mix of ember-data and custom ajax requests. I have relational data stored that is retrieved by ember-data but I augment the models to include access to the calculation portions.
For example:
App.Event = DS.Model.extend({
name: DS.attr('string'),
items: DS.hasMany('App.Item'),
...etc...
searchData: null,
searchInEvent: function(data) {
var _this = this;
return $.ajax({
url: "/api/events/" + this.get('id') + "/search/",
dataType: 'json',
type: 'POST',
data: data
}).then(function(result){
_this.set('searchData', result);
});
}
});
App.Event is a normal ember-data model and is loaded by the router through the usual ember conventions, and as the various controllers need access to the search functionality they can access it through searchInEvent and searchData that were added to the model.
I am trying to implement a search function for my website. When the user types a search term foobar into a input box and submits it, he is redirected to http://mydomain.com/search?query=foobar.
Problem:: How should I grab the GET parameters query from the URL, and send it to the backend and get a array of results back as a JSON response? Should I even do it this way?
My current attempt below does not even cause the search function to be triggered.
Router
var AppRouter = Backbone.Router.extend({
routes: {
'search?query=:query': 'search'
// ... and some other routes
},
search: function(query) {
this.photoList = new SearchCollection();
var self = this;
this.photoList.fetch({
data: {query: query},
success: function() {
self.photoListView = new PhotoListView({ collection: self.photoList });
self.photoListView.render();
}
});
}
});
var app = new AppRouter();
Backbone.history.start({
pushState: true,
root: '/'
});
There have been several issues filed against Backbone for this very issue. There is an existing plugin that works well for this:
https://github.com/jhudson8/backbone-query-parameters
Alternatively, I'm currently using query string parameters in a mock API that matches Backbone's route matching. Looks something like this
Route
"/api/v2/application/:query"
Query
application: function(query) {
var params = $.deparam(query.slice(1));
// params.something...
}
As to your actual issue at hand how are you redirecting to index.html to support pushState?
I hit this same issue and contemplated using backbone-query-parameters, but that should be considered generally an incorrect approach.
The url query string is not meant for the front end. They get sent to the server and force a refresh when navigating from page.html to page.html?something=something.
You should be using hash fragments instead. i.e. http://www.example.com/ajax.html#key1=value1&key2=value2 then just get those values the normal backbone way and build your request params from that.
See https://github.com/jashkenas/backbone/issues/891, https://developers.google.com/webmasters/ajax-crawling/docs/specification, https://www.rfc-editor.org/rfc/rfc3986#section-3.5
You can always read the URL via jQuery URL plugin. It works well.
https://github.com/allmarkedup/jQuery-URL-Parser
There are very few cases when you need to read the URL and extract the GET params. I think that you are doing things wrong and here are my options:
1) if you are having just one page in your app (single app page) you can display results as they type in your input field or after they hit submit
2) if you are redirecting the user to a different page that means you can bootstrap data so that after the page is loaded backbone will just have to render your results and only make other requests if you change your search word
3) you can have a javascript variable which is initialized on page load directly from the server where working with GET params is probably easier