Separate Backbone View functions into separate files - javascript

I have a Backbone View.
To organize better the code of it, I would to prefer to separate some functions of it into a separate file.
So, I would like to maintains the structure but simply I need to separate the functions into different files:
- MyView
- functions A -> file A;
- functions B -> file B;
I would like to use the view in the same current mode. So I need to call the functions 'A' on my view: myViewInstance.functionA_1()
What is the correct way/pattern to implement this?
I use also RequireJS.

You can use the Mixin pattern. Divide the prototype of MyView to the main part and to other parts, put them to separate modules, make the main module depend on them and merge the parts together.
Add prototype members from the main part to the declaration of MyView as usual:
var MyView = Backbone.View.extend({
method: function () {
this.otherMethod();
}
});
Put the other parts of the prototype to separate modules and export them as object literals:
var prototypePart = {
otherMethod: function () {}
};
Make the main module depend on them and merge the prototype from all imported parts either by Object.assign or by _.extend:
// Merging the prototype with ES6.
Object.assign(MyView.prototype, prototypePart);
// Merging the prototype with Underscore.js.
_.extend(MyView.prototype, prototypePart);
You will get MyView as if declared "in one piece":
var MyView = Backbone.View.extend({
method: function () {
this.otherMethod();
},
otherMethod: function () {}
});
For example, myview.js exports MyView. It depends on two other modules in order to import other parts of the MyView prototype from them:
define([
'underscore', 'backbone', './myview-a', './myview-b'
], function (_, Backbone, myViewA, myViewB) {
var MyView = Backbone.View.extend({
// ...prototype members from this module
initialize: function () {
this.fromModuleA();
}
});
// Add prototype members imported from other modules
_.extend(MyView.prototype, myViewA);
_.extend(MyView.prototype, myViewB);
return MyView;
});
myview-a.js exports an object with a group of additional MyView prototype members:
define(function () {
var myViewA = {
// ...prototype members from this module
fromModuleA: function () {
this.fromModuleB();
}
};
return myViewA;
});
myview-b.js exports an object with another group of MyView prototype members:
define(function () {
var myViewB = {
// ...prototype members from this module
fromModuleB: function () {}
};
return myViewB;
});

Related

Requirejs dynamic reference to sub module methods?

