I've got a JSON file that looks like this.
{
"config": {
"setting1": 'blabla',
"setting2": 'blablabla'
},
"content": {
"title": "Title of an exercise.",
"author": "John Doe",
"describtion": "Exercise content."
},
"answers": [
{
"id": "1",
"content": "Dog",
"correct": true
},
{
"id": "2",
"content": "Fish",
"correct": false
}
]
}
Than, I create a Backbone View, combined from content model, and answers (which are randomly selected, but It's not most important now).
I've also got a config, which has settings that will determinate which view and collection methods to use.
It seems like a simple task, but as I'm new to Backbone, I'm wondering which is the best way to fetch JSON file, creating one model with url to JSON and than using parse and initialize creating another models and collections (with answers), or using $.getJSON method that will create exactly the models that I need?
I was trying using $.getJSON
$.getJSON(source, function(data) {
var contentModel = new ContentModel(data.content);
var contentView = new ExerciseView({ model: contentModel });
var answerCollection = new AnswersCollection();
_.each(data.answers, function(answer) {
answerCollection.add(answer);
});
var answersView = new AnswersView({collection: answerCollection});
$(destination).html( contentView.render().el );
$('.answers').append( answersView.el );
)};
But It doesn't seem very elegant solution, I know that this application needs good architecture, cause It will be developed with many other Views based on 'config'.
Hope you guys give me some suggestions, have a good day!
I think what you've done works fine and is correct. But you may need to refactor a little bit since "it will be developed with many other Views based on 'config'".
IMHO, the first thing you need to do is to handle failure in your getJson callback to make the process more robust.
Second, it is useful to create a Factory to generate your views because your logic is to generate different views based on the config data from server. So the factory maybe:
contentViewFactory.generate = function(data) {
var config = data.config;
....
var ActualContentView = SomeContentView;
var contentModel = new ContentModel(data.content);
return = new ActualContentView({ model: contentModel });
}
If your logic is simple, you can have a dict map from config to view class like:
var viewMaps = {
"exercise" : ExerciseView,
"other": SomeOtherView,
//....
}
And if every workflow has a AnswersView you can keep that in your getJSON callback. So maybe now your getJSON looks like this:
$.getJSON(source, function(data) {
// keep the config->view logic in the factory
var contentView = contentViewFactory.generate(data);
var answerCollection = new AnswersCollection();
_.each(data.answers, function(answer) {
answerCollection.add(answer);
});
var answersView = new AnswersView({collection: answerCollection});
$(destination).html( contentView.render().el );
$('.answers').append( answersView.el );
})
.fail(){
//some failure handling
};
Furthermore, if you have common logics in you "ContentView"s, it's natural that you can have a "BaseContentView" or "ContentViewMixin" to extract the common logic and use extends to make your code more OO:
Backbone.View.extend(_.extend({}, ContentViewMixin, {
//.....
}
So if someone is trying to add a new ContentView, he/she just needs to add some code in the factory to make the new View be generated by config. Then extends the ContentViewMixin to implement the new View.
Related
I'm following along on the Angular JS Tutorial and I was wondering if there is an alternate approach to how I'm modifying it.
Currently, I am returning data with a factory that is defined as such:
angular.
module('core.card').
factory('Card', ['$resource',
function($resource){
return $resource('cards/:cardId.json', {}, {
query: {
method: 'GET',
params: {cardId: 'cards'},
isArray: true
}
});
}
]);
This is all good and working, as cards.json has all of the cards available and that's exactly what I want to return.
The method that they're describing, such as dealing with a RESTful service, assumes that there are multiple other specific JSON files that could get returned based on the route. I understand how to use that with an actual service, but let's say I wanted to alter the returned JSON data before it gets bound to my module so I don't have a bunch of extra data that I don't need?
Lets say /cards/foo.json contains something like this:
[{
"id": "foo",
"name": "Bar",
"img": "foobar.png",
"unnecessaryKey": "remove me"
}]
But where would I write a function that only returns:
[{
"id": "foo",
"name": "Bar",
"img": "foobar.png"
}]
Would I assign it in the same place where the query function is, such as:
...
return $resource('cards/:cardId.json', {}, {
query: {
method: 'GET',
params: {cardId: 'cards'},
isArray: true
},
alterReturnedData: {
// doStuffToFormatData
}
});
...
Or would it be best to just modify it in my Component as I'm doing now?
function alterReturnedData(data){
// doStuffToFormatData
}
var unmodified = Card.get({cardId:'foo'}, function(){
self.data = alterReturnedData(unmodified);
});
I just feel like it'd be better to return the data from the Service I actually want to the Component Controller instead of having a lot of logic in there to skew it around.
Is my approach OK to run this function in the Controller?
Or is it best to alter it in the Service, and how would I do so?
I'm having trouble getting a Backbone collection to sort properly. I inherited the project, so there may be some shenanigans someplace else, but I want to rule out any syntax error on my part.
The project uses a JSON file to handle the data:
"classifications": [
{
"name": "1 Bedroom",
"alias": "1BR",
"id": "1BR",
"menu_desc": "Residences"
},
{
"name": "2 Bedroom",
"alias": "2BR",
"id": "2BR",
"menu_desc": "Residences"
},
{
"name": "3 Bedroom",
"alias": "3BR",
"id": "3BR",
"menu_desc": "Residences"
},
{
"name": "4 Bedroom",
"alias": "4BR",
"id": "4BR",
"menu_desc": "Residences"
},
{
"name": "Common Areas",
"alias": "Common",
"id": "Common",
"menu_desc": "Resident Amenities"
}
]
Previously, there were no one-bedroom units, and the order in which it rendered was this:
I added the one-bedroom classification, and suddenly the order was this:
I did some digging and found documentation about the comparator property, but it only seems to apply to collections. This project doesn't use a collection for the classifications. It does for the submenu items (which floor the units are on, etc.), but not the main menu:
var MenuClassificationListView = Backbone.View.extend({
id: "classification_accordion",
template: _.template( "<% var classifications = this.options.classifications; _.each(this.collection.attributes, function(v,k) { %>"+
"<h3 class='<%= k %>'><%= classifications.get(k).get('name') %>"+
"<p><%=classifications.get(k).get('menu_desc')%></p></h3>"+
"<% var model = new MenuClassificationList(v); var view = new MenuClassificationItemView({collection:model, classification:k}); %>"+
"<% print(view.render().el.outerHTML); %>"+
"<% }); "+
"%>"),
render: function(){
//console.log(this.options.classifications);
//console.log(this.collection.attributes);
//alert(1);
this.$el.html(this.template());
return this;
}
});
How do I incorporate the comparator?
Thanks,
ty
One way could be to define a collection for the classifications, same way they are defined for the other items you mention:
var Classifications = Backbone.Collections.extend({ // etc. etc.
That way you can add the comparator and it will always be sorted.
Another way is to sort (http://underscorejs.org/#sortBy) the array in the initialize function in your view:
initialize: function(options) { // sorry don't remember the exact syntax for the parameters passed in, but I believe options is what you need
this.options.sortedclassifications = _sortBy(options.classifications, function (c) { return parseInt(c.id); }); // or any other sorting logic
}
Then in the template use the sorted classifications:
template: _.template( "<% var classifications = this.options.sortedclassifications; _.each(this.collection.attributes, function(v,k) { %>" + // etc. etc.
This should give you what you need. However, if I may add a personal opinion, I would go through the effort of defining a Collection for the classifications and a model for the single classification. Moreover, I would keep the MenuClassificationListView but also create a MenuClassificationView that will hold the single classification template.
In this way you are able to compose views, change rendering of the single classification without changing the list and scope the events to the inner views (so clicking on a single classification is handled by the single classification view). It makes everything cleaner, more composable and readable, in my opinion.
_.sortBy does not need to be used as Backbone collections already come with built in functionality for sorting.
See: http://backbonejs.org/#Collection-comparator
Example:
var SortedCollection = Backbone.Collection.extend({
comparator: 'key'
});
var mySortedCollection = new SortedCollection([{a:5, key:2}, {key:1}]);
console.log( mySortedCollection.toJSON() );
// [{key:1}, {a:5, key:2}]
However, the collection will not be automatically re-sorted when changing the key attribute. See:
mySortedCollection.at(0).set( 'key', 3 );
console.log( mySortedCollection.toJSON() );
// [{key:3}, {a:5, key:2}]
You have multiple options to solve this problem: you can manually call mySortedCollection.sort() or you can initialize the collection by binding its change:key event to re-sort the collection. The change:key event is triggered by the model whose key attribute is changed. This event is automatically propagated to the collection.
var AutoSortedCollection = Backbone.Collection.extend({
comparator: 'key',
initialize: function() {
this.listenTo( this, 'change:key', this.sort );
}
});
In addition, I suggest removing functionality from the templates. It is easy to debug Backbone Views, but it gets harder to read the stack trace as you move functionality inside the template string. You also enforce proper separation of concerns by using your Backbone View for preparing all data for presentation and your template should just display it.
var MyView = Backbone.View.extend({
//...
serializeData: function() {
return {
classifications: this.collection.toJSON(),
keys: this.collection.length > 0 ? this.collection.at(0).keys() : []
}; // already sorted
}
render: function() {
this.$el.html(this.template( this.serializeData() ));
}
});
Your template string becomes much easier to read: you can directly use the variables classifications and keys, iterate on them with _.each and simply reference to values without having to deal with the Collection syntax.
I have a Backbone application where the JSON I get from the server isn't exactly 1 on 1 with how I want my models to look. I use custom parse functions for my models, ex:
parse: function(response) {
var content = {};
content.id = response.mediaId;
content.image = response.image.url;
return content;
}
This works. But, in some cases I have an API call where I get lots of information at once, for instance, information about an image with its user and comments:
{
"mediaId": "1",
"image": {
"title": "myImage",
"url": "http://image.com/234.jpg"
},
"user": {
"username": "John"
},
"comments": [
{
"title": "Nice pic!"
},
{
"title": "Great stuff."
}
]
}
How would I go about creating a new User model and a Comments collection from here? This is an option:
parse: function(response) {
var content = {};
content.id = response.mediaId;
content.image = response.image.url;
content.user = new User(response.user);
content.comments = new Comments(response.comments);
return content;
}
The trouble here is, by creating a new User or new Comments with raw JSON as input, Backbone will just add the JSON properties as attributes. Instead, I'd like to have an intermediate parse-like method to gain control over the objects' structure. The following is an option:
parse: function(response) {
// ...
content.user = new User({
username: response.user.username
});
// ...
}
...but that's not very DRY-proof.
So, my question is: what would be a nice pattern to create several models/collections out of 1 JSON response, with control over the models/collections attributes?
Thanks!
It may not be the nicest way possible, but this is how I do it:
content.user = new User(User.prototype.parse(response.user));
The only problem is that the this context in User.parse will be wrong. If you don't have any specific code in the User constructor, you can also do:
content.user = new User();
content.user.set(user.parse(response.user));
I also noticed an interesting note in the Backbone version 0.9.9 change log:
The parse function is now always run if defined, for both collections and models — not only after an Ajax call.
And looking at the source code of Model and Collection constructor, they do it like so:
if (options && options.parse) attrs = this.parse(attrs);
Maybe upgrading to 0.9.9 will give you what you need? If upgrade is not an option, you can of course implement the same in your own constructor.
I'm using backbone and handlebars for templating and i'm new to this.
My current json is in the below format and the code works fine.
[
{
"id": "10",
"info": {
"name": "data10"
}
},
{
"id": "11",
"info": {
"name": "data11"
}
}
]
But when i change my json structure to something like shown below i'm having difficulty in getting things to be populated.
{
"total_count": "10",
"dataElements": [
{
"id": "10",
"info": {
"name": "data10"
}
},
{
"id": "11",
"info": {
"name": "data11"
}
}
]
}
How can i populate name, info and total_count keeping the current code structure ?
JSFiddle : http://jsfiddle.net/KTj2K/1/
Any help really appriciated.
A few things that you need to do in order for this to work.
Replace Backbone's core 'reset' on your collection with a custom one that understands the data you are passing to it. For example:
reset: function (data) {
this.totalCount = data.total_count;
Backbone.Collection.prototype.reset.call(this, data.dataElements);
}
Now when you reset your collection, it will pull the total_count out of the object you are resetting it with, and use Backbone's core reset with the dataElement array. Keep in mind you may have to do a similar thing with 'parse' if you're intending on pulling this from the server.
I'd recommend that (if your example looks anything like the real code you're working with) you reset your collection before getting to rendering.
var dataCollectionList = new dataCollection();
dataCollectionList.reset(jsonData);
var App = new AppView({model : dataCollectionList});
Now in your view's "render" method you can grab the 'totalCount' property off the collection -
render : function() {
//Should spit the total count into the element, just as an example
this.$el.append(this.model.totalCount);
//or console.log it
console.log(this.model.totalCount);
return this;
}
Voila. Side note - as someone who works with Backbone a lot, it drives me nuts when people set an attribute of something like "model" (i.e. peopleModel, itemModel, etc) and it ends up being a backbone collection. It's much clearer to name it after what it is - though some MVC purists may disagree a bit.
Also, in this code block:
_.each(this.model.models, function (myData) {
$(this.el).append(new ItemView({model:myData}).render().el);
}, this);
You don't need to do _.each(this.model.models.......). Since you're working with a collection, the collection has a built in 'each' method.
this.model.each(function (myData) { ..... } , this);
Quite a bit cleaner.
How to implement a simple non persistent datastore in ember? I've tried to follow the Article of Jiri Zajpt but somehow i can't figure out how it's working. I think i need a deeper understanding of the datastore concept.
As i understand the concept correctly, the datastore provides methods to deal with data and storing these data into an application wide accessible object, right?
This is what i've so far:
Ember App
App = Ember.Application.create();
App.DataSource = Ember.Object.extend({
store : null,
getTickets: function(callback) {
var store = this.store;
jQuery.get('/data/tickets.json', function(data) {
store.pushObjects(data);
callback(store);
});
}
});
App.dataSource = App.DataSource.create({
store: App.store
});
App.Store = Ember.ArrayProxy.extend({
content: [],
init: function() {
this._super();
}
});
App.store = App.Store.create();
Loaded JSON
[
{
"id":"1",
"type":"bug",
"prority":"normal",
"status":"open",
"title":"Router is stopping at a non-leaf state",
"discription":"Lorem ipsum"
},
{
"id":"2",
"type":"Change request",
"prority":"normal",
"status":"open",
"title":"Add Ember.Deferred mixin",
"discription":"Lorem ipsum"
}
]
Note: The code above has currently no output. Here is a Fiddle to play with: http://jsfiddle.net/WHqVJ/
I know the ember-data project but i think it's a little bit to much for my project and also i want to learn to create a datastore.