I am confused by Backbone's built-in REST capability. I was under the impression that Backbone models, upon a model.save(), would automatically append the value identified by idAttribute to the end of the urlRoot. But I see a lot of examples online, like the one below, where the application imperatively appends the the id to the url. Why is that? Which one is better?
An example using RequireJS:
define(function(require) {
var Backbone = require('Backbone');
return Backbone.Model.extend({
urlRoot: 'http://rest-service.guides.spring.io/greeting',
url: function() {
return this.urlRoot + '?name=' + this.id;
}
});
});
Backbone assumes that you were following some common REST practices while you were designing your REST API.
For instance, an API which updates a user should be exposed as:
PUT /users/:id
rather than
PUT /users?id=:id
Of course there are some edge cases when you must rewrite the default URL function but, in general, leaving it as is means you were following the best practices while designing your REST API.
One case I can think of when rewrites are necessary is when a sub-resource is identified by multiple IDs:
PUT /apps/:appId/schedules/:scheduleId
then the url function will be:
url: function () {
return this.urlRoot + '/' + this.appId + '/schedules/' + this.id
}
When applying REST, the id is typically the unique identifier of an item that is contained by a collection (expressed in plural).
The url /greeting/id doesn't seem to make a lot of sense to me.
Parameters sent via the query string (behind the question mark) serve as filters to the collection that is currently queried.
Related
I'm trying to build multilingual website in AngularJS. Delivering proper translation in templates seems pretty straightforward but I'm stuck on implementing proper multilingual routes using UI-Router. I couldn't have found any suitable examples.
It has to scale easily while adding new languages. Example of routes structure for two languages is:
/en
/en/products
/en/products/green-category
/en/products/green-category/green-product-1
/cs
/cs/produkty
/cs/produkty/zelena-kategorie
/cs/produkty/zelena-kategorie/zeleny-produkt-1
Product categories are hard-coded, products are loaded from database.
I'm trying something like this (leaving out product categories since they are similar to 'products', just one level deeper in hierarchy):
$stateProvider.state('product', {
url: '/{lang:' + Translate.getLangs() + '}/{products:' + Translate.getRouteVariants('products') + '}/{productSlug}',
templateUrl: 'app/product.html',
resolve: {
product : function($stateParams){
return Data.products.get( Data.products.deslug($stateParams.productSlug, $stateParams.lang), $stateParams.lang );
}
}
});
And in controller / template:
$scope.productLink = function(id) {
return {
lang:$rootScope.lang,
products:Translate.getRoute('products', $rootScope.lang),
productSlug:Data.products.slug(1, $rootScope.lang)
};
}
<a ui-sref="product(productLink(1))">Green product 1</a>
Where
Translate, Data are providers
Translate.getLangs() -> 'en|cs'
Translate.getRoute('products', lang) -> 'products' or 'produkty' based on lang
Translate.getRouteVariants('products') -> 'products|produkty'
Data.products.slug(productId, lang) -> returns productSlug from model
Data.products.deslug(productSlug, lang) -> returns productId from model
Data.products.get(productId, lang) -> loads data
Handling with current language should be done better (probably in Translate provider).
Routes shouldn't match cross-lang urls such as '/en/produkty'. Big problem.
/edit: I probably could use $stateChangeStart, check whether all parametres are in one language and if not, redirect to top-level state.
And this whole solution doesn't seem way too elegant (since I'm begginer with Angular) so if anyone can provide any insights on this subject, I'll be glad.
Thanks.
First I would like to advise about one particular point about displaying multi-lingual content if your presentation is going to be same irrespective of language and locale. And that is...
You do not want to repeat your view/presentation code.
Your code could duplicate quite a lot and cause you some problem.
However if you really want to have them separate template I am thinking that your following line may have problem;-
url: '/{lang:' + Translate.getLangs() + '}/{products:' + Translate.getRouteVariants('products') + '}/{productSlug}',
If you use curly brace approach the text after : is actually a regular expression.
So you can't just return the getLangs() as 'en|ch' it should be something like
'[a-z][a-z]{en|ch}'
(I have not tested but should work.)
I have the below that is setup and working properly.
require(['models/profile'], function (SectionModel) {
var sectionModel = new SectionModel({id: merchantId, silent: true});
sectionModel.fetch({
success: function (data) {
$('#merchant-name').html(data.attributes.merchantName);
}
});
});
But it will only work in one instance. I am wondering how to correctly edit the above code to allow multiple instances.
<h3 id="merchant-name"></h3>
The content is generated within 'Save' function.
merchantName:$('#merchantName').val(),
What you want to do is set up the rest of the components for the Backbone application. The beauty of Backbone.js is it's ability to separate collections, models and views so your logic stays in a proper place.
You'll want to use an AJAX call to retrieve your models from the server using a Collection. Then, use the collection's reset function.
Here's an example of how you might fetch a collection of models from the server.
var MyCollectionType = Backbone.Collection.extend({
getModelsFromServer:function()
{
var me = this;
function ajaxSuccess(data, textStatus, jqXHR)
{
me.reset(data);
}
$.ajax(/* Insert the ajax params here*/);
}
});
var collectionInstance = new MyCollectionType({
model:YourModelTypeHere
});
collectionInstance.getModelsFromServer();
Then, to render each one, you'll want to make a View for each model, and a Collection View. There are a lot of resources though on learning basic Backbone.js and I feel that you might benefit from looking at a few of those.
Keep in mind that Backbone collections will by default merge models with the same id. 'id' usually references a model in the backend of an application, so make sure each id is actually what you want it to be. I work with an application that has a non-Restfull back end, and so ID's are never transferred to the front end.
There are some excellent resources available to begin starting with Backbone.js.
https://www.codeschool.com/courses/anatomy-of-backbonejs
(This is a free course up to a point, and a great starter.)
http://net.tutsplus.com/tutorials/javascript-ajax/getting-started-with-backbone-js/
http://javascriptissexy.com/learn-backbone-js-completely/
I'm developing a RESTful API for a Quiz app, which is going to be built with Backbone.js and Marionette. I'm quite new to backbone and was wondering what de best URL structure would be. I have the following resources:
Answer,
Question which contains Answers,
Question Group which contains Questions,
Quiz which contains Question Groups.
Two possible URL structures come to mind:
GET /quizzes/:id
GET /quizzes/:id/questiongroups
GET /quizzes/:id/questiongroups/:id
GET /quizzes/:id/questiongroups/:id/questions
GET /quizzes/:id/questiongroups/:id/questions/:id
GET /quizzes/:id/questiongroups/:id/questions/:id/answers
or:
GET /quizzes/:id
GET /quizzes/:id/questiongroups
GET /questiongroups/:id
GET /questiongroups/:id/questions
...
Now, I have been trying to use both of these options. With the first one, I can't figure out how to define the collections as a property of the parent models in Backbone so that I can use fetch() on them. The problem with the second option is a bit different: as I understand it, Backbone derives the url for a model from its collection, but the collection is a child of another resource, whereas the url for getting a single resource uses another collection, namely the global set of resources.
I'm pretty sure I'd have to override url() in both cases. I tried some things but didn't come up with anything useable at all. Also, I'd rather not override every single url()-model in the app, changing the API structure to suit the preferences of Backbone seems like a better option to me.
Any pointers as to what seems the right way to do it with Backbone would be great!
Thanks
If questiongroups can only appear in a single quiz, then the first option (the hierarchical one) is an obvious choice. To comply with RESTful conventions, you might want to consider using singular nouns instead: /quiz/:id/questiongroups/:id/question/:id/answer/:id
To solve your fetching problem, I would recommend using nested backbone models as per this answer: https://stackoverflow.com/a/9904874/1941552. I've also added a cheeky little parentModel attribute.
For example, your QuizModel could look something like this:
var Quiz = Backbone.Model.extend({
urlRoot: '/quiz/', // backbone appends the id automatically :)
defaults: {
title: 'My Quiz'
description: 'A quiz containing some question groups.'
},
model: {
questionGroups: QuestionGroups,
},
parse: function(response){
for(var key in this.model){
var embeddedClass = this.model[key];
var embeddedData = response[key];
response[key] = new embeddedClass(embeddedData, {
parse:true,
parentModel:this
});
}
return response;
}
});
Then, your QuestionGroups model could have the following url() function:
var QuestionGroups = Backbone.Model.extend({
// store metadata and each individual question group
url: function() {
return this.parentModel.url()+'/questiongroup/'+this.id;
}
});
Alternatively, if you don't need to store any metadata, you could use a Backbone.Collection:
var QuestionGroups = Backbone.Collection.extend({
model: QuestionGroup,
url: function() {
return this.parentModel.url()+'/questiongroup/'+this.id;
}
});
I'm afraid I haven't tested any of this, but I hope it can be useful anyway!
I've a merged collection in Backbone which contains photos and albums.
To distinguish between them, i've added a field type which is either photo or album. When I populate the collection, I create different models within the Collection#model method
model: (attrs, options) ->
switch attrs.type
when 'album' then new App.Models.Album(attrs, options)
when 'photo' then new App.Models.Photo(attrs, options)
Now I've discoverd a strange bug where adding a photo and an album with the same ID (let's say 2) results in a merge.
I've tracked this down to these LOC in the source code. It seems that it's undoable without creating a fork of Backbone itself. I've tried it but it also fails 35 tests.
I thought of 4 different ways of doing this, I don't know which of them is the better one:
I could add a prefix to the id. Let's say photo_2. This causes a change in the backend as well as some changes in the frontend to don't hit the server at /photos/photo_2
I could fork Backbone and change these LOC.
I could create two separate collections but have to deal with a merge and a sort in the view (which effects clients performance and requires a rewriting of the backend)
I could start with a photo ID of, let's say 1000000. This would extremely decrease the probability that a given user which has uploaded a photo with a given ID has also created an album with the same ID.
Since version 1.2 you can use Collection.modelId to specify how your collection will uniquely identify models. In your case, you can do the following to ensure that your types have different IDs.
var MyCollection = Backbone.Collection.extend({
modelId: function (attrs) {
return attrs.type + "-" + attrs.id;
}
// ...
})
I would suggest that on both Album and Photo, you add the following:
idAttribute: 'uniqueId'
parse: function(response) {
response.uniqueId = type+'_'+response.id
return response;
}
idAttribute:'uniqueId'
if this uniqueId is not known while declaring, try
idAttribute:'UUID'
I generated one from https://www.uuidgenerator.net/ and put it here, this attribute defined here need not be in model, so I just put in a UUID.
Say I have a route setup:
'photos/:id' : 'showPhoto'
and somebody shares the url: www.mysite.com/photos/12345 with a friend.
When their friend clicks on the shared link, showPhoto gets called back with 12345 passed as the id. I cant figure out how to fetch the model from the server, because even when setting its id property and calling fetch(), backbone thinks that the model isNew and so the ajax request url is just /photos instead of /photos/12345:
showPhoto: (id) ->
photo = new models.Photo _id:id
photo.fetch #does a GET to /photos, I would have expected it to request /photos/12345
success: () ->
render photo view etc...
Photo = Backbone.Model.extend
idAttribute: '_id'
urlRoot: '/photos'
The model Photo is usually part of a collection, but in this scenario someone is visiting the site directly and only expects to see data for one photo, so the collection is not instantiated in this state of the app.
Is the solution to load the entire collection of photos and then use collection.getById(id)? This just seems way too inefficient when I just want to load the properties for one model.
if you don't have the model as part of a collection, you have to tell the model the full url manually. it won't auto-append the id to the urlRoot that you've specified. you can specify a function as the urlRoot to do this:
Photo = Backbone.Model.extend({
urlRoot: function(){
if (this.isNew()){
return "/photos";
} else {
return "/photos/" + this.id;
}
}
});
Backbone uses the id of the model to determine if it's new or not, so once you set that, this code should work correctly. if it doesn't, you could always check for an id in the if-statement instead of checking isNew.
You do not need to tell backbone whether or not to append the id the url. Per the documentation: http://backbonejs.org/#Model-fetch, you may simply set the urlRoot to the equivalent of the url in a collection.
Backbone will automatically append the desired id to the url, provided you use one of the following methods:
model.set("id", 5); //After initialized
model = new Backbone.Model({id: 5}); //New model
If you manually set the id in the attributes hash or directly on the model, backbone won't be aware of it.
model.id = 5; //Don't do this!
there's already a similar question: "How do I fetch a single model in Backbone?"
my answer there should work for you (and it's in coffeescript)
also remember to check Backbone Model#url documentation, it's all explained there
I would bootstrap the collection (by rendering the following to the page) with just one model in it like this:
photos = new PhotoCollection();
photos.reset([ #Html.ToJson(Model) ]);
Note that the server side code that I use is ASP.Net MVC so use something specific to your server side architecture. Also note that the square brackets are important as they take your singular model and wrap it in an array.
Hope that's helpful.