Render view with overwritten getters in Backbone - json

I am building a small Backbone.js application and added some custom getters to one of the models (the name getter returns a concatenated first- and last name for example):
PersonModel = Backbone.Model.extend({
get: function (attr) {
if (typeof this[attr] == 'function') {
return this[attr]();
}
return Backbone.Model.prototype.get.call(this, attr);
},
name: function() {
return firstName + " " + lastName;
}
})
I can now use person.get("name") to retrieve the name, nice. However, when I call toJSON on the the model these values aren't included (and I suppose that makes sense). Problem is I use this to render my views:
this.template({people: this.collection.toJSON()});
What's the best way to do this in Backbone.js? Manually creating the JSON with the overwritten getters?
Thanks!

You could provide your own toJSON method on PersonModel:
toJSON: function() {
var attr = Backbone.Model.prototype.toJSON.call(this);
attr.name = this.name();
return attr;
}
The collection toJSON just calls toJSON on each model:
// The JSON representation of a Collection is an array of the
// models' attributes.
toJSON : function() {
return this.map(function(model){ return model.toJSON(); });
},
so adding your own toJSON to your model should work.
You could also add name as a real attribute and then adjust your model's validate method to update name if firstName or lastName changes and to ignore any direct attempts to change name or complain about "an attempt to edit a read-only attribute" when someone tries to pass a name change to set. There's nothing that says that validate can't change the attribute object that it is given so you could be given {firstName: 'x'} and change it to {firstName: 'x', name: 'x ' + this.get('lastName')} before validate returns. This would be a bit of an abuse of validate but there is no explicit prohibition against validate altering the attribute set and it is the only hook you have. I suppose you could have the model listen to change events on its own firstName and lastName and then trigger a set({name: ...}) but then you could have event ordering problems if someone else is watching only the first and last names.

I choose to extend the backbone model and have all my apps models extend from a Base model which includes a method so enable setting virtual attributes which are merged into models toJSON method.
toJSON: function() {
return _.extend(toJSON.__super__.constructor.apply(this, arguments), this.getVirtualAttributes());
},
getVirtualAttributes: function() {
var attrs, key;
attrs = {};
for (key in this.virtualAttributes) {
attrs[key] = this.virtualAttributes[key].call(this);
}
return attrs;
}
This allows you to define a virtualAttributes object on a model which can be used for retrieving a virtual/dynamic attribute.
virtualAttributes: {
email_length: function() {
return this.get("email").length
}
}

Related

Asp.net mvc deserialize ajax.beginForm

