I have a collection of items they all share some data (like an id, title) however outside of their shared stem of attributes they're functionally unique items and have separate views and business logic.
My problem is without prior experience in Backbone style MVC, I don't know the pros / cons of each... or perhaps if there is a much more elegant solution I'm missing. Here's an example of the 3 techniques I could potentially use?
var gizmoCollection = new Backbone.Collection(); // or extend
var gizmoModel = Backbone.Model.extend({ ... });
var morgView = Backbone.View.extend({ ... });
var blarView = Backbone.View.extend({ ... });
// 1.) Create an attribute for the view in the model?
gizmoCollection.add(new gizmoModel({ title: 'Gizmo1': view: morgView }));
gizmoCollection.add(new gizmoModel({ title: 'Gizmo2': view: blarView }));
// 2.) Or create a seperate model for each type of model?
var morgModel = morgModel.extend({});
var blarModel = blarModel.extend({});
gizmoCollection.add(new morgModel({ title: 'Gizmo1' });
gizmoCollection.add(new blarModel({ title: 'Gizmo2' });
// 3. Or register 'types' of views?
gizmoView.subClassView('morg', morgView);
gizmoView.subClassView('blar', blarView);
gizmoCollection.add(new gizmoModel({ title: 'Gizmo1', type: 'morg' });
gizmoCollection.add(new gizmoModel({ title: 'Gizmo2', type: 'blar' });
My choice would be to create separate models and it views if necessary. The reason is that each model should hold business logic for it self. Now, you may find sometimes easier to do this just with subviews if there is only presentational logic which is different for each model type or model attribute value.
You should keep in mind following:
Presentational logic goes to Presenter(s) (Backbone.View)
Business logic goest to model(s) (Backbone.Model)
Navigation logic either router (aka controller) or you can make your Event Bus from Backbone.Events or jQuery.callbacks() which will do this job and probably some other things which you want separate from your presenters and models.
Final note. Always keep in mind that your app will grow, sometimes it is wiser to add few more lines of code regardless you don't need so much complexity at the moment. But if you senses tell you that at some point that code will become more complex, well you should do it right away or later you will not have enough time.
Related
I am looking for some description of best practices for views and models/collections in Backbone. I know how to add models to collections, render views using templates and use views in parent views, however I'm looking for more context and perhaps some example links.
I've updated this question to be more specific.
Let's say you have a more grid layout with all kinds of variation, that gets pulled from the same collection. What would you do here to create this page? A simple child view repeated in a parent view won't work because the variation of the grid items is too great. Do you:
create tons of tiny views and collections and render all of these different views using the relevant collections into that one page?
create a complex template file that has a loop in it, that as you go through the loop, the loop outputs different markup?
Do people put multiple views inside a parent view, all from the same model?
Similarly, do people mix different models into the same parent view? For example movies and tv shows - these different models, can get they added to the same collection that renders that list?
Thanks!
You've asked good question. To answer it lets take a look to this from other angle:
On my exp i used to check first is there any logic on parent view, like sorting, validation, search and so on. Second - Collection with models or just model with array as property : is the model is independent and may exist without collection , for example you have navigation item, and there are no sense to make separate model for each item and navigation as collection as you will never use item itself. Another case you have user list. You may use user model in a lot of places and its better to make a separate model for user and collection to combine it.
Your case with UL may be resolved with single model and items properties with array of li, simple grid may have same approach as i don't see some special logic on wrap from your description.
But i should point out - i had close task to build mansory grid with collection parsed from server, items were models as it had different data structure, different templates and different events listener.
Taking decision i considered folowing:
item as independent tile, may be used as in grid and also independent.
item is model + template + view. different Models types helped to support different data structure, different Views types helped to support different events listeners and user interaction, different templates - diff looks.
collection as a tool to fetch initial data + load extra items + arrange mansonry view + create models according to fetched result.
UPDATE
Lets consider this pseudo code as masnonry implementation:
Models may looks like these:
var MansonryModel = Backbone.Model.extend({
/* Common methods and properties */
}),
MansonryVideoModel = MansonryModel.extend({
defaults: {
type: 'video',
videoUrl: '#',
thumbnail: 'no-cover.jpg'
}
}),
MansonryImageModel = MansonryModel.extend({
defaults: {
type: 'image',
pictureUrl: '#',
alt: 'no-pictire'
}
});
var MansonryCollection = Backbone.Collection.extend({
model: MansonryModel
});
Views could be like this:
var MansonryTileView = Marionette.ItemView.extend({
/* place to keep some common methods and properties */
}),
MansonryVideoTile = MansonryTileView.extend({
template: '#video-tile',
events: {
'click .play': 'playVideo'
},
playVideo: function(){}
}),
MansonryImageTile = MansonryTileView.extend({
template: '#image-tile',
events: {
'click .enlarge': 'enlargePicture'
},
enlargePicture: function(){}
});
var MansonryListView = Marionette.CollectionView.extend({
childView : MansonryItem
})
Hope this help
Consider the Backbone app listed below. When its started, it creates a restaurants collection and view of local restaurants in the users area. The user can later change their search area and then update the entire collection by submitting a search form.
The Problem: How can I reference the collection in the search method if it isn't related to the view?
Currently, I'm setting the restaurant collection and view as properties of the App view itself so that it can be easily referenced from within the method, but this seems in appropriate because the view isn't solely related to that collection.
Is it better to reference the collection as I have or is it better to reference it as App.collection, as backbone does when you in create views realted to collections with Backbone.View.extend({collection: mycollection})?
App view:
var App = new (Backbone.View.extend({
...
events: {
submit: "search",
},
start: function(){
App.render();
App.restaurants = new App.Collections.Restaurants();
App.restaurantsView = new App.Views.Restaurants({collection: App.restaurants});
App.restaurants.fetch({});
},
search: function(e){
e.preventDefault();
var payload = $( App.targets.searchForm ).serializeObject();
App.restaurants.fetch({data: payload, processData: true,});
},
...
}):
Those are big questions: How to structure a Backbone application, how to define and maintain module boundaries, and how to decouple modules while allowing notifications where needed, like between search and restaurants?
Big-picture answer: Look at something like Marionette. It's a framework built on top of Backbone, it makes it easy to follow a lot of best practices, and it does some heavy lifting for you. David Sulc has an excellent book about Marionette that covers all of those topics & more. Highly recommended.
Immediate-problem answer: Directly referencing objects outside of a single module (like search) is a bad idea. It couples together concerns that should be kept separate. At a minimum, think of adding an application-level event bus: search can trigger a search event, and the restaurant code can listen for that event and act on its collection in response. You could use something like the backbone.wreqr library's EventAggregator class, which is tailor-made for this kind of thing. In a pinch, you could use an instance of Backbone.Events to roll your own:
App.vent = _.extend( {}, Backbone.Events );
Trigger an event from search code like so:
App.vent.trigger('search', payload);
And in your start function, register to listen for that event:
App.vent.on('search', function(payload) {
App.restaurants.fetch({data: payload, processData: true,});
});
I know the MVC concept in ExtJs just very briefly. Could you please help me to fill up the knowledge gaps please? I only know how to create a single view this way...
Ext.define('My.controller.Contacts', {
extend: 'Ext.app.Controller',
stores: ['Contacts'],
views: ['ContactsGrid'],
refs: [{ref: 'grid', selector: '', xtype: 'contacts-grid', autoCreate: true}],
getGrid: function() {
var g = this.getGrid();
return g;
}
});
this.getGrid() seems to give you the same grid view. But what if:
I want to create multiple instances of grid views dynamically, how? and where do I store them by convention?
For each view I have created, I want to give it a config object, like how I do Ext.create(somecontrol, config); but this case in MVC they are in refs? Where do I insert this config object for every view instance I create?
I have a store Contacts, what is the relationship between all these views and the store? One each, or all sharing one store?
Thank you very much.
For all of these the answer is going to be "it depends",
I would create dynamic views in the view definition if it's something that you read at startup, otherwise if you are clicking on a button and adding a view element, you could have everything in the controller, or you could have the controller call a method on the view that actually creates the view. I'd probably go with the latter but it depends on how you want to encapsulate your view logic.
As far as 'storing' these views is concerned, the convention is to give your views an unique id so you can reference it later, similar to DOM lookups. But you can also store references to a component in a variable obviously. It really depends on what you are doing. If a controller is constructing a bunch of dynamic elements, it might make sense to just hold on to references to those elements in the controller.
Views are typically defined in their own files under the MVC approach, essentially this is an Ext.define block with a configuration in it. There are a few sample MVC applications on the Sencha site, I recommend looking them over.
This really depends on what your doing. If you have multiple Contacts views, it might make sense to have a single store be referenced by multiple views, but in general Stores represent a collection of specific data. Like Books, or Contacts, or Employees. So if a view needs to show Books and Employees, it would make sense to references those stores in the view.
I think the crux of what you're asking is where do I encapsulate the logic for dynamic views. I would recommend creating reusable view components that encapsulate your display logic and have the controller create these components and give them the data to populate themselves. This gives you a nice flexible separation of concerns.
If you're just getting started with ExtJS and their implementation of MVC I highly recommend playing around with Sencha Architect. I wouldn't build a real project with it, but it's great for throwing together quick little demo apps, and it generates an MVC structure for you. Take a look at what it gives you and take a look at the demo applications on the Sencha site.
Here's some code to explain as well. The button text config is set to 'ZZZ' in my example. The component being instantiated from view 'view-XXX' (xtype) extends 'Ext.window.Window' in my example. If a component is being instantiated within a view, the Ext.create is implicit when using lazy instantiation via the xtype config. If a component is being instantiated within the controller, I use the standard notation Ext.create. If you want to give an id, use the Ext.id() to dynamically generate an id for the component. I won't repeat #James' answer for #2 and #3.
Try this:
Ext.define('App.controller.ControllerExample', {
extend : 'Ext.app.Controller',
stores: ['XXXs', 'YYYs'],
models: ['XXX', 'YYY'],
refs : [{
ref: 'viewport',
selector: 'view-viewport'
}, {
ref: 'XXX',
selector: 'view-XXX-window'
}, {
ref: 'YYY',
selector: 'view-YYY'
}],
init : function() {
this.control({
'view-XXX-window': {
minimize: function(win, obj) {
this.getXXX().hide();
},
close: function(panel, eOpt) {
this.getXXX().hide();
}
},
'view-XXX-window button[text=ZZZ]': {
click: function() {
var XXX = this.getXXX();
if (XXX === undefined) {
var viewportWidth = this.getViewport().getWidth();
var viewportHeight = this.getViewport().getHeight();
var windowWidth = viewportWidth * 0.9;
var windowHeight = viewportHeight * 0.9;
var x = (viewportWidth / 2) - (windowWidth / 2);
var y = (viewportHeight / 2) - (windowHeight / 2);
XXX = Ext.create('App.view.XXX', {
x: x,
y: y,
width: windowWidth,
height: windowHeight
});
XXX.show();
}
else {
XXX.show();
}
}
},
});
},
});
I am working on a project where we have data models that look basically like this...
var library = {
location: 'Somewhere'
books: [
{
name: 'Good Book',
publisher: 'Great publisher'
authors: [
{
name: 'Joe Schmoe',
age: '65'
},
{
name: 'Robert Smith',
age: '47'
}
}
}
I am trying to figure out what the best way of laying out the model structure.
Something like...
var LibraryModel = Backbone.Model.extend({});
var Book = Backbone.Model.extend({});
var Books = Backbone.Collection.extend({
model: Book
});
var Author = Backbone.Model.extend({});
var Authors = Backbone.Collection.extend({
model: Author
});
Is this the best way to tackle this? Has anyone else faced anything like this?
Whether or not you need N to N, 1 to N or whatever relationships is largely a back-end concern, IMO. Unless you dealing strictly with CRUD screens, Backbone models typically don't translate directly to a back-end model, though they often approximate a back-end model.
That being said, Backbone-relational is definitely a good option for handling situations like this. It provides a very robust set of features for handling relational code and is worth looking in to.
If you'd rather stay clear of a large plugin and it's associated configuration and requirements, though, there are some simple tricks that you can use in your models directly. Since you know your data structure up front, you can make large assumptions about the code you need to write instead of having to create a very flexible system like BB-Relational.
For example, getting a list of book into a library, using the above data structure, can be as simple as:
Book = Backbone.Model.extend({});
Books = Backbone.Collection.extend({
model: Book
});
Library = Backbone.Model.extend({
initialize: function(){
this.parseBooks();
},
parseBooks: function(){
var data = this.get("books");
this.unset("books", {silent: true});
this.books = new Books(data);
}
});
I've used this code half a dozen times and it's worked out just fine for me every time. You can extend this same pattern and set of assumptions in to each layer of your object model.
You might also need to override the toJSON method on your model:
Library = Backbone.Model.extend({
// ... code from above, here
toJSON: function(){
var json = Backbone.Model.prototype.toJSON.call(this);
json.books = this.books.toJSON();
return json;
}
});
Depending on how far down this path you need to go, you will likely end up re-creating half of the functionality that BB-Relational already provides. It might be easier to use a plugin like that, with declarative relationship handling if you have a large enough structure.
IMO, it's worth understanding these basic patterns, knowing how to do this for yourself before you dig in to BB-Relational. But if you're constrained for time, I'd start with BB-Relational or some other relational mapper (I think there are a few more out there, these days).
i would not tackle this that way, because you have an N to N relationship,
1 book can have multiple authors
and 1 author has most likely written more than 1 book.
i would take a look at Backbone Relational plugin
it's not default backbone, but it does the trick the best way i can think off.
however if you only need a small solution
you can just have all your authors in a collection, and use a simple author ID array in your book model.
I understand how to get a collection together, or an individual model. And I can usually get a model's data to display. But I'm not clear at all how to take a collection and get the list of models within that collection to display.
Am I supposed to iterate over the collection and render each model individually?
Is that supposed to be part of the collection's render function?
Or does the collection just have it's own view and somehow I populate the entire collection data into a view?
Just speaking generally, what is the normal method to display a list of models?
From my experience, it's the best to keep in your collection view references to each model's view.
This snippet from the project I'm currently working on should help you get the idea better:
var MyElementsViewClass = Backbone.View.extend({
tagName: 'table',
events: {
// only whole collection events (like table sorting)
// each child view has it's own events
},
initialize: function() {
this._MyElementViews = {}; // view chache for further reuse
_(this).bindAll('add');
this.collection.bind('add', this.add);
},
render: function() {
// some collection rendering related stuff
// like appending <table> or <ul> elements
return this;
},
add: function(m) {
var MyElementView = new MyElementViewClass({
model: m
});
// cache the view
this._MyElementViews[m.get('id')] = MyElementView;
// single model rendering
// like appending <tr> or <li> elements
MyElementView.render();
}
});
Taking this approach allows you to update views more efficiently (re-rendering one row in the table instead of the whole table).
I think there are two ways to do it.
Use a view per item, and manipulate the DOM yourself. This is what the Todos example does. It's a nice way to do things because the event handling for a single model item is clearer. You also can do one template per item. On the downside, you don't have a single template for the collection view as a whole.
Use a view for the whole collection. The main advantage here is that you can do more manipulation in your templates. The downside is that you don't have a template per item, so if you've got a heterogeneous collection, you need to switch in the collection view template code -- bletcherous.
For the second strategy, I've done code that works something like this:
var Goose = Backbone.Model.extend({ });
var Gaggle = Backbone.Collection.extend({
model: Goose;
};
var GaggleView = Backbone.View.extend({
el: $('#gaggle'),
template: _.template($('#gaggle-template').html()),
render: function() {
$(this.el).html(this.template(this.model.toJSON()));
}
};
var g = new Gaggle({id: 69);
g.fetch({success: function(g, response) {
gv = new GaggleView({model: g});
gv.render();
}});
The template code would look something like this:
<script type="text/template" id="gaggle-template">
<ul id="gaggle-list">
<% _.each(gaggle, function(goose) { %>
<li><%- goose.name %></li>
<% }); %>
</ul>
</script>
Note that I use the _ functions (useful!) in the template. I also use the "obj" element, which is captured in the template function. It's probably cheating a bit; passing in {gaggle: [...]} might be nicer, and less dependent on the implementation.
I think when it comes down to it the answer is "There are two ways to do it, and neither one is that great."
The idea of backbone is that view rendering is event driven.
Views attach to Model data change events so that when any data in the model changes the view updates itself for you.
What you're meant to do with collections is manipulate a collection of models at the same time.
I would recommend reading the annotated example.
I've also found this a confusing part of the Backbone framework.
The example Todos code is an example here. It uses 4 classes:
Todo (extends Backbone.Model). This represents a single item to be todone.
TodoList (extends Backbone.Collection). Its "model" property is the Todo class.
TodoView (extends Backbone.View). Its tagName is "li". It uses a template to render a single Todo.
AppView (extends Backbone.View). Its element is the "#todoapp" . Instead of having a "model" property, it uses a global TodoList named "Todos" (it's not clear why...). It binds to the important change events on Todos, and when there's a change, it either adds a single TodoView, or loops through all the Todo instances, adding one TodoView at a time. It doesn't have a single template for rendering; it lets each TodoView render itself, and it has a separate template for rendering the stats area.
It's not really the world's best example for first review. In particular, it doesn't use the Router class to route URLs, nor does it map the model classes to REST resources.
But it seems like the "best practice" might be to keep a view for each member of the collection, and manipulate the DOM elements created by those views directly.