I have a problem that seems complicated that I don't know how to solve.
My single page application uses knockoutJS and I want to cache my view model every time one of my input values change on my view.
Where the problem lies is the complexity of my view model. To use local storage I need to stringify my object but it has recursive values. I have some logic to handle this and stringify my object. But when I try to parse the JSON string back to an object, I lose my functions.
function cacheForm(agency) {
//var serialized = stringifyOnce(agency);
//var serialized = amplify.store("Agency", agency);#
//var obj = new Object();
//obj.test = "test";
value = agency;
var cache = [];
parsed = JSON.stringify(value, function (key, value) {
if (typeof value === 'object' && value !== null) {
if (cache.indexOf(value) !== -1) {
// Circular reference found, discard key
return;
}
// Store value in our collection
cache.push(value);
}
return value;
});
//var jsonString = JSON.stringify(cache);
//cache = null; // Enable garbage collection
var parsedRecursive = "{" + '"data"' + ":" + parsed + "," + '"expires"' + ":" + null + "}"; // Build the string based on how amplify likes it
var obj = eval('(' + parsed + ')')
var obj = jQuery.parseJSON(parsedRecursive);
//// Put the object into storage
//localStorage.setItem('Agency', parsedRecursive);
myData = JSON.parse(parsedRecursive, function (key, value) {
var type;
if (value && typeof value === 'object') {
type = value.type;
if (typeof type === 'string' && typeof window[type] === 'function') {
return new (window[type])(value);
}
}
return value;
});
//// Retrieve the object from storage
// var retrievedObject = localStorage.getItem('Agency');
// var obj = jQuery.parseJSON(parsedRecursive);
//var agency2 = mapping.fromJS(obj);
//agency;
//amplify.store("Agency", agency);
//amplify.store("Obj", obj);
//var store = amplify.store(); // Look at all items by not providing a key
}
});
I tried using eval but this returned an object without my data. To help you understand this is what my view model looks like, which is the agency parameter of cachForm.
Also I tried using amplify but because of my view model structure I run into the same issue.
I have also attached some screenshots to show what happens with my viewmodel in the code.
It is not a good idea to cache the whole viewModel. A viewModel contains observables, computeds and functions or events handlers.
You have better to cache the view model data, only the raw data.
On serialization :
So just serialize the view data (use ko.mapping.toJSON):
var json = ko.mapping.toJSON(viewModel);
In order to rebuild a proper view model it is better to have a initialisation function.
This is your view model constructor e.i. a function that adds the event handlers, functions and computed.
var initializer = your_constructor;
You also need to determine a cache key e.g. the page path.
var cacheItem = {data : json, constructor : initializer};
cache[key] = cacheItem;
On deserialization :
Get the cache item :
var cacheItem = cache[key];
var viewModel = JSON.parse(cacheItem.data);
Now viewModel contains the raw data.
viewModel = ko.mapping.fromJS(viewModel);
The properties were converted into observables.
If there is a constructor call it.
if(cacheItem.constructor)
cacheItem.constructor.call(viewModel);
Then your view model is as you stored it.
I hope it helps.
This is the answer I have posted in another post. They are both related.
JSON.parse returns children objects with null value, children values not being parsed
Related
Trying to convert JSON data into int in order to perform calculations, multiply by numbers or percentages (or whichever method is best recommended)
Tried performing calculation on the object (using addition for example), but it only added numbers on to the end of the resulting string. I have seen suggestions on using JSON parse (reviver) but can't seem to get my head round getting the desired data when it is only one specific part of the JSON data required rather than multiple items of data from the JSON link.
var xmlhttp = new XMLHttpRequest();
var url = "https://api.coindesk.com/v1/bpi/currentprice.json";
xmlhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var json = JSON.parse(this.responseText);
parseJson(json);
}
};
xmlhttp.open("GET", url, true);
xmlhttp.send();
function parseJson(json) {
var gbpValue = "1 BTC equals to £" + json["bpi"]["GBP"]["rate"];
document.getElementById("data").innerHTML =
gbpValue;
As mentioned, have tried performing calculations on the result but it only adds numbers to the end of the string. Thanks for any advice or help.
What I can see in your code is that you are adding your json variable with a string which would always result in string concatenation in below line:
var gbpValue = "1 BTC equals to £" + json["bpi"]["GBP"]["rate"];
First check your json object if it a number or string.
You could use the Number() or parseInt() functions to convert a string to number.
Example:
var num=Number("23");
or
var num=parseInt("23");
Hope it helps. :)
var addnote = function (title, body) {
var notes = [];
var note = {
title: title,
body: body
}
need explanation on the two lines under try
try {
var noteString = fs.readFileSync("data.json");
notes = JSON.parse(noteString);
} catch (e) {
}
And explanation on how duplicateNotes works ..
var duplicateNotes = notes.filter(function(note){
return note.title === title
})
if (duplicateNotes.length === 0) {
notes.push(note);
fs.writeFileSync("data.json", JSON.stringify(notes));
}
}
JSON.parse converts a JSON object to String.
{
field1:field1Value,
field2:fieldValue
}
If this is in Json format you can access it's elements using JSONObjectName.fieldName
But,if it's converted to String it looses it's JSON properties. You can't access fields in same way. Output will act like String.
need explanation on the two lines under try
var noteString = fs.readFileSync("data.json");
There are two kinds of response back asynchronous and synchronous. Synchronous call is like you will not start playing until you get a pass and score a goal. But, Asynchronous call is like you start playing with your friend but, you run near him and, there is a promise you won't shoot until you get the ball.
readFileSync will read the file and the next line will wait until it gets the pass.JSON.parse() will convert file content to JSON object.
And explanation on how duplicateNotes works ..
var duplicateNotes = notes.filter(function(note){
return note.title === title
})
Whenever a match is found , that element in notes will be pushed to duplicate note. That's all.
Trying to store indexReferences per user, I've found that when I store one (or more) directly in a map, it works fine. However, when stored in an object (or a custom realtime object), the realtime API generates Circular JSON errors.
This works fine:
function doRegisterTypes() {
gapi.drive.realtime.custom.registerType(MyCustomType, "MyCustomType");
MyCustomType.prototype.startPoints = gapi.drive.realtime.custom.collaborativeField('startPoints');
MyCustomType.prototype.endPoints = gapi.drive.realtime.custom.collaborativeField('endPoints');
MyCustomType.prototype.elements = gapi.drive.realtime.custom.collaborativeField('elements');
gapi.drive.realtime.custom.setInitializer(MyCustomType, initializeMyCustomType);
}
function initializeMyCustomType() {
var model = gapi.drive.realtime.custom.getModel(this);
this.startPoints = model.createMap();
this.endPoints = model.createMap();
this.elements = model.createList();
}
function initializeModel(model) {
var o = model.create("MyCustomType");
o.elements.pushAll(["foo", "bar"]);
var startIndex = o.elements.registerReference(0, false);
var endIndex = o.elements.registerReference(0, false);
o.startPoints.set(UserId, startIndex);
o.endPoints.set(UserId, endIndex);
model.getRoot().set("MyCustomObject", o);
}
But this doesn't, failing with circular JSON errors when storing the range object in the map:
function doRegisterTypes() {
gapi.drive.realtime.custom.registerType(MyCustomType, "MyCustomType");
MyCustomType.prototype.ranges = gapi.drive.realtime.custom.collaborativeField('ranges');
MyCustomType.prototype.elements = gapi.drive.realtime.custom.collaborativeField('elements');
gapi.drive.realtime.custom.setInitializer(MyCustomType, initializeMyCustomType);
}
function initializeMyCustomType() {
var model = gapi.drive.realtime.custom.getModel(this);
this.ranges = model.createMap();
this.elements = model.createList();
}
function initializeModel(model) {
var o = model.create("MyCustomType");
o.elements.pushAll(["foo", "bar"]);
var startIndex = o.elements.registerReference(0, false);
var endIndex = o.elements.registerReference(0, false);
// FAILS:
o.ranges.set(UserId, {start:startIndex, end:endIndex});
model.getRoot().set("MyCustomObject", o);
}
I should stress the error appears for a single indexReference, and whether the object is a specific custom type or not, and also WHENEVER the value is set into the map: while initializing the model or later. It's as if the indexReferences cannot be stored at anything but a "top level", though that makes little sense.
Feature? Bug? User stoopidity?
You can't store CollaborativeObjects within arbitrary json within a CollaborativeObject. CollaborativeObjects (including IndexReferences) must be stored directly in other CollaborativeObjects.
(There are a few reasons for this, mostly having to do with how the collaboration works.. json objects are treated as arbitrary blobs whose contents are ignored.)
In this case, you could create a Range custom object type that has a start and end CollaborativeField. (Or a CollaborativeList with 2 elements..)
I have a scope variable jsonData as below:
$scope.jsonData={id:'1234',abcd:{array:[{a:'data',b:'bdata',c:'cdata'},{a2:'a2data',b2:'b2data',c2:'c2data'}]},efg:{test:'testdata'}}
in my HTML I have a function calladd('jsonData.abcd.array') with a string
in my js file I want to add an JSON object to arrayinside the abcd JSON object
$scope.add(data) {
$scope[data].push({a3:'a3data',b3:'b3data',c3:'c3data'});
}
but I am unable to push data into array.
If you wanted to get ID, you can do
var id = $scope.jsonData.id;
//id = "1234"
You can use the same logic to add or get values
Calling myobject['prop1.prop2.whatever'] just doesn't work.
What you need is a recursive parser like :
$scope.add = function(data, scope){
scope = (typeof scope === "undefined") ? $scope : scope;
var datas = data.split('.');
if(datas.length == 1){
scope[datas[0]].push({
a3: 'a3data',
b3: 'b3data',
c3: 'c3data'
});
}else{
var first = datas.splice(0,1);
$scope.add(datas.join('.'), scope[first]);
}
};
And the fiddle
I have a metro application(HTML5 & WinJS) in which am trying to display service data . Actually here am retrieving JSON data from my service but am unable to bind this data into listview . Anyone give me some working example.
Thank you.
You can use the WinJS.xhr() for this. You can read more about it on this link https://msdn.microsoft.com/pt-br/library/windows/apps/br229787.aspx and here is an example:
var path = "data/file.json";
function getData(path) {
WinJS.xhr({ url: path }).then(
function (response) {
var json = JSON.parse(response.responseText);
// Since this is an asynchronous function, you can't
// return the data, so you can:
// 1) retrieve the data to a namespace once the app loads.
var list = new WinJS.Binding.List(json);
Somenomespace.data = list;
// 2) or do all the binding inside the function.
var listView = document.getElementById("listViewID");
listView.winControl.itemDataSource = list.dataSource;
});
}
If you use the built in JSON.parse(jsonString) function you can loop through the content using a normal for loop as it then is a normal object and add it as usuall. Just remember to process or render the data.
Her is an example from code i had in a search page using listview:
var response = JSON.parse(data) ;
var originalResults = new WinJS.Binding.List();
for (x in response) {
originalResults.push(response[x]);
}
this.populateFilterBar(element, originalResults);
this.applyFilter(this.filters[0], originalResults);