Declaring a backbone model with defaults relative to other fields - javascript

How can I get the following code to do default based on other model values?
This is roughly what I have
var Model = Backbone.Model.extend({
defaults:{
name:"",
surname:"",
fullTextString:""
}
});
model = new Model({name"John", surname:"Doe", fullTextString:"John"+" "+"Doe"})
What I want is:
Backbone.Model.extend({
defaults:{
name:"",
surname:"",
fullTextString:name+" "+surname
}
});

Default values is only used when you do not set the values, so what you want is not possible.
I would use the following to get the functionality you want:
var Model = Backbone.Model.extend({
defaults: {
firstName: "",
lastName : ""
},
fullName: function() {
return this.get("firstName") + " " + this.get("lastName");
},
get: function( attr ) {
if ( typeof this[attr] === "function" ) {
return this[attr]();
}
return Backbone.Model.prototype.get.call(this, attr);
}
});
This way you can initialize your model this way:
model = new Model({firstName: "John", lastName: "Doe"});
And use model.get("fullName") to get the name "John Doe".

Given that you want one of the attributes to be a calculated value based upon both passed in values and, potentially, default values (for example, if they specify a {surname: "Munsch"} but provide no name), I think the easiest thing would be to add an initializer like so:
var Model = Backbone.Model.extend({
defaults:{
name:"",
surname:""
},
initialize: function() {
this.set({fullTextString: this.get("name") + " " + this.get("surname")});
}
});
model = new Model({name:"John", surname:"Doe"});
model2 = new Model({surname:"Doe"});
console.log("model fullTextString: ", model.get("fullTextString"));
console.log("model2 fullTextString: ", model2.get("fullTextString"));

Related

Knockout.js Adding a Property to Child Elements

My code doesn't create a new property under the child element of knockout viewmodel that is mapped by knockout.mapping.fromJS.
I have:
//model from Entity Framework
console.log(ko.mapping.toJSON(model));
var viewModel = ko.mapping.fromJS(model, mappingOption);
ko.applyBindings(viewModel);
console.log(ko.mapping.toJSON(viewModel));
The first console.log outputs:
{
"Id": 0,
"CurrentUser": {
"BoardIds": [
{
"Id": 0
}
],
"Id": 1,
"UserName": "foo",
"IsOnline": true
},
"Boards": []
}
And then the mappingOption is:
var mappingOption = {
create: function (options) {
var modelBase = ko.mapping.fromJS(options.data);
modelBase.CurrentUser.UserName = ko.observable(model.CurrentUser.UserName).extend({ rateLimit: 1000 });
//some function definitions
return modelBase;
},
'CurrentUser': {
create: function (options) {
options.data.MessageToPost = ko.observable("test");
return ko.mapping.fromJS(options.data);
}
}
};
I referred to this post to create the custom mapping, but it seemed not working as the second console.log outputs the same JSON to the first one.
Also, I tried to create nested mapping option based on this thread and another one but it didn't work too.
var mappingOption = {
create: function (options) {
//modelBase, modifing UserName and add the functions
var mappingOption2 = {
'CurrentUser': {
create: function (options) {
return (new(function () {
this.MessageToPost = ko.observable("test");
ko.mapping.fromJS(options.data, mappingOption2, this);
})());
}
}
}
return ko.mapping.fromJS(modelBase, mappingOption2);
}
};
How can I correctly add a new property to the original viewmodel?
From the mapping documentation for ko.toJS (toJS and toJSON work the same way as stated in the document)
Unmapping
If you want to convert your mapped object back to a regular JS object, use:
var unmapped = ko.mapping.toJS(viewModel);
This will create an unmapped object containing only the properties of the mapped object that were part of your original JS object
If you want the json to include properties you've added manually either use ko.toJSON instead of ko.mapping.toJSON to include everything, or use the include option when first creating your object to specify which properties to add.
var mapping = {
'include': ["propertyToInclude", "alsoIncludeThis"]
}
var viewModel = ko.mapping.fromJS(data, mapping);
EDIT: In your specific case your mapping options are conflicting with each other. You've set special instructions for the CurrentUser field but then overridden them in the create function. Here's what I think your mapping options should look like:
var mappingOption = {
'CurrentUser': {
create: function (options) {
var currentUser = ko.mapping.fromJS(options.data, {
'UserName': {
create: function(options){
return ko.observable(options.data);
}
},
'include': ["MessageToPost"]
});
currentUser.MessageToPost = ko.observable("test");
return ko.observable(currentUser).extend({ rateLimit: 1000 });
}
}
};
and here's a fiddle for a working example

