Knockout mapping model that contains more models - javascript

I have the next data coming from the server and I want to map this data to my model by calling ko.mapping.fromJS(data, AcquisitionDetailsModel);
By inspecting the AcquisitionDetailsModel, after calling the specified method, I see that the model contains acquisitions: Object[0]. Why? I was expecting to find the array of acquisitions inside my model. What I'm doing wrong?
data: Object
acquisitions: Array[2]
0: Object
acquisition: Object
date: "2012-06-20"
__proto__: Object
provider: Object
id: "1"
name: "Some name"
__proto__: Object
truck: Object
mark: "DAF"
model: "95FX"
__proto__: Object
__proto__: Object
1: Object
length: 2
The models are looking like this:
function TruckModel() {
this.mark = ko.observable("");
this.model = ko.observable("");
}
function AcquisitionModel() {
this.date = ko.observable("acquisition_date");
}
function ProviderModel() {
this.id = null;
this.name = ko.observable("name");
}
var AcquisitionDetailsModel = {
acquisitions: ko.observableArray([{
acquisition: new AcquisitionModel(),
provider: new ProviderModel(),
truck: new TruckModel()
}])
}

Actually the mapping was done right but it was me who was doing debugging in the wrong way :)
After ko.mapping.fromJS(data, AcquisitionDetailsModel); if I call AcquisitionDetailsModel.acquisitions() I can see my data.
Also, by calling AcquisitionDetailsModel.acquisitions()[0].truck.model() I still can see the right data.
Stupid me :)

Related

How can I get variable data in the object prototype?

I have an array of objects, and I want to create a method which returns the data inside the method. How do I do this? Thanks
// for example, I have this data
var data = [{ number: 1, name: "Bob" }, { number: 2, name: "Jim" }];
Object.prototype.number = () => {
// how do I access the object data inside this prototype
// i am trying to return something like this, but it doesnt work:
// return this.number;
};
// i want this to return the number 1
data[0].number();
^
| I am trying to access this data in the prototype function and return the number
I know this can be done with data[0].number, but I am trying to do it with an object prototype method.
You need to:
Use different property names for the property holding the method and the property holding the data
Not use an arrow function (which has lexical this)
// for example, I have this data
var data = [{
xnumber: 1,
name: "Bob"
}, {
xnumber: 2,
name: "Jim"
}];
Object.prototype.number = function() {
return this.xnumber;
};
// i want this to return the number 1
console.log(data[0].number());
Note that editing the prototype of built-in objects is considered dangerous.

Unwrap an object in javascript

I have triggered an event called "search" along with an object.
$(".btn-class").on("click", function() {
var params = {
"searchText": "sometext",
"searchProps": ["prop1", "prop2", "prop3"]
};
COMP.Util.announce("search", {
params: params
})
});
And in another class, I'm listening to this event and processing that object:
var ListView = Backbone.View.extend({
init: function() {
COMP.Util.listenTo("search", $.proxy(this.applySearch, this));
},
applySearch: function(params) {
// do something
}
}
})
My question is when 'params' is passed to applySearch function, it becomes a wrapped object:
params: Object
searchProps: Array[3]
searchText: "sometext"
__proto__: Object
__proto__: Object
If printed, I got:
params=%5Bobject+Object%5D
I want to be able to unwrap 'params' from Object as plain text, and also wonder why wrapping happens in this case
searchProps: Array[3]
searchText: "sometext"
__proto__: Object
Printed like this:
params=searchText=sometext&searchProps=prop1&searchProps=prop2&searchProps=prop3
You can "unwrap" the object with: JSON.stringify(params)
However, I think you might be confusing yourself by adding a key to your params object called params. You might want to leave that extra key out:
$(".btn-class").on("click", function() {
var params = {
"searchText": "sometext",
"searchProps": ["prop1", "prop2", "prop3"]
};
COMP.Util.announce("search",params)
});
You could reuse jQuery method param() which does the job for you.
It accepts as argument arrays and plain objects:
var params = {
"searchText": "sometext",
"searchProps": ["prop1", "prop2", "prop3"]
};
var paramsEncoded = $.param( params );
alert(paramsEncoded)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

How can I fake Collection data?

What I mean by this is I want to create it artificially.
This is for testing purposes.
But for models, it is quite simple. I just set defaults I instantiate the model object and from there I can use this.model.toJSON() to grab the created data.
I want to use this same trick with collections. Is there a similar way to do this with collections? What I would want to do is have the collection create x ( 8 in this case ) copies of Model defaults.
Basically what I was doing before for models but a little bit more complex as it applies to Collections.
Here is the actual use case. It should be simple.
/**Model
**/
// name, picture, time, tweet, h_file
var FeedRow = Backbone.Model.extend({
Name: 'FeedRow',
defaults: {
name: "default",
picture: 0,
time: "0",
tweet: "default",
h_file: "default"
}
});
/**Collection
**/
var FeedTable = Backbone.Collection.extend({
Name: 'FeedTable',
model: FeedRow
});
When your FeedTable collection is constructed you could set the model on it multiple times in the initialize method.
var FeedTable = Backbone.Collection.extend(
{
Name: 'FeedTable',
model: FeedRow,
initialize: function()
{
model = this.model;
models = [];
_.times(8, function(n)
{
models.push(new model({id: (n + 1)}));
});
this.set(models);
}
});

How to get model attributes within a view in Backbone.js?