Using requires, I’ve split larger class structures down into modules that use other modules within directory. There’s a main file that instantiates the other sub modules. This is an API class with two modules. One that deals with posting data to the endpoint, and the other that holds functions that are helpers to that post module:
define([
'./Post',
'./Helper'
], function (PostModule, HelperModule) {
'use strict';
var module = function () {
this.Post = new PostModule();
this.Helper = new HelperModule();
};
return module;
});
Now I can chain these modules like this:
var foo = new ApiModule();
foo.Post.postToAPI();
foo.Helper.matchAPIactionToEvent();
which is exactly what I want..
BUT, the problem is within the Post.js file, is that it doesn’t know anything about the Helper.js file. So I can’t take advantage of any of those methods. What I would like to do within the Post.js file is to be able to reference the other functions within the same class like so:
define([
'../environment',
'loglevel',
'../utility/Utility',
'jquery'
], function (EnvironmentModule, log, UtilityModule, $) {
'use strict';
var module = function () {
var environment,
utility,
api;
environment = new EnvironmentModule();
utility = new UtilityModule();
this.postToAPI = function (event, payload, callback) {
var apiEndpoint,
requestIdString,
eventAndAction;
apiEndpoint = environment.getEnvironmentPath().API;
requestIdString = utility.String.generateRandomString(32);
/*** THIS IS HOW I WANT TO CALL THE HELPER METHODS ***/
eventAndAction = this.Helper.matchAPIactionToEvent(event);
/*** THIS IS HOW I WANT TO CALL THE HELPER METHODS ***/
payload.event = eventAndAction.event;
payload.action = eventAndAction.action;
payload.requestID = requestIdString;
payload = $.param(payload);
$.post(apiEndpoint + '?' + payload, function (result) {
if (callback) {
callback(result);
}
});
return;
};
};
return module;
});
I figured out a working solution to the this, where I pass '../api/Helper' as one of the array values in the define statement of Post.js; but I don’t want to do that. What I want is to have Post.js be able to access any method from any other modules that are contained within the same directory. That way I don’t have to explicitly define them. It seems wrong to instantiate a new ApiModule() within Post.js. Here’s the directory structure:
Modules/api/Api.js
Modules/api/Post.js
Modules/api/Helper.js
...
I hope that makes sense. Is this possible?
Since you want any of the child modules to access any other child modules, what you could do is pass the parent module as an argument to the constructors of the child modules:
var module = function () {
this.Post = new PostModule(this);
this.Helper = new HelperModule(this);
};
Have the child modules store this information:
var module = function (parent) {
this.parent = parent;
Then use this this.parent to call the methods on other child modules:
eventAndAction = this.parent.Helper.matchAPIactionToEvent(event);
Note that if you would load '../api/Helper' with RequireJS in Post.js as you were thinking of doing, you would not be able to use the same object instance as the one defined on the parent module. You'd have to construct a new object for use in Post.js.

Backbone.js & Inheritance Methods

i was wondering how to use functions within the Backbone.View code. Can someone advise/show me how this is done properly. I understand that extending is only put into the var. I've looked at extend, prototype, super, parent, baseview and other fancy stuff. But that only confused me even more ;).
var jsHelpers = {
intVar: 300,
sum: function(a, b, callback) {
// do something interesting:
c = a + b;
d = c + intVar;
callback(c);
} //end sum function
} //end jsHelpers
/* somewhere else */
ViewThingy = Backbone.View.extend({
initialize: function() {
this.render();
},
render: function() {
var result = jsHelpers.sum(1, 1, function(callbackData) {
//let's do something with the return stuff:
this.$el.html(callbackData);
}); //end jsHelpers
} // end render
}); //end extend
The error is of course that the function jsHelpers.sum(); is not available in the extend.
TIA ! Vince
var View = Backbone.View.extend({
hello: function() {
console.log('hello');
},
// You can also override Backbone methods here.
initialize: function() {
// Do init code shared by all your views
}
});
// All the views in your app should extend off this view instead of Backbone.View.
var RandomView = View.extend({
initialize: function() {
// Call the parent to do init code.
View.prototype.initialize.apply(this, arguments);
this.hello();
},
// You can override methods too..
hello: function() {
// You can call the parent.
View.prototype.hello.apply(this, arguments);
}
});
Actually it is a good idea to always extend View, Model, Collection and Router when you make an app as there will always be shared functionality you want to do to not repeat the same code everywhere in your app. Typically for a view this would be stuff like the render routine such as rendering the template and rendering sub views - you wouldn't want to do that logic again in every view in your app.
Typically to share other code you would use a dependency manager like RequireJS or Browserify. But you can also have a single global object:
window.app = {};
and attach things to it:
window.app.utils = ....;
That is accessible from anywhere. Having an app object is pretty common - e.g. you would often have a Model that maintained the state of the app at app.state.
You can hook your helpers to some global namespace or make them global.
window.jsHelpers = {...}
Second way :
jsHelpers = {..} //Remove the var, it'll make jsHelpers a global variable.
I use the first one, for similar purposes.

Switching between singleton and prototype scope using RequireJS

Spring has very useful option, that whey I define a bean, I define a scope. If it's singleton, only one instance is created. By prototype, each time a bean is required, a new instance is created.
RequireJS provides by default singletons, so with such simple module:
Singleton.js
define([], function() {
console.log('Instance initialization')
var items = []
var singleton = {
getItems: function() {
return items
},
setItems: function(newItems) {
items = newItems
},
addItem: function(item) {
items.push(item)
}
};
return singleton;
})
and the usage:
require(["showcase/modules/Singleton"], function(Singleton){
Singleton.addItem('item1')
console.log(Singleton.getItems())
})
require(["showcase/modules/Singleton"], function(Singleton){
Singleton.addItem('item2')
console.log(Singleton.getItems())
})
the output will be:
Instance initialization
["item1"]
["item1", "item2"]
Is it possible to define and use the module in such way, that I could switch in the module definition, if I want to use prototype or singleton scope? So in my case, without changing the usage, I'd get:
Instance initialization
["item1"]
Instance initialization
["item2"]
I'm using RequireJS from Dojo, just in case of syntax differences
Well, first of all the problem is that when you import a module using an AMD loader, you will actually get an instance, but the second time you import the same module, the same instance is actually returned (problem 1).
To overcome this problem you should use the factory design pattern to get your instance and also translate your singleton object to a class that can be instantiated (problem 2). Your factory could have a method called getInstance() that accepts a boolean parameter that can toggle between singleton/prototype.
So without changing your usage you won't be able to do this because of the problems I just addressed. The best solution I can come up with (with a factory) is:
Singleton.js
define([], function() {
console.log('Instance initialization');
// Singleton class
var Singleton = function() {
this.items = [];
this.getItems = function() {
return this.items;
};
this.setItems = function(newItems) {
this.items = newItems;
};
this.addItem = function(item) {
this.items.push(item);
}
};
// Factory
var factory = {
singletonInstance: new Singleton(),
getInstance: function(/** Boolean */ isSingleton) {
if (isSingleton === true) {
return this.singletonInstance;
} else {
return new Singleton();
}
}
};
return factory;
});
Usage (singleton)
require(["app/Singleton"], function(singletonFactory){
var Singleton = singletonFactory.getInstance(true);
Singleton.addItem('item1');
console.log(Singleton.getItems());
});
require(["app/Singleton"], function(singletonFactory){
var Singleton = singletonFactory.getInstance(true);
Singleton.addItem('item2');
console.log(Singleton.getItems());
});
Usage (multiple instances)
require(["app/Singleton"], function(singletonFactory){
var Singleton = singletonFactory.getInstance(false);
Singleton.addItem('item3');
console.log(Singleton.getItems());
});
require(["app/Singleton"], function(singletonFactory){
var Singleton = singletonFactory.getInstance(false);
Singleton.addItem('item4');
console.log(Singleton.getItems());
});
In case you're interested in a full example, it's on Plunker.
Eventually you could wrap the factory as a plugin so that you could actually do something like:
require([ "app/Object!singleton", "app/Object!prototype" ], function() {
});
However I don't know if RequireJS also supports this (and if I'm understanding well it should be a generic story for both AMD loaders).

