Variable function names in Require & Backbone - javascript

I'm using Require.js and Backbone, and have a Backbone router module like:
define([
"views/global",
"views/project/edit",
"views/project/list",
], function(GlobalView, edit, list){
return Backbone.Router.extend({
routes: {
"projects/:action/" : "projectsAction",
},
projectsAction : function(action) {
/* .... lots of code cut out here .... */
/* Create and render the action specified */
this.subView = new eval(action+"()").render();
}
});
});
This is an example, I've cut a lot of setup code out of projectAction.
I would like the URL: /projects/list to run projectAction, with the action param = list, and then the list module from the Require.js function to be called. I'm currently doing it with eval(), but I'm wondering if there is a better way?
Basically, in Javascript, can you refer to a variable, with another variable name, without using eval()?
I guess a shorter version would be, how do you do:
var name = "Math.random";
name(); // = 0.34343....
Without eval()?

You cannot access a variable having the name in a string. But you can create a mapping:
var actions = {
edit: edit,
list: list
};
And then you can access the function by the key:
projectsAction : function(action) {
this.subView = new actions[action]().render();
}

The best way imo, is to use the require function of requirejs:
projectsAction : function(action) {
/* .... lots of code cut out here .... */
/* Create and render the action specified */
var self = this;
require('views/project/' + action, function(view) {
(self.subView = new view).render();
}
}
As it would also cut the boilerplate from having lots of actions.

Related

foreach binding in knockout

I am trying to do a simple ko binding but get an error if I do the following:
<div data-bind="foreach: collections">
.....
</div>
Here is the js code:
define(
['jquery', 'knockout', 'RestClient', 'Constants'],
function($, ko, ccRestClient, Constants) {
var collections = ko.observableArray([]);
return {
onLoad: function() {
RestClient.request(Constants.ENDPOINT, input,
function(data) {
for (var i = 0; i < data.childData.length; i++) {
var level = {
"firstName": ko.observable(data.childData[i].firstName),
"Id": ko.observable(data.childData[i].Id)
};
categories.push(level);
}
});
}
}
}
);
I get the following error:
Error - collections is not defined
With Knockout, you should use something like ViewModel which is simply an object with properties and functions that you will use in the view. I cannot see that in your code. It should look like:
function ViewModel() {
var self = this;
self.collections = ko.observableArray();
// do what you want with collections
}
In view onload function you should use ko.applyBindings(new ViewModel()) to apply all of your bindings to the view. Only then you will be able to access them with data-bind attributes.
UPDATE
If ko.applyBindings is applied internally, the problem is with the way you declare collections. You made it a private variable with var, but it should be made a property of the model which is applied with ko.applyBindings. If object returned by the function in your code is the model, just make it like this:
return {
collections: ko.observableArray(),
onLoad: //...
}
If not, then I cannot tell you the solution without more details about your application.
First of all, you have a mistype in your js code. See the define
function($, ko, ccRestClient, Constants) {
and then you use RestClient, not ccRestClient from above
RestClient.request(Constants.ENDPOINT, input,
Replace RestClient with ccRestClient.
As strange as it sounds, removing the use: strict did the trick for me.

Initialize Backbone Models based on another Backbone Model

I have a config.json that I am going to load into my app as a Backbone Model like:
var Config = Backbone.Model.extend({
defaults: {
base: ''
},
url: 'config.json'
});
Other models should be dependent on some data contained in Config like:
var ModelA = Backbone.Collection.extend({
initialize: function(){
//this.url should be set to Config.base + '/someEndpoint';
}
});
In above example, ModelA's url property is dependent on Config's base property's value.
How do I go about setting this up properly in a Backbone app?
As I see it, your basic questions are:
How will we get an instance of the configuration model?
How will we use the configuration model to set the dependent model's url?
How can we make sure we don't use the url function on the dependent model too early?
There are a lot of ways to handle this, but I'm going to suggest some specifics so that I can just provide guidance and code and "get it done," so to speak.
I think the best way to handle the first problem is to make that configuration model a singleton. I'm going to provide code from backbone-singleton GitHub page below, but I don't want the answer to be vertically long until I'm done with the explanation, so read on...
var MakeBackboneSingleton = function (BackboneClass, options) { ... }
Next, we make a singleton AppConfiguration as well as a deferred property taking advantage of jQuery. The result of fetch will provide always(callback), done(callback), etc.
var AppConfiguration = MakeBackboneSingleton(Backbone.Model.extend({
defaults: {
base: null
},
initialize: function() {
this.deferred = this.fetch();
},
url: function() {
return 'config.json'
}
}));
Now, time to define the dependent model DependentModel which looks like yours. It will call AppConfiguration() to get the instance.
Note that because of MakeBackboneSingleton the follow is all true:
var instance1 = AppConfiguration();
var instance2 = new AppConfiguration();
instance1 === instance2; // true
instance1 === AppConfiguration() // true
The model will automatically fetch when provided an id but only after we have completed the AppConfiguration's fetch. Note that you can use always, then, done, etc.
var DependentModel = Backbone.Model.extend({
initialize: function() {
AppConfiguration().deferred.then(function() {
if (this.id)
this.fetch();
});
},
url: function() {
return AppConfiguration().get('base') + '/someEndpoint';
}
});
Now finally, putting it all together, you can instantiate some models.
var newModel = new DependentModel(); // no id => no fetch
var existingModel = new DependentModel({id: 15}); // id => fetch AFTER we have an AppConfiguration
The second one will auto-fetch as long as the AppConfiguration's fetch was successful.
Here's MakeBackboneSingleton for you (again from the GitHub repository):
var MakeBackboneSingleton = function (BackboneClass, options) {
options || (options = {});
// Helper to check for arguments. Throws an error if passed in.
var checkArguments = function (args) {
if (args.length) {
throw new Error('cannot pass arguments into an already instantiated singleton');
}
};
// Wrapper around the class. Allows us to call new without generating an error.
var WrappedClass = function() {
if (!BackboneClass.instance) {
// Proxy class that allows us to pass through all arguments on singleton instantiation.
var F = function (args) {
return BackboneClass.apply(this, args);
};
// Extend the given Backbone class with a function that sets the instance for future use.
BackboneClass = BackboneClass.extend({
__setInstance: function () {
BackboneClass.instance = this;
}
});
// Connect the proxy class to its counterpart class.
F.prototype = BackboneClass.prototype;
// Instantiate the proxy, passing through any arguments, then store the instance.
(new F(arguments.length ? arguments : options.arguments)).__setInstance();
}
else {
// Make sure we're not trying to instantiate it with arguments again.
checkArguments(arguments);
}
return BackboneClass.instance;
};
// Immediately instantiate the class.
if (options.instantiate) {
var instance = WrappedClass.apply(WrappedClass, options.arguments);
// Return the instantiated class wrapped in a function so we can call it with new without generating an error.
return function () {
checkArguments(arguments);
return instance;
};
}
else {
return WrappedClass;
}
};

Backbone: Best way to handle variable common to all models

I'm currently developing my first Backbone single page app project and I'm facing an issue.
Basically I have a menu (html select input element) implemented as a View. Its value is used to control pretty much every other data requests since it specifies which kind of data to show in the other Views.
Right now I handle the DOM event and trigger a global event so that every model can catch it and keep track internally of the new value. That's because that value is then needed when requesting new data. But this doesn't look like a good solution because A) I end up writing the same function (event handler) in every model and B) I get several models with the same variable.
var Metrics = Backbone.Collection.extend({
url: "dummy-metrics.json",
model: MetricsItem,
initialize: function () {
this.metric = undefined;
},
setMetric: function (metric) {
this.metric = metric;
globalEvents.trigger("metric:change", this.get(metric));
}
});
var GlobalComplexity = Backbone.Collection.extend({
url: function () {
var url = "http://asd/global.json?metric=" + this.metric;
return url;
}, //"dummy-global.json",
model: GlobalComplexyItem,
initialize: function () {
this.metric = undefined;
this.listenTo(globalEvents, "metric:change", this.updateMetric);
},
updateMetric: function (metric) {
this.metric = metric.get("id");
this.fetch({ reset: true });
}
});
All my other Collections are structured like GlobalComplexity.
What's the cleanest way to solve this problem?
Thank you very much.
Define a global parametersManager. Export an instance (singleton) then require it when you need it.
On "globalupdate" you update the parametersManager then trigger "update" for all your model/collections so they'll look what are the current parameters in the parametersManager.

Standard way of returning backbone collection in requirejs

I've seen different examples in different articles about how to return a Backbone collection (or View, for that matter) from a RequireJS define. For example:
define(['models/person'], function( person ) {
var personCollection = Backbone.Collection.extend({
model: person,
url: "api/person"
});
// do this?
return new personCollection();
// or this?
//return personCollection;
});
Is there a memory advantage to either approach? Is there standard design pattern that dictates which should be used?
The same question would apply to views, as I've seen them done both ways too.
I would do the second way, because then you would receive reference to "blueprint" not the object itself. In most cases one should want to initialize object creation itself.
Also, even if you want only single instance of collection created I would advise using factory method like this:
define(['models/person'], function( person ) {
var personCollection = Backbone.Collection.extend({...}),
singleCollection,
export = {
getInstance = function (options) {
if (!singleCollection) {
singleCollection = new personCollection(options);
}
return singleCollection;
}
}
return export;
});
And then you could call it like this:
require('models/person', function (person) {
var personCollection = person.getInstance(options);
}

Trying to concatenate all my JS into one file and needing some JS framework/pattern type advice

Just in case it matters, I use ASP.NET 3.5 with VB.NET. I have nested MasterPages and UpdatePanels with Partial PostBacks. I include Modernizr 1.7 with YepNopeJs/IE Shim in my head section. Right before the closing body tag, I include my jQuery 1.6, jQuery UI 1.8.12, and this script.js I'm trying to build.
I'm thinking of using something like:
SITE = {
PAGES : { ... },
VARS : { ... },
HELPERS : { ... },
PLUGINS : { ... },
init : function() { ... }
};
SITE.init();
UPDATE
Ok with Levi's advice, I came up with this solution:
var SFAIC = {}; // Global namespace
SFAIC.common = { ... }; // Shared properties
SFAIC.common.fn = { ... }; // Shared functions
SFAIC.plugin = {
qtip: $.fn.qtip,
validate: $.fn.validate,
validator: $.fn.validator
};
SFAIC.init = function() { ... }; // Global initializer
$(document).ready(function() { SFAIC.init(); });
Then each page would have its own object literal like:
SFAIC.Main = {}; // Main.aspx
SFAIC.Main.someSection = { ... }; // Some Section's properties
SFAIC.Main.someSection.fn = { ... }; // Some Section's functions
SFAIC.Main.anotherSection = { ... }; // Another Section's properties
SFAIC.Main.anotherSection.fn = { ... }; // Another Section's functions
SFAIC.Main.init = function() { ... }; // Main.aspx's intializer
$(document).ready(function() { SFAIC.Main.init(); });
I recommend that you make a new object for section and a new function for each page/item.
However, the more scripts you add in this way, the harder it gets to manage the whole in an editor. Netbeans has a feature that lets you jump to parts of the object and helps manage this.
Example:
var lib = {}; // your library
//maybe you like the plural name plugins better. That's fine.
lib.plugin = {
//define plugins here
};
//maybe you like the plural name helpers better. That's fine too.
lib.helper = {
//define your helpers here
cycle: function() {
//code for the cycle plug-in
}
};
lib.account = {
//you could stick code that is general to all account pages here
};
lib.account.overview = function() {
//you could stick code that is specific to the account overview page here
//maybe you'd use the cycle plug-in to show their latest posts.
lib.plugin.cycle();
};
lib.account = {
//you could stick code that is general to all account pages here
};
lib.account.overview = function() {
//you could stick code that is specific to the account overview page here
//maybe you'd use the cycle plug-in to show their latest posts.
lib.plugin.cycle();
};
Then on the Account Overview page you'd call lib.account.overview().
For Production:
Use a package like closure, uglify or the one I mention at the end to package all your code into one file and send that.
For Development:
I would recommend for structure you use a asynchronous javascript loader like
require.js.
This means you have lot's of modules and you specifically state the dependancies.
For example you would have one main.js
// main.js
require([
"jquery.js",
"jquery.ui.js",
...
], function() {
// if the correct location is "mysite.com/foo/" then url will be "foo"
var url = window.location.pathname.match(/\/(\w+)\//)[1] || "mainpage";
require(url + ".js", function(pageObj) {
// ...
});
});
// foo.js
define({
pageStuff: ...
});
I recommend you read through the requireJS docs to understand their structuring system. It's one of the best I've found.
When it comes to optimising all javascript into one file you just use their builder. This should be part of your project deploy system.

Categories