Where to define the model for the view - javascript

I looked at some Views from Backbone.js, but i don't see at which point it is declared which model is binded to the view ?
For example here where does the view defines which model is this.model ?
https://github.com/addyosmani/todomvc/blob/gh-pages/dependency-examples/backbone_require/js/views/todos.js

When you pass a model property in the options argument to the View's constructor, Backbone automatically sets it as view.model:
var someModel = new Model();
var view = new View({model:someModel});
console.log(view.model === someModel); // -> true
This feature is documented here.
When creating a new View, the options you pass — after being merged into any default options already present on the view — are attached to the view as this.options for future reference. There are several special options that, if passed, will be attached directly to the view: model, collection, el, id, className, tagName and attributes.
In the Todolist example the model is set in app.js, line 75.
addOne: function( todo ) {
var view = new TodoView({ model: todo });
$('#todo-list').append( view.render().el );
},

Related

Using Backbone.Marionette, why can I not reference #ui elements when creating a new instance of a view?

I have created a simple view, MyView, that extends from ItemView. Then when I create the instance of MyView, I am trying to add references to UI elements within the view as well as events that use those UI elements.
HTML
<div id="container"></div>
<script type="text/template" id="my-template">
<p>This is a rendered template.</p>
<button data-ui="changeModelNameButton">Change Model Name</button>
</script>
JS
// Define a custom view that extends off of ItemView
var MyView = Marionette.ItemView.extend({
template: "#my-template"
});
// Instantiate the custom view we defined above
var view = new MyView({
el: "#container",
ui: {
changeModelNameButton: '[data-ui~=changeModelNameButton]'
},
events: {
'click #ui.changeModelNameButton': function() {
alert('here');
}
}
});
// Render the view in the element defined within the custom view instantiation method
view.render();
I am receiving the following error in the console:
Uncaught TypeError: Cannot read property 'changeModelNameButton' of
undefined
I noticed that if I move the ui declarations to the view definition, it works fine, but I would like to know why I can't add those when I create the instance. Is there no way to add them to the instance or am I missing something?
Note: I am using Backbone 1.3.3, Marionette 2.4.4, Underscore 1.8.3, and jQuery 3.1.1
Options overriding view's properties
Not all the options are automatically overriding the view class properties when passed on instantiation.
ui looks like it's not one of them.
Backbone will automatically apply the following (private) viewOptions on a view:
// List of view options to be set as properties.
var viewOptions = [
'model',
'collection',
'el',
'id',
'attributes',
'className',
'tagName',
'events'
];
In the view constructor, this is extended with the chosen options (source):
_.extend(this, _.pick(options, viewOptions));
How to pass the ui option?
You either need to put the ui hash in the view class definition, or to apply the ui hash yourself.
var MyView = Marionette.ItemView.extend({
template: "#my-template",
initialize: function(options) {
// merge the received options with the default `ui` of this view.
this.ui = _.extend({}, this.ui, this.options.ui);
}
});
Note that options passed to a view are available in this.options automatically.
What's the real goal behind this?
If you're going to mess around the ui and the events with its callbacks, it would be best to define a new view.
It looks like a XY problem where the real problem lies in the architecture of the app, but we can't help since you're asking about what's blocking you right now.

Communicating Between Backbone Marionette views

