I am working with some legacy website which is using the Backbone.js framework for the frontend. I'm new to the frontend and Backbone.js seems very confusing when compared with simple JavaScript.
Simple JavaScript function call will be like
document.getElementById("myBtn").addEventListener("click", myFunction);
function myFunction(){
alert("Hello");
}
<!DOCTYPE html>
<html>
<body>
<button id="myBtn">Click me for alert</button>
</body>
</html>
How to implement the same in Backbone.js?
How to add an event listener and call a simple function in Backbone.js on the click of a button?
The functions and scripting are different and is very confusing. All the functions are packaged into another variable and have a prefix but no name. It's something like this.
define(['app',
'underscore',
'handlebars',
'backbone',
'marionette',
'i18next',
'backbone.syphon',
'jquery-validation'
], function(MyApplication, _, Handlebars, Backbone, Marionette, i18n, Syphon, Validation, compiledTemplate) {
MyApplication.module('MyModule.View', function(View, MyApplication, Backbone, Marionette, $, _) {
View.MyView = Marionette.View.extend({
myFunction: function(){
alert("Hello"); // This is not working
}
});
});
return MyApplication.MyModule.View;
});
<!DOCTYPE html>
<html>
<body>
<button id="myBtn" onclick="myFunction();">Click me for alert</button>
</body>
</html>
Views 101
Let's take this one step at a time. Generally when creating a view, you create a subclass of
Backbone.View(orMarionette.View, which itself is a subclass ofBackbone.View):Now, this only creates a blueprint or class for the type of view that we call
MyView. To actually useMyView, we have to create an instance of it:But at this point, we are still not done. The view is not visible to the user until we insert its element somewhere in the DOM. I tend to refer to this as placing the view. A view always has exactly one HTML element, even if you don't explicitly define it. In that case, it is a
<div></div>by default. The raw element is accessible as its.elproperty and a convenient, jQuery-wrapped version of it is available as.$el. There are many ways you can go about placing the view; discussing all the options is beyond the scope of this answer. As a simple example, the following line will make it the last child element of the<body></body>element:Intermezzo: modules
If your application is modular, defining the blueprint for a view generally happens in a different module than instantiating and placing it. A modern and relatively straightforward pattern for this is using ES modules:
MyView.js
someOtherModule.js
The example code in the question appears to use two module systems on top of each other, something I would generally recommend against. The outer system is AMD, which was commonly used in the browser before ESM became a thing, and is still commonly used in order to emulate ESM. By itself, it looks like this:
MyView.js
someOtherModule.js
The inner system does not look familiar to me, so I cannot comment on how it works. If you can avoid using it, I recommend doing so.
Rendering views
Anyway, back on track. Besides modules, we covered three aspects of Backbone views so far:
While we covered enough to enable the user to see the view, there is nothing to see yet; the view's element is empty by default.
We render a view to give its element internal HTML content. Note how this is roughly the dual operation of placing a view, where we give it an external context. Since the content is an internal affair, I would generally recommend that the view is in charge of rendering itself.
By convention, views have a
templatemethod and arendermethod.templatetakes a data payload (any JavaScript value, usually an object) and returns a string with HTML code.renderserves as a trigger to actually update the contents of the view's element; it prepares a data payload, passes it to thetemplatemethod and sets the return value as the inner HTML ofthis.el.Here's how we might define a blueprint for a view that invites a website visitor to enter their name:
When we instantiate the above view, its element will look like this:
There are a couple of things to note about the above example code:
<fieldset>) "for free", we do not include it in the template.rendermethod ininitializeso that the view sets its internal content immediately when it is created. I generally recommend this, unless you want to postpone rendering until some condition is met, or unless rendering is very expensive. However, you should strive to make rendering cheap and idempotent (i.e., safe to repeat).templateandinitializeabove both have a parameter that is never used:payloadandoptions, respectively. I included them anyway to show that they are there.renderusesthis.templateto generate raw HTML code. It then callsthis.$el.html, which is a jQuery method, to set that HTML code as the inner HTML of the view's element.renderreturnsthis. This makes it possible to chain other view methods after callingrender. This is commonly done with methods in Backbone classes if they don't have some other value to return.Handling events
We have reached the point that we can show a name entry field to a user. Now, it is time to actually do something with the input. Handling user events generally involves three parts in Backbone:
eventshash, which binds user events in the view's element to methods of the view.eventargument, which is a jQuery-wrapped representation of the DOM event. They havethisbound to the view instance. Like all event handlers, their return values are ignored, but they can have their effect by operating on the view instance.Starting with the last part, this is how we create a plain, empty model
user:and here is how we create a view
askNameFormthat is aware of theusermodel:Simply because we pass
useras themodeloption to the view constructor, methods of our view will be able to access it asthis.model. Here is how we might use that in an event handler in our definition ofAskNameView:The model will trigger an event of its own whenever we change its contents. This will enable us to respond elsewhere in the application ("spooky action at a distance"). We will see an example of this next. First, however, let us complete this setup by actually registering the event handler:
This notation means "when an internal element with the selector
inputtriggers a'change'event, call thehandleNamemethod".Responding to a model change
It is time to close the loop. Suppose that after the user has entered their name, we want to show a nice personal welcome message. To do this, we can exploit the fact that multiple views may share the same model. Let us define a simple new view type that does exactly that:
Again, there are a couple of things to note about this view:
tagName, so by default, this view will have a<div>as its outer element.templatemight be generated using a template engine, rather than by hand-writing a function. I have chosen Handlebars for this example, since this is what appeared in the question, but you could use any other templating solution.this.model.has('name')in theinitializemethod, to check whether the model has anameattribute yet.this.listenTo(this.model, ...)in theinitializemethod in order to respond to model events. In this case, I'm updating the view contents whenever thenameattribute changes. I could simply listen for'change'instead of'change:name'in order to re-render on any model change.this.model.toJSON(), which is a safe way to extract all data from the model, in therendermethod in order to supply the payload for the template. Remember that I declared apayloadparameter for thetemplatemethod of theAskNameViewbut didn't use it? Now I did.Putting it all together
In conclusion, here is a snippet that lets you play with all of the above interactively. A few notes in advance:
'change'user event only triggers when you remove focus from the input field, for example by clicking outside of the box. Useinputorkeyupinstead if you want to see immediate effect after typing into the box.import/exportsyntax from above is not repeated here.For more information, please refer to the documentation. I wish you much success on your Backbone journey!