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.
Related
I am currently using SAPUI5/OpenUI5 to consume and modify OData Services.
I want to create a new product entry over an HTTP POST Request and have problems to properly config the associations to a category. For developing reasons I am using a reference OData Service with this metadata. The Product already has the NavigationProperty to the right Category EntrySet.
<NavigationProperty Name="Category" Relationship="ODataDemo.Product_Category_Category_Products" FromRole="Product_Category" ToRole="Category_Products"/>
I am using the following JavaScript code in my controller:
var oCategory = oModel.getData("/Categories(0)");
var oEntry = {};
oEntry.ID = "10";
oEntry.Name = "Beer";
oEntry.Category = oCategory;
oModel.create("/Products", oEntry, {
method: "POST",
success: function(data) {...},
error: function(response) {...}
});
The product is successfully created /Products(10) but the relation to the existing category /Products(10)/Category is not working properly. Instead a new category with the same ID and information is created (is this meant with 'deep insert'?) but I want to use the elected category (of course).
Do I have to reference the category differently or can I create the associations manually somehow? Shouldn't the OData Service check if the category ID already exists and then use the existing entry?
Is there any best practices for such cases?
It's important to note that you are using an OData V2 service. Yes, by building the request the way you are doing it, you are actually doing a deep insert.
If you think about it, it makes sense, because you would not need to send the whole category information to just link the new product to the exiting category. What if you would change something in the category data? Should a deep insert result in an update?
In any case, OData v2 has something called "links" (see the OData terminology - www.odata.org). Basically each "association" between entities is represented through such a link. You can manage these links separately from the entity (e.g. you can remove and create links between existing entities; without having to change the entity itself - see the OData v2 operations, chapters 2.9 to 2.12).
Depending on the data format that you are using (by default, JSON if you are using sap.ui.model.odata.v2.ODataModel), you can create entity links in the same time when creating new entities. Check out this answer: https://stackoverflow.com/a/4695387/7612556.
In a nutshell, you would have to write something along the lines of:
oModel.create("/Products", {
ID: "10",
Name: "Beer",
Category: {__metadata: {uri: "/Categories(0)"}}
}, {
method: "POST",
success: function(data) {...},
error: function(response) {...}
});
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.
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.
Disclaimer: I'm a WebApi/BackBone beginner, so the question might be a bit odd since there is a lot about these components I don't really know and/or understand.
It would be nice to have the possibility to issue just ONE sync() call to the server to synchronize everything. I mean, when I saw sync() method, at first I thought it's used like that, but as soon as I saw the "create", "update", "delete" params I realized it's not. But there is an underlying problem related to Backbones default implementation for DELETE.
I've learned that classic implementation of Backbone.js allows one deleted (destroyed) model at a time to be sync'ed to the server. Created/modified (POST/PUT operations) content is sent in the request body itself, so the JSON is filled with the data and deserialized by WebApi model binding on the server. It doesn't work like that for DELETE, since body is always empty and reference to the model is made by URL parameters in query string. So, I guess to achieve that functionality, the request for DELETE should be sent in body as well as for POST/PUT.
Is there a possibility to change all of this behavior AND make it work with WebApi? I googled for that stuff already, but can't find anything to point me to the right direction.
What I have until now is a Backbone model, collection and a view set up.
Backbone.sync("create", this.collection); is called by the view on button click.
On the server side there is a WebApi controller set up with scaffolded methods:
// GET
public IEnumerable<Ponuda> Get()
{
return _storageService.GetPonude().ToList();
}
// GET
public Ponuda Get(int id)
{
return (Ponuda)_storageService.GetPonuda(id);
}
// POST
public void Post([FromBody]IEnumerable<Ponuda> value)
{
_storageService.CreatePonude(value);
}
// PUT
public void Put([FromBody]IEnumerable<Ponuda> value)
{
_storageService.ModifyPonude(value);
}
// DELETE
public void Delete(IEnumerable<int> value)
{
_storageService.RemovePonude(value);
}
EDIT: I'm reading about Marionette.js and it seems to offer standard model/view related functionalities out of the box. However, I still can't see the possibility to save/sync e.g. the entire modified collection at once.
To sync all contents at once :
For POST and PUT Http methods, you can use Backbone.Sync API.
For DELETE, you can directly use the ajax API for deleting the content in the server and use Backbone Collection remove API to delete the content in the client side.
I have written skeleton code which demonstrates on how to achieve the functionality:
var PersonModel = Backbone.Model.extend({
url: '/demo',
defaults: {
"id": 0,
"name": "",
"age": 0
}
});
var PersonCollection = Backbone.Collection.extend({
url: '/demo',
model: PersonModel
});
var model1 = new PersonModel({"name": "John", "age": 30});
var model2 = new PersonModel({"name": "Joseph", "age": 30});
var collection = new PersonCollection();
// model will be added locally on client side. It will not sync to the server.
collection.add(model1);
collection.add(model2);
// POST. This will create both the models together using a single REST API request.
Backbone.sync('create', collection);
// PUT. This will update both the models together using a single REST API request.
Backbone.sync('update', collection);
// Extract the model ids to be deleted
var modelIds = [model1.get('id'), model2.get('id')];
$.ajax({
method: 'DELETE',
url: '/demo',
data: JSON.stringify(modelIds), // This will add ids to the request body
contentType: 'application/json',
success: function() {
// On successful deletion on server end, delete the models locally.
collection.remove([model1, model2]);
}
});
Regarding the WebApi, since I have not worked on it, will not be able to guide you. Having worked on Spring Rest API, I can tell you above functionality should work with the WebApi.
(sorry for the english)
I have a ASP .net webservice that get data from a oracle database returning JSON data.
TestWebService.asmx/getUserData
I test this using simply ajax request with jQuery
$.ajax({
type:"POST",
data:"{}",
dataType:"json",
contentType:"application/json; charset=utf-8",
url:"TestWebService.asmx/getUserData",
success:function(data){
console.log(data.d);
}
});
This work.
But now i want to try use Backbone.js
The Application have this: User data, Articles and Buy Order where a Buy order is a collection of Articles, so i think in this models for Backbone
User = Backbone.Model.extend({})
Article = Backbone.Model.extend({})
ArticleCollection = Backbone.Collection.extend({})
BuyOrder = Backbone.Model.extend({})
BuyOrderCollection = Backbone.Collection.extend({})
The Views are just 2. A Form where i show the User Data and inputs to add Articles and create the Buy Order and a Visualize view to show the Buy Orders where the user can see an check the content of one buy order clicking in the code.
The UserData, and part of the Article Data are get from the service: (User Data like name and Article Data like code, description, price, etc).
¿How can i populate the Backbone models with this data?
Thanks in advance.
So, basically, you want to override Backbone.sync. It is the thing that is currently doing your RESTful stuff (GET/POST/PUT/DELETE) via the $.ajax function as well. See how it is implemented by default: http://documentcloud.github.com/backbone/docs/backbone.html#section-134
As you can tell, it is really quite simple... about 30 or so lines of code to map create/update/delete/read to post/put/delete/get in $.ajax.
Now that you have seen how they do it, you just implement your own using the same pattern:
Backbone.sync = function(method, model, options) {
// your implementation
};
Once you do that, you are golden. Your models will do all the CRUD that you want them to, abstracted through your implementation of Backbone.sync.