I've been scratching my head with this for the last day.. hope someone can shed some light. I have a simple javascript object -- data -- JSON.stringify(data) returns it like this;
{
"dataList": [
{
"Id": 0,
"Name": "0",
},
{
"Id": 1,
"Name": "1",
}
]
}
I also have a really simple knockout viewmodel;
var viewModel = {
dataList: ko.observableArray([])
};
I then do a simple knockout.JS mapping call as per the doc site;
ko.mapping.fromJS(data, viewModel);
I would expect my viewModel to now have a dataList member (it does) that is an array of 2 (it isn't!). Instead I get an empty array.. What am I missing in the mapping here??
You shouldn't need to define the properties on your viewModel object; the mapping plugin will create them for you. Just declare viewModel as such:
var viewModel = ko.mapping.fromJS(data);
http://jsfiddle.net/vqaVT/
You only need to make the other call, ko.mapping.fromJS(data, viewModel), when you need to update your viewModel with updated data from the server.
Related
What I am trying to do is to get data from the server and then putting it all in an observable and then make all the properties observable. The issue I am facing is that it does not make all my properties observable and I need them all to be observable as sometimes depending on the data it makes some properties observable and sometimes it doesn't.
var viewModel = this;
viewModel.Model = ko.observable();
viewModel.SetModel = function (data) {
viewModel.Model(ko.mapping.fromJS(data));
}
The data that I am receiving from the server is like this for example: normaldata,items(this is an array with unknown number of elements).
so if i try to access data like viewModel.Model().Items[0]().Layer() i sometimes have Layer as a function and sometimes it is a normal element with observable elements.I want all my objects inside Items to have Layer as a function.
Server data example:
Name: "test"
Items: [Layer[ID: 132]]
In this example Name,Items and ID are observable but Layer is not.
Fiddle example:
jsfiddle.net/98dv11yz/3
So the problem is that sometimes the layer is null resulting in ko making the property observable but sometimes that property has id and ko makes only the child elements observable. The problem is that i have if's in the code and i want it to be a function so i can always reffer to it as layer() because now it is sometimes layer or layer()
An explenation for what's happening:
When the ko.mapping plugin encounters an object in your input, it will make the object's properties observable, not the property itself.
For example:
var myVM = ko.mapping.fromJS({
name: "Foo",
myObject: {
bar: "Baz"
}
});
Will boil down to:
var myVM = {
name: ko.observable("Foo"),
myObject: {
bar: ko.observable("Baz")
}
}
and not to:
var myVM = {
name: ko.observable("Foo"),
myObject: ko.observable({
bar: ko.observable("Baz")
})
}
The issue with your data structure is that myObject will sometimes be null, and sometimes be an object. The first will be treated just as the name property in this example, the latter will be treated as the myObject prop.
My suggestion:
Firstly: I'd suggest to only use the ko.mapping.fromJS method if you have a well documented and uniform data structure, and not on large data sets that have many levels and complexity. Sometimes, it's easier to create slim viewmodels that have their own mapping logic in their constructor.
If you do not wish to alter your data structure and want to keep using ko.mapping, this part will have to be changed client-side:
Items: [
{ layer: {id: "0.2"} },
{ layer: null}
]
You'll have to decide what you want to achieve. Should the viewmodel strip out the item with a null layer? Or do you want to render it and be able to update it? Here's an example of how to "correct" your data before creating a view model:
var serverData = {
Name: "Example Name",
Id: "0",
Items: [
{layer: {id: "0.2"} },
{layer: null}
]
};
var correctedData = (function() {
var copy = JSON.parse(JSON.stringify(serverData));
// If you want to be able to render the null item:
copy.Items = copy.Items.map(function(item) {
return item.layer ? item : { layer: { id: "unknown" } };
});
// If you don't want it in there:
copy.Items = copy.Items.filter(function(item) {
return item.layer;
});
return copy;
}());
Whether this solution is acceptable kind of relies on how much more complicated your real-life use will be. If there's more complexity and interactivity to the data, I'd suggest mapping the items to their own viewmodels that deal with missing properties and what not...
I need an advice for the best practice in order to add behavior to an object received as a json object.
I have a REST services that allow me to define a sort of state machine.
The API define a /sessions resources. When creating a session via POST /sessions/:id I get an json object in my controller:
var session = {
"id": "",
"steps": [ ... ]
}
With this object I would like it to inherit some behavior:
var baseSession = {
"nextStep": function() {... },
"getCurrentStep": function() { ...}
}
So what I would like to do is:
session.__proto__ = baseSession;
But using __proto__ seems not the thing to do.
The other possibility would be to duplicate every property in a new object:
function copyOwnProperyTo(origin, obj) {
Object.keys(origin).forEach(function(prop) {
obj[prop] = origin[prop];
});
}
var newSession = Object.create(baseSession);
copyOwnProperyTo(session, newSession);
This solution work but to me it look a bit heave. Any other suggestions?
The "proper" ES6 solution is to combine Object.assign with Object.create:
var session = Object.assign(Object.create(baseSession), {
"id": "",
"steps": […]
});
Of course you can also use your own copying method instead of Object.assign.
And finally, there is Object.setPrototypeOf, which could be used like
var session = Object.setPrototypeOf({
"id": "",
"steps": […]
}, baseSession);
So in ASP.NET MVC I have a Controller action somewhat like this:
public JsonResult People()
{
var people = db.People.ToList();
return Json(people);
}
and when ajaxed this will return something like this:
[
{
"ID": 1,
"Name": "John Smith"
},
{
"ID": 2,
"Name": "Daryl Jackson"
}
]
However, what I'm looking for is not a JSON array of the records as shown above, but more a JSON object where the IDs of each record is the key and then the record nested as the value, like so:
{
1: {
"Name": "John Smith"
},
2: {
"Name": "Daryl Jackson"
}
}
My initial thought would be to create a Dictionary in C# similar to this structure and pass it into Json() however the method does not know how to handle Dictionary objects.
Is there a way to achieve this kind of structure in C#? Currently I'm having to resort to restructuring it on the client-side or using loops to find a record with the ID I'm searching for. It'd be nicer if I could just get the record by ID in Javascript. Am I going about this all wrong? I'm new to the ASP.NET MVC environment.
Any suggestions would be greatly appreciated. Thank you :)
You can use the ToDictionary() extension method.
public ActionResult People()
{
var peopleDictionary = db.People
.Select(x=> new { Id = x.Id, Name= x.FirstName})
.ToDictionary(d => d.Id.ToString(), p => p);
return Json(peopleDictionary, JsonRequestBehavior.AllowGet);
}
Assuming your People table has an Id and FirstName column, this code will return a json dictionary like this from your table data
{ "101" : {"Id":101,"Name":"John"},"102":{"Id":102,"Name":"Darryl"} }
Using the Json metod, You cannot serialize a dictionary where the dictionary key is of type int, so you need to explicitly convert your int value to a string.
If you want to get a specific user from the a userId, you can use the dictionary style syntax yourJsObject[key]
var url="PutRelativeUrlToReportScoreHere";
$.getJSON(url, function(data) {
console.log(data);
//Get the User with Key 102
console.log(data[102]);
});
There is an nested object with certain properties which i don't want to be watched. It could be a pattern of properties starting with perhaps "_".
Here's a sample structure.
$scope.ObjectToBeWatched = {
"company": {
"ts": {
"_msg": {"nm":""},
"status": "success"
},
"ids": [
"000000010",
"000000011"
]
},
"_f": [
{
"code": "TY_IO",
"status": "fail"
}
]
}
Standard deep watch:
$scope.$watch("ObjectToBeWatched",function(newObj,oldObj){
},true);
Right now the watch is firing for any any change in any properties which is expected. So in above case any changes to properties
_msg, _f
should not fire.
Thanks for help.
You can try something like this:
$scope.$watch(function($scope) {
return $scope.listOfBigObjects.
map(function(bigObject) {
return bigObject.foo.
fieldICareAbout;
});
}, myHandler, true);
This grabs only the props you care about from the objects in an array. You can use an expression to check for certain field types inside the object map. If you don't have an array just skip that part.
Underscore has tons of functional methods to help w/ this as well if 'map' isn't exactly what you need to return fields you care about.
I am trying to render a view for a collection using Backbone.View. But I cannot render the comment view to show the individual comments as a list inside the comments view. Only comments form is rendered. when visited the url of the collection from the address bar the below array is returned as I have written it on the server side code with express.
What can be the issue here I cannot manage to fix? It seems very natural to achieve it with this code, but it is certain that I am missing something. General issue is I am stuck at such detailed points although I can learn a say mvc framework, Backbone, Node, express etc.
CommentsView:
var CommentsView = Backbone.View.extend({
initialize: function (options) {
this.post = options.post;
this.collection = new Comments({post: this.post});//injecting collection from this views options, was injected to this view form router.
this.collection.on('add', this.addComments, this);
},
addComments: function (comment) {
this.$el.append(new CommentView({ model: comment }).render().el);
},
render: function () {
this.$el.append("<h2> Comments </h2>");
this.$el.append(new CommentFormView({post: this.post}).render().el);
return this;
}
});
This is the array returned when the url of collection is visited form the address bar:
[
{
"_id": "547e36df0ca19b2c1a6af910",
"postId": "547d00e30ca19b2c1a6af90b",
"name": "comment one",
"date": "Mon 21 Oct 2014",
"text": "gibberjabber"
}
]
Router method when the route to the comments of the related post is routed to:
comments: function(_id) {
var csv = new CommentsView({ post: this.posts.findWhere( {_id: _id} ) });
this.main.html(csv.render().el);
}
I think it could have something to do with your constructor function for this.collection. When creating a Collection, you should pass in the array as the first parameter and object literal with the options as the second (if you didn't define it when creating the collection class. What I'm thinking is that the "add" event on the collection isn't getting fired so the comments are not being rendered.
var Comments = Backbone.Collection.extend({
model: Post
});
this.collection = new Comments(posts)
I'm guessing that posts is just an array of models