JSON.stringify() with collection in backbone.js - 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.

Related

Find Object Array and its Properties from Ajax Post Request

I'm sending an AJAX request to an internal PHP and receiving back an object. The object has properties of "data" and "status", yet when I try to access them, it does not return anything. How can I show each property separately?
For reference, the returned obj array is:
{"data:[{"tagId":"8787","tagDescription":"001","tagMin":"0","tagMax":"100"},{"tagId":"8729","tagDescription":"1","tagMin":"44","tagMax":"555"}]
function GetAll() {
var PostRequest ={};
PostRequest['tagId']= 'all';
$.post('php here',PostRequest,ShowAllTags);
}
function ShowAllTags( responseData, responseStatus ) {
console.log(responseStatus);
var tagData = {};
tagData = responseData;
console.log(tagData['data']);
}
So according to the above comment mention by me, The problem is with json object, in response.
So first of all fix that,
Generic solution of this problem will be;
var obj = [{"tagId":"8787","tagDescription":"001","tagMin":"0","tagMax":"100"},{"tagId":"8729","tagDescription":"1","tagMin":"44","tagMax":"555"}];
obj.forEach(function(value, index){console.log(value.tagId)});
This might help, how to get value of each property

How to make $resource accept array of strings (AngularJS)

I would like to make a request to a REST-service in which the query parameters contain an array of strings:
productRestService.getProductsInfo(productIdsArray,"id,name,rating").$promise.
then(function(productData) { // success: Produktdaten auslesen
updateProductList(productData);
}, function (error) {
console.log("Status: " + error.status);
});
The Resource-Service is as follows:
productRestService.getProductsInfo = function(productIds, properties) {
console.log('productRestService.getProductsInfo(): productIds' + productIds);
var productInfoResourceData;
var ProductInfoResource = $resource('/rest/products/productsInfo/:productIds/:properties',
{
productIds:'#productIds',
properties:'#properties'
}
);
productInfoResourceData = ProductInfoResource.query(
{
productIds: productIds,
properties: properties
}
);
return productInfoResourceData;
}
Calling the service results to an 404-Error, because the default behaviour of the $resource object is that it expects an array of an object when "query" is used.
How can I achieve that my $resoure-service will accept an array of strings? I tried to use "transformRequest" (see snippet below), but that did not work either.
{
query: {
method: 'GET',
isArray: true,
transformResponse: function (data, headers) {
var tranformed = [];
[].forEach.call(eval(data), function (d) {
tranformed.push({ name: d });
});
return tranformed;
}
}
}
A console.log within the function of the REST service productService.getProductsInfo shows the correct data that the service received:
["212999cc-063b-4ae8-99b5-61a0af39040d","17e42a28-b945-4d5f-bab1-719b3a897fd0","9307df3e-6e7a-4bed-9fec-a9d925ea7dc0"]
The URL is correct with the other REST-URLS and should look this way (and is being concatenated to the domain accordingly):
'/rest/products/productsInfo/:productIds/:properties'
EDIT:
The other functions within the productService responds in order, they do not use arrays but JSON objects and do not show unexpected behaviour.
(This was originally a comment, but it needed cleanly formatted code samples.)
I suspect your :productIds template parameter is getting filled into the template as "[object Object]". I've only seen your template URL, not the actual constructed URL, so I can't be sure.
If your server is expecting a URL where the :productsIds template parameter is JSON, like for example ---
rest/products/productsInfo/["id1","id2","id3"]/{"prop1":true,"prop2":false}
--- then try editing your getProductsInfo definition to something like this:
productRestService.getProductsInfo = function (productIds, properties) {
var ProductsInfo = $resource('/rest/products/productsInfo/:productIds/:properties', {
productIds: function () {
return angular.toJson(productIds);
},
properties: function () {
return angular.toJson(properties);
}
});
return ProductsInfo.query();
}
(Fair warning, I didn't test this code. It's just a quick edit of your example.)
This way, you're making sure that the parameter values are converting to the JSON that the server expects (if the server is expecting JSON in the URL, that is).

How to convert json array to backbone collection

This is the json data returned by a rest-full service as a response:
{
"calendarEvent":[
{
"event_date":"2014-09-01",
"event_location":"bbbbb",
"event_name":"aaaa"
},
{
"event_date":"2014-08-04",
"event_location":"hotel",
"event_name":"bday"
},
{
"event_date":"2014-09-11",
"event_location":"cccc",
"event_name":"aaaa"
}
]
}
How can I convert this json array to backbone collection?
Override collection.parse when you want to customize how Backbone handles a server response :
parse collection.parse(response, options)
parse is called by Backbone whenever a collection's models are returned by the server, in
fetch. The function is passed the raw response object, and should
return the array of model attributes to be added to the collection.
In your case, your collection would look like:
var C = Backbone.Collection.extend({
parse: function(response) {
return response.calendarEvent;
}
});

Trouble running underscore templates

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.

Backbonejs - Avoid parse after save

Backbone documentation says,
parse is called whenever a model's data is returned by the server, in
fetch, and save. The function is passed the raw response object, and
should return the attributes hash to be set on the model.
But i have customized parse function for my model. I want to execute it only when i fetch data not when i save data.
Is there a way to do it? I can check my response inside parse function. But is there any built-in option to do it?
This is from the backbone source file regarding saving a model:
var model = this;
var success = options.success;
options.success = function(resp) {
model.attributes = attributes;
var serverAttrs = model.parse(resp, options);
if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
return false;
}
if (success) success(model, resp, options);
model.trigger('sync', model, resp, options);
};
You could pass a custom option on your save like: model.save(null, { saved: true }), then in your custom parse:
parse: function(response, options) {
if ( options.saved ) return this.attributes;
// do what you're already doing
}
I haven't tested this at all, but it should at least get you started.
Just pass a parse:false into the save method as an option.
m = new MyModel()
s.save(null, {parse: false})

Resources