I was trying out Backbonejs read through couple of docs. It is an MVC
framework for client side. Inside the views we compile a template and
render them(attach them to DOM, or do some manipulation). Backbone has
a dependency of underscore js, which is the templating stuff.
With Bbone, when manipulating views el comes into picture. My
understanding of el is that it references the DOM Object. If no
dom object is assigned to it, it assumes an empty div.
var choose_view = new SearchView({ el: $("#choose_me") });
So in the above case, the div with id choose_me will be manipulated,
when the choose_view is invoked. So far so good, but what are the
stuff happening below in chronology, I was unable to get, also is
there any redundancy, read the comments for the queries:
// the div with id search_container will be picked up by el
<div id="search_container"></div>
<script type="text/javascript">
SearchView = Backbone.View.extend({
initialize: function(){
this.render();
},
render: function(){
//1. Compile the template using underscore, QUESTION: **what does this mean**?
var template = _.template( $("#search_template").html(), {} );
//2. Load the compiled HTML into the Backbone "el"
this.el.html( template );
}
});
// QUESTION: **When in 2. we have already mentioned, el, can we not over there
provide the div element?**
var search_view = new SearchView({ el: $("#search_container") });
</script>
<script type="text/template" id="search_template">
<label>Search</label>
<input type="text" id="search_input" />
<input type="button" id="search_button" value="Search" />
</script>
Question 1
Compiling template means that you get a template function that you can pass in a data object as argument of the template function. That way, you can evaluate many times your template function with different data objects while it's compiled only once.
compiledTemplate = _.template( $("#search_template").html());
this.el.html(compiledTemplate(data));
another.el.html(compiledTemplate(otherData));
In the example above you compile your template once, then you use it twice.
In the code you have given, you compile and use your template at the same time
So
_.template( $("#search_template").html()); // Create a template function, the result is a function that you can call with a data object as argument
_.template( $("#search_template").html(), data); // Create a template function and call it directly with the data object provided, the result is a string containing html
Ref: http://underscorejs.org/#template
Question 2
You can provide the div element directly, but el is an helper to retrieve the root element of your view on which all DOM events will be delegated.
As indicated in the backbone doc
If you'd like to create a view that references an element already in
the DOM, pass in the element as an option: new View({el:
existingElement})
Ref: http://backbonejs.org/#View-el
Related
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.
How does the this property function in this Backbone.js code sample?
Since the use strict directive is not specified within the code and no object is passed to any of the functions, does the Backbone.js code default to the global app object or does it do something else?
I'm assuming that this.render() probably renders to the DOM element specified by the el property passed in via the underscore template, but what about this.$el.html?
<script type="text/template" id="search_template">
<label>Search</label>
<input type="text" id="search_input" />
<input type="button" id="search_button" value="Search" />
</script>
<div id="search_container"> </div>
<script type="text/javascript">
var SearchView = Backbone.View.extend({
initialize: function({
this.render();
},
render: function(){
// Compile the template using underscore
var template = _.template( $("#search_template").html(), {} );
// Load the compiled HTML into the Backbone "el"
this.$el.html( template );
}
});
var search_view = new SearchView({ el: $("#search_container") });
</script>
In this code sample this is the pointer to the instance view of the SearchView.
You could create many instances of that view, and each of those would have this directed at itself.
Every view instance has two properties that point to the DOM element of that view instance. .el points to the DOM element, and .$el is a jQuery object that points to that DOM element.
The benefit of .$el is that you can call any jquery methods on that object.
So this.$el.html() means you're calling jQuery.html method on the DOM element of that view instance.
In your code, you've compiled the template of that view and passed it to $el.html(), which inserts the HTML of the template into the DOM element of that view.
As for this.render() in the initialize, it just calls that instance's render method at the moment when that instance gets initialized, that is where you see the new keyword. New instance is created, initialize method is called automatically, which calls this.render().
Instead of calling this.render() in initialize you could, for example, call search_view.render() directly after the last line of your script.
I am starting with Handlebars, and was wondering:
Is there the possibility to pass more than one model to the view?
I am passing my model with $(template(model)) to the view:
var source = $('#template').html();
var template = Handlebars.compile(source);
var model = this.model.toJSON();
$(template(model)).appendTo(this.$parent);
So I can pass one variable with stored JSON data to the view. But what if I want to have two different variables/models in one template?
Is this possible? This would be much easier than generating another template and loading into the other.
A compiled Handlebars template just wants an object as its argument, you can build that object however you want. If you want two models, just add an extra level of indirection:
var html = template({
model: this.model.toJSON(),
other: this.other_model.toJSON()
});
and then inside your template you can say things like:
{{model.attribute}}
{{other.other_attribute}}
and the like.
As an aside, a Backbone view adding HTML to anything other than this.$el (i.e. this.$parent) is a bit dodgy. Events are bound to this.$el so events won't work without help. You'll probably an easier time if you turn that around a bit so that the parent places your view's $el somewhere so that your view can be self contained.
Hi I have been trying to use backbonejs and handlebar templates but seems like either my jSON is wrong or data is not properly parse . Getting
Uncaught Error: You must pass a string to Handlebars.compile. You passed undefined
Code can be found at
jsfiddle
any advice will be appreciated
Your fiddle is broken in various ways but the odds are that you're doing this:
template: Handlebars.compile($('#tpl-page-list-item').html()),
before there is a #tpl-page-list-item available. This will happen if your page looks like:
<script src="your_backbone_javascript.js"></script>
<script id="tpl-page-list-item" ...></script>
as your Backbone view will be parsed before <script id="tpl-page-list-item"> gets added to the DOM. You can wrap your Backbone views in a document-ready handler (with proper namespacing to account for the function wrapper):
$(function() {
window.PageListItemView = Backbone.View.extend({
template: Handlebars.compile($('#tpl-page-list-item').html()),
//...
});
});
or compile the template when instantiating your view:
initialize: function() {
// Or check if it is in the prototype and put the compiled template
// in the prototype if you're using the view multiple times...
this.template = Handlebars.compile($('#tpl-page-list-item').html());
//...
}
I am trying to use Knockout ViewModels as self-contained "widgets" that can be placed into any (or multiple) DOM nodes on the page. I had an approach in Backbone that seemed to work well and I'm trying to convert the concept to Knockout.
In Backbone view I would do something like this, using the RequireJS text plugin to pull the template and inject it into the el:
define(['text!templates/myTemplate.html',], function(templateHTML){
var view = Backbone.View.extend({
initialize:function() {
// yes I know the underscore templating stuff doesn't apply in Knockout
this.template = _.template( templateHTML );
this.render();
},
render:function( ) {
// the $el is provided by external code. See next snippet
this.$el.append(this.template(myData));
return this;
}
// other view behavior here
});
return view;
});
And then some other piece of external code could place that view into an existing DOM node:
new MyBackboneView({el: $('#myExistingDivID')});
In Knockout, the closest I can find to this approach is to have the external code use the Text plugin to pull the template, inject it into the div, and then apply the KO bindings:
var mydiv = $('#myExistingDivID');
mydiv.html(myTemplateHTML);
ko.applyBindings(new MyKOViewModel(), mydiv[0]);
1 - Is there a recommended way in Knockout to have the ViewModel itself inject the external template HTML based on the equivalent of Backbone's "el" concept? The key is that the external (router-ish) code controls where the content will be placed, but the ViewModel controls the actual details of the content and where to get the template.
2 - If yes, should I take this approach, or am I abusing the way that Knockout and MVVM are intended to be used?
You can override the default template source and then use that with the default render engine like
var stringTemplateEngine = new ko.nativeTemplateEngine();
stringTemplateEngine.makeTemplateSource = function (template) {
return new StringTemplateSource(template);
};
ko.setTemplateEngine(stringTemplateEngine);
Quick example I did
http://jsfiddle.net/3CQGT/