Structing a backbone.js application - javascript

I have a backbone.js application with multiple models, views, collections and templates (in a script tag with type text/template with an ID which the views use to load the template in using _.template($(id).html())).
Currently, all of the above are in a single file, which makes it quite ugly. This is an offline application (i.e, its loaded from the local filesystem and talks to a web server which allows cross-origin requests). I've been trying to figure out how to move the views, models and templates to separate files, I tried just moving the views and models out to views.js and models.js, but the views depend on the templates, and the templates can't be put into views.js (as its a JS file, and therefore can't have script tags..).
My only solution at this point seems to be moving the templates into global variables, which would be fine except for the javascript string escaping/multi line strings that would be required..
How is this usually handled?
Thanks!

Use RequireJS to package your views and models up into modules, and RequireJS will take care of the dependency resolution for you. You can get down to one view or model per file that way, too, instead of putting them all in one views.js or models.js.
Then, use the text! plugin to store your templates in text files, and require them like normal modules.
define(function (require, exports, module) {
var templateText = require("text!./person.tmpl");
var $ = require("jquery");
exports.PersonView = Backbone.View.extend({
render: function () {
doStuffWith(_.template(templateText));
}
});
});

Take a look at these starter applications that use Backbone and RequireJS.
This one has Backbone.LocalStorage in it, which could help you with your offline stuff.
https://github.com/jcreamer898/Savefavs
This other one is just a simple starter for building an app...
https://github.com/jcreamer898/RequireJS-Backbone-Starter
They way you can handle templates with is...
define([
'jquery',
'backbone',
'underscore',
'models/model',
'text!templates/main.html'],
function($, Backbone, _, model, template){
var MyView = Backbone.View.extend({
initialize: function(){
this.template = _.template(template);
},
render: function(){
this.template(model);
}
});
return new MyView();
});

You might want to check out the Backbone boilerplate. This add a nice modular structure to your backbone app without polluting the global namespace, and provides a simple template caching.
It's really easy to change the template renderer to someting different that JST.
Even if the general idea can be followed without any server side requirements, the boilerplate provides a basic node.js app as well.

Related

I just added angularjs to my BackboneJS site using requireJS to use for rendering a few views and gradually transitioning. Should I expect problems?

So, I've read in different places that Backbone and angular don't get along always, and that makes sense with respect to certain modules (like anchor link routing, since there can only be one top dog).
I went ahead and did the work to add angular anyway because it is clearly the better choice and there are way better plugins available for it, including a few table plugins that I am loving.
Now, I've yet to actually do this, but this has been my plan all along: use angular to generate and render a particular view or set of views, but always invoke it using Backbone.
For instance, I will need a "grid view" in the interior of one of my existing Backbone.Views. I am planning on booting angular with the root element provided by the client Backbone.View:
var ClientBBView = Backbone.View.extend({
...
render: function() {
... //do my normal rendering
require(['app/app.boot'],function(appBoot) { appBoot.boot($(targetElement)); });
}
...
}
Of course I would need to make the call specific to the view needed.
Anyway, if I do this, my intent is to demote angular so it doesn't try to take over the whole document. I noticed this line...
define([
'angular',
'app/js/app'
], function (angular, app) {
'use strict';
// as script is at the very bottom of the page no waiting for domReady
angular.bootstrap(document, ['app']); // <-- REPLACE DOCUMENT
});
so that's where the targetElement parameter will go. This way, I am thinking, it won't be able to mess with my routes at least.
Are there any gotchas I should know about with this sort of patchwork use of angular? Is this a common use case? It seems like it would work fine but I could use some advice.

Keeping angularjs decoupled from an application

I'm planning out an application that ideally I'd like to keep decoupled from Angular, so all the dom interaction will go into an Angular layer but the core business logic will be kept in a standalone app that is totally unaware of Angular. The two will communicate via Angular services that are able to send messages to the core application.
The standalone app will most likely use requirejs to handle dependency injection.
What I'm unsure of is how to get a module that may be declared something like this:
define('AppInteractor', ['require'], function(requre){
// Set up interactor functionality
});
Into an Angular service.
My first though was to have some kind of adapter like this:
define(['AppInteractor', 'angular'], function(interactor, angular) {
var app = angular.module('appInteractor', []).service('AppInteractor', interactor);
});
This would register the AppInteractor as a service within an angular module, the problem with this approach seems to be that because requirejs loads things asynchronously the service hasn't been set when the angular code runs. It also seems to end up with angular loading twice which presumably also doesn't help matters.
I've seen a few articles covering loading all Angular components via require but I was hoping there might be simpler way to make this work.
Can anyone recommend how I might make this work?
If you are going to add a service after the main modules initialization, I recommend you add it to your application module
define(['AppInteractor', 'angular'], function(interactor, angular) {
// There is no second parameter, because we are getting an existing
// module, not creating a new one.
var app = angular.module('appModule');
app.service('AppInteractor', interactor);
});
Otherwise, if you want to group all your other logic in a different module, you will need to import the module and add it as a requirement of your application module.
define(['AppInteractorModule', 'angular'], function(interactorModule, angular) {
var app = angular.module('appModule', [interactorModule.name]);
});
I also recommend you use a tool like ES6 (6to5, traceur), browserify or Typescript for your module management, and then compile to AMD. I find it easier and cleaner for development.

Backbone and RequireJS effective loading

I looked at many examples on the internet how to start develop BB applications with requireJS but I am a kind of lost.
I think AMD has a goal that it load files only if they really needed. Not sooner.
Why I am seeing examples only where the developer put almost every files as a dependency at the beginning of his/her main file?
Here is an example:
https://github.com/jcreamer898/RequireJS-Backbone-Starter/tree/master/js
This application instantly loads main.js which depends on app.js which loads routers/home.js which requires views/view.js which loads the view's template and models/model.js which ... and end.
I can't see how to extend this application for example with more views where views' dependencies (its models, templates, collections, third party APIs, etc) load only when the router calls and initialize them. Other way this would be nonsense to use AMD where you load all your files when initializing your app.
Similar example here:
http://backbonetutorials.com/organizing-backbone-using-modules/
see router.js file.Actually it loads 'views/projects/list' and 'views/users/list' dependencies while the router does not know yet whether the user will need them in the future or not.
Please advise, thanks in advance!
It's a bit hard to see in such a small sample app, because you have to load something on the initial route, and loading something in Backbone usually means a model, a collection, and a view. Since the sample you linked has only one of each, then yes you're loading almost everything.
Where you start to see the "on demand" feature is where you add additional routes/views/models/etc. Keep in mind, however, that on-demand loading is probably a secondary goal of AMD/RequireJS. The primary goal is modularity. They then give you lots of options for either loading things on demand, or bundling everything up via the optimizer
Also there is nothing which says you have to put all the require() at the beginning of the file. You can do them later (e.g. when initiating a route). Here is a modified version of home.js from your first linked example. If you're using Chrome dev tools you can look at the network tab when the "debugger;" statement pauses execution. Then continue execution and see how the rest of scripts get loaded.
define([
'jquery',
'backbone',
'underscore'
],
function($, Backbone, _){
var Router = Backbone.Router.extend({
initialize: function(){
Backbone.history.start();
},
routes: {
'': 'home'
},
'home': function(){
debugger;
require(['views/view'], function (mainView) {
mainView.render();
});
}
});
return Router;
});
See this person's article for more background and where you might go next with it.

Using RequireJS with legacy code

I'm working with a very large project that uses:
Legacy JSP pages that includes javascript files with script tags
Backbone models and views that uses other javascript modules without RequireJS
We now want to start using RequireJS with jQuery, BackboneJS and UnderscoreJS for everything we develop from now on, but we don't have the resources to rewrite all the legacy JSP pages. We may have time to rewrite the Backbone models and views we have already developed.
The problem is that for our legacy code (both 1 and 2 above) we include all our javascript files in a huge file and ship to the browsers. This huge file must be able to co-exist with our new RequireJS templates, but how can I e.g. separate certain parts of the huge file, so I can use them with the templates using RequireJS? Without having to rewrite all pages that uses this part of the file, or having duplicate code.
Thanks!
I don't know if I fully grasp the problem at hand, but I think a the shim or map functions of RequireJS will help you out.
Extract the parts you want in a new module from your huge javascript file. Then tell RequireJS that your huge javascript file is a dependecy for this new module using shim. Something like:
requirejs.config({
shim: {
'new-module': {
deps: ['huge-javascript-file'],
exports: 'NewModule'
}
});
Shim documentation: http://requirejs.org/docs/api.html#config-shim
The map function might be useful when only portions of your new code have to use your old huge file. Check out this documentation: http://requirejs.org/docs/api.html#config-map
I don't think there is One True Way to achieve this, but I've approached a similar problem by defining module "facades" around the globally scoped code. Let's say your legacy scripts define a global variable called foo. You can define a AMD module and export that variable from it:
//foo.js
define(function() {
return window.foo;
});
//bar.js
define(['foo'], function(foo) {
//do something with foo
});
This way you only need to write a single-line facade every time you need to use a new piece of the existing, globally defined code, without breaking existing code which expects the same code to be globally defined. Over time you can move and refactor the actual implementation into the module without breaking consumer code.

Injecting additional mvc logic into ext js app

I'm new to ext js, but went through the getting started guide and have managed to create my own app.
Now I'm building a plugin for an e-commerce system (shopware) and need to dynamically extend an app.
I've managed to add a view by monkey patching an existing controller:
//{extends file="[default]backend/article/controller/main.js"}
//{namespace name=backend/article/view/main}
//{block name="backend/article/controller/main" append}
Ext.define('Shopware.apps.Article.controller.MyApp', {
override: 'Shopware.apps.Article.controller.Main',
openMainWindow: function() {
var me = this,
mainwindow = me.callOverridden();
oldTabCreation = mainwindow.createMainTabPanel;
mainwindow.createMainTabPanel = function() {
mainTab = oldTabCreation.apply(this);
mainTab.add(
Ext.create('Ext.panel.Panel',
{
title: 'Pricify',
layout: 'card'
})
);
return mainTab;
};
return mainwindow;
}
});
//{/block}
This works. I'm not sure, if it's the preferred way, but the View gets loaded at the right place and I'm quite happy (as it costs me several hours).
But there's some way to go.
How would I inject the entire logic here?
My requirements are:
I need a controller, a view and a store/model
Preferably in an own namespace
I want to separate my classes into files.
the file must reside in the plugin folder, not in the original app folder.
I could append code to the app.js file, but as far as I know, I can't attach controllers and views, nor would I know how to autoload the files.
Is there any preferred way of doing so?
EDIT
I'm now building a simple app, that's loaded before and try to inject the controllers that are then available into the app. I'll report back, once I'm ready.
Usually I would do something like this by intercepting controller events (with Ext.ux.Application override) and adding some logic on top of that. Alternatively you could extend existing controller and replace it with your custom one in the whole app, if that's feasible. Otherwise monkey patching could be the only way to extend an existing monolithic application that wasn't written with extensibility in mind.
Also, 4.2 Beta 2 should be available shortly; it includes a whole host of MVC improvements, including something like the override mentioned above. You may want to take a look at it.

Categories