Underscore, can't call replace of undefined - backbone.js

I've experienced this error before and tried the solutions I've found on SO for it, but I can't get around it in this case by trying the solutions I've found. I have a question_template that I placed in the header of my index file with the js script tags at the bottom of the file. In the initializer to the view, I get the template using jQuery html function, and the console log shows the template is retrieved from the index.html. However, when i try to insert it into underscore _.template, it's triggering the can't call replace of undefined error
var QuestionView = Backbone.View.extend({
el: $(".east"),
initialize: function(){
var template = $('#question_template').html();
console.log(template);
this.template = _.template(template); #error triggered
},
Since I'm able to log the template, I don't see what my problem is? This is part of the underscore code.
text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
source += text.slice(index, offset)
.replace(escaper, function(match) { return '\\' + escapes[match]; });
First question: What would 'text' represent, which, in my case, is undefined? I would have thought that 'text' is the template, but since I can log my template how is it undefined?
I also have all of the js code (including the initialization of the Question view) wrapped in the document ready, which in other SO questions on this issue was the solution
$(function() {
...code ommitted...
var question_view = new QuestionView({ model: game});
});
Second question: is there anything else I can try
Update
Note, I subsequently pass the model data to the template but it never gets that far because the error is triggered
**$(this.el).html(this.template(response));**
I prepare the templates in three steps
1. var template = $('#question_template').html();
console.log(template);
2. this.template = _.template(template);
3. $(this.el).html(this.template(response));

You should pass the model data to the template,
this.template = _.template(template, this.model.toJSON());

Related

angularjs: controller hijacking another controller's variable

I am facing a very strange issue with variable in one controller being hijacked by another controller. Here are the details:
In my HTML I have two ng-view tags. Each tag leads to a templateURL (an html) that has its own corresponding controller. Ctrl1 and Ctrl2
the two ng-views are at the same level in the html hierarchy - that is, one is NOT the child of another
Controller1 looks like this:
ngEpMod.controller('Ctrl1',[function() {
selfX = this;
abc = 'abc controller1';
console.log(abc); // break point 1
selfX.query = function() {
console.log("abc=");
console.log(abc); // break point 2
console.log("search query=");
console.log(selfX.searchQ);
lySP.searchHomes();
};
}]);
Controller2 looks like this:
ngEpMod.controller('Ctrl2',[function() {
self = this;
abc = 'abc controller2';
}]);
Both controllers are associated in the html using a "controller as" syntax.
The query() method in Ctrl1 is fired when user user clicks a button (ng-click)
Mystery: As I load the html page ($state) that has the two ng-views, I am observing the browser console. I note that abc value at break-point1 is "abc controller1", but when the query() method is fired, it mysteriously changes to "abc controller2". There is no global variable by that name! As I understand, when the page is being laid out, Ctrl1 is created first so at break-point 1 abc has the correct value, then Ctrl2 is created and somehow it high-jacks the abc variable! Stranger even is that I noticed this problem first with my self variable (self = this) and then I introduced abc just for additional check
Gurus, I am a newbie and would really appreciate your help.
By creating a variable without var (or let in ES6), you've created a global variable attached to window:
abc = 'abc controller1'; equals to window.abc = 'abc controller1';
When the 1st controller instantiates it declares the variable abc on window. When the 2nd controller instantiates, it changes the global abc variable content.
To avoid it in this case define var abc in both controllers.
To avoid it in the future add 'use strict'; to each function deceleration, for example:
ngEpMod.controller('Ctrl2',[function() {
'use strict';
self = this;
var abc = 'abc controller2';
}]);
Strict mode will throw error when you make this mistake (any many others). From MDN:
First, strict mode makes it impossible to accidentally create global
variables. In normal JavaScript mistyping a variable in an assignment
creates a new property on the global object and continues to "work"
(although future failure is possible: likely, in modern JavaScript).
Assignments which would accidentally create global variables instead
throw in strict mode:
I would drop the below code into your app above this instantiation (most modern browsers should understand this syntax for debugging). Call window.trackCtrl in every controller constructor and then pop open the console in chrome or firefox and type printCtrls() and you should get a print out of when they were created in order.
window.trackCtrl = (name) => {
var newCtrl = {}
newCtrl.name = name + '_' + performance.now()
window.trackingCtrls = window.trackingCtrls || []
window.trackingCtrls.push(newCtrl)
}
window.printCtrls = () => Array.isArray(window.trackCtrls) ? window.trackingCtrls.forEach(x => console.info(x)) : console.error('trackCtrls not defined')
This will find bugs such as the controllers getting loaded out of order or duplicate copies of your code or libraries getting loaded on the same page. Performance API helps a lot in these situations => https://developer.mozilla.org/en-US/docs/Web/API/Performance/now

Testing events and document content in karma-jasmine unit-testing

I want to test the following functions using jasmine-karma unit testing but I can't find a way to do so.
function One:
$scope.info = function(text){
$(":root").find("#info").remove();
var htmlObject = "<div id='info' info-alert text='"+$sanitize(text)+"'/>";
var comments = $compile( htmlObject )( $scope );
$(document.body).append( comments );
};
function 2:
$scope.$on('settingsChanged',function(){
$scope.getSettings();
});
I have tried reading the documentation on how to test events and play with the tests but nothing seems to work.
This is my last attempt for the first but the last line is not the right way of doing it I suppose:
it('info', function() {
var text="Some information";
var controller=createController();
scope.info(text);
expect($(document.body).val()).toContain(text);
});
I tried for the second one something similar but nothing seems to work
Your text might have been altered by $sanitize(text)
AngularJS official documentation states that:
- $sanitizeProvider
- service in module ngSanitize
Sanitizes an html string by stripping all potentially dangerous tokens.
So please check if document.body contains your text, and that it has not been altered by $sanitize(text)

Updating the Model and then Getting a Document Element

I'm trying to do the following:
vm.request.StatusDescription = 'In Progress';
var contents = $document[0].getElementById(elementId).innerHTML;
The contents I get from the DOM, within the requested element, contain a binding for {{ vm.request.StatusDescription }}, but when I use the HTML contents in a new window (intended for printing a subset of the screen), the status description hasn't updated. It still reads 'New'.
Is there any simple way to deal with this? I've looked at using $scope.$apply(); with no success.
Here is an example on how to update an value in AngularJS. You can then read from $scopeStatusDescription where you need the current status in your controller or in your view.
$scope.StatusDescription = 'New';
$timeout(function() {
$scope.StatusDescription = 'In Progress';
}, 1000);
http://jsfiddle.net/NBhn4/101/

Variable passing on Backbone template

I just started learning 'Backbone.js', I am currently following this video tutorial. For templating, I just kept my template to be simple like this -
<script type="text/template" id="songlist_template">
<%_.each(songs, function(song){}); %>
<h1>Loaded</h1>
</script>
and my view extended as-
var SongList=Backbone.View.extend({
el:'.page',
render: function(){
var that=this;
var songs=new Songs();
songs.fetch({
success:function (){
var temp=_.template($("#songlist_template").html());
var html=temp(songs);
that.$el.html(html);
},
error: function (collection, response, options) {
alert("error!! "+response.responseText);
}})
}});
Everything is perfect until I reach templating section where console log says-
Uncaught ReferenceError: songs is not defined is not defined.
According to documentation , I think my template syntax is OK and I have passed the correct fetched data. Furthermore I have also defined variable songs. It would be helpful if someone can point it out my mistake.
Complete code here and json file here.
to fix your issue pass the songs as array temp({songs:songs.models})
in success callback you could add the parameter songs
success:function (songs){
var temp=_.template($("#songlist_template").html());
var html=temp({songs:songs.models});
that.$el.html(html);
},
http://backbonejs.org/#Collection-fetch
The options hash takes success and error callbacks which will both be passed (collection, response, options) as arguments
From the documentation of Underscore's template:
When you evaluate a template function, pass in a data object that has properties corresponding to the template's free variables
(Emphasis mine)
What #VladuIonut is trying to - correctly - explain is that you must pass in an object containing the properties that you reference in your template.
If your collection of songs doesn't have a property also called songs for the template to use, it will fail with this undefined ReferenceError.
Your template call should look like this:
var html=temp({
"songs": songs
});

How to use Backbone.Marionette.ItemView with Mustache

The following code works fine using Backbone.Marionette.ItemView but not Mustache.
Backbone.Marionette.ItemView - no Mustache
I would like to use the same code but loading the template varaible using Mustache.
Here is my code:
Backbone.Marionette.ItemView - with Mustache
Any idea why my code does not work and why?
Thanks
I'd like to update the answer here a bit as I was just struggling with this, and I was using this answer as a reference.
Here are my findings:
The answer here is a bit out of date with the current version of Mustache (which is understandable as it's pretty old)
Mustache.to_html is now deprecated, but still exists as a simple wrapper around Mustache.render for backwards compat. Check out this link.
Additionally, I found overriding Marionette.Renderer.render, as in the accepted answer above, completely bypasses the Marionette.TemplateCache layer which may not be the desired behavior.
Here's the source for the Marionette.Renderer.render method:
render: function(template, data){
if (!template) {
var error = new Error("Cannot render the template since it's false, null or undefined.");
error.name = "TemplateNotFoundError";
throw error;
}
var templateFunc;
if (typeof template === "function"){
templateFunc = template;
} else {
templateFunc = Marionette.TemplateCache.get(template);
}
return templateFunc(data);
}
Source
As you can see it accesses the Marionette.TemplateCache.get method and the above answer does nothing to maintain that functionality.
Now to get to my solve (note: the above answer is not wrong necessarily; this is just my approach to maintain the Marionette.TemplateCache layer):
As the comments suggest above, override compileTemplate instead:
Marionette.TemplateCache.prototype.compileTemplate = function(rawTemplate) {
// Mustache.parse will not return anything useful (returns an array)
// The render function from Marionette.Renderer.render expects a function
// so instead pass a partial of Mustache.render
// with rawTemplate as the initial parameter.
// Additionally Mustache.compile no longer exists so we must use parse.
Mustache.parse(rawTemplate);
return _.partial(Mustache.render, rawTemplate);
};
Here's a working JSFiddle as proof.
In the fiddle, I've also overridden Marionette.TemplateCache.loadTemplate to demonstrate that it's only called once. The body of the function only adds some debug output and then re-implements most of the original functionality (minus error handling).
Marionette assumes the use of UnderscoreJS templates by default. Simply replacing the template configuration for a view isn't enough. You also need to replace how the rendering process works.
In your simple example, you only need to override the Marionette.Renderer.render function to call Mustache, and then set the template of your views to the string template that you want:
Backbone.Marionette.Renderer.render = function(template, data){
return Mustache.to_html(template, data);
}
var rowTemplate = '{{ username }}{{ fullname }}';
// A Grid Row
var GridRow = Backbone.Marionette.ItemView.extend({
template: rowTemplate,
tagName: "tr"
});
Note that your JSFiddle still won't work even when you put this code in place, because the GridView is still using a jQuery selector/string as the template attribute. You'll need to replace this with the same type of template function to return mustache.
http://jsfiddle.net/derickbailey/d7qDz/

Resources