Using backbone.js:
I have a collection of ModelA that contains several attributes and also one "node" that holds several ModelB. ModelB also contains several attributes.
CollectionA
ModelA
Attribute 1
.
.
Attribute n
"Node"
ModelB
Attribute 1
Attribute 2
ModelB
Attribute 1
Attribute 2
ModelA
Attribute 1
.
.
Attribute n
"Node"
ModelB
Attribute 1
Attribute 2
ModelB
Attribute 1
Attribute 2
I'm still working on the views but the main idea is that the views should be structured in a similar way. (Collection View holds several View A that holds several View B). The same ModelB will never be shared between multiple ViewB.
Q1: Does the design make sense or is it any obvious flaws I should consider? (This is my first try with BackboneJs).
Q2: What is the best way to set up the "Node"? Right now I'm using an array of ModelBs. Is it possible to have a collection of ModelB on each ModelA instead? Which approach is better?
Any guidelines are appriciated!
I would also like to point out that I've read this excellent post on a related issue (recommended). That case did however not contain ModelB in a array/collection in the same way as this one does.
Q1: Does the design make sense or is it any obvious flaws I should consider?
Design Assumptions: Collection A has many Model As, Model A has Collection of Model Bs, each Model B has it's own View B.
Q2: Possible to have a collection of ModelBs on each ModelA?
Answer: Doable.
It's fairly common for models to have a bunch of related sub-models "in" them. You can definitely go about it by creating (like you mention) an array, an object, a collection to hold those Model Bs (let's call it the container.) The basic way would be just to create the container and then instantiate new models and plug them in. Most likely you want some kind of reference in those Model Bs to the related parent Model A so that you can return to that state later. Like each Model B has a ModelA_ID as an attribute.
This might be a good use of Paul Uithol's Backbone-Relational.
With Backbone-Relational, you can setup your Model (class) and define it in such a way that it has a collection of Model Bs. Here is what the code looks like.
ModelA = Backbone.RelationalModel.extend({
relations: [{
type: Backbone.HasMany,
key: 'modelBs',
relatedModel: 'ModelB',
collectionType: 'ModelBCollection',
reverseRelation: {
key: 'parentA'
}
}],
defaults: ...,
initialize: ...,
etc. ... // Other instance properties and functions of model.
});
When you do this, every model A that you instantiate will have a collection of Model Bs that can be referenced through the ModelA attribute modelBs. Or any key you designate.
Here is what it looks like to instantiate the ModelA.
myModelA = new ModelA({
// Setting up ModelA attributes...
'modelBs': [1, 3, 7]
});
I've instantiated the ModelA and along with setting the other attributes you may have defined, I'm also setting up the the value for attribute modelBs of ModelA. This is an array of ids of each ModelB that is related to ModelA. This is how your collection knows which ModelBs are associated to this ModelA.
myModelA.fetchRelated('modelBs');
Your .fetchRelated() call sends a request to the server to populate the modelBs collection with ModelB 1, 3, and 7.
When you want to access the collection of ModelBs in ModelA, you would simply do something like this.
myModelA.get('modelBs');
This returns the collection of ModelBs related to that ModelA assuming you've fetched the related models and the collection has ModelBs in it.
The nice thing about Backbone-Relational is that it makes it easy to establish these defined relations automatically, on instantiation of the parent model. It also creates the reverse relation so traversing up and down from parent to child, child to parent is easy.
If I were manipulating ModelB and wanted to get to ModelA, I use the reverseRelation key I established parentA.
someModelB.get('parentA'); // Points to the related ModelA
It also helps with bindings and some other problems that prop up when you have models in models in models. Relational works well with one to one, one to many and inverse relationships. There is a workaround with many to many but I don't think it's common because it's a difficult proposition with the way things are set up.
As for views, I'm not sure exactly what you're thinking but do you need to have a "collection" of views?
Let's say you have ModelA with a collection of ModelBs. Each ModelB has an associated view. This is illustrated very nicely with the concept of the old-fashioned LIST.
<div id="ModelAView">
<ul>
<li>ModelB_1</li>
<li>ModelB_3</li>
<li>ModelB_7</li>
</ul>
</div>
While there are a bunch of ModelBs, you already have them nice and tidy in a collection. If you go with a data driven way of managing your views, there is no need to put the view objects in another list. Instead, you can make them self managing!
// Assume when you instantiate ViewB, you pass to it a ModelB.
ViewB = Backbone.View.extend({
initialize: function() {
this.model.bind('remove', this.remove, this);
this.model.bind('update:colorAttr', this.updateColor, this);
},
updateColor: function() {
this.$el.css('color', this.model.colorAttr);
}
});
Now say you get rid of ModelB from your collection.
myModelA.get('modelBs').remove(someModelB);
Then the view associated with this modelB will automatically be removed from the view.
Not sure if this is what you had in mind. I usually go with this approach. Anyway, if there are more specifics just post em in the comments. Hope this helps.
Related
The Backbone docs says this -
Collections are ordered sets of models.
But what exactly is an ordered set? I know it's an object but what I don't understand the data structure because it has keys like an object-
Object.keys(exampleCollection
//returns ["length", "models", "_byId", "currentSort", "fetched", "_listenerId", "_events", "comparator"]
but you can call array functions on it like map-
exampleCollection.map(function (mod) {console.log(mod)})
//displays 3 models
The underlying data structure is an array. Most of the Backbone.Collection methods operate on this.models which is an array. The great thing about Backbone is that it's easy to open it up and take a look at what's under the hood -- especially with their annotated source.
In my Backbone application I have a Model. Is there a way of getting all instances of this Model in the app if not all of the instances belong to same Collection? Some of the instances might not belong to any Collection at all.
WHY do I need to do this?
I have a namespaced models (let's say Order.Models.Entry). This is the model of the instances I am talking about. There are some collections like Order.Models.Entries that contain instances of Order.Models.Entry type. But instances get to this collection only when they are selected (with a certain attribute). Before being selected the models are just… well… models.
UPDATE
Obviously I could load every model I create to some allModels collection as it has been suggested in the comments. But the question is not really about a workaround but about exact lookup. The reason why I would like to avoid adding models to an uber-collection is that most of the models will be added to different collections anyway and I just wanted to avoid creating dummy collection for just tracking purpose.
No. There are a couple of good design reasons why you shouldn't be doing it. (encapsulation, not polluting the global scope, etc...)
If you still wish to have this solution you could just maintain a Gloabl list and have your Backbone.Model insert itself into this list, here is a quick solution:
var allMyModels = [] ;
var MyModel = Backbone.Model.extend({
initialize:function(){
allMyModels.push(this);
// Some more code here
},
// also if you wish you need to remove self on close delete function
});
var x = new MyModel();
var y = new MyModel();
in the end of this code, allMyModels will have both models
Why don't you create a Collection (we'll call it allModels) and each time a model is created, do: allModels.add(model). That way, you can access the models, and if you destroy one it will remove itself from the collection.
I've posted my code here: http://jsfiddle.net/HYDU6/6/
It's a pretty stripped-down version of what I'm actually working with, but captures the essence of my problem. My view model is like so:
var viewModel = {
objects: {
foo: [
{ text: "Foo's initial" },
],
bar: [
{ text: "Bar's initial" },
]
}
}
I'm using the ko.mapping plugin and my create handler for objects instantiates Obj from objects.foo and then objects.bar, returning the resulting two items in an array. This part works fine; I use
var view = {};
ko.mapping.fromJS(viewModel, mapping, view);
My issue is updating based on new data. (i.e., getting data from the server). I have an object of new data and I attempt
ko.mapping.fromJS(new_model, mapping, view);
I suspect this is incorrect but I have not been able to get it working despite extensive searching. (Trust me, it's been days. ): Anyway, thanks for any help.
EDIT: So I've mostly figured it out - I was depending too heavily on mapping.fromJS and certain things were not being wrapped into observables. I also realized that I didn't need the create(), only the update(), as it is called after create() anyway. If you have a similar problem let me know!
John,
When updating your data using ko.mapping be sure you don't create a new item. Your UI is already bound to the existing items, so you just want to update the values of the existing item properties; not create new ones. For the example you posted, you'll want to adjust your "update" method of your map to insert the new values into the correct ko.observable property, rather than creating a new object in it's place. The ko.mapping "update" method has a few different parameter lists depending on usage, with the third parameter being the target object of the map. You would want to update that object's properties.
obj.target[label].items[0].text(obj.data[label][0].text);
But, that's a bit of a mess. You'll probably want to create a second level of mappings (create / update) to handle "deep" object hierarchies like in your fiddle. For example one map for objects at the "foo/bar" level, and another call to ko.fromJS from within "update" with another map for the child Obj() objects.
After fixing that, you'll run into a couple simple binding errors that you can fix using another "with" binding, or a "foreach" binding for the child arrays.
Overall, you've just run into a couple common pitfalls, but nothing too severe. You can learn a bit more about a few of these pitfalls on my blog here : http://ryanrahlf.com/getting-started-with-knockout-js-3-things-to-know-on-day-one/
I hope this helps!
Goal: I am trying to create a case insensitive search that goes through my collection and tries to match the user query against model's name attribute. Right now if I want to find a specific model, the search query must be exact.
It seems there is no easy way to do something so simple in Backbone, not out of the box. The function map came to mind. What if I could go through the entire collection and change model's name attribute to lower case, then change user query to lower case as well and voila!
But the problem is I have no idea how to use Backbone Collection and map function. There is no documentation on map in Backbone docs, other than a link that leads you to underscore documentation with a super primitive example code using an array of three numbers.
This does not work...why?
this.collection.map(function(model) {
return model.get('name').toLowerCase();
});
Actually, all of underscore's collection methods are proxied on Backbone.Collection objects. when you do a collection.map(... it returns an array of objects returned by the mapped function. The solution presented by raina77ow does not work since a Backbone.Collection is not an array and assigning the result of the map to this.collection will destroy the collection itself.
If you want to filter a collection, I would recommend using the filter method. ( I assume you are working from a Backbone.View:
var filter = this.$('#search-field').val(),
filteredModels = this.collection.filter( function( model ) {
return model.get('name').toLowerCase() === filter;
};
this.collection.reset( filteredModels );
Note that any of underscore's proxied methods on collections will return an array of models. If you then want to use them, you can reset the collection with these models or, equivalently, set the collection's models attribute to the filtered results: this.collection.models = filteredModels. The first form has the advantage of triggering a reset event on the collection to which you can listen to and, for example re-render your view.
So I'm creating reusable UI components that can be easy instantiated later, eg:
this.emailInput = new TextInput() {
label : 'Email Address',
validators : [validatorFunc, secondValidatorFunc],
placeholder : 'email#domain.com'
});
Would it make sense for this View to create its own Model that would store a value on keydown and handle the validation? Would it still make sense if there were no validation?
And then more complicated, with an autocomplete:
this.countryInput = new AutocompleteInput() {
label : 'Country',
placeholder : 'eg. United States',
suggestions [
{ id : '1', value : 'USA', text : 'United States' },
{ id : 2, value : 'DE', text : 'Germany' },
...
]
});
Would it make sense for those suggestions to be stored in a Model of their own that is created by the View as well? Should I also store the selected suggestion in that Model?
It seems like without using a Model I've been replicating a lot of the logic the Model gives you automatically.
I've also been thinking about having each suggestion be it's own View that creates its own Model, and have the suggestionListView know about the Collection. Is that how Collection/ListViews work? The ListView knows the Collection and the ItemView knows a Model?
The validators part of your TextInput view are definitely more appropriate for a model. Models in Backbone self-validate, so that buys you a lot. In your keydown scenario, the model would have a persistent value field that stores the current value of the input. You don't have to explicitly validate against any new values on keydown, you can just directly try to set the model:
If the model has a validate method, it will be validated before the
attributes are set, no changes will occur if the validation fails, and
set will return false.
— Backbone docs
So just make sure to define your validate method appropriately.
Would it still make sense if there were no validation?
This is an interesting question, since your model/view situation isn't really typical. What I mean is, it looks like you're creating form fields for the purpose of creating form fields. If these fields are tied to actual models in your application (i.e., things that are stored in your persistence layer), then those models should be your Backbone models. Otherwise I'm assuming that you actually want a model that means "this is an input field." But I would say no, probably it wouldn't make much sense if you didn't care about validation—because in that case you're just responding to user events and doing whatever rendering you want, careless about what the actual data is.
To your second question: I don't think it buys you much to put the suggestions into their own model. You're not going to be setting them individually, you're just retrieving them based on the value of another model.
To your last point: that is certainly how collections work, but again I don't think it would buy you much. However, just to illustrate the point, it would look something like this: suggestions would be a collection of Suggestion models; collectionView would be the view that shows the models of suggestions, which would each be a suggestionView that renders a single suggestion. Again, this would be total overkill in this situation IMO. The only reason to do it would be if you actually care about each individual suggestion, i.e., if you want to respond to events on it, or for some reason save its data. I don't think you do, though.