How to load views and templates dynamically using backbonejs and requirejs - javascript

I am creating a form builder application using backbonejs and would like to know how load view dynamically
I have a dropdown where i can choose what type of element should be added eg i choose input field. i have some default values that go with every element they are in formstemplate and based on what field is chosen i want to load different html templates.
define([
'jquery',
'underscore',
'backbone',
'modal',
// Pull in the Collection module from above
'collections/builder',
'text!templates/forms/builder.html',
'text!templates/forms/formsTemplate.html',
'text!templates/forms/textBox.html',
'models/builder'
], function ($, _, Backbone, popupModal, attributesCollection, builderTemplate, formsTemplate, inputTemplate, attributesModel) {
var attributesBuilderView = Backbone.View.extend({
el: $("#page"),
initialize: function () {
},
render: function () {
this.loadTemplate();
},
loadTemplate: function () {
var that = this;
this.el.html(builderTemplate);
},
// Listen to events on the current el
events: {
"change #attributeType": "loadAttributeTemplate"
},
loadAttributeTemplate: function () {
if ( ($("#attributeType").val()) != '0') {
$("#attributesArea").append(_.template(formsTemplate, { elementType: $("#attributeType :selected").text(), _: _ }));
var template = $("#attributeType").val() + 'Template';
$(".formElement").html(_.template(template));
}
}
});
return new attributesBuilderView;
});
here when i run this code i get inputTemplate text in the html rather than template if i put $(".formElement").html(_.template(inputTemplate)); it works fine. I just need to know how to load these dynamically
Thanks in advance

You can place the require call anywhere if you only want to do conditional loading:
edited (added the function params to the require statement)
loadAttributeTemplate: function () {
if ( ($("#attributeType").val()) != '0') {
require(['text!templates/forms/builder.html',
'text!templates/forms/formsTemplate.html',
'text!templates/forms/textBox.html'],
_.bind(function(builder, formsTemplate, textBox) {
$("#attributesArea").append(_.template(formsTemplate, { elementType: $("#attributeType :selected").text(), _: _ }));
var template = $("#attributeType").val() + 'Template';
$(".formElement").html(_.template(template));
},this);
);
}
}
Note, I also did a _.bind(...,this) to maintain the execution scope. I know that's not necessarily needed here, but it does come in handy.
I do this in a few places in my application; especially when I want to load modules only if and when I need them.

Related

RequireJS modules are undefined in Backbone view

I have been pretty much beginner at this part of javascript and I would appreciate any ideas how could be solved this problem.
I use requirejs to define my own modules where I also use backbone.js.
Let say I have the main module where I initialize my Backbone view which is rendered without any problem. Also, the click event where is calling method createSchemeForm creates the form correctly. The problem raises up in a situation when I call cancel method by click and the modules which are defined for Backbone view (e.g. "unicorn/sla/dom/helper"...) are undefined but when I called method createSchemeForm at the beginning the modules were executed without any problem.
Thank you in advance for any suggestions.
Backbone view
define("unicorn/sla/view/scheme", [
"unicorn/sla/dom/helper",
"unicorn/soy/utils",
"unicorn/sla/utils"
], function (DOMHelper, soyUtils, jsUtils) {
return Backbone.View.extend({
el: 'body',
inputData: {},
btnSaveScheme: 'btn-save-sla-scheme',
btnCancel: 'btn-cancel-sla-scheme',
btnCreate: 'btn-create-sla-scheme',
btnContainer: '#sla-scheme-buttons-container',
schemeContent: '#sla-scheme-content-section',
btnSpinner: '.button-spinner',
events: {
'click #btn-create-sla-scheme' : "createSchemeForm",
'click #btn-cancel-sla-scheme' : "cancel"
},
initialize: function(){
console.log("The scheme view is initialized...");
this.render();
},
createSchemeForm: function () {
this.spin();
DOMHelper.clearSchemeContent();
DOMHelper.clearButtonsContainer();
//Get button
$btnSave = soyUtils.getButton({isPrimary: 'true', id: this.btnSaveScheme, label: 'Save'});
$btnCancel = soyUtils.getButton({isPrimary: 'false', id: this.btnCancel, label: 'Cancel'});
//Append new created buttons
DOMHelper.addContent(this.btnContainer, AJS.format("{0}{1}", $btnSave, $btnCancel));
//Call service to get entry data for scheme creation form
AJS.$.ajax({
url: AJS.format('{0}={1}',AJS.I18n.getText('rest-url-project-scheme-input-data'), jsUtils.getProjectKey()) ,
type: "post",
async: false,
context: this,
global: false,
}).done(function (data) {
this.inputData = data;
$slaSchemeForm = soyUtils.getSchemeCreateForm({slaScheme : data, helpText: AJS.I18n.getText("sla-time-target-tooltip-text")});
DOMHelper.addContent(this.schemeContent, $slaSchemeForm);
jsUtils.scroll(this.schemeContent, 'slow');
}).fail(function () {
jsUtils.callFlag('error', AJS.I18n.getText("message-title-error"), AJS.I18n.getText("sla-error-load-scheme-input-data"));
}).always(function () {
this.stopSpin();
});
},
spin: function () {
AJS.$('.button-spinner').spin();
},
stopSpin: function () {
AJS.$('.button-spinner').spinStop();
},
cancel: function () {
jsUtils.clearButtonsContainer();
jsUtils.clearSchemeContent();
$btnCreateScheme = soyUtils.getButton({isPrimary: 'false', id: this.btnCreate, label: 'Create SLA Scheme'});
DOMHelper.addContent(this.btnContainer, $btnCreateScheme);
DOMHelper.addContent(this.schemeContent, soyUtils.getSchemesTable(new Array())); // TODO - get current data from server instead of empty array
}
});
});
Main module where is Backbone view initialize
define("unicorn/sla/project/batch", [
"unicorn/sla/utils",
"unicorn/sla/data/operations",
"unicorn/sla/data/validator",
"unicorn/sla/dom/helper",
"unicorn/sla/model/confirm/message",
"unicorn/sla/view/scheme",
"exports"
], function (jsUtils, operations, validator, DOMHelper, ConfirmMessage, SchemeView, exports) {
//Load project batch
exports.onReady = function () {
$schemeView = new SchemeView();
$schemeView.render();
}
});
AJS.$(function () {
AJS.$(document).ready(function () {
require("unicorn/sla/project/batch").onReady();
});
});

