I am working (for the first time) with Backbone.js in creating a reasonably simple application. I am currently working with two entities that have a one-to-many relationship:
Project: has many groups
Group: belongs to one project
I looked at a few different ways of representing relationships between models, from Backbone's own recommendations to Ligament to Backbone-relational. However, I eventually settled on using simple function calls. For example, within my 'Project' model, there's a function called groups():
groups: function() {
return new Collections.Groups(groups.where({project_id: this.get('id')}));
}
And within the Group model:
project: function() {
return projects.where({id: this.get('project_id')})[0];
}
This allows me to do things like:
var foo = new Project();
var foo_groups = foo.groups();
foo_groups.at(0).get('name');
var bar = new Group();
var bar_project = bar.project();
bar_project.get('name');
To me, the tradeoff here is the overhead of maintaining up-to-date relationships (when using something like backbone-relational) versus the processing time used calling the 'groups()' function each time you want to get a project's groups.
Can anyone advise me on any pitfalls I may encounter down the road as I build out more relationships in this way, perhaps recommending a better solution (other than the extensions mentioned above)? The application will eventually have about 6 entities with a variety of one-to-many and many-to-many relationships
Project.groups
I have an implementation of what I call AutoUpdatedCollection.
I use it in the scenario you have a shared Collection which contains every Model of one class, and this Collection is used to populate sub-Collections those have to be updated when the shared Collection is updated.
Check the implementation of the Backbone AutoUpdatedCollection.
Following your example I suppose there is a common/shared Collection called window.groups, then I'll implement Project Model like this:
// code simplified and no tested
var Project = Backbone.Model.extend({
initialize: function(){
this.groups =
new App.AutoUpdatedCollection({
name: "Project-" + this.id + "-groups",
sourceCollection: window.groups,
filterField: "project_id",
filterValue: this.id
}).filteredCollection;
this.groups.on( "add", this.addGroup, this );
this.groups.on( "remove", this.removeGroup, this );
}
});
Maybe is not as simple as this, if you have any problem let me know.
Group.project
I'd just initialize the Group.project attribute in the Group.initialize():
var Group = Backbone.Model.extend({
this.project = window.projects.where({id: this.get('project_id')})[0];
});
Related
I am trying to wrap my head around Backbone, more specifically how the an application flows throughout it's life. Unfortunately at my job I do not have access (or say for that matter) on how our API is structured. We have many different calls from different time periods with crazy inconsistent structure.
Overriding fetch or sync is not a problem to standaraize the return but what I run into (at the very beginning of my dive in the a Backbone application) is a how to layout the actual code.
Here is my real world example. This page is non-critical and I am trying to re-write it with Backbone. Here is the flow:
Page loads a list of genre types from a call
Clicking on a genre type loads sub genres based off of the genre type (the sub genre type requres a genre code as the parameter)
Clicking on the sub genre type loads all products with that criteria.
I can get pretty far but at some point I feel the code is getting mangled - or doesn't feel natural. Like I am shoving things in.
So my official questions is: How do I manage a Backbone app?
Here is a summary of my though process:
I created a global namespace as one should
var App = App || {};
Okay, lets start with the main application view as all examples show:
App.MainView = Backbone.View.extend({
//this loads the outer stuff
//and creates an instance of the Genre View
});
Alright pretty straightforward, I am going to need a genre model, collection, and view (this applies to sub genre as well)
App.Genre = Backbone.Model.extend();
App.Genres = Backbone.Collection.extend({
url: 'returns a list of genres',
model: App.Genre,
initialize: function() {
this.fetch();
},
fetch: function() {
var self = this;
$.ajax({
url: this.url,
success: function(response) {
**format return**
self.add(formattedArrayOfModels);
}
});
}
});
Now on to the view, the confusing part
App.GenreView = Backbone.View.extend({
el: 'element',//easy enough
tmpl: 'my handlebars template',//implementing handlebars...no problem
initialize: function() {
//this produces a collection full of genres
this.genreList = new App.Genres();
this.genreList.on('add', _.bind(this.render, this));
},
render: function() {
//rendering not a problem, pretty straight forward
}
});
Up until here I have no problems. The genre list loads and we're good to go. So, now when the user clicks a genre I want it to load a sub genre
events: {
'click a': 'getSubGenres'
},
getSubGenres: function(e) {
}
Here is my problem. In getSubGenres do I keep it local?
var subGenre = new App.SubGenreView();
Or should I make it part of the Genre view?
this.subGenre = new App.SubGenreView();
Should I somehow put it in a parent object so it can be accessed by other views? How do I control things like that?
And if I already have a collection of sub genres how do I just use the loaded collection (instead of another ajax call).
Is this the approach you would use?
couple of things before I answer,
first: the fetch function doesn't need an $ajax call since it's its job, so, you can evaluate error:function(){} and success:function(){} immediately inside fetch, but that's assuming that the URL is set correctly.
second: one thing that helped me a lot in my backbone keyboard-head-fight is the addy osmani Backbone Fundamentals which contains a very rich tutorial in pdf format.
now back to the question: from my experience, you will mostly need 'this', so it's a good habbit to get used to it, plus there is something that solves a lot of these issues if implemented correctly: backbone layoutmanager
anyway, the decision of where to place the subview, is totally a design decision in your case and depends a lot on how you structure your page and files.
about how to use the "collection" that is preloaded: I really didn't get it, because the collection you're talking about contains all the subgenres, so usually it shouldn't change even if the view changes to a certain genre view, you are still able to use it.
but still everything I said, is relative to how you structure your files, I do an app.js and a router.js and lots of other files, but the main work is always on the main two, so basically I always get access to everything.
I hope this answered your question
In three.js I've created a basic 'hero creation' program. Similar to Elder Scrolls swapping through heads,body etc to create a full character. I'd like to make it more interactive by letting other users edit the same hero. Each user will just read the same JSON file off the server.
To structure my code better I want to use MVC pattern but I'm confused about how to apply it.
I think all my event listeners will be a controller but would the View just be my three.js render() and the Model just the underlying JSON? Specifically applying MVC to this graphics domain is my big problem. If this is very bad form, would you have any suggestions on a different pattern/way to structure?
The View layer should:
Contain the rendering logic
Process user input and forward it to the controller layer (using the strategy pattern)
Keep your three.js objects in sync with the models
The Controller layer should:
Process the user input and update the models
The Models are your main entities. They shouldn't have any three.js related functionality.
I wrote a blog post that explains how to combine MVC and Three.js in detail: http://hecodes.com/2016/07/using-mvc-pattern-for-building-complex-three-js-applications/ .
You might find it helpful.
You may use a combination of MVC and Bridge pattern.
In this combination, you may abstract your view and use bridge pattern to allow your view to be created in several different formats.
Following link might help you : http://www.oodesign.com/bridge-pattern.html
To understand easily, you may consider this:
var modelObject = {};
var viewObject = {};
var controllerObject = {};
modelObject.str = "Welcome";
viewObject.showStr = function(modelObject) {
console.log(modelObject.str);
}
controllerObject.handler = function() {
viewObject.showStr(modelObject);
}
You can now call the function by handler while onclick or any event etc.,
Usually model is an abstraction of a data/data source
So I'm using Backbone for my web application and I'm having trouble figuring out how to work with nested models. I have a model based on a java class whos JSON would look something like this:
"AdminSettings":
{
"defaults": {
"deletable":true,
"transferable":false;
"issueLimit":1
}
"displaySettings": {
"tabs":2,
"amounts:[10,20]
}
}
Currently, I have an endpoint set up and a backbone model for the AdminSettings object. I was wondering if there was a specific way to get all the backbone benefits for the objects inside AdminSettings. For example, currently I have to use:
adminSettings.get("defaultValues").shareable
But I want to use:
adminSettings.get("defaultValues").get("shareable")
This isn't the only benefit I'm trying to obtain, just an example.
So yeah, what would be a good way to go about this. I was thinking making a backbone model for each one of the nested objects and setting up endpoints for each one of those, but I'm not completely sure. Anyways, thanks for looking.
You could write your own custom backbone parser:
var DisplaySettingsModel = Backbone.Model.extend({});
var DefaultValuesModel = Backbone.Model.extend({});
var AdminSettingsModel = Backbone.Model.extend({
model: {
defaultValues: DefaultValuesModel
displaySettings: DisplaySettingsModel
},
parse: function(response){
response["defaultValues"] = new DefaultValuesModel(response["defaultValues"], {parse:true});
response["displaySettings"] = new DisplaySettingsModel(response["displaySettings"], {parse:true});
return response;
}
});
I am designing a generic object browser plugin which functions similar to OS X's Finder in column view. I have divided up the interface in to several nested views, the browser, the columns and the objects.
I will be using this plugin in several scenarios where the browser view, object view and column view may or may not need to be customised. Sometimes the objects will be files and folders for example.
This is OS X's Finder in column view in case you don't know what it looks like.
At the moment I am using RequireJS to pass around the dependencies however in order to simply inherit and extend the ObjectView, I must replace the entire stack.
Is there any better structure where the plugin can be extended but only part of?
BrowserView.js
var BrowserView = Backbone.View.extend({
open: function () {
var collectionView = new CollectionView( {collection: objects} );
}
});
CollectionView.js
var CollectionView = Backbone.View.extend({
render: function () {
this.collection.each( function (object) {
var objectView = new ObjectView( {model: objects} );
objectView.bind('click', this.select, this);
this.container.append( objectView.el );
objectView.render();
this.objectViews.push(objectView);
}, this );
},
});
ObjectView.js
var ObjectView = Backbone.View.extend({
});
I would put these views in the same module.
The purpose of a module - whether you're using RequireJS or just plain old JavaScript modules - is to encapsulate a set of related objects and functions, for a specific purpose. In this case, your purpose is the Finder View.
By keeping all of the related objects in the same file, you'll have more freedom and flexibility for how you make the objects work together.
As a side note, but related to what you're doing, you might be able to get some ideas for how to make this work from the "CompositeView" of my Backbone.Marionette plugin. I've built a hierarchical tree-view of folders and files with it before, and the column view of Finder would be fairly easy to build with it, too.
Note that I'm not suggesting you need to use my plugin. Rather, I think it might be helpful in figuring out how you want to structure your code.
I've got a blog post that talks about it here: http://lostechies.com/derickbailey/2012/04/05/composite-views-tree-structures-tables-and-more/
You can find the code and docs here: https://github.com/derickbailey/backbone.marionette
And the annotated source code for the composite view is here: http://derickbailey.github.com/backbone.marionette/docs/backbone.marionette.html#section-26
In this stackoverflow post i read about filtering backbone collections and using subsets.
One answer (by sled) recommends using backbone.subset.js (usage example).
I could not find any further resources on backbone.subset.js and I failed implementing it into my project.
It seems like backbone.subset.js is the perfect solution for what i'm trying to achieve.
(Having one "parent" collection that holds all models at all times, and depending on user input filtering the relevant models from the parent collection into a backbone.subset collection.)
My "parent" collection, holding all tasks:
var TasksAll = Backbone.Collection.extend({
url: '/tasks', // the REST url to retrieve collection data
model: Task // the models of which the collection consists of
});
var allTasks = new TasksAll();
Now i want to create a subset collection for e.g. tasks where task.status = 0:
var TasksTrash = new Backbone.Subset({
superset: allTasks,
filter: function(Task) {
return Task.isTrash();
}
});
var trashTasks = new TasksTrash();
Whereas inside the Task model, the method "isTrash" returns true if:
this.get('status') == 0
a) Are there any more resources on backbone.subset.js?
b) How do I implement above scenario?
c) Can I pass 'superset' and 'filter' options as params to the Backbone.Subset init function?
d) I looked into the backbone.subset.js code, when I 'reset' my parent Collection my subset Collections should be updated straight away, right?
PS: I'm fairly new to Backbone. Thanks for your help.
Looking at the source for backbone-subset, it looks as though there is a pre-initialization hook which you could utilize in order to make the 'sieve' or filter available as an option or argument:
https://github.com/masylum/Backbone.Subset/blob/master/backbone.subset.js#L50
As for providing parent as an argument, there is an outstanding patch to add that exact functionality:
https://github.com/masylum/Backbone.Subset/pull/5
With it, you can pass in parent as an option, if it is not an option the library will fall back to looking for it on the object Prototype