I need to pass my model, built in this form:
using (Ajax.BeginForm("Index", null, new AjaxOptions() { UpdateTargetId = "FormContainer", HttpMethod = "Post", InsertionMode = InsertionMode.Replace, OnSuccess = "successFunc" }, new { id = "UpdateForm" }))
To this method:
public ActionResult SavePreset(DocumentFilterModel model, string name)
{
//Do some magic
return PartialView("Partial/FilterListPartial", model);
}
The point is that by default, this form will collect report presets, however i need to add and option to save preset in my DB, that is why SavePreset method is needed.
I have tried to use this script:
$("#SavePresetButton").on("click", function () {
$.ajax({
type: "POST",
url: '#Url.Action("SavePreset", "Reports")',
data: {
name: $("#PresetNameEditor").val(),
model: $("#UpdateForm").serialize()
}
}).success(function(result) {
$("#FilterSettingsContainer").html(result);
});
});
But i have encountered a problem, where i either get null in DocumentFilterModel model either (if change model parametr's type to string) can't deserialize it. Things i have tried:
var SettingsModel = new JavaScriptSerializer().Deserialize<DocumentFilterModel>(model);
var a = JsonConvert.DeserializeObject<DocumentFilterModel>(model);
By the way, (these filters located in separate partial view) i would like to keep my form as it is, because i still need to update my second partial view with lists of record, and DocumentFilterModel is too big to parse it manually.
The serialize method reads your form and generates a url encoded string with your input element names and values. So basically it will be a big querystring. When you pass that as the data property of the $.ajax call, jquery will use that for the request body (like FormData)
So when you try something like this
data:{
name: $("#PresetNameEditor").val(),
model: $("#UpdateForm").serialize()
}
It it trying to send an object like this
{name: "Abc", model: "FirstName=Scott&Email=someEmail"}
You can see that you have a js object with 2 properties and the second property has all your input values in a query string format. The model binder cannot read this data and map to your DocumentFilterModel object!.
You cannot mix the result of serialize method to build a js object you want to send as data.
Simply use the result of serialize as the data property value of the ajax call and pass name in querystring.
$("#SavePresetButton").on("click", function () {
$.ajax({
type: "POST",
url: '#Url.Action("SavePreset", "Reports")?name='+$("#PresetNameEditor").val(),
data: $("#UpdateForm").serialize()
}).done(function(result) {
console.log(result);
});
});

Autodesk Forge Viewer3d search using attributeNames

I'm trying to implement .search() and restrict attributeNames using the optional parameter but it always brings back an empty array.
https://developer.autodesk.com/en/docs/viewer/v2/reference/javascript/viewer3d/
Can someone clarify how this filter is being applied? I was expecting it to look at the returned property.displayName but apparently that's not the case.
Example:
viewer.search('13-097', function (ids) {
console.log(ids);
var id = ids[0];
viewer.getProperties(id, function (obj) {
console.log(obj.properties);
});
}, function (e) { });
viewer.search('13-097', function (ids) {
console.log(ids);
}, function (e) { }, ['ADDRESS']);
Output:
first search:
[8095]
second search:
[]
from object 8095, properties:
10:Object
displayCategory:"DWF - Construction"
displayName:"ADDRESS"
displayValue:"13-097"
hidden:false
type:20
units:null
Please note the Autodesk.Viewing.Viewer3D.search() method is NOT case sensitive on the text parameter, but it IS case sensitive on the attributeNames parameter, and you need to use the full name of the attribute.
If you are using the displayName of properties to correlate, note that viewer.getProperties() is currently returning the displayName. When there is no displayName, then (and only then) attribute name is returned.
Below is a sample I tried before (adjusted to your dataset):
function search() {
viewer.clearSelection(); // remove previously highlighted searches
var searchStr = '13-097';
var searchPropList = new Array('ADDRESS');
viewer.search(searchStr, searchCallback, searchErrorCallback, searchPropList);
}
function searchCallback(ids) {
alert(ids.length);
}
function searchErrorCallback(error) {
console.log(error);
}
EDIT (Oct 24, 2016)
The Viewer 2.11 .getProperties method returns attributes, which can be used on the .search attributesNames parameter.

Weird .hasOwnProperty behaviour

In an effort to properly instantiate Typescript objects from data received over HTTP as JSON, I was exploring the possibility of using the for..in loop coupled with .hasOwnProperty() like so:
class User {
private name: string;
private age: number;
constructor(data: JSON) {
console.log('on constructor\ndata', data);
for (var key in data) {
console.log('key:', key);
if (User.hasOwnProperty(key)) {
console.log('User has key:', key);
this[key] = data[key];
}
}
}
displayInfo(): string{
return JSON.stringify(this);
}
}
let button = document.createElement('button');
button.textContent = "Test";
button.onclick = () => {
try{
let json = JSON.parse('{"name": "Zorro","age": "24"}');
let usr = new User(json);
console.log(usr.displayInfo());
}catch (error){
alert(error);
}
}
document.body.appendChild(button);
Using similar code in my project fails completely. That is expected as the compiled JS code has no awareness of the private TS vars and so, hasOwnProperty is always false.
However, I was using the Typescript Playground, and running that code there produces the following output in the console:
on constructor
data Object {name: "Zorro", age: "24"}
key: name
User has key: name
key: age
{"name":"Zorro"}
As you can see, there are clearly some unexpected things happening here. The first key is recognized and the new User instance is initialized with the value from the JSON, yet that does not happen for the second key.
Can someone explain why this happens?
As was pointed out in the comments, you should be using this.hasOwnProperty instead of User.hasOwnProperty. And as you noticed, this code is busted anyway because the property declarations in the class don't actually create own properties on the object (they would need to be initialized for this to happen).
But why did you get a hit on the name key? The User object is a constructor function for your class. Functions do have a name property:
function fn() { }
console.log(fn.name); // prints 'fn'
They don't have an age property, of course.
Your constructor would of course just have to look like this, if you want to construct User instances from plain JavaScript objects:
constructor(data: any) {
this.name = data.name;
this.age = data.age;
}

Convert Mongoose docs to json

I returned mongoose docs as json in this way:
UserModel.find({}, function (err, users) {
return res.end(JSON.stringify(users));
}
However, user.__proto__ was also returned. How can I return without it? I tried this but not worked:
UserModel.find({}, function (err, users) {
return res.end(users.toJSON()); // has no method 'toJSON'
}
You may also try mongoosejs's lean() :
UserModel.find().lean().exec(function (err, users) {
return res.end(JSON.stringify(users));
});
Late answer but you can also try this when defining your schema.
/**
* toJSON implementation
*/
schema.options.toJSON = {
transform: function(doc, ret, options) {
ret.id = ret._id;
delete ret._id;
delete ret.__v;
return ret;
}
};
Note that ret is the JSON'ed object, and it's not an instance of the mongoose model. You'll operate on it right on object hashes, without getters/setters.
And then:
Model
.findById(modelId)
.exec(function (dbErr, modelDoc){
if(dbErr) return handleErr(dbErr);
return res.send(modelDoc.toJSON(), 200);
});
Edit: Feb 2015
Because I didn't provide a solution to the missing toJSON (or toObject) method(s) I will explain the difference between my usage example and OP's usage example.
OP:
UserModel
.find({}) // will get all users
.exec(function(err, users) {
// supposing that we don't have an error
// and we had users in our collection,
// the users variable here is an array
// of mongoose instances;
// wrong usage (from OP's example)
// return res.end(users.toJSON()); // has no method toJSON
// correct usage
// to apply the toJSON transformation on instances, you have to
// iterate through the users array
var transformedUsers = users.map(function(user) {
return user.toJSON();
});
// finish the request
res.end(transformedUsers);
});
My Example:
UserModel
.findById(someId) // will get a single user
.exec(function(err, user) {
// handle the error, if any
if(err) return handleError(err);
if(null !== user) {
// user might be null if no user matched
// the given id (someId)
// the toJSON method is available here,
// since the user variable here is a
// mongoose model instance
return res.end(user.toJSON());
}
});
First of all, try toObject() instead of toJSON() maybe?
Secondly, you'll need to call it on the actual documents and not the array, so maybe try something more annoying like this:
var flatUsers = users.map(function() {
return user.toObject();
})
return res.end(JSON.stringify(flatUsers));
It's a guess, but I hope it helps
model.find({Branch:branch},function (err, docs){
if (err) res.send(err)
res.send(JSON.parse(JSON.stringify(docs)))
});
I found out I made a mistake. There's no need to call toObject() or toJSON() at all. The __proto__ in the question came from jquery, not mongoose. Here's my test:
UserModel.find({}, function (err, users) {
console.log(users.save); // { [Function] numAsyncPres: 0 }
var json = JSON.stringify(users);
users = users.map(function (user) {
return user.toObject();
}
console.log(user.save); // undefined
console.log(json == JSON.stringify(users)); // true
}
doc.toObject() removes doc.prototype from a doc. But it makes no difference in JSON.stringify(doc). And it's not needed in this case.
Maybe a bit astray to the answer, but if anyone who is looking to do the other way around, you can use Model.hydrate() (since mongoose v4) to convert a javascript object (JSON) to a mongoose document.
An useful case would be when you using Model.aggregate(...). Because it is actually returning plain JS object, so you may want to convert it into a mongoose document in order to get access to Model.method (e.g. your virtual property defined in the schema).
PS. I thought it should have a thread running like "Convert json to Mongoose docs", but actually not, and since I've found out the answer, so I think it is not good to do self-post-and-self-answer.
You can use res.json() to jsonify any object.
lean() will remove all the empty fields in the mongoose query.
UserModel.find().lean().exec(function (err, users) {
return res.json(users);
}
It worked for me:
Products.find({}).then(a => console.log(a.map(p => p.toJSON())))
also if you want use getters, you should add its option also (on defining schema):
new mongoose.Schema({...}, {toJSON: {getters: true}})
Try this options:
UserModel.find({}, function (err, users) {
//i got into errors using so i changed to res.send()
return res.send( JSON.parse(JSON.stringify(users)) );
//Or
//return JSON.parse(JSON.stringify(users));
}
Was kinda laughing at how cumbersome this was for a second, given that this must be extremely common.
Did not bother digging in the docs and hacked this together instead.
const data = await this.model.logs.find({ "case_id": { $regex: /./, $options: 'i' }})
let res = data.map(e=>e._doc)
res.forEach(element => {
//del unwanted data
delete element._id
delete element.__v
});
return res
First i get all docs which have any value at all for the case_id field(just get all docs in collection)
Then get the actual data from the mongoose document via array.map
Remove unwanted props on object by mutating i directly

Automatic population of Backbone.Collection using JSON objects

//Model
var Dog = Backbone.Model.extend({
name:'',
breed:''
});
//Collection
var Dogs = Backbone.Collection.extend({
model : Dog,
url : '/dogs'
parse : function(res)
{
alert('response' + res);
return res;
}
});
This is the JSON objec that I receive from server which is implemented using Jersey.
I return a List of DogModel from Server, it is converted to JSON
#Produces(MediaType.APPLICATION_JSON)
{"DogModel":[{"name":"Jane","breed":"Great Dane"},
{"name":"Rocky","breed":"golden Retriver"},
{"name":"Jim","breed":"Lab"}]}
Wonder I haven't understood the usage of Collection and its url attribute correctly.
My assumption is that, when ever fetch is called on Collection, it'll fetch the dogs details from the server and populate the collection.
I do get the response as stated above but the collection is not populated as expected.
What should I do to automatically populate the list of models with the collection?
Do I need to work on the representation of JSON objects?
Help Appreciated!!!
The parse function needs to return the array of dogs. So you can update your code as follows.
parse : function(res)
{
alert('response' + res);
return res.DogModel;
}
On a side note, you want to declare your model's default attribute values on the defaults hash like the code below shows (see documentation)
var Dog = Backbone.Model.extend({
defaults: {
name:'',
breed:''
}
});