Dijit buttons don't work when I move to a different page

[UPDATE]
Improved code according to advice here.
--
I have build a grid using dijit and dgrid:
define([
...
], function (declare, _WidgetBase, _TemplatedMixin, ...) {
return declare("MyGrid.widget.MyGrid", [ _WidgetBase, _TemplatedMixin ], {
deleteButton: new MendixButton({
className: "btn mx-button claro haasgridbutton",
iconClass: "dijitIconDelete"
}),
...
postCreate: function () {
...
addButtons();
},
...
addButtons: function () {
...
dojoConstruct.place(this.deleteButton.domNode, this.objectButtonsNode);
this.deleteButton.on("Click", dojo.hitch(this, function () {
this._deleteObjectDialog();
}));
},
});
});
require(["MyGrid/widget/MyGrid"]);
For whatever reason the delete button (or any other) does not work if I move away from the page and come back. the function deleteObjectDialog(); is simply not called and I don't know why, neither do I know how to debug this.
The Debugger starts at the function and I can't find the 'onClick' part using the Inspector.
The syntax you are using here is wrong. The events are not attached using set. They are attached using on. And your reference of this is also not correct. Inside the onClick call, this would refer to the deleteButton and not the grid widget. Instead of:
this.deleteButton.set("onClick", function () {
l_this._deleteObjectDialog();
});
It should be:
this.deleteButton.on("Click", dojo.hitch(this, function () {
this._deleteObjectDialog();
}));

How to test this with Jasmine test (Behaviour Driven Development)?

