I want to add a class to one of the elements in a Handlebar template while initialising the view.
Here is the where I initialise the LayoutView
export default LayoutView.extend({
className: 'LandingPageHeaderDetail',
template: fs.readFileSync(`${__dirname}/LandingPageHeaderDetail.hbs`, 'utf8'),
initialize: function (options) {
this.setMenu(options)
},
setMenu (options) {
// in here I want to add a className to one of the elements
// in the template file
// for example template = <ul><li id="id1">dkjfls</li><li id="id2">kdjfkls</li>
// if (options == id1) { add class x to element} else { add class to y element }
}
My question is how do I navigate the template tree, find the element I'm looking for and add a class to it.
I've tried using jQuery selectors as follows: $('#id1') but it returns null, probably because the template hasn't rendered yet. Any ideas?
You can use Marionette's serializeData function.
initialize: function(options){
this.myOptions = options;
},
serializeData: function(){
return {id: this.myOptions};
}
Then you can create a helper for Handlebars using the answer from this question: Handlebars.js if block helper ==
Then in your template, put the actual logic to apply the class:
<ul>
<li id="id1" {{#if_eq id "id1"}}class="classname"{{/if_eq}}>dkjfls</li>
<li id="id2" {{#if_eq id "id2"}}class="classname"{{/if_eq}}>kdjfkls</li>
</ul>
As you said, you can't work with the template inside initialize function cause the template is not rendered yet. Use Render event, it's triggered after the view has been rendered.
Marionette.View.extend({
onRender: function(){
// manipulate the `el` here. it's already
// been rendered, and is full of the view's
// HTML, ready to go.
this.setMenu();
},
setMenu: function(){
// now i can manipulate the template...
}
});
Other solution would be to use serializeModel or templateContext.
Related
I am using twitter bootstrap link. When the user clicks the link a bootstrap modal appears.
Now because of some bootstrap technical difficulties in modal rendering i need to seperate the link and put the modal out the navbar div.
So consider i have two separate div
<div id="linkDiv">
</div>
and
<div id="modalDiv">
</div>
Now i have only one View which makes a call to the server to get the collection
app.View.FriendRequestListView = Backbone.View.extend( {
templateModalLink: _.template($('#link').html()),
templateModal: _.template($('#modal').html()),
tagName: 'div',
initialize: function(){
this.friendRequestCollection = new app.Collection.FriendRequestCollection();
this.friendRequestCollection.bind("reset", this.render, this);
this.friendRequestCollection.fetch();
},
render: function() {
$(this.el).html(this.templateModalLink({
friendRequestCollection: this.friendRequestCollection}));
return $(this.el);
},
});
Than i can render only one div like following
var list = new app.View.FriendRequestListView();
$('#linkDiv').html(list.$el);
My question is , Is it possible to render two templates at the same time and add the two templates to different DIV like for example in my case i want to get update
templateModalLink template to linkDiv and templateModal template to modalDiv with the collection I am getting from the server.
You have to instantiate the collection before app.View.FriendRequestListView(s) and pass app.View.FriendRequestListView(s) the collection:
var friendRequests = new app.Collection.FriendRequestCollection();
friendRequests.fetch(
success: function(collection, response, options) {
var list1 = new app.View.FriendRequestListView({collection: collection});
var list2 = new app.View.FriendRequestListView({collection: collection});
$('#linkDiv').html(list1.$el);
$('#modalDiv').html(list2.$el);
}
);
In the Marionette docs for CompositeView, it describes overriding appendHTML to customize the target the HTML of each itemView should be added to using jQuery's append.
Using a similar line of thinking, if I have two ItemView objects, a Parent and a Child, and Parent's template has an empty target <div class="target"></div> I'd like to inject Child's template inside of. I can use jQuery's html with an onRender in Parent to do this.
Throughout the course of the Parent's life, I want to flush out or replace the contents of .target with something else. If I later want to re-render Child into .target (since it may render it's template differently based on user interaction since the first rendering), I can use the same logic:
Render the Child template
Set .target's contents to Child's el with jQuery's html
The issue is, once this second rendering of Child has occurred, any events from the Child view seem to be lost.
Here is a concrete example of what I'm describing:
The HTML
<script type="template" id="parent">
<p><a class="re-render" href="javascript://">Re-Render</a></p>
<div class="target"></div>
</script>
<script type="template" id="child">
<p>The Link</p>
</script>
<div id="main"></div>
The Javascript
var Views = {};
Views.Child = Backbone.Marionette.ItemView.extend({
template: '#child',
events: {
'click a': 'changeBg'
},
changeBg: function() {
this.$el.css('background-color', '#'+Math.floor(Math.random()*16777215).toString(16));
}
});
Views.Parent = Backbone.Marionette.ItemView.extend({
template: '#parent',
childView: undefined,
ui: {
target: '.target'
},
events: {
'click .re-render': 'onRender'
},
initialize: function() {
this.childView = new Views.Child();
},
onRender: function() {
this.childView.render();
this.ui.target.html(this.childView.el);
this.childView.$el.css('background-color', 'transparent');
}
});
var App = new Backbone.Marionette.Application();
App.addRegions({
mainRegion: "#main"
})
App.mainRegion.show(new Views.Parent());
Clicking The Link in Child at first works great. Once Re-Render is clicked in Parent, the Child click event is never re-applied to the newly rendered version of the Child's template. How can I ensure the Child template's events will be re-applied each time the Child is rendered?
You can find a jsFiddle for this here.
You should call .delegateEvents() on the child view after it has been re-rendered:
onRender: function() {
this.childView.render();
this.ui.target.html(this.childView.el);
this.childView.delegateEvents();
this.childView.$el.css('background-color', 'transparent');
}
This is how to solve it in vanilla Backbone. I don't know if Backbone.Marionette has another way of handling the problem, but in Marionette terms you could add the delegateEvents call to the child view's onRender method to encapsulate the re-renderability.
Views.Child = Backbone.Marionette.ItemView.extend({
//...
onRender: function() {
this.delegateEvents();
}
});
So I'm checking out the changes related to the latest backbone/underscore version. Prior I have a project running with BB 0.5.2 and underscore 1.1.7. I'm noticing something strange with regards to defining a template property within a view in the new version, which gives me reservations in going forward with upgrading.
In my current version I would define a view as such:
var MyView = Backbone.View.extend({
template: _.template($('#exampleTemplate').html()),
initialize: function() {...},
render: function() { $(this.el).html(this.template(someObjectParam)); },
});
However, if I attempt to work in the same manner, using a simplified todo clone attempt as an example, I setup an html with an inline script template as such:
<script>
$(document).ready(function() {
app.init();
});
</script>
<script type="text/template" id="itemViewTemplate">
<div class="item">
<input type="checkbox" name="isComplete" value="<%= item.value %>"/>
<span class="description"><%= item.description %></span>
</div>
</script>
In my included JS file I have:
var ItemView = Backbone.View.extend({
el: 'body',
// Below causes error in underscore template, as the jquery object .html() call
// returns null. Commenting will allow script to work as expected.
templateProp: _.template($('#itemViewTemplate').html()),
initialize: function() {
// Same call to retrieve template html, returns expected template content.
console.log($('#itemViewTemplate').html());
// Defining view template property inside here and calling it,
// properly renders.
this.template = _.template($('#itemViewTemplate').html());
this.$el.html(this.template({item: {value: 1, description: 'Something'}}));
},
});
var app = {
init: function() {
console.log('Fire up the app');
var itemView = new ItemView();
}
}
So I'm left confused as to why defining the template property directly causes the call to retrieve the template html to return a null value, thus breaking the attempt to define an underscore template object (mouthful). However, if the definition is done within the initialize function, the call to retrieve the template html properly finds the template so its contents can be passed to the underscore template. Anyone see what I'm potentially missing?
Thanks in advance!
If this:
var ItemView = Backbone.View.extend({
//...
templateProp: _.template($('#itemViewTemplate').html()),
//...
});
is failing because $('#itemViewTemplate').html() is null, then you have a simple timing problem: you're trying to read the content of #itemViewTemplate before it exists. Your old version should suffer from exactly the same problem.
Either make sure everything is loaded in the right order (i.e. your views after your template <script>s) or compile the template in your view's initialize. You can check for the templateProp in your view's prototype and only compile it on first use if you want:
initialize: function() {
if(!this.constructor.prototype.template)
this.constructor.prototype.template = _.template($('#itemViewTemplate').html());
//...
}
Demo: http://jsfiddle.net/ambiguous/HmP8U/
Take a HTML tabbar as example. Usually you have a ul and a list of div's. All the Backbone examples that I have found, link the View with only one node by the 'el', 'tagName', etc...
HTML TabBar:
<div class=".tabbar">
<ul class=".tabbar-header">
<li>Cars</li>
<li>Houses</li>
</ul>
<div id="tab-cars" class=".tabbar-item">...</div>
<div id="tab-houses" class=".tabbar-item">...</div>
</div>
Backbone Code:
window.TabBarView = Backbone.View.extend({
el: ???,
tabs: [],
render:function (eventName) {
// Render all tabs in this.tabs
_.each(this.tabs, function (item, position) {
// Render each tab with item.render()
}, this);
return this;
}
});
window.TabBarItemView = Backbone.View.extend({
el: ???,
initialize:function () {
this.model.bind("change", this.render, this);
this.model.bind("destroy", this.close, this);
},
render:function (eventName) {
// Render the tab header and tab content
return this;
}
});
I wish to add several TabBarItemView's to the TabBarView and each one creates itself the li node inside the ul.tabbar-header and the div.tabbar-item as content.
I've written an article that addresses this issue: http://lostechies.com/derickbailey/2011/10/11/backbone-js-getting-the-model-for-a-clicked-element/
It will show you how you can either use a single view to do what you want, or a parent/child setup with a collection view and item view like you're showing in your sample code
you can go as far as to make a separate navigation view, and have the navigation add an item through the render method of your tab-item-view.
when you render the tab item view, you do something like navigation.add(new nav item);
and also add a way to remove the navigation item.
or you can keep the navigation in pure html and append a <li> item with jquery / javascript when you are rendering a tab below.
can't give you a fully working example though, if you really need it i can probably make one tonight,.
I have model Post and collection Posts. And want to make form with list of all post in <select id="multi" multiple="multiple">. So i have to make a PostView render inside my #multi with just this template:
<option value=""><%= title %></option>
But finally I get it wrapped with div. Is there any solution for not wrapping this template with <div>?
If you don't define an el (or tagName) for the view (in the class or during instantiation) the view will be placed inside a div tag. http://documentcloud.github.com/backbone/#View-el
var PostView = Backbone.View.extend({
tagName: 'option'
});
UPDATE
Starting v0.9.0, Backbone has view.setElement(element) to get this done.
var PostView = Backbone.View.extend({
initialize: function() {
var template = _.template('<option value=""><%= title %></option>');
var html = template({title: 'post'});
this.setElement(html);
}
});
If you don't want to have the view wrap your HTML, you'll have to do a few things:
Replace this.el entirely
Call delegateEvents on the new el
render: function(){
var html = "some foo";
this.el = html;
this.delegateEvents(this.events);
}
Since Backbone generates a div or other tag (based on your tagName setting for the view), you have to replace it entirely. That's easy to do. When you do that, though, you lose your declared events because Backbone uses jQuery's delegate under the hood to wire them up. To re-enable your declared events, call delegateEvents and pass in your events declarations.
The result is that your view.el will be the <option> tag that you want, and nothing more.
In version 0.9.0, Backbone introduced view.setElement(element) to handle this operation.