Share prototype instance through requirejs without singelton pattern?

I got a mediator object which handles sandboxes.
Each sandbox has to register at the mediator. Because I also use requirejs this is a little problem because I have no idea how I could share the instance and not the prototype:
mediator.js
define([], function() {
var Mediator = function() {};
Mediator.prototype.start = function() {};
Mediator.prototype.stop = function() {};
Mediator.prototype.register = function() {};
Mediator.prototype.unregister = function() {};
return Mediator;
});
sandbox_one.js
define(['mediator'], function(Mediator) {
var mediator = new Mediator();
mediator.register('sandboxOne', SandboxObject);
});
sandbox_two.js
define(['mediator'], function(Mediator) {
var mediator = new Mediator();
mediator.register('sandboxtwo', SandboxObject);
});
As you have mentioned with the current approach I register the sandboxes at two different mediators. One idea to solve this would be the use of a singleton pattern but this conflicts with the architecture and requirejs recommandations..
So what other ways would I have to let the sandboxes register all to the same instance of Mediator?
Since I can't post comments, I'm going to post an answer.
ggozad explains how to do a singleton in RequireJS here: Is it a bad practice to use the requireJS module as a singleton?
His code:
define(function (require) {
var singleton = function () {
return {
...
};
};
return singleton();
});
Actually, with RequireJS you are already receiving singletons, so if you call:
require(['mediator'], function(Mediator) {
....
});
Mediator will be always the same object. If it's a constructor function, by calling new Mediator() you'll always create a new instance, but you'll use the same function each time.
But you don't have to return a function:
define([], function() {
return {
start: function() {},
stop: function() {},
register: function() {},
unregister: function() {}
};
});
Now your Mediator will be an instance, not a function, and you can't use it as function. You're example would now look like:
require(['mediator'], function(Mediator) {
Mediator.register('sandboxOne', SandboxObject);
});
Disclaimer: I'm using RequireJS version build-in in Dojo.

How do I set Backbone Views as singleton by default?

all my Backbone.Views are only used once in their final state. (Except item views).
Currently I handle Backbone.Views as Singleton this way:
var Singletonizer = function(Singleton) {
if (Singleton._instance) return Singleton._instance;
Singleton._instance = new Singleton();
Singletonizer(Singleton);
};
Unfortunately it isn't that nice to add this little function as dependency to each amd module in my repository.
Is there another way to handle this? Maybe overwriting the base view class?
Just have your module return a function besides your view constructor, one that returns a single instance of it instead, not unlike the following. This way when you load the module you will not automatically get an instance whether you like it or not. Instead, after loading our "FailedXhrView" module, we then get our singleton by calling FailedXhrView()
'use strict';
define(['jquery',
'underscore',
'backbone',
'text!templates/failedXhr.html'],
function($, _, Backbone, failedXhrTemplate) {
var FailedXhrView = Backbone.View.extend({
el : $('#failedxhr-modal-container'),
template : _.template(failedXhrTemplate),
render : function() {
this.$el.html(this.template({}));
this.$el.find('failedxhr-modal-containee').modal();
return this;
}
});
var instance;
return function() {
if (!instance) {
instance = new FailedXhrView();
}
return instance;
}
});
From the very recommendable book Recipes with Backbone:
From here:
// definition
MyApplication.Views.List = Backbone.View.extend();
// instantiation
$(function() {
MyApplication.ListView = new MyApplication.Views.List({
el: $('#list')
});
})
To here:
$(function() {
MyApplication.ListView = new (Backbone.View.extend({
initialize: function() {
// ...
}
}))({el: $('#list')})
});
We assign an instantiation of an anonymous class to MyApplication.ListView. In this approach, we are doing the typical extension of a top-level Backbone class with custom attributes and methods. The difference, however, is that we do not assign the result to a class name as we did earlier. Instead, we immediately create a new instance of this anonymous class. Lastly, MyApplication.ListView is assigned to the resulting object.
I have to say I've never used techniques like this. It looks to messy for me, I prefer legibility than architecture.

Categories