I'm a junior javascript developer, got an internship in a company that uses Backbone and Marionette.
My first task is to create searching, filtering and sorting functionality in a collection based in some inputs, the thing is i got 2 differents views: one itemView renders the input fields(search field, sorting selection,etc ) and a collectionView renders the collection.
I've bee analizing backbone event aggregator, listenTo method, etc to find a way to make the collectionView listen to submit, click events in the itemView so it can render itself accordingly. For example when the user enters "frog" in the search field, the collectionView displays models containing that criteria, if the user clicks the last modified sorting option, the collectionView renders itself that way.
Any suggestion is really welcome.
Thanks in advance.
Basically Marionette does everything for you, you just need to initialize collection view properly.
You can specify which child view events your collection view should listen to (anyway it listens to some default events by default)
Here is example of search functionality and event handling of child views:
HTML
<script id='itemViewTemplate' type = "text/template">
<div class='itemView'><%= title %></div>
</script>
<script id='collectionViewTemplate' type = "text/template">
<div class="collectionView"></div>
</script>
<input value='' id='search' placeholder='search'>
Javascript
// our data to show and filter
var data = [
{title: "title 1"},
{title: "title 2"},
{title: "title 3"}
];
// item model
var FooBar = Backbone.Model.extend({
defaults: {
title: ""
}
});
// collection of items
var FooBarCollection = Backbone.Collection.extend({
model: FooBar
});
// item view
var FooView = Marionette.ItemView.extend({
template: "#itemViewTemplate",
events: {
"click": "_onClick"
},
_onClick: function() {
this.trigger('click', this); // here we trigger event which will be listened to in collection view
}
});
// collection view
var MyCollectionView = Marionette.CollectionView.extend({
childView: FooView,
template: "#collectionViewTemplate",
childEvents: {
'click': '_onItemClick' // listen to any child events (in this case click, but you can specify any other)
},
_onItemClick: function(item) {
$('.message').text("item clicked: " + item.model.get("title"));
console.log(arguments); // event handler
}
});
// init our collection
var myCollection = new FooBarCollection(data);
// another collection which will be filtered
var filterCollection = new FooBarCollection(data);
// init collection view
var myCollectionView = new MyCollectionView({
collection: myCollection
});
// render collection view
$("body").append(myCollectionView.render().$el);
// search
$('#search').change(function() {
var value = $(this).val(); // get search string
var models = filterCollection.where({
title: value
}); // find models by search string
value ? myCollection.reset(models) : myCollection.reset(filterCollection.models);
// reset collection with models that fits search string.
// since marionette collection view listens to all changes of collection
// it will re-render itself
// filter collection holds all of our models, and myCollection holds subset of models, you can think of more efficient way of filtering
});
// just to show click event info
$('body').append("<div class='messageContainer'>Click on item to see its title:<div class='message'></div></div>");
Marionette collection view listens to all myCollection events, for exaple
If you'll write
myCollection.add({title: 'title 4'});
It will automatically render new itemView in collection view.
Same for remove, reset and other defaut Backbone.Collection methods (which trigger events, and marionette listens to them);
Here is jsfiddle: http://jsfiddle.net/hg48uk7s/11/
And here is docs on marionette:
http://marionettejs.com/docs/v2.4.3/marionette.collectionview.html#collectionviews-childevents
I suggest to start reading docs on marionnet from beginnig, because CollectionView inherits a lot from ItemView and ItemView inherits a lot from View and so on, so you could know all the features Collection.View has.
UPDATE
Maybe I misunderstood a question a bit, you need a communication between collectionView and some other view (itemView in this case another view, not the view collectionView uses to render its children, that's what I thought). In this case, here is an updated fiddle:
http://jsfiddle.net/hg48uk7s/17/
You need third entity to handle communication between collectionView and searchView for example. Usually it some controller, which listens to searchView events, then calls some handler, giving control to collectionView, which uses search value to filter itself.

Marionette CompositeView in the select>option context

I'm trying to set up a simple Marionette's CompositeView. Here's what I want in the end:
%select#target-id
%option{:value => "#{#id}"} = #name
%option{:value => "#{#id}"} = #name
etc
In my CompositeView I specify the childViewContainer: select and I need to display both #name (for the readability) and #id (for the related event) in the options of this select. Due to the nature of default div element I can to speficfy tagName as option in my ItemView:
class New.TargetView extends App.Views.ItemView
template: "path/to/template"
tagName: 'option'
And in the template I can pass only the content of to-be-created option element: = #name. This works fine, Marionette creates an option element for each model and populates it with the name of the model. The problem is that I don't know how to pass an attributes as well, since I can't specify an attribute of the element that hasn't been created yet.
I've also tried to set an attributes property on the ItemView like this:
attributes:
value: "#{#id}"
And it technically works: the options are populated with the value="" attribute, but the content is undefined. Please advice.
I'm not sure about part when you use attributes. You should pass hash or function that will return hash as stated in Backbone view.attributes docs.
attributes:
value: "#{#id}"
In old money it works like this. Here is jsfiddle.
attributes: function () {
return {
value: this.model.id
};
}
CompositeView and ItemView are views that require a Marionette collection and model, respectively. When you create a CompositeView you pass a collection of models, each of which will be passed to the corresponding ItemView.
My guess is that it's not a problem with the template but with how you are setting up the data. As far as I know, there is no attributes option for ItemView, you have to either initialize the CompositeView with a properly formed collection or create the new attributes with the serializeData method on the ItemView.
In the first case, you will do something like this:
var SomeCompositeView = Marionette.CompositeView.extend({
template: "your-template-name",
childViewContainer: select
});
var SomeItemView = Marionette.ItemView.extend({
template: "your-other-template",
tagName: 'option'
});
// This collection can be retrieved from a database or anywhere else
// Just make sure that the models have the fields you want
var myCollection = new Backbone.Collection([{id: "someid1", name: "name1"}, {id: "someid2", name: "name2"}]);
var view = new SomeCompositeView({collection: myCollection});
On the second case you'll have something similar but on the ItemView you'll have:
var SomeItemView = Marionette.ItemView.extend({
template: "your-other-template",
tagName: 'option',
serializeData: function () {
return { someAttribute: "someValue" }
}
}
Just remember that for this second approach, the ItemView has to have access to the values you are returning. serializeData is only for reformatting data or performing operations on the data that you cannot perform on the template. The template will only have access to the variables you are returning from serializeData, independently of the original model.

Instantiate model from existing model in Backbone

What is the preferred way, if any, to instantiate a Backbone model from an existing model, assuming that the model to be instantiated is of a derived type of the existing model?
The case I have in mind arises when dealing with nested models. For instance, let's say I am using DeepModel and I define a function on my "parent" model that returns this.get("childModel"). Now the child model is likely of type Backbone.Model but I would like it to be of type ChildModel which extends Backbone.Model. I have been doing this by literally copying over the interesting attributes one at a time. Surely there must be a better way...
You can create new instance of the same model by using Backbone.Model#clone() method or just using new model.constructor().
var ChildModel = Backbone.Model.extend({
...
});
var child = new ChildModel({ key: "value" });
var new_child = child.clone();
If we see the source of clone method:
clone: function() {
return new this.constructor(this.attributes);
},
we can use the same approach to create new instance but with our data
var new_child = new child.constructor({ new_key: "new_value" });

Backbone.js bug?

I have the following in test.html:
<script>
var Foo = Backbone.Model.extend({
initialize: function(options) {
console.log('hello!');
}
});
var Bar = Backbone.Collection.extend({
model: Foo
});
var m = new Bar({});
</script>
As it turns out, when the variable m is initialized, the initialize function of Foo is called. Thus, in the Firebug console, I get 'hello!'. When I comment out the line:
model: Foo,
There is no 'hello!' in the console output. Thus, declaring a model for a collection calls that model's initialize function. I think this behavior is a bit silly. I haven't read through backbones code, but is there a reason for this?
Well, there's nothing wrong with the behaviour of the code.
When you pass the model in the collection definition, you specify that every model in that collection will be of type Foo.
When you initialize the collection new Bar({}), you pass a model to the collection (although, as #alexanderb stated, I think that the collections expects an array as a first argument) and it initializes it, thus outputting 'hello!'.
For example, if you do not pass any models to the collection constructor :
new Bar();// no console output
there will be no console output, and on the other hand, if you would pass an array of objects, then the collection would initialize all of the provided models :
new Bar([{},[},{}]);// will output 'hello!' 3 times
I believe the constuctor of collection is expecting the array of models. So, what you should do is:
var collection = new Bar( [ {} ] );
There, the initialize method of model should be called.
After a bit of investigation here is what i found out, the Backbone.Collection function is as follows:
var Collection = Backbone.Collection = function(models, options) {
options || (options = {});
if (options.model) this.model = options.model;
if (options.comparator !== void 0) this.comparator = options.comparator;
this._reset();
this.initialize.apply(this, arguments);
if (models) this.reset(models, {silent: true, parse: options.parse});
};
So if you create an initialize method for your Collection like this
initialize: function() {
console.log('hello collection!');
}
You will notice that the hello collection is logged before the hello from model. So the model initialisation must come from the reset function after the initialize-call. rest won't be called unless you have models passed onto your collection, which you at a quick glance don't seem to be doing, but actually in
var m = new Bar({});
Backbone interprets the {} as a model and thus initializes it in the reset-function. But {} isn't a model you say? Well, Backbone isn't too picky about that, it just needs an array of hashes that could or could not contain model attributes. The reset-function eventually leads to the add-function and finally all roads go to Rome, or should i say the _prepareModel-function
_prepareModel: function(attrs, options) {
if (attrs instanceof Model) {
if (!attrs.collection) attrs.collection = this;
return attrs;
}
options || (options = {});
options.collection = this;
var model = new this.model(attrs, options);
if (!model._validate(model.attributes, options)) return false;
return model;
}
What happens here is that the Collection checks whether or not it has been passed a model or a hash of attributes and in the hash-of-attributes case it just creates a new model based on its defined model and passes that hash along.
Hope this not only solves the problem, but sheds some additional light on what happened there. And of course I warmly promote for everyone to read up on the backbone source code, the baddest OG of documentation.

Categories