Require.js JST files - backbone.js

This is my sample JST file
(function() {
var _ref;
if ((_ref = window.JST) == null) {
window.JST = {};
}
window.JST['test'] = function(context) {
return (function() {
var $o;
$o = [];
$o.push("<h1>yayyyyyyaa</h1>");
return $o.join("\n");
}).call(context);
};
}).call(this);
I use require.js in a backbone app, like
define ['backbone', 'marionette', 'text!javascripts/backbone/templates/test.jst'],
(Backbone, Marionette, template) ->
class Test extends Backbone.Marionette.ItemView
template: JST[template]
And when i load the app, i get:
ReferenceError: JST is not defined
Why oh why!
Thanks!

The problem with your code is that you are getting the text of the function in your "template" variable. You still need to eval that text to create an actual JST instance on the window.
The problem as a whole is that you are abusing the text! plugin, what you really need to do is use the modules of requireJs instead of hanging your variables on the window.

Related

BackboneJS How to import module and insert in specific html element of main view

I have a Main view where I import external views(modules), fetch the collections and insert them as new Views inside specific HTML elements. But now I get an error I dont understand...
I have this view defined:
function (App, Backbone, auth) {
var userNav = App.module();
UserNav = Backbone.View.extend({
...
)}
return UserNav;
}
then in my Main view, I want to import the View userNav.
define(['app', 'backbone', 'modules/userNav'],
function (App, Backbone, UserNav,) {
var Main = App.module();
Main.View = Backbone.View.extend({
template: 'main',
...
afterRender: function(){
var userNav = new UserNav.View();
this.insertView('.usernav', userNav);
}
But I get Uncaught TypeError: undefined is not a function - and its referring to the line
var userNav = UserNav.View();
what is the issue here?
I guess that the problem is the UserNav.
try to define this as a module using this code.
define(['Backbone','App','auth'], function(Backbone,App, Auth){
var UserNav = Backbone.View.extend({
//Your code
)}
return UserNav;
});
This is the way that requireJs define a module.
Hope it helps.
new UserNav() instead of new UserNav.View()
UserNav is the View

Inject module dynamically, only if required

I'm using Require.js in combination with Angular.js.
Some controllers need huge external dependencies which others don't need, for example, FirstController requires Angular UI Codemirror. That's a extra 135 kb, at least:
require([
"angular",
"angular.ui.codemirror" // requires codemirror itself
], function(angular) {
angular.module("app", [ ..., "ui.codemirror" ]).controller("FirstController", [ ... ]);
});
I don't want to have to include the directive and the Codemirror lib everytime my page loads just to make Angular happy.
That's why I'm right now loading the controller only when the route is hit, like what's done here.
However, when I need something like
define([
"app",
"angular.ui.codemirror"
], function(app) {
// ui-codemirror directive MUST be available to the view of this controller as of now
app.lazy.controller("FirstController", [
"$scope",
function($scope) {
// ...
}
]);
});
How can I tell Angular to inject ui.codemirror module (or any other module) in the app module aswell?
I don't care if it's a hackish way to accomplish this, unless it involves modifying the code of external dependencies.
If it's useful: I'm running Angular 1.2.0.
I have been trying to mix requirejs+Angular for some time now. I published a little project in Github (angular-require-lazy) with my effort so far, since the scope is too large for inline code or fiddles. The project demonstrates the following points:
AngularJS modules are lazy loaded.
Directives can be lazy loaded too.
There is a "module" discovery and metadata mechanism (see my other pet project: require-lazy)
The application is split into bundles automatically (i.e. building with r.js works)
How is it done:
The providers (e.g. $controllerProvider, $compileProvider) are captured from a config function (technique I first saw in angularjs-requirejs-lazy-controllers).
After bootstraping, angular is replaced by our own wrapper that can handle lazy loaded modules.
The injector is captured and provided as a promise.
AMD modules can be converted to Angular modules.
This implementation satisfies your needs: it can lazy-load Angular modules (at least the ng-grid I am using), is definitely hackish :) and does not modify external libraries.
Comments/opinions are more than welcome.
(EDIT) The differentiation of this solution from others is that it does not do dynamic require() calls, thus can be built with r.js (and my require-lazy project). Other than that the ideas are more or less convergent across the various solutions.
Good luck to all!
Attention: use the solution by Nikos Paraskevopoulos, as it's more reliable (I'm using it), and has way more examples.
Okay, I have finally found out how to achieve this with a brief help with this answer.
As I said in my question, this has come to be a very hacky way. It envolves applying each function in the _invokeQueue array of the depended module in the context of the app module.
It's something like this (pay more attention in the moduleExtender function please):
define([ "angular" ], function( angular ) {
// Returns a angular module, searching for its name, if it's a string
function get( name ) {
if ( typeof name === "string" ) {
return angular.module( name );
}
return name;
};
var moduleExtender = function( sourceModule ) {
var modules = Array.prototype.slice.call( arguments );
// Take sourceModule out of the array
modules.shift();
// Parse the source module
sourceModule = get( sourceModule );
if ( !sourceModule._amdDecorated ) {
throw new Error( "Can't extend a module which hasn't been decorated." );
}
// Merge all modules into the source module
modules.forEach(function( module ) {
module = get( module );
module._invokeQueue.reverse().forEach(function( call ) {
// call is in format [ provider, function, args ]
var provider = sourceModule._lazyProviders[ call[ 0 ] ];
// Same as for example $controllerProvider.register("Ctrl", function() { ... })
provider && provider[ call[ 1 ] ].apply( provider, call[ 2 ] );
});
});
};
var moduleDecorator = function( module ) {
module = get( module );
module.extend = moduleExtender.bind( null, module );
// Add config to decorate with lazy providers
module.config([
"$compileProvider",
"$controllerProvider",
"$filterProvider",
"$provide",
function( $compileProvider, $controllerProvider, $filterProvider, $provide ) {
module._lazyProviders = {
$compileProvider: $compileProvider,
$controllerProvider: $controllerProvider,
$filterProvider: $filterProvider,
$provide: $provide
};
module.lazy = {
// ...controller, directive, etc, all functions to define something in angular are here, just like the project mentioned in the question
};
module._amdDecorated = true;
}
]);
};
// Tadaaa, all done!
return {
decorate: moduleDecorator
};
});
After this has been done, I just need, for example, to do this:
app.extend( "ui.codemirror" ); // ui.codemirror module will now be available in my application
app.controller( "FirstController", [ ..., function() { });
The key to this is that any modules your app module depends on also needs to be a lazy loading module as well. This is because the provider and instance caches that angular uses for its $injector service are private and they do not expose a method to register new modules after initialization is completed.
So the 'hacky' way to do this would be to edit each of the modules you wish to lazy load to require a lazy loading module object (In the example you linked, the module is located in the file 'appModules.js'), then edit each of the controller, directive, factory etc calls to use app.lazy.{same call} instead.
After that, you can continue to follow the sample project you've linked to by looking at how app routes are lazily loaded (the 'appRoutes.js' file shows how to do this).
Not too sure if this helps, but good luck.
There is a directive that will do this:
https://github.com/AndyGrom/loadOnDemand
example:
<div load-on-demand="'module_name'"></div>
The problem with existing lazy load techniques is that they do things which I want to do by myself.
For example, using requirejs, I would like to just call:
require(['tinymce', function() {
// here I would like to just have tinymce module loaded and working
});
However it doesn't work in that way. Why? As I understand, AngularJS just marks the module as 'to be loaded in the future', and if, for example, I will wait a bit, it will work - I will be able to use it. So in the function above I would like to call some function like loadPendingModules();
In my project I created simple provider ('lazyLoad') which does exactly this thing and nothing more, so now, if I need to have some module completely loaded, I can do the following:
myApp.controller('myController', ['$scope', 'lazyLoad', function($scope, lazyLoad) {
// ........
$scope.onMyButtonClicked = function() {
require(['tinymce', function() {
lazyLoad.loadModules();
// and here I can work with the modules as they are completely loaded
}]);
};
// ........
});
here is link to the source file (MPL license):
https://github.com/lessmarkup/less-markup/blob/master/LessMarkup/UserInterface/Scripts/Providers/lazyload.js
I am sending you sample code. It is working fine for me. So please check this:
var myapp = angular.module('myapp', ['ngRoute']);
/* Module Creation */
var app = angular.module('app', ['ngRoute']);
app.config(['$routeProvider', '$controllerProvider', function ($routeProvider, $controllerProvider) {
app.register = {
controller: $controllerProvider.register,
//directive: $compileProvider.directive,
//filter: $filterProvider.register,
//factory: $provide.factory,
//service: $provide.service
};
// so I keep a reference from when I ran my module config
function registerController(moduleName, controllerName) {
// Here I cannot get the controller function directly so I
// need to loop through the module's _invokeQueue to get it
var queue = angular.module(moduleName)._invokeQueue;
for (var i = 0; i < queue.length; i++) {
var call = queue[i];
if (call[0] == "$controllerProvider" &&
call[1] == "register" &&
call[2][0] == controllerName) {
app.register.controller(controllerName, call[2][1]);
}
}
}
var tt = {
loadScript:
function (path) {
var result = $.Deferred(),
script = document.createElement("script");
script.async = "async";
script.type = "text/javascript";
script.src = path;
script.onload = script.onreadystatechange = function (_, isAbort) {
if (!script.readyState || /loaded|complete/.test(script.readyState)) {
if (isAbort)
result.reject();
else {
result.resolve();
}
}
};
script.onerror = function () { result.reject(); };
document.querySelector(".shubham").appendChild(script);
return result.promise();
}
}
function stripScripts(s) {
var div = document.querySelector(".shubham");
div.innerHTML = s;
var scripts = div.getElementsByTagName('script');
var i = scripts.length;
while (i--) {
scripts[i].parentNode.removeChild(scripts[i]);
}
return div.innerHTML;
}
function loader(arrayName) {
return {
load: function ($q) {
stripScripts(''); // This Function Remove javascript from Local
var deferred = $q.defer(),
map = arrayName.map(function (obj) {
return tt.loadScript(obj.path)
.then(function () {
registerController(obj.module, obj.controller);
})
});
$q.all(map).then(function (r) {
deferred.resolve();
});
return deferred.promise;
}
};
};
$routeProvider
.when('/first', {
templateUrl: '/Views/foo.html',
resolve: loader([{ controller: 'FirstController', path: '/MyScripts/FirstController.js', module: 'app' },
{ controller: 'SecondController', path: '/MyScripts/SecondController.js', module: 'app' }])
})
.when('/second', {
templateUrl: '/Views/bar.html',
resolve: loader([{ controller: 'SecondController', path: '/MyScripts/SecondController.js', module: 'app' },
{ controller: 'A', path: '/MyScripts/anotherModuleController.js', module: 'myapp' }])
})
.otherwise({
redirectTo: document.location.pathname
});
}])
And in HTML Page:
<body ng-app="app">
<div class="container example">
<!--ng-controller="testController"-->
<h3>Hello</h3>
<table>
<tr>
<td>First Page </td>
<td>Second Page</td>
</tr>
</table>
<div id="ng-view" class="wrapper_inside" ng-view>
</div>
<div class="shubham">
</div>
</div>

Backbone templates: Using HBS to dynamically switch templates

I found this example on here of how to use the HBS plug-in to manage templates. It seems like a great solution. #machineghost suggests using RequireJS to include templates like this:
define(['template!path/to/someTemplate'], function(someTemplate) {
var MyNewView = BaseView.extend({template: someTemplate});
$('body').append(new MyNewView().render().el);
}
This is great, except I need to dynamically switch templates. Here is an example of one of my views:
define([
'jquery',
'underscore',
'backbone',
'models/tableModel',
'collections/tablesCollection',
'views/tablesView'
], function($, _, Backbone, tableModel, tablesCollection, tablesView) {
var t = new tablesCollection(null, {url: 'applications-lab'});
return new tablesView({ collection: t, template: 'applications-lab-template', url: 'applications-lab'});
});
As you can seem, I'm passing in the template when the view is rendered. What I'm wondering is can I pass in a variable to the define statement that would tell Backbone which template path to use? I'm a newbie to Backbone and especially RequireJS, and am not sure. Suggestions anyone?
Preliminary notes:
require.js does not allow parameters in a module definition, define accepts a dependency array and a definition function :
define(['dep1', 'dep2', ...], function(dep1, dep2) {
})
I would not define a view, instantiate it and inject its el in the same module but feel free to mix and match to your taste
Let's start with a module defining a simple view with a default template, let's say views/templated.js
define(['backbone', 'hbs!path/to/defaultTemplate'],
function(Backbone, defaultTemplate) {
var MyNewView = Backbone.View.extend({
template: defaultTemplate,
initialize: function(opts) {
opts = opts || {};
// use the template defined in the options or on the prototype
this.template = opts.template || this.template;
}
});
return MyNewView;
});
Now you just have to pull you view definition and an optional template with require:
require(['views/templated', 'hbs!path/to/anotherTemplate'],
function(MyNewView, anotherTemplate) {
// a view with the default template
var v1 = new MyNewView();
// a view with a new template
var v2 = new MyNewView({
template: anotherTemplate
});
});
To create a new class with an overridden default template, you would define a new module (views/override.js)
define(['views/templated', 'hbs!path/to/anotherTemplate'],
function(MyNewView, anotherTemplate) {
var AnotherNewView = MyNewView.extend({
template: anotherTemplate
});
return AnotherNewView;
});
Finally, you can always change the template on a given instance by directly assigning a new value.
var v = new MyNewView();
v.template = tpl;
A Fiddle simulating the views hierarchy : http://jsfiddle.net/nikoshr/URddR/
Coming back to your code, your blocks could look like
require(['models/tableModel', 'collections/tablesCollection', 'views/templated', 'applications-lab-template'],
function(tableModel, tablesCollection, tablesView, tpl) {
var t = new tablesCollection(null, {url: 'applications-lab'});
var v = new tablesView({
collection: t,
template: tpl
url: 'applications-lab'
});
// or, if you prefer and you don't render in initialize
v.template = tpl;
});

NoTemplateError Backbone.Marrionette but show template in error msg

Here is my error:
Uncaught NoTemplateError: Could not find template: '<!-- HTML Template -->
<div id="start_div">
<h2>Choose your path to ... // the rest of the template
It is telling me that there is no template but then it outputs the template that it said it could not find.
Here is my code:
require(["jquery", "marionette", "views/StartView" ],
function($, marionette, StartView) {
var SCApp = new marionette.Application();
SCApp.addRegions({
mainRegion: "#center_court"
});
var startView = new StartView();
SCApp.mainRegion.show(startView);
SCApp.start();
}
Here is the StartView.js
define(["jquery", "marionette", "text!templates/startDiv.html"],
function($, marionette, template){
var StartView = marionette.ItemView.extend({
//template: "#start_div"
template: template
});
// Returns the View class
return StartView;
});
Can anyone see what I'm doing wrong? Do I need something for templating in the require method?
Any suggestion are greatly appreciated.
Andrew
Usually Marionette search for a template inside the DOM with an ID equal to the one you reference in your view, so you have to change the loadTemplate from Marionette.TemplateCache in this way:
Backbone.Marionette.TemplateCache.prototype.loadTemplate = function(templateId) {
var template = templateId;
if (!template || template.length === 0){
var msg = "Could not find template: '" + templateId + "'";
var err = new Error(msg);
err.name = "NoTemplateError";
throw err;
}
return template;
};
I actually don't remember where I found this function, I can't find it anymore in Marionette's Wiki, anyway it's working fine for me.
I had the same issue yesterday and found the next interesting facts:
When I've changed the 'not found' template's content, it wasn't changed in error message.
When I've changed it's file name (and updated it in import statement) — the error was fixed, updated content was shown.
... then I've changed the name back, everything was fine.
Looks like some bug with caching.
Upd: here I found deep analysis and solution:
http://blog.icanmakethiswork.io/2014/03/caching-and-cache-busting-with-requirejs.html

Handlebar compiled html not recognizing template function in backbone.js

The project I am on is currently using Backbone.js to create a website and is using Handlebars (http://handlebarsjs.com/) as the templating system. I am attempting to create a sub-view that gets values from a json document into a corresponding template and then return that to a parent view.
The problem I am running into is that when I use
Handlebars.Compile(referenceViewTemplate)
it then doesn't recognize the template function when I try to replace the tokens using
this.template({ identifier: value })
The template code is:
<div id="reference-template">
<div class="id">{{id}}</div>
<div class="reference">{{content}}</div>
</div>
The backbone model is:
define(['underscore','backbone'],
function(_, Backbone){
var reference = Backbone.Model.extend({
initialize: function(){}
});
return reference;
});
The backbone collection code is:
define(['underscore','backbone','models/reference'],
function(_, Backbone, Reference){
var References = Backbone.Collection.extend({
model: Reference,
parse:function(response){ return response; }
});
return new References;
});
The code in the parent view which calls the reference view is:
this.ref = new ReferenceView();
this.ref.model = this.model.page_refs; //page_refs is the section in the json which has the relevant content
this.ref.render(section); //section is the specific part of the json which should be rendered in the view
And the code in the ReferenceView is:
define([
// These are path alias that we configured in our bootstrap
'jquery','underscore','backbone','handlebars',
'models/reference','collections/references','text!templates/reference.html'],
function($, _, Backbone, Handlebars, Reference, References, referenceViewTemplate) {
var ReferenceView = Backbone.View.extend({
//Define the default template
template: Handlebars.Compiler(referenceViewTemplate),
el: ".overlay-references",
model: new Reference,
events:{},
initialize : function() {
this.model.bind('change', this.render, this);
return this;
},
// Render function
render : function(section) {
//this is where it says "TypeError: this.template is not a function"
$(this.el).append(this.template(References.get(section).get("content")));
return this;
}
});
I know this is a lot to read through and I appreciate anyone taking the time to do so, please let me know if there is anything else I can provide to clarify.
The answer is that apparently I was using the wrong function to compile the html. For some reason I typed in Handlebars.Compiler instead of Handlebars.compile
This hasn't solved all the problems in my project (template is being passed back now, but without the values entered), but at least it's a step forward.

Resources