I have a handlebars template like
<select accesskey="z" class="timezone">
{{#each timezones}}
<option value="{{#key}}">{{this}}</option>
{{/each}}
</select>
and the corresponding portion of Marionette.ItemView like :
var TimeView = Backbone.Marionette.ItemView.extend({
template : TimeTemplate ,
className : 'time-selector-extra',
onRender: function(){
var context = this.model.toJSON();
context.isnew = this.model.isNew();
context.timezones = JSON.parse(Timezones.timezonesList);
}
.
.
});
What I need now is to populate the option fields in my template during onRender as soon as I get the value of all the timezones list.
you could use the serializeData View method to populate your template with data coming from other sources than the current Model.
var TimeView = Backbone.Marionette.ItemView.extend({
template : TimeTemplate ,
className : 'time-selector-extra',
serializeData: function(){
var context = this.model.toJSON();
context.isnew = this.model.isNew();
context.timezones = JSON.parse(Timezones.timezonesList);
return context;
}
.
.
});
As this method overrides the default serialization (that just return this.model.toJSON()), make sure you do return an object to populate the template as wished.
If Timezones data aren't available at the time the view is rendered, you could listenTo an event on the Timezones object to re-render the view when data become available.
var TimeView = Backbone.Marionette.ItemView.extend({
initialize: function(){
this.listenTo(Timezones, 'change', this.render)
},
...
})
(I assume here that Timezones is an instance of Backbone.Model, would fire a change event)
Related
I have backbonejs form inside the lightbox with html <select> as child view.
For the select <option> data I am loading from server and I have separate model and collection for this select
<select name="organization" id="organization" class="main__form--select main__form--select--js">
<option value="no">Organizations not found, Please add one</option>
</select>
Model for option (optionModel)
return Backbone.Model.extend({
defaults : {
"name" : 'KFC',
"email" : 'info#kfc.com',
"image" : '/kfc.jpg',
"descrption" : 'Lorem Ipsum'
}
});
This is view for the model
return Backbone.View.extend({
model : optionModel,
template : _.template(template),
render : function () {
this.$el.html(this.template(this.model.attributes));
return this;
}
});
This is options collection
return Backbone.Collection.extend({
model : optionModel,
getQuery : function(){
//all my query codes
}
});
Options collections view render() code
this.collection.each(function (optionModel) {
// inserting each options view to an array
_this._optionsViewArray.push(
new OptionView({
model: optionModel
}).render().el
);
});
//inserting array to collection view container
_this.$el.html(_this._optionsViewArray);
return this;
My Parent view (form view) i create after render function with underscore _.wrap and inside that function
//<select>
var _selector = this.$el.find('#organization');
optionsView = new OptionsCollectionView({
collection : optionsCollection,
$el: _selector
});
optionsCollection.getQuery();
optionsView.render();
But Form is loading perfectly and Options collection querying successfully but nothing changes on <select> html, It's not updating.
Assuming that getQuery() does an asynchronous query (either using jQuery or Collection.fetch()), the problem (or at least one problem) is that you're calling optionsView.render() to soon, before the query results have returned.
You could throw in an arbitrary 5 second timeout to verify that this is the problem like this: setTimeout(function() { optionsView.render(); }, 5 * 1000);, but the correct way to do it is to call render from a jQuery done handler function ($.ajax({done: function() { ... }})) or (better) a Backbone sync event handler (this will only get called if you did the query via a Backbone collection fetch()): optionsCollection.on('sync', function() { ... });
You example is incomplete. However, given that fetches are generally asynchronous in Backbone and jQuery, this is the most obvious issue is that optionsView.render will run before optionsCollection has had a chance to fully load the collection. You can remedy this by listening to the collection update event:
optionsView = new OptionsCollectionView({
collection : optionsCollection,
$el: _selector
});
optionsView.listenTo(optionsCollection, 'update', optionsView.render);
optionsCollection.getQuery();
You could listen to the sync event instead, but listening to update means that local changes to the collection will also trigger updating your view.
There is also an issue with your render function, as you are passing an array of html elements into the jQuery html function.
render: function() {
var $el = this.$el;
$el.empty();
this.collection.each(function (optionModel) {
var view = new OptionView({ model: optionModel });
$el.append(view.render().$el);
});
return this;
}
However you may be better off using a standard collection view pattern, or something like Backbone.CollectionView - http://rotundasoftware.github.io/backbone.collectionView/
I can create a simple model like so:
define(["models/base/model"], function(Model) {
"use strict";
var IssueModel = Model.extend({
defaults:{
lastName: "Bob",
firstName: "Doe"
}
});
return IssueModel;
});
And then from my controller I can do this:
this.model = new IssueModel();
And then when I create my view I can pass it my model like so:
this.view = new IssueView({model: this.model});
Finally, in my template I can successfully get properties on the model by doing this:
Hi {{firstName}} {{lastName}}
But when I define a collection using IssueModel and I try to pass the collection to my view (and not the model like I showed previously) I can't figure out how to reference the models in my Handlebars template:
var self = this;
this.collection = new IssueCollection();
this.collection.fetch({
success: function(collection) {
self.view = new IssueView({collection: collection});
console.log(self.collection);
},
error: function(collection, error) {
// The collection could not be retrieved.
}
});
I know fetch properly retrieves 5 models from my Parse.com backend because this is what I get on the console:
My question is this. I know Chaplin.js uses getTemplateData, but when I pass a model I don't have to do anything special in order to reference the properties in my view. How would I reference, specifically iterate, over the collection I passed to my view in my Handlebars template?
{{#each [Model in the collection I passed to the view]}}
{{title}}
{{/each}}
Chaplin will render a collection using a CollectionView, it's basicly an extention of a normal view that listens for changes in your collection and adds/removes subviews accordingly.
this.view = new IssueCollectionView({collection: this.collection});
Also there is no need to wait for success call when using a collection view since it will automaticly render every child item when data is added.
The basic situation is this:
I have a Composite View and an Item View. I construct the Composite view passing it a model and a collection. The model data is used to populate the template for the Composite view. The collection data is used to populate the Item View for the Composite View.
What I want to do is this: in a template helper for the Item view I want to access the model data for the Composite View. I've got as far as accessing the view instance of the Item View. I thought that might give me a handle on the Composite View, from where I could get to its model, but it doesn't.
Is there a way I can do this - access the composite view instance from one of its item view instances?
Thanks
--Justin Wyllie
If you want to access data from the parent CompositeView you can do a number of different things.
Either pass that data directly to the ItemView through the itemViewOptions helper function on the CompositeView. Note: This option has changed to childViewOptions in Marionette 2.
Invoke a method directly on all of the children view from the CompositeView and pass whatever you want into that method.
Trigger an event on or listened for by the ItemView.
None of these options are directly accessing the parent view from the child but should do what you want. Below is code for how to use each of these approaches to pass the CompositeView's model to the children view.
// Pass model into ItemView on init
var MyItemView = Backbone.Marionette.ItemView.extend({
initialize : function (options) {
this.parentsModel = options.parentsModel;
}
});
var MyCompView = Backbone.Marionette.CompositeView.extend({
itemViewOptions : function () { return { parentsModel: this.model }; }
itemView : MyItemView
});
// Invoke function on ItemView, passing data in
var MyItemView = Backbone.Marionette.ItemView.extend({
doSomethingWithParent : function (parentModel) {
// do cool thing with parentModel
}
});
var MyCompView = Backbone.Marionette.CompositeView.extend({
itemView : MyItemView,
onRender : function () {
this.children.call("doSomethingWithParent", this.model);
}
});
// Trigger event that ItemView knows about
var MyItemView = Backbone.Marionette.ItemView.extend({
initialize : function () {
this.listenTo(this, "special:event", function (parentModel) {
// Do cool things
});
}
});
var MyCompView = Backbone.Marionette.CompositeView.extend({
itemView : MyItemView,
onRender : function () {
this.children.each(_.bind(function (childView) {
childView.trigger("special:event", this.model);
}, this));
}
});
I didn't answer the question. But changing the approach works. Instead of trying to access the 'parent' Composite View from the Item View I access the Item View from the Composite View:
https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.collectionview.md#onbeforeitemadded-callback
I can modify the model of the current item view (based on a value in the model of the Composite View).
Thought I'd share how Andrew Hubbs suggestion helped me. I was trying to display a parent model property inline with my item template. I used Marionette's templateHelpers property to do this in combination with one of Andrew's suggestions.
I tried to keep the example brief:
Example Composite template - myView Template:
<h1>Page {{name}}</h1>
<h6>Children</h6>
<ul></ul>
Example item template - myItemTemplate:
{{name}} is child of: {{getParentName}}
Views:
App.module( 'App.View', function( View ){
View.MyItemView = Marionette.ItemView.extend({
initialize: function( options ) {
this.parentModel = options.parentModel;
},
template: myItemTemplate,
tagName: 'li',
templateHelpers: function() {
var view = this;
return {
// Called by item template, returns parent model 'name' property.
getParentName: function() {
return view.parentModel.get('name');
}
};
}
});
View.MyView = Marionette.CompositeView.extend({
template: myViewTemplate,
itemView: View.MyItemView,
itemViewContainer: 'ul',
itemViewOptions: function() {
return { parentModel: this.model };
}
});
});
An example of how this would be implemented:
// example of how implementation
// parent model has an attribute called 'children', which is a collection of models
var children = parent.get('children');
var view = new App.View.MyView( { model: parent, collection: children } );
App.mainRegion.show( view );
Background:
I am making changes to an application that uses backbone.js with Handlebars as the templating engine. After a change event fires I need to create html that is appended to the current DOM structure which is basically just a spit-out of information that is contained in the model. This change needed to fit in the already established application structure.
Issue:
I have created a new view that uses a Handlebars template and the model to create the html. I then instantiate that view and call the render function and append the output using JQuery. What I am noticing is that when the html is rendered the model that is passed in because attributes on the $el instead of filling in the template (like I think it should).
View I'm altering:
$.hart.TestView = Backbone.View.extend({
tagName: "li",
template: Handlebars.compile($('#templateOne').html()),
initialize: function () {
this.model.on('change', function () {
this.createMoreInfoHtml();
}, this);
},
selectSomething: function () {
this.$el.removeClass('policies');
this.createMoreInfoHtml(); //function created for new view stuff
},
createMoreInfoHtml: function () {
var id = this.$el.attr('data-id', this.model.get("ID"));
$('.info').each(function () {
if ($(this).parent().attr('data-id') == id
$(this).remove();
});
var view = new $.hart.NewView(this.model, Handlebars.compile($("#NewTemplate").html()));
$('h1', this.$el).after(view.render().el);
},
render: function () {
... //render logic
}
});
View I Created:
$.hart.NewView = Backbone.View.extend({
initialize: function (model, template) {
this.model = model;
this.template = template;
},
render: function () {
this.$el.html(this.template({ info: this.model }));
this.$el.addClass('.info');
return this;
}
});
Json the is the model:
{
"PetName":"Asdfasdf",
"DateOfBirth":"3/11/2011 12:00:00 AM",
"IsSpayNeutered":false,
"Sex":"F",
"SpeciesID":2,
"ID":"ac8a42d2-7fa7-e211-8ef8-000c2964b571"
}
The template
<script id="NewTemplate" type="text/html">
<span>Pet Name: </span>
<span>{{this.PetName}}</span>
</script>
So now to the question: What am I doing wrong? Why are the properties of the model being created as attributes on the $el instead of filling in the template? Can someone please direct me as to how to get the results I am looking for?
Let's skip the problem Jack noticed.
The way you're creating your view is just wrong. It may work as you get the expected arguments in the initialize function, but it has unexpected behaviors you don't see. See the View's constructor:
var View = Backbone.View = function(options) {
this.cid = _.uniqueId('view');
this._configure(options || {});
Now let's have a look at this _configure method:
_configure: function(options) {
if (this.options) options = _.extend({}, _.result(this, 'options'), options);
_.extend(this, _.pick(options, viewOptions));
And of course...
var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
Ok here we are... Basically when passing the model as the options argument, you're passing an object with an attributes key (the attributes of your model). But this attributes key is also used in the View to bind attributes to its element! Therefore the behavior your noticed.
Now, other wrong thing. You're compiling your template each time you create a new function, but not using it as a singleton either. Put your template in the view:
$.hart.NewView = Backbone.View.extend({
template: Handlebars.compile($("#NewTemplate").html(),
And change your view's creation to make the whole thing work:
new $.hart.NewView({model: this.model});
Oh, and get rid of this useless initialize method. You're just doing things Backbone already does.
I would like to attach a new model to a view and have the view re-render. I'm able to render the view in the first place, but I'm having trouble changing the data in that view to a new model.
my_model_1 = Backbone.Model.extend({});
my_model_2 = Backbone.Model.extend({});
my_view = Backbone.View.extend({
initialize : function() {
_.bindAll(this, 'render');
this.model.bind('change', this.render);
},
render : function(){
}
});
var view_instance = new my_view({ model: my_model_1 });
//Template gets rendered
try{
view_instance.changeModel(my_model_2);
}catch(e){console.log(e)};
try{
view_instance.set(my_model_2);
}catch(e){console.log(e)};
try{
view_instance.fetch(my_model_2);
}catch(e){console.log(e)};
try{
view_instance.model = my_model_2;
}catch(e){console.log(e)};
//Template should get updated with data from model 2
Any advice?
The change event if fired when the data in your model has changed, in your case your view isn't "rerenering" because the data of it's model hasn't changed rather you just switched models. What you can do is manually trigger the change event after switching your view's model or call it's render method
For example
view_instance.model = my_model_2;
my_model_2.change();