I've just developed this JavaScript/Backbone module as a part of a web page I am developing. I would like to create a Jasmine test for it, but I am brand new to Jasmine, therefore I am not sure what should I be testing in this class. What should be the "skeleton" of the test? In order to avoid redundancy in tests, what parts will you test?
editdestinationview.js:
define([
'common/jqueryex',
'backbone',
'marionette',
'handlebars',
'text!education/eet/templates/editdestination.hb',
'text!common/templates/validationerror.hb',
'lang/languageinclude',
'common/i18nhelper'
], function ($, Backbone, Marionette, Handlebars, templateSource, errorTemplateSource, i18n) {
'use strict';
var errorTemplate = Handlebars.compile(errorTemplateSource),
EditDestinationView = Marionette.ItemView.extend({
initialize: function (options) {
this._destinationTypes = options.destinationTypes;
},
onRender: function () {
this.stickit();
this._bindValidation();
},
_bindValidation: function () {
Backbone.Validation.bind(this, {
valid: this._validAttributeCallback,
invalid: this._invalidAttributeCallback,
forceUpdate: true
});
},
_validAttributeCallback: function (view, attr) {
view.$('#error-message-' + attr).remove();
},
_invalidAttributeCallback: function (view, attr, error) {
view.$('#error-message-' + attr).remove();
view.$('#destinationTypes').parent('div').append(errorTemplate({
attr: attr,
error: error
}));
},
template: Handlebars.compile(templateSource),
ui: {
saveAnchor: '#ed_eetSaveDestinationAnchor',
deleteAnchor: '#ed_eetDeleteDestinationIcon'
},
triggers: {
'click #ui.saveAnchor': 'click:saveDestination',
'click #ui.deleteAnchor': 'click:deleteDestination'
},
bindings: {
'select#destinationTypes': {
observe: 'destinationTypeId',
selectOptions: {
collection: function () {
return this._destinationTypes;
},
labelPath: 'description',
valuePath: 'destinationTypeId',
defaultOption: {label: i18n.EDUCATION_EET_SELECT_INTENDED_DESTINATION, value: null}
}
}
}
});
return EditDestinationView;
});
Thanks everyone!
UPDATE:
After thinking a lot about it, I think that I should try these aspects:
-Triggers: Check if they can be clicked.
-"_validAttributeCallback" and "_invalidAttributeCallback": Check if they behave accordingly to the code.
-Template: Spy on it to check if it is performing it's mission. (Optional test)
So, the test skeleton will be:
define([
'education/eet/views/editdestinationview'
], function (EditDestinationView) {
describe('description...', function () {
beforeEach(function () {
//EditDestinationView.triggers
});
describe('blablabla', function () {
beforeEach(function () {
// ...
});
it('blablabla', function () {
// blablabla
});
});
});
});
Any help on how to test this please?
One common pattern is to use two describe statements, one for the class and one for the method being tested, and then an it statement for each thing you want to test about that method. The rspec people have a convention (which I use in my JS tests) of using a '#' on the method describe for an instance method, and a "." for a describe of a static method.
Now, if you adopt all of the above, and you want to test (for instance) that your View's click-handling method triggers a certain event on the View's Model, it would look something like this:
define([
'education/eet/views/editdestinationview'
], function (EditDestinationView) {
describe('EditDestinationView', function () {
var view;
beforeEach(function () {
// do setup work that applies to all EditDestinationView tests
view = new EditDestinationView({model: new Backbone.Model()});
});
describe('#handleClick', function () {
beforeEach(function () {
// do setup work that applies only to handleClick tests
});
it('triggers a foo event', function () {
var wasTriggered;
view.model.on('foo', function() {
wasTriggered = true;
});
view.handleClick();
expect(wasTriggered).toBe(true);
});
});
});
});
P.S. Instead of creating a fake "foo" handler like I did, most people use a mocking library like Sinon. Using that library our "it" statement could instead be:
it('triggers a foo event', function () {
var triggerStub = sinon.stub(view.model, 'trigger');
view.handleClick();
expect(triggerStub.calledOnce).toBe(true);
expect(triggerStub.args[0][0]).toBe('foo');
//NOTE: args[0][0] == first arg of first call
});

How to call a backbone view function from another view in separate files