Updating a view in backbone when a collection is updated

I have a web app that I am building, I have form input that allows you to enter a name, on entering this name, I want to update a list with that inputted name, my problem is however that if add one name and then another the previos name that is outputted to the view, is overwritten (but if I refresh the page I get the full list). Here is my code,
GroupModalHeaderView.prototype.render = function() {
this.$el.empty();
if (this.model.isNew()) {
this.$el.append(this.template({
m: this.model.toJSON()
}));
return this.edit();
} else {
this.$el.append(this.template({
m: this.model.toJSON()
}));
this.$el.find(".modal-header-menu").show();
return this.$el.find(".icon-button-close-modal").show();
}
};
GroupModalHeaderView.prototype.save = function(e) {
var $collection, $this;
if (e) {
e.preventDefault();
}
$this = this;
if (this.$("#group-name").val() !== "") {
$collection = this.collection;
if (this.model.isNew()) {
this.collection.push(this.model);
}
return this.model.save({
name: this.$("#group-name").val(),
async: false,
wait: true
}, {
success: function() {
return $this.cancel();
}
});
}
};
GroupListView.prototype.events = {
"click .list-header-add": "add",
"click .list-header-expander": "showHide",
"keyup #search-query": "keyup"
};
GroupListView.prototype.initialize = function() {
//console.log("fired");
this.collection.on("change", this.renderList, this);
this.collection.on("reset", this.render, this);
return this.renderList();
};
GroupListView.prototype.renderList = function(collection) {
var responsiveHeight = $("body").height() - 400;
if($("#people-network-requests").is(":visible")) {
this.$el.find("#people-groups-list").height($("#people-people-list").height()-250+"px");
} else {
this.$el.find("#people-groups-list").height($("#people-people-list").height()+"px");
}
var $collection, $this;
if (!collection) {
collection = this.collection;
}
this.$el.find(".list-items").empty();
$this = this.$el.find("#people-groups-list");
this.$el.find(".list-items").removeClass("list-items-loading").empty();
$collection = collection;
if ($collection.length < 1) {
/*this.$el.find("#people-groups-inner").hide();
$(".activity-no-show").remove();
return this.$el.find("#people-groups-inner").append('<div class="activity-no-show">\
<p>To add a new group, click the + in the top right hand corner to get started.</p>\
</div>');*/
} else {
this.collection.each(function(item) {
var displayView;
displayView = new app.GroupListDisplayView({
model: item,
collection: $collection
});
console.log($this);
return $this.append(displayView.render());
});
return this;
}
};
return GroupListView;
})(app.BaseView);
GroupListDisplayView.prototype.render = function() {
//console.log(this.$el);
//alert("1");
var $body;
this.$el.html(this.template({
m: this.model.toJSON()
}));
$body = this.$el.find(".card-body");
$text = $body.text();
$.each(this.model.get("people"), function(i, person) {
var personTile;
this.person = new app.Person({
id: person.id,
avatar: person.avatar,
first_name: person.first_name,
last_name: person.last_name
});
personTile = new app.PersonTileView({
model: this.person
});
if(person.id) {
$body.append(personTile.render()).find(".instruction").remove();
}
});
return this.$el.attr("id", "group-card-" + this.model.id);
};
GroupListView.prototype.keyup = function() {
this.filtered = $collection.searchName(this.$el.find("#search-query").val());
//console.log(this.filtered);
return this.renderList(this.filtered);
};
this.collection.on("add", this.addDisplayView, this);
Then create a function addDisplayView that accepts the model for the view. You will need to refactor the this.collection.each(function(item)... part of your code to use the addDisplayView function.
GroupListView.prototype.addDisplayView = function(model){
var displayView = new app.GroupListDisplayView({
model: model,
collection: this.collection
});
// use this.$, as it is already mapped to the context of the view
return this.$("#people-groups-list").append(displayView.render());
}
You should also change this.collection.push(this.model); to this.collection.add(this.model);
addcollection.add(models, [options])
Add a model (or an array of models) to the collection, firing an "add" event. If a model property
is defined, you may also pass raw attributes objects, and have them be
vivified as instances of the model. Pass {at: index} to splice the
model into the collection at the specified index. If you're adding
models to the collection that are already in the collection, they'll
be ignored, unless you pass {merge: true}, in which case their
attributes will be merged into the corresponding models, firing any
appropriate "change" events.
http://documentcloud.github.io/backbone/#Collection-add

Add JSON objects to JSON object

I have a JSON object employees which I would like to populate with the data in my localstorage. I first saved my JSON object to local storage using stringify() .
sessionStorage.setItem('Employee3', JSON.stringify({id: 3, firstName: 'Dwight', lastName: 'Schrute', title: 'Assistant Regional Manager', managerId: 2, managerName: 'Michael Scott', city: 'Scranton, PA', officePhone: '570-444-4444', cellPhone: '570-333-3333', email: 'dwight#dundermifflin.com', reportCount: 0}));
Now I want to populate my employees object:
employees: {},
populate: function() {
var i = i;
Object.keys(sessionStorage).forEach(function(key){
if (/^Employee/.test(key)) {
this.employees[i] = $.parseJSON(sessionStorage.getItem(key));
i++;
}
});
},
The function $.parseJSON(sessionStorage.getItem(key)) returns the JSON object correctly. Assigning it to the employees object fails:
Uncaught TypeError: Cannot set property 'undefined' of undefined
Array.forEach doesn't preserve this, so you'll have to preserve it yourself. Either of these will work (see mdn):
Object.keys(sessionStorage).forEach(function(key){
if (/^Employee/.test(key)) {
this.employees[i] = $.parseJSON(sessionStorage.getItem(key));
i++;
}
}, this);
var self = this;
Object.keys(sessionStorage).forEach(function(key){
if (/^Employee/.test(key)) {
self.employees[i] = $.parseJSON(sessionStorage.getItem(key));
i++;
}
});
Also, consider using the browser's JSON.parse() instead of jQuery's. Any browser that supports Array.forEach will support JSON.parse().
Yet another way:
Object.keys(sessionStorage).forEach((function(key){
if (/^Employee/.test(key)) {
this.employees[i] = $.parseJSON(sessionStorage.getItem(key));
i++;
}
}).bind(this));
Calling .bind(this) on a function will return a new function bound to the value for this (in the current scope).
the advantage to this is that you don't need to remember which methods support the second "value for this" parameter. It always works. For example, this also works for when adding event listeners to DOM nodes.
tjameson's first suggestion is probably to be preferred in this specific case.
You have a problem with the scope of this. When you are inside the foreach-callback this is not referring to the correct instance.
You need to save a reference to this before and then access the object through that reference (self in the following example):
function something(){
var self = this;
** snip **
employees: {},
populate: function() {
var i = i;
Object.keys(sessionStorage).forEach(function(key){
if (/^Employee/.test(key)) {
self.employees[i] = $.parseJSON(sessionStorage.getItem(key));
i++;
}
});
},
** snip **
}
You have a problem with the value of this inside the forEach callback. Also, And you don't need jQuery to parse JSON.
You can do this instead:
employees: {},
populate: function() {
var that = this;
var i = i;
Object.keys(sessionStorage).forEach(function(key){
if (/^Employee/.test(key)) {
that.employees[i] = JSON.parse(sessionStorage.getItem(key));
i++;
}
});
},
There is no reason to parse JSON, two simple functions will do the job (hope that was the idea):
var employees = {};
function setEmployee( data ) {
var id = data.id;
sessionStorage.setItem('Employee' + id, JSON.stringify( data ));
};
function getEmployee( id ) {
employees[id] = sessionStorage.getItem('Employee' + id);
};
var oneEmployee = {
id: 3,
firstName: 'Dwight',
lastName: 'Schrute',
title: 'Assistant Regional Manager',
managerId: 2,
managerName: 'Michael Scott',
city: 'Scranton, PA',
officePhone: '570-444-4444',
cellPhone: '570-333-3333',
email: 'dwight#dundermifflin.com',
reportCount: 0
};
setEmployee( oneEmployee );
getEmployee( 3 );

Why are my Backbone Models nested strangely within a Collection, requiring drilling down to access methods/properties?

I've got a Collection and a Model, both using attributes/options to augment them with additional capabilities. Here's the Model (LoadRouteGroup):
return Backbone.Model.extend({
initialize: function () {
console.log(this);
},
fetchf: function () {
console.log("FETCH");
}
});
And the Collection (LoadRouteGroups):
return Backbone.Collection.extend({
constructUrl: function(options) {
if (options.groupingType === "facility") {
// TODO: new endpoint: /api/v1/loadroutes?grouping=facility
this.url = clawConfig.endpoints.webApiRootUrl + "/api/loads/facilities";
}
else {
this.url = clawConfig.endpoints.webApiRootUrl + "/api/v1/loadroutes";
}
},
initialize: function (models, options) {
options || (options = {});
this.constructUrl(options);
console.log(this);
}
});
They're instantiated as such:
var loadRouteGroup = new LoadRouteGroup({
entityType: "facility"
});
// WORKS
loadRouteGroup.fetchf();
// assign groupingType option to collection to denote which URL to use
var loadRouteGroups = new LoadRouteGroups({
model: loadRouteGroup
}, {
groupingType: "facility"
});
var firstGroup = loadRouteGroups.at(0);
// DOESN'T WORK
firstGroup.fetchf();
// WORKS
firstGroup.attributes.model.fetchf();
I would expect that call to firstGroup.fetchf() to work... but it doesn't. Instead, I have to weirdly drill down and use firstGroup.attributes.model.fetchf() in order to access the method.
What's going on here? This would seem straightforward to me, but I can't for the life of me figure out what's wrong with the relationship between my Collection and Model.
The collection definition should include the model type:
return Backbone.Collection.extend({
// ....
model: LoadRouteGroup
});
When initializing the collection, pass in an array of models:
var loadRouteGroup = new LoadRouteGroup({
entityType: "facility"
});
var loadRouteGroups = new LoadRouteGroups([loadRouteGroup], {
groupingType: "facility"
});
Specify the model when you extend the collection instead of when you instantiate.

Backbone Model defaults - unnecessary code in todos.js example?

In the backbone.js ToDos example, The initialize method of the ToDo constructor sets the title attribute to the default title.
Isn't this unnecessary? I thought the point of defaults is that they get assigned automatically? Or am I missing something?
var Todo = Backbone.Model.extend({
// Default attributes for the todo item.
defaults: function() {
return {
title: "empty todo...",
order: Todos.nextOrder(),
done: false
};
},
// Ensure that each todo created has `title`.
initialize: function() {
if (!this.get("title")) {
this.set({"title": this.defaults().title});
}
},
///...
);}
A default value will only be applied if no corresponding attribute is passed to the constructor. In this case, it's probably to ensure that an item created with an empty string as a title gets displayed with something in it. Compare
var Todo1 = Backbone.Model.extend({
defaults: function() {
return {
title: "empty todo...",
done: false
};
},
initialize: function() {
if (!this.get("title")) {
this.set({"title": this.defaults().title});
}
}
});
var t1 = new Todo1({
title: ""
});
with
var Todo2 = Backbone.Model.extend({
// Default attributes for the todo item.
defaults: function() {
return {
title: "empty todo...",
done: false
};
}
});
var t2 = new Todo2({
title: ""
});
t1.get('title') will be empty todo... and t2.get('title') will be an empty string. Passing no argument to both constructors would indeed use the default values.
And a Fiddle http://jsfiddle.net/nikoshr/CeEDg/

Categories

Resources