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.
Related
I'm trying to understand the backbone bindAll function in addition to handling its contexts. In this case, the following implementation doesn't work.
Create a view to insert data from a list.
_.bindAll (this, 'render'); Could you see the context of this in the function do? $(this.el) is undefined. Shouldn't it work using _bindAll?
The following does not append in the ol list
$(this.el).append('idCurso->', cursoModelo.get('idCurso'),' titulo-> ', cursoModelo.get('titulo'));
$(this.el).append('hola caracola');`.
HTML
<ol id="ListaLibros"/>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js"></script>
JS
// create a view to print the model data in the html
var ListaLibrosView = Backbone.View.extend({
el: '#ListaLibros',
initialize: function() {
this.collection = cursoCollection;
_.bindAll(this, 'render');
this.render();
},
render: function() {
console.log("funcion render");
var listaLibrosLi = '';
this.collection.each(function(cursoModelo) {
$(this.el).append('idCurso->', cursoModelo.get('idCurso'), ' titulo-> ', cursoModelo.get('titulo'));
$(this.el).append('hola caracola');
});
}
});
You're not using using this.el directly inside render, but inside a _.each() callback, which is another function with different context. You need to make sure it has the same context as well, you can simply achieve it like _.each(function(){}, this) since it accepts the context as second argument.
Or if you're using ES6 you can use an arrow function as callback which does not create a new context like _.each(i => {})
TJ's right, though you're using this.collection.each, it's just a proxy to Underscore's _.each.
Also, this.$el = $(this.el). You should use this.$el directly.
Note that _.bindAll is an Underscore's function and it is unrelated to Backbone. Since the problem comes from the nested each callback context, once fixed, you won't need _.bindAll at all.
In fact, I never needed _.bindAll and when seen in tutorials, most of the time, it's because it's outdated or wrongly used.
I have a knockout component that I add to the page. I am trying to call ko.applyBindings with an instance of the view model that I created. But knockout seems to ignore it and create its own instance.
Code:
ko.components.register("my-component", {viewModel: MyViewModel, template: "....."});
ko.applyBindings(new MyViewModel(this.config), document.getElementsByTagName("my-component")[0]);
I have a console.log in the constructor of MyViewModel and I am seeing that two instances are created: One with the parameters I pass and one without. And knockout seems to be using the one without.
What am I doing wrong?
Components actually have their own view models by design. You can, however, pass them an instance of a viewmodel you've created earlier:
var myViewModel = new MyViewModel(this.config);
ko.components.register("my-component", { viewModel: { instance: myViewModel }});
ko.applyBindings(myViewModel);
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.
I'm trying to target an element using jQuery which is embedded in one of my knockout templates:
<script type="text/html" id="video-file-template">
<div class="video" data-bind="attr: { 'data-index': $index }">
</div>
</script>
Yet, when I attempt to select $('.video') using jQuery, wrapped in a document ready function, I get an object with a length of 0 returned:
$(document).ready(function() {
console.log($('.video')); // Returns an object with a length of 0
});
Why is this? Is it because the element is not part of the DOM when my jQuery script is evaluated? If so, how can I target the element when it is loaded into the DOM via Knockout.js?
It's true that the document is ready before ko.applyBindings finishes, so that's why you're not seeing the element. However, you should not be using jQuery to violate the boundary between your view model and the DOM like that. In knockout, the way to accomplish what you need is with custom bindings.
Basically, you define a new knockout binding (like text, value, foreach, etc) and you have access to an init function, which fires when the element is first rendered, and an update function, which fires when the value you pass to the binding is updated. In your case, you would only need to define init:
ko.bindingHandlers.customVideo = {
init: function (element) {
console.log(element, $(element)); // notice you can use jquery here
}
};
And then you use the binding like this:
<div data-bind="customVideo"></div>
Perhaps it's better to add the "video" class and do other initialization right in the init callback:
ko.bindingHandlers.customVideo = {
init: function (element) {
$(element).addClass('video');
}
};
If this feels a little wonky at first, remember there's a very good reason for the indirection. It keeps your view model separate from the DOM it applies to. So you can change the DOM more freely and you can test the view model more independently. If you waited for ko.applyBindings to finish and called some jQuery stuff after that, you'd have a harder time testing that code. Notice that knockout custom bindings are not "special" in any way, and you can see that the built in bindings are defined exactly the same: https://github.com/knockout/knockout/tree/master/src/binding/defaultBindings
As the previous comments have suggested, it's because your $(document).ready fires before your knockout templates have been rendered.
Whenever I need to do this sort of thing I tend to have an 'init' (or whatever) function on my ko view model that I call after applyBindings has completed;
So:
var ViewModel = function(){
var self=this;
//blah
self.init = function(){
//jquery targeting template elements
}
}
var vm = new ViewModel();
ko.applyBindings(vm);
vm.init();
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