I have two backbone views defined in two separate files namely:
LandingView.js
define([
'jquery',
'underscore',
'backbone',
'marionette',
'text!templates/landing/landingTemplate.html',
'text!templates/invitations/invitationsTemplate.html',
'views/invitations/InvitationsView',
], function ($, _, Backbone, Marionette, landingTemplate, invitationsTemplate, InvitationsView) {
var LandingView = Backbone.View.extend({
el : $("#landing"),
id : 'landing',
transition : 'slide',
initialize : function () {
this.GetNotificationsCounts();
},
events : {
'click #invitations' : 'onInvitations',
},
render : function () {
var that = this;
$('.menu li').removeClass('active');
$('.menu li a[href="#"]').parent().addClass('active');
this.$el.html(landingTemplate);
},
cleanup: function() {
this.undelegateEvents();
$(this.el).empty();
},
onInvitations : function () {
//do something
},
GetProfile: function (userLogin) {
// do something
},
GetNotificationsCounts: function () {
// do something
},
formatAccountName: function () {
//do something
}
});
return LandingView; });
Then there is another file InvitationsView.js
define([
'jquery',
'underscore',
'backbone',
'marionette',
'views/landing/LandingView',
'text!templates/invitations/invitationsTemplate.html',
], function ($, _, Backbone, Marionette, LandingView, invitationsTemplate ) {
var InvitationsView = Backbone.View.extend({
el : $("#invitations"),
id : 'invitations',
transition : 'slide',
initialize : function () { debugger;
this.$el.attr('data-transition', this.transition);
this.currentUserLogin = currentUserLogin;
var that = this;
},
events : {
},
render : function () { debugger;
$('.menu li').removeClass('active');
$('.menu li a[href="#"]').parent().addClass('active');
this.GetUserInvitationDetails();
this.$el.html(invitationsTemplate);
},
cleanup: function() {
this.undelegateEvents();
$(this.el).empty();
},
GetUserInvitationDetails: function () {
var landingView = new LandingView();
currentUserName= landingView.formatAccountName();
curUser = currentUserName.replace("\\", "^").replace("^", "%5E");
var profilemsg = landingView.GetProfile(currentUserName);
},
});
return InvitationsView;});
Now I need to call the formatAccountName and GetProfile functions defined in the first JS to the second JS. I am unable to do that. I get errors.
When I try
var landingView = new LandingView();
currentUserName= landingView.formatAccountName();
This also fails. Can somebody help me in this regard and tell me how can I achieve this
Your current approach of calling the formatAccountName method works. The following jsfiddle shows this:
http://jsfiddle.net/v4h11qac/
The problem is likely caused by another error that has not been handled correctly, resulting in the code not being run. You should fix the existing errors and the method call should work as expected.
Orignal Answer:
You could call the method directly on the prototype object:
LandingView.prototype.formatAccountName();
If you need to pass through a new context you can use the call or apply method as below:
LandingView.prototype.formatAccountName.call(context);
A better approach might involve creating a helper module that can be shared by both views.
var viewHelpers = {
formatAccountName: function(account) {
// ...
}
};
var ViewOne = Backbone.View.extend({
render: function() {
var formattedName = viewHelpers.formatAccountName(this.model);
// ...
}
};
var ViewTwo = Backbone.View.extend({
render: function() {
var formattedName = viewHelpers.formatAccountName(this.model);
// ...
}
};
You could also use a system bus, however that may be a little too heavy for such a use case. If you want to take a look at that path, then Backbone.Radio provides a request/response pattern which could be used to fulfill this requirement.
You could use a global event dispatcher that you declare in some kind of main-js-file like this:
var vent = _.extend({}, Backbone.Events);
Then in your view, listen for an event and run function.
vent.on('your:event',this.function_to_run_from_other_view,this);
Dispatch the event like this from the other view.
vent.trigger('your:event',args)

Marionette best way to do self render

Hello here is my little code :
i don't know how to make this more marionette ... the save function is too much like backbone...
self.model.save(null, {
success: function(){
self.render();
var vFormSuccess = new VFormSuccess();
this.$(".return").html(vFormSuccess.render().$el);
}
var VFormSuccess = Marionette.ItemView.extend({
template: "#form-success"
} );
http://jsfiddle.net/Yazpj/724/
I would be using events to show your success view, as well as using a layout to show your success view, if it's going into a different location.
MyLayout = Marionette.Layout.extend({
template: "#layout-template",
regions: {
form: ".form",
notification: ".return"
}
initialize: function () {
this.listenTo(this.model,'sync',this.showSuccess);
this.form.show(new FormView({model: this.model}));
},
showSuccess: function () {
this.notification.show(new VFormSuccess());
}
});
Or, you could do the same with just the one region, and having the FormView be the layout itself. You just need to ensure there is an element matching the notification region exists in the layout-template.
MyLayout = Marionette.Layout.extend({
template: "#layout-template",
regions: {
notification: ".return"
}
initialize: function () {
this.listenTo(this.model,'sync',this.showSuccess);
},
showSuccess: function () {
this.notification.show(new VFormSuccess());
}
});
What this allows you to do:
You can then show an error view quite easily, if you wanted. You could replace initialize with
initialize: function () {
this.listenTo(this.model,'sync',this.showSuccess);
this.listenTo(this.model,'error',this.showError);
},
and then add the following, ensuring you create a VFormError view.
showError: function () {
this.notification.show(new VFormError());
}
You should be able to write
self.model.save(null, {
success: function(){
self.render();
}
...
Why are you doing this
this.$(".return").html(vFormSuccess.render().$el);
If you define that template as the view template you could simply refer to it with $el, if you need two different templates then you might think about using a Controller, to decide what to use and who to use it.
If you use Marionette, you don't call render directly but instead use Marionette.Region to show your views.

Categories