I am trying to pass a model as parameter in a view.
I am getting my object in the view, but no way to access its attributes...
Here is the code :
From the router :
var Product = new ProductModel({id: id});
Product.fetch();
var product_view = new ProductView({el: $("#main-content"), model: Product});
From the model :
var ProductModel = Backbone.Model.extend({
urlRoot: 'http://192.168.1.200:8080/store/rest/product/'
});
From the view :
ProductView = Backbone.View.extend({
initialize: function(){
console.log(this.model);
this.render();
},
render: function(){
var options = {
id: this.model.id,
name: this.model.get('name'),
publication_start_date: this.model.get('publication_start_date'),
publication_end_date: this.model.get('publication_end_date'),
description: this.model.get('description'),
applis_more: this.model.get('applis_more')
}
var element = this.$el;
var that = this;
$.get('templates/product.html', function(data){
var template = _.template(data, options);
element.html(template);
});
}
});
Here is the result of the "console.log":
child {attributes: Object, _escapedAttributes: Object, cid: "c1", changed: Object, _silent: Object…}
Competences: child
Editor: child
Hobbies: child
Opinions: child
_changing: false
_escapedAttributes: Object
_pending: Object
_previousAttributes: Object
_silent: Object
attributes: Object
createdDate: null
deletedDate: null
descriptionId: 0
galleryImage: null
id: 13
image: null
manufacturerId: 0
name: "sdf"
owner: 0
status: 0
thumbnail: null
titleId: 0
type: 1
uid: "fdsf"
updatedDate: null
__proto__: Object
changed: Object
cid: "c1"
id: 13
__proto__: ctor
In my view, all my options are "undefined" (name, dates,...)
Any idea on what I am doing wrong ?
After creating the initial model, you're immediately creating the view using
var product_view = new ProductView({..., model: Product});
In the initialize method of ProductView, you're calling this.render(), which in turn reads and renders values from the model - most of these values are undefined (because the server did not have sufficient time to send back the model's values).
Don't call this.render() directly, but bind an event, eg:
// in ProductView::initialize
this.model.on('sync', function() {
this.render();
}, this);
You might also want to bind the change event, so that local (not synchronizsed yet) changes to the model are also reflected in the view. (An overview of Backbone events can be found here.)
Because Product.fetch() happens asynchronously, you'll want to create the view once the data has been retrieved from the server:
var Product = new ProductModel({id: id});
var product_view;
Product.fetch().done(function() {
product_view = new ProductView({el: $("#main-content"), model: Product});
});
Product.fetch() is an asynchronous call, so my guess is that the fetch has not completed yet when you initialize the view. What you probably want to do is use fetch's success + error callbacks, to assure the model has data in it before rendering anything
http://backbonejs.org/#Model-fetch

Backbone.js model with collection

I have 2 models and one collection. JobSummary is a model, JobSummaryList is a collection of JobSummary items, and then I have a JobSummarySnapshot model that contains a JobSummaryList:
JobSummary = Backbone.Model.extend({});
JobSummaryList = Backbone.Collection.extend({
model: JobSummary
});
JobSummarySnapshot = Backbone.Model.extend({
url: '/JobSummaryList',
defaults: {
pageNumber: 1,
summaryList: new JobSummaryList()
}
});
When I call fetch on the JobSummarySnapshot object, it gets everything... Except when I move through the summaryList collection they are all of type object and not JobSummary.
I suppose this makes sense since other than the defaults object, it doesn't know that the summaryList should be of type JobSummaryList. I can go through each item and convert it to a JobSummary object, but I was hoping there was a way to do it without having to do it manually.
Here's my test code (working jsfiddle here):
var returnData = {
pageNumber: 3,
summaryList: [
{
id: 5,
name: 'name1'},
{
id: 6,
name: 'name2'}
]
};
var fakeserver = sinon.fakeServer.create();
fakeserver.respondWith('GET', '/JobSummaryList', [200,
{
'Content-Type': 'application/json'},
JSON.stringify(returnData)]);
var callback = sinon.spy();
var summarySnapshot = new JobSummarySnapshot();
summarySnapshot.bind('change', callback);
summarySnapshot.fetch();
fakeserver.respond();
var theReturnedList = callback.getCall(0).args[0].attributes.summaryList;
_.each(theReturnedList, function(item) {
console.log('Original Item: ');
console.log(item instanceof JobSummary); // IS FALSE
var convertedItem = new JobSummary(item);
console.log('converted item: ');
console.log(convertedItem instanceof JobSummary); // IS TRUE
});
UPDATE:
It occurred to me that I could override the parse function and set it that way... I have this now:
JobSummarySnapshot = Backbone.Model.extend({
url: '/JobSummaryList',
defaults: {
pageNumber: 1,
summaryList: new JobSummaryList()
},
parse: function(response) {
this.set({pageNumber: response.pageNumber});
var summaryList = new JobSummaryList();
summaryList.add(response.summaryList);
this.set({summaryList: summaryList});
}
});
This works so far. Leaving the question open in case someone has comment on it....
Your parse() function shouldn't set() anything, its a better practice to just return the attributes, Backbone will take care of setting it. e.g.
parse: function(response) {
response.summaryList = new JobSummaryList(response.summaryList);
return response;
}
Whatever you return from parse() is passed to set().
Not returning anything (which is like returning undefined) is the same as calling set(undefined), which could cause it not to pass validation, or some other unexpected results if your custom validate()/set() methods expects to get an object. If your validation or set() method fails because of that, the options.success callback passed to Backbone.Model#fetch() won't be called.
Also, to make this more generic, so that set()ing to a plain object from other places (and not only from the server response) also effects it, you might want to override set() instead:
set: function(attributes, options) {
if (attributes.summaryList !== undefined && !(attributes.summaryList instanceof JobSummaryList)) {
attributes.summaryList = new JobSummaryList(attributes.summaryList);
}
return Backbone.Model.prototype.set.call(this, attributes, options);
}
You might also find Backbone-relational interesting - it makes it much easier to deal with collections/models nested inside models.
edit I forgot to return from the set() method, the code is now updated

Categories

Resources