I wish to save my view model to a database as a JSON string. The idea is that I can then re-load my view model by reading the JSON back and using the direct approach to load my view model's data:
From the Knockout documentation:
// Load and parse the JSON
var someJSON = /* Omitted: fetch it from the server however you want */;
var parsed = JSON.parse(someJSON);
// Update view model properties
viewModel.firstName(parsed.firstName);
viewModel.pets(parsed.pets);
That all works great but where I've already initialised my model and I'm simply updating it with one that I've already saved, I can't see how I can select the originally selected entry in the array's drop-down list.
To put it another way, the pets drop-down list is selected as "Cat" when I save my model. I then change the drop-down list selection to "Dog". On re-loading the saved model, I need my drop-down list selection to be reset to "Cat".
I'm a bit concerned about this because I have some arrays of objects which also need to be read in from the saved model and it's looking like it's going to be very difficult to do.
Any ideas or suggestions are welcome :)
What you are looking for is the mapping plugin for Knockout. http://knockoutjs.com/documentation/plugins-mapping.html
It has methods that handle both JSON to observables and back.
So in your example you could do:
var viewModel = ko.mapping.fromJSON(someJSON);
And when you're ready to go back to the server:
var jsonData = ko.mapping.toJSON(viewModel);
There are also object literal helpers if you need that (ko.mapping.toJS & ko.mapping.fromJS)
Related
I have seen a similar question, but in my case it doesn't work.
I have a JSON model, called data, which corresponds to a SAPUi5 form with comboboxes. I want to copy the state of the model the first time I open my application and keep it like that. After that I want to use it to reset my form and bring the comboboxes back to their default values.
When I first start my application:
this.getView().setModel(new JSONModel(data)); //create the original model
//copy the original model (copyModel is global variable
copyModel = $.extend({}, data);
Until here everything is fine. The two models are exactly the same. After that I have a button and a reset Function:
resetP: function(){
this.getView().setModel(new JSONModel(copyModel));
console.log(copyModel);
}
The first time I select something in the comboboxes and click the reset button and run the reset function, the copymodel is the right one. Same with the original data model. When I change again the selected value of the combobx, the copyModel, starts taking the selected value. Somehow it's overwritten. I don't know what I am doing wrong. Are there any suggestions? I have also tried to use JSON.strignify instead of extend.
JSON models be default have two way binding. So when you are triggering events like selectionChange on the ComboBox, because of two way binding, the set data to the model keeps getting updated. Also Javascript has objects by reference, so it is the original copyModel object that gets updated.
You can prevent this by setting a copy of the copyModel to the JSON model.
Another thing I would like to mention is that do not keep setting the model again and again.
You can just update the data that is set to the model and update the model.
This can be done in 2 ways.
a.
resetP: function(){
this.getView().getModel().setData(copyModel);
console.log(copyModel);
}
b. You could also update the required property and do a
this.getView().getModel().updateBindings();
We use jQuery.extend(true, {}, object_to_copy); in this way to create a "deep copy" from the object we want an independed copy from.
Sencha Fiddle
My API returns JSON object visible in Attachments.store.Attachments data. It seems to me that the structure consists of 4 models: AttachmentObject, AttachmentDef, AttachmentFile and AttachmentNote. I've defined them in the fiddle and associated each other using hasOne and hasMany. I've also created a simple grid and a form.
In the grid I want to present attachment's name in the last column, but I can't access child nodes using dataIndex property. When passing only "attachmentDef" as dataIndex grid converts child to "[object Object]" string, which means the nested data is noticable by the grid. I've also tried to retrieve the name using renderer, but then sorting functionality is gone.
API expects same JSON format in case of posting a new attachment. How to instantiate above models and bind chosen model fields (i.e. name and note) with form fields?
here's the fiddle with child nodes and sort: https://fiddle.sencha.com/#view/editor&fiddle/257b
I've changed the store load and I've used 'templatecolumn' and 'widgetcolumn' to show data.
the creation of a new attachment depends on how you'd like to make it work, I suppose you might want to call a specific ws (defined in attachment model) to create a new attachment and then reload the grid's store
Let's imagine a situation like this:
I have an ViewBag dynamic object which is basically a list fille with some results;
Scenario 1:
User 1 comes in and fills the ViewBag.Products object with a list of 50 items inside;
Scenario 2:
User 2 comes in and fills ViewBag.Products object with a list of 50 NEW items which ARE DIFFERENT than the previous 50 ones of a user 1.
Now when the both users get displayed results onto their page, which is located at /Analyze/Index <- view
I enable them so that they can sort out that list by a certain property of a class which is located inside the object like this:
public JsonResult GetSortedBySales()
{
var list = lista.OrderByDescending(x => x.SaleNumber).ToList();
return Json(list, JsonRequestBehavior.AllowGet);
}
public JsonResult GetSortedByFeedback()
{
var list = lista.OrderByDescending(x => x.Feedback).ToList();
return Json(list, JsonRequestBehavior.AllowGet);
}
As you can see this produces an issue like this:
The last user that added it's own items to the list are the items which will be shown to User 1 when he tries to sort out the list, since the list is now filled with User #2's items...
The list is filled with the items from the eBay API, therefore I cannot guarantee the integrity and uniqueness of the data to each user...
What I thought I can do here?? Is that I can store these items from the list somehow into the local jquery array and then perform the sorting from that local array, so that each jquery array is local to each user in their browser and no mixing of data is done...
Do you guys understand me what I'm trying to achieve here?
I apologize if my English is bad, I've tried my best to explain the issue that I have.
Edit: here is more data on what I'm trying to achieve
Basically I have a form where users perform a search of ebay items based on a certain keyword. After the search via http request I display the results to them in a manner where they can select all of those products in a table and then perform analyzing of those selected products.
Then they are transferred with another page with the analyzed data and I display the results to them in the list object called "lista".
The "lista" list is always filled differently based on what the user searches on the page via the process that I just explained above.
So the "lista" object is always filled with the new data and when the user performs sorting of data in list "lista" they are always displayed differently if 2 users perform analyzing of data like in scenario 1 and 2 that I explained above.
Does this helps?
Edit 2:
Here is a graphical explanation of what I ment
Step 1:
Step 2:
P.S. the "lista" list is declared as static, is that what's causing the issue?
Edit again:
Okay so guys I've found a way so that the data isn't changed when I sort it. Instead of doing a jQuery post, I perform sorting of data based on two extra attributes that I added into my table tr - sales and feedback and then sorted it like following:
$(".feedbackClick").click(function() {
var $wrapper = $('#tableSellers');
$wrapper.find('.test').sort(function(a, b) {
return +$(b).attr('feedback') - +$(a).attr('feedback');
}).appendTo($wrapper);
});
This sorts the data locally in jQuery thus no data is lost at the time when multiple users perform a search.
P.S. the "lista" list is declared as static, is that what's causing the issue?
Yes. That is the problem.
You should almost never be using static variables in a web site, as those variables will end up being shared across all users.
I'm using a factory to create different user-Objects from data which comes from the Server. Each user-Object has a "userGroup"-property. A list of users is displayed using ng-repeat in the View. And there it is possible to change the userGroup-value from "basic" to "admin", because of the AngularJS 1 two way binding. So the original values of the Object are gone. So my question is: when we want to cancel the made changes where should the initial value {userGroup: "basic"} be stored?
I was thinking about two possible solutions:
create an additional property "initUserGroup" in the User Factory Class and store a value for each Object
use localstorage (up to 20 records must be saved at once)
Are there any best practices for such cases?
For example, you can backup whole object in a property like _backup (using angular.copy) and restore if you with to undo changes (using angular.extend). Here is an example:
$scope.editItem(item) {
item._backup = angular.copy(item);
}
$scope.undoEdit(item) {
angular.extend(item, item._backup);
//delete unused data
delete(item._backup);
}
In this case you won't need to save data outside current object.
I've been trying to figure this out for quite some time now. I couldn't find anything that addresses this problem, but please correct me if I'm wrong.
The problem:
I have data from a JSON API comming in, with an nested array/object structure. I use mapping to initially fill the model with my data. To update this, I want to extend the model if new data arrives, or update the existing data.
As far as I found out, the mapping option key, should do this trick for me, but I might have misunderstood the functionality of the mapping options.
I've boiled down the problem to be represented by this example:
var userMapping = {
key: function(item) {
return ko.utils.unwrapObservable(item.id);
}
};
// JSON call replaced with values
var viewModel = {
users: ko.mapping.fromJS([], userMapping)
};
// Should insert new - new ID?
ko.mapping.fromJS([{"id":1,"name":"Foo"}, {"id":2,"name":"Bar"}], userMapping, viewModel.users);
// Should only update ID#1 - same ID?
ko.mapping.fromJS([{"id":1,"name":"Bat"}], userMapping, viewModel.users);
// Should insert new - New ID?
ko.mapping.fromJS([{"id":3,"name":"New"}, {"id":4,"name":"New"}], userMapping, viewModel.users);
ko.applyBindings(viewModel);
Fiddle: http://jsfiddle.net/mikaelbr/gDjA7/
As you can see, the first line inserts the data. All good. But when I try to update, it replaces the content. The same for the third mapping; it replaces the content, instead of extening it.
Am I using it wrong? Should I try to extend the content "manually" before using mapping?
Edit Solution:
I solved this case by having a second helper array storing all current models. On new data i extended this array, and updated the view model to contain the accumulated items.
On update (In my case a WebSocket message), I looped through the models, changed the contents of the item in question, and used method valueHasMutated() to give notice of changed value to the Knockout lib.
From looking at your example code the mapping plugin is behaving exactly as I would expect it to. When you call fromJS on a collection you are effectively telling the mapping plugin this is the new contents of that collection. For example:
On the second line, How could it know whether you were updating or whether you had simply removed id:2?
I can't find any mention of a suitable method that treats the data as simply an update, although you could add one. Mapped arrays come with some helpful methods such as mappedIndexOf to help you find particular items. If you receive an update data set simply loop through it, find the item and update it with a mapping.fromJS call to that particular item. This can easily be generalized into reusable method.
You can use ko.mapping.updateFromJS() to update existing values. However, it does not add new values so that would be a problem in your instance. Take a look at the link below for more details.
Using updateFromJS is replacing values when it should be adding them
Yes, you should first collect all data into a list or array and then apply the mapping to that list. Otherwise you are going to overwrite the values in your viewModel.