Trouble running underscore templates - backbone.js

I'm unable to run render HTML from an Underscore template. Here's the code and the error:
//Code
class QuestionView extends Backbone.View {
template:(data:any) => string = null;
$el = $root;
constructor(options?:any, question?:Question) {
super(options);
this.model = question;
var q = this;
require(["text!add-new-question.html"],
function (html) {
q.template = _.template(html);
}
);
}
render() {
var data = this.model.toJSON();
var html = this.template(data);
this.$el.html(html);
return this;
}
}
class SurveyView extends Backbone.View{
tagName = "div";
template:(data:any) => string = null;
$el = $root;
constructor(options?:any, survey?:Survey){
super(options);
this.template = _.template("<div ><%= enlistQuestions %></div>");
}
enlistQuestions():string{
var questions = "";
_.each(this.collection.models, function(question:Question){
var view = new QuestionView(null,question);
questions += view.template(view.model.toJSON()); // This is line for which the error is shown.
});
return questions;
}
}
//Error
Uncaught TypeError: Property 'template' of object #<QuestionView> is not a function Main.ts:63
(anonymous function) Main.ts:63
j.each.j.forEach underscore.js:79
SurveyView.enlistQuestions Main.ts:61
SurveyView.render Main.ts:69
SurveyView Main.ts:56
(anonymous function) Main.ts:80

Looks like you are using the "text" AMD loader plugin. If not, disregard this answer.
This plugin loads the resource asynchronously and the function supplied to the require statement is called back after the file is loaded.
It appears that the Main is attempting to use the template property on the QuestionView instance before the callback has chances to create it, hence the error.
The text module creates the XHR object and invokes open set the async flag to true. You need to set this flag to false. The only way this is possible is to use the onXhr callback and invoke OPEN again setting the async flag to false.
requirejs.config({
config: {
text: {
onXhr: function (xhr, url) {
// This callback is invoked AFTER open but before send.
// Call open again with the async flag turned off.
// Not this will also clear any custom headers
xhr.open('GET', url, /*async*/ false);
// xhr.setRequestHeader('header', 'value');
}
}
}
});
As noted above, any custom HTTP header added by the text module will be cleared when you re-invoke open, but looking at their code there doesn't appear to be any way of supplying extra headers into their 'get' function.

Related

Breeze - constructor not being invoked

I'm building a new Ng 1 project using Breeze. I need to add client-side properties to some entities. I added a constructor, but it does not get called. I added a post initializer, it is not called either.
// a convention can self-register as the default
breeze.NamingConvention.camelCase.setAsDefault();
// create a manager to execute queries
service.manager = new breeze.EntityManager("/api/pts");
// access to the manager's store to add custom client-side properties ..
service.metastore = service.manager.metadataStore;
// entities with client-side properties ..
service.metastore.registerEntityTypeCtor('PtsWine__Pts_BreezeModel', PtsWine, ptsWineInitializer);
var PtsWine = function () {
debugger;
this.uiDayUid = 0;
}
var ptsWineInitializer = function (pw) {
debugger;
pw.uiDayUid = 0;
}
var query = new service.EntityQuery().from("Wines");
return service.manager.executeQuery(query)
.then(function (d) {
debugger;
/// ??? examining d.results shows 40 PtsWine__Pts_BreezeModel entities but
/// constructor and initializer above were never invoked .. ???
})
.fail(function (response) {
toastr.error("Server Error: failed to retrieve listing.")
});
The initialization sequence does not seem correct to me. At the time registerEntityTypeCtor is executed PtsWine and ptsWineInitializer are undefined. Remember javascript hoisting concept? https://developer.mozilla.org/en-US/docs/Glossary/Hoisting
You need to define the variable before calling registerEntityTypeCtor.

Angular - update services object during asynchronous function

Folks: Creating an app in angular and node webkit - where users queue up files for downloading, navigate to their dashboard view and this initiates the downloads.
I've created a service which holds an object of the files data:
..
var downloadObj = {};
// fileObj = {'name':'The file name'; 'download_progress' : dlProgress}
showcaseFactory.myDownloads = function(eventId, fileObj) {
if(eventId){
console.log('update the object');
downloadObj['event_'+eventId] = fileObj;
}
console.log(downloadObj);
};
showcaseFactory.getDownloads = function() {
return downloadObj;
};
..
When the dashboard view loads - ng-repeat loops over $scope.downloadFiles which references this object returning the data.
<div ng-repeat="file in downloadFiles">
<div><span>{{file.name}}</span> [{{file.download_progress}}%]</div>
</div>
I've created a custom module which utilises node_modules to perform the download of the files:
nwjsDownloadFactory.commenceDownload = function(event_id, url, dest, cb) {
var http = require('http');
var fs = require('fs');
var statusBar = require('status-bar');
var path = require('path');
// THIS UPDATES THE OBJECT AND DISPLAYS FINE --------- >>
var id = 7;
var testFileObj = {
'name' : 'This is the file name prior to the download...',
'download_progress' : 10
};
ShowCase.myDownloads(id, testFileObj);
// <<< THIS UPDATES THE OBJECT AND DISPLAYS FINE ---------
var file = fs.createWriteStream(dest);
var request = http.get(url, function(response) {
response.pipe(file);
file.on('finish', function() {
file.close(cb); // close() is async, call cb after close completes.
});
bar = statusBar.create({ total: response.headers['content-length'] })
.on('render', function (stats) {
// var percentage = this.format.percentage(stats.percentage);
// console.log(event_id + '....' + percentage);
var id = 7;
var testFileObj = {
'name' : 'This is the new file name during the download...',
'download_progress' : 35 // this will be replaced with percentage
};
ShowCase.myDownloads(id, testFileObj);
});
response.pipe(bar);
}).on('error', function(err) { // Handle errors
fs.unlink(dest); // Delete the file async. (But we don't check the result)
if (cb) cb(err.message);
});
}
QUESTION: Prior to the line var request = http.get(url, function(response) the object gets updated, and the changes are reflected in the UI. However, I need to constantly update the object with download complete % so I can create a progress bar.. However, as this asynchronous function executes, the object
appears to be updating - see the attached screen shot - but the UI is not reflecting this.
Can somebody please steer me in the right direction - I need the object to update during the function bar = statusBar.create({ and for the changes to reflect in the UI..
Call $scope.$apply() after making changes to your model to notify Angular that it has to update the UI.
showcaseFactory.myDownloads = function(eventId, fileObj) {
if(eventId){
console.log('update the object');
downloadObj['event_'+eventId] = fileObj;
$scope.$apply();
}
console.log(downloadObj);
};
If you use Angular's $http object, this is handled automatically for you, but if you update your model from other asynchronous callbacks, you have to take care of it yourself.
See this blog post and this documentation page for more in-depth explanations about what's going on.

Backbone Router has no method navigate

Just wondering why my router has no method navigate?
I have new App.Router; called properly. And in my view I try to call my index view like so:
addTrip: function(e){
e.preventDefault() ;
//Adding to collection
var newTrip = this.collection.create({
title: this.$('#new_trip #title').val(),
where: this.$('#new_trip #where').val(),
desc: this.$('#new_trip #desc').val(),
rating: this.$('#new_trip .active').text()
}, { wait: true }) ;
if(newTrip.validationError){
this.errorReport(newTrip.validationError) ;
}
this.clearForm() ;
//Navigate to home route
App.Router.navigate('', true) ;
}
I get the following error in Chrome Dev Tools:
Uncaught TypeError: Object function (){ return parent.apply(this, arguments); } has no method 'navigate'
I even tried to call navigate from the console and it doesnt seem to work either.
What am I doing wrong?
You should call navigate on your router object. It seems you are calling it on the class itself.
App.myRouter = new Backbone.Router() //or if you have your own base router class App.myRouter = new App.Router()
myRouter.navigate('', true);

Backbone collection.create not creating the model locally

I gave up finally. I have struggling to get this one to work but no luck. I simply have a collection.create call like this:
var createData = {
full_name : full_name,
email : email,
role_id : role_id
};
var that = this;
app.collections.teamMembers.create(createData,{
wait: true,
success : function(){
log("in success")
},
error : function(a,b,c){
log("in error")
}
})
The server is PHP and it returns the result like this:
header('Content-type: application/json');
echo json_encode(array(
"data" => $data,
"meta" => $meta
));
In the above, the $data is actually the array("attr"=>"val", ...) which matches exactly how the model for this collection is defined.
The problem is that since I am not returning directly a JSON object similar to the original model, but using namespacing (data/meta), I use model.parse on the model like this:
parse : function(response){
log(response, "inside model parse, this is the response from server")
return response.data;
},
ISSUE: The model doesn't get created on the client end. No 'add' event is fired. I am also using the wait:true option.
However, the model gets created on the local if:
- I don't use wait:true
- I use wait true but return the exact JSON model from server, with no name spacing.
I WANT to use wait:true as well as namespacing. Please help :(
Finally I was able to fix it, I was overriding backbone collections and models in my bootstrap to have a loading state which I am not using anyway. So I commented out that whole code. Now it works fine. this was the code in my bootstrap that I commented out:
// OVERRIDINGS AND SETTINGS
//----------------------
// Adding close method to all views
Backbone.View.prototype.close = function() {
if (this.onClose) {
this.onClose();
}
_.each(this.childViews, function(childView){
childView.close();
delete childView;
})
this.remove();
this.unbind();
};
// Adding loading state to every model and collection
Backbone.Collection.prototype.loading = false;
Backbone.Model.prototype.isLoading = false;
// Set isLoading to true when fetch starts
var oldFetch = Backbone.Collection.prototype.fetch;
Backbone.Collection.prototype.fetch = function(options) {
this.isLoading = true;
oldFetch.call(this, options);
}
Backbone.Model.prototype.fetch = function(options) {
this.isLoading = true;
oldFetch.call(this, options);
}
// Turn off isLoading when reset
Backbone.Collection.prototype.on('reset', function(){
this.isLoading = false;
})
Backbone.Model.prototype.on('reset', function(){
this.isLoading = false;
})

JSON.stringify() with collection in backbone.js

Can someone explain how JSON.stringify() magically ONLY stringifies JSON as fetched by URL and does NOT bother with other backbone-specific parts of the full collection object?
I am curious about underlying implementation and/or design patterns that explain this very impressive capability. I had to use json2.js to get stringify functionality, so I don't think backbone is overriding or decorating stringify.
I discovered that if I pass a collection directly to JS OBJECT code, the code "sees" model keys and other backbone-specific parts of a collection object, whereas if I perform JSON.stringify THEN jquery.parseJSON on that stringified object, my code "sees" only the JSON as returned by URL.
Code:
enter code here
$(function () {
var Person = Backbone.Model.extend({
initialize: function () {
// alert("Model Init");
}
}),
PersonList = Backbone.Collection.extend({
model: Person,
url: '/Tfount_Email/Email/SOAInbox',
initialize: function () {
// alert("Collections Init");
}
}),
personlist = new PersonList();
personlist.fetch({
error: function () {
alert("Error fetching data");
},
success: function () {
// alert("no error");
}
}).complete(function () {
// first call to makeTable w collection obj, we see MORE than just the JSON returned by URL
makeTable(personlist);
// stringify then parse, we see only JSON returned by URL
jsonString = JSON.stringify(personlist);
var plistJSON = jQuery.parseJSON(jsonString);
makeTable(plistJSON);
});
});
function makeTable(obj) {
var type = typeof obj
if (type == "object") {
for (var key in obj) {
alert("key: " + key)
makeTable(obj[key])
}
} else {
alert(obj)
}
}
This is the intended and by-design behavior of JSON.Stringify. From Douglas Crockford's JSON2.js file:
When an object value is found, if the object contains a toJSON method, its toJSON method will be called and the result will be stringified.
https://github.com/douglascrockford/JSON-js/blob/master/json2.js#L38-39
When you call JSON.stringify on a Backbone.Collection, it calls that collection's toJSON method, as described by this comment.

Resources