I am trying to develop an offline HTML5 application that should work in most modern browsers (Chrome, Firefox, IE 9+, Safari, Opera). Since IndexedDB isn't supported by Safari (yet), and WebSQL is deprecated, I decided on using localStorage to store user-generated JavaScript objects and JSON.stringify()/JSON.parse() to put in or pull out the objects. However, I found out that JSON.stringify() does not handle methods. Here is an example object with a simple method:
var myObject = {};
myObject.foo = 'bar';
myObject.someFunction = function () {/*code in this function*/}
If I stringify this object (and later put it into localStorage), all that will be retained is myObject.foo, not myObject.someFunction().
//put object into localStorage
localStorage.setItem('myObject',JSON.stringify(myObject));
//pull it out of localStorage and set it to myObject
myObject = localStorage.getItem('myObject');
//undefined!
myObject.someFunction
I'm sure many of you probably already know of this limitation/feature/whatever you want to call it. The workaround that I've come up with is to create an object with the methods(myObject = new objectConstructor()), pull out the object properties from localStorage, and assign them to the new object I created. I feel that this is a roundabout approach, but I'm new to the JavaScript world, so this is how I solved it. So here is my grand question: I'd like the whole object (properties + methods) to be included in localStorage. How do I do this? If you can perhaps show me a better algorithm, or maybe another JSON method I don't know about, I'd greatly appreciate it.
Functions in javascript are more than just their code. They also have scope. Code can be stringified, but scope cannot.
JSON.stringify() will encode values that JSON supports. Objects with values that can be objects, arrays, strings, numbers and booleans. Anything else will be ignored or throw errors. Functions are not a supported entity in JSON. JSON handles pure data only, functions are not data, but behavior with more complex semantics.
That said you can change how JSON.stringify() works. The second argument is a replacer function. So you could force the behavior you want by forcing the strinigification of functions:
var obj = {
foo: function() {
return "I'm a function!";
}
};
var json = JSON.stringify(obj, function(key, value) {
if (typeof value === 'function') {
return value.toString();
} else {
return value;
}
});
console.log(json);
// {"foo":"function () { return \"I'm a function!\" }"}
But when you read that back in you would have to eval the function string and set the result back to the object, because JSON does not support functions.
All in all encoding functions in JSON can get pretty hairy. Are you sure you want to do this? There is probably a better way...
Perhaps you could instead save raw data, and pass that to a constructor from your JS loaded on the page. localStorage would only hold the data, but your code loaded onto the page would provide the methods to operate on that data.
// contrived example...
var MyClass = function(data) {
this.firstName = data.firstName;
this.lastName = data.lastName;
}
MyClass.prototype.getName() {
return this.firstName + ' ' + this.lastName;
}
localStorage.peopleData = [{
firstName: 'Bob',
lastName: 'McDudeFace'
}];
var peopleData = localStorage.peopleData;
var bob = new MyClass(peopleData[0]);
bob.getName() // 'Bob McDudeFace'
We don't need to save the getName() method to localStorage. We just need to feed that data into a constructor that will provide that method.
If you want to stringify your objects, but they have functions, you can use JSON.stringify() with the second parameter replacer. To prevent cyclic dependencies on objects you can use a var cache = [].
In our project we use lodash. We use the following function to generate logs. Can be used it to save objects to localStorage.
var stringifyObj = function(obj) {
var cache = []
return JSON.stringify(obj, function(key, value) {
if (
_.isString(value) ||
_.isNumber(value) ||
_.isBoolean(value)
) {
return value
} else if (_.isError(value)) {
return value.stack || ''
} else if (_.isPlainObject(value) || _.isArray(value)) {
if (cache.indexOf(value) !== -1) {
return
} else {
// cache each item
cache.push(value)
return value
}
}
})
}
// create a circular object
var circularObject = {}
circularObject.circularObject = circularObject
// stringify an object
$('body').text(
stringifyObj(
{
myBooblean: true,
myString: 'foo',
myNumber: 1,
myArray: [1, 2, 3],
myObject: {},
myCircularObject: circularObject,
myFunction: function () {}
}
)
)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
Does not fix functions as requested, but a way to store variables locally...
<html>
<head>
<title>Blank</title>
<script>
if(localStorage.g===undefined) localStorage.g={};
var g=JSON.parse(localStorage.g);
</script>
</head>
<body>
<input type=button onClick="localStorage.g=JSON.stringify(g, null, ' ')" value="Save">
<input type=button onClick="g=JSON.parse(localStorage.g)" value="Load">
</body>
</html>
Keep all variables in object g. Example:
g.arr=[1,2,3];
note some types, such as Date, you'll need to do something like:
g.date=new Date(g.date);
stores locally per page: different pages have different gs
Related
I was tearing my hair out to get this done...particularly for an html5 detection script. I wanted a variable that is set only once and that can't be overwritten again. This is it:
var StaticConfiguration = {};
StaticConfiguration.Main = {
_html5: null
}
StaticConfiguration.getVariable = function(name) {
return StaticConfiguration.Main["_" + name];
}
StaticConfiguration.setVariable = function(name, value) {
if(StaticConfiguration.Main["_" + name] == null) {
StaticConfiguration.Main["_" + name] = value;
}
}
First, I define a global object StaticConfiguration containing all of these variables - in my case, just "html5". I set it to null, since I want to set it inside the application. To do so, I call
StaticConfiguration.setVariable("html5", "true");
It's set then. If I try to set it again, it fails - of course, since _html5 is not null anymore. So I practically use the underscore to "hide" the static variable.
This is helping me a lot. I hope it's a good approach - please tell me if not :)
First off, it's true, not "true" all strings (apart from the empty string) evaluate to true, including the string "false".
Second off, do you really need to protect data like this? There's not really any way to safely run a user's Javascript i your context anyway. There's always a way around protection like this. If offending code really cared, it could just replace the whole StaticConfiguration object anyway.
Matthew's code is a better approach to the problem, but it doesn't follow a singleton pattern, but is a class that needs to be instanciated. I'd do it more like this, if you wanted a single object with "static" variables.
StaticConfiguration = new (function()
{
var data = {}
this.setVariable = function(key, value)
{
if(typeof data[key] == 'undefined')
{
data[key] = value;
}
else
{
// Maybe a little error handling too...
throw new Error("Can't set static variable that's already defined!");
}
};
this.getVariable = function(key)
{
if (typeof data[key] == 'undefined')
{
// Maybe a little error handling too...
throw new Error("Can't get static variable that isn't defined!");
}
else
{
return data[key];
}
};
})();
Personal sidenote: I hate the "curly brackets on their own lines" formatting with a passion!
Take a look at Crockford's article on Private Members in JavaScript. You can do something like this:
var StaticConfiguration = (function() {
var html5; /* this is private, i.e. not visible outside this anonymous function */
return {
getVariable: function(name) {
...
},
setVariable: function(name, value) {
...
}
};
)();
How about:
var StaticConfiguration = new (function()
{
var data = {}
this.setVariable = function(key, value)
{
if(typeof data[key] == 'undefined')
{
data[key] = value;
}
};
this.getVariable = function(key)
{
return data[key];
};
})();
Similar to the other answer, but still allows arbitrary keys. This is truly private, unlike the underscore solution.
I'm a little curious as to why you think that you have to go to this extent to protect the data from being overwritten. If you're detecting the browser, shouldn't it only be done once? If someone's overwriting it with invalid data, then I would assume that it would be a problem in the client implementation and not the library code - does that make sense?
As a side note, I'm pretty big on the KISS principle, especially when it comes to client side scripting.
I know i'm a little late to the party but in situations like this i usually
var data;
if (data === undefined || //or some other value you expect it to start with{
data = "new static value"
};
I have a dataset and some functions that manipulate that data. I would like to store the data and functions in a logical structure that is readable and easy to use without violating good practices.
I see 2 options:
1. Define the data as an array and add the manipulation functions to the array, which is possible because an Array is a kind of Object.
Example:
var dataSet = [1, 2, 3, 4];
var dataSet.add = function (newData) {
if (newData === badData) {
console.log('bad data!');
return;
}
dataSet.push(newData);
};
Pros: Cleaner, more readable, easier to use
Cons: We are modifying an Array object, which means it will not behave as expected, so we should not try to copy this object for example, or document the fact that doing so causes it to lose it's functions.
2. Define a new Object and define the data as a property of the object along with all the manipulation functions.
Example:
var dataSet = {
data: [1, 2, 3, 4],
add: function (newData) {
if (newData === badData) {
console.log('bad data!');
return;
}
data.push(newData);
}
};
Pros: Object works as expected, can be duplicated easily, and packages the data and functions neatly.
Cons: Data cannot be referenced as a canonical representation of the object by simply calling 'dataSet'. Data Manipulation will become tedious to write in some cases, due to the need to constantly call dataSet.data instead of just calling dataSet.
Which of these 2 options is best and why? What are some examples of these options in use today?
Feel free to offer better options as well.
Edit:
Option 2a: (Use an Prototype if you have more than one dataset)
Option 3: Don't attach the data to the functions at all. Instead create a separate array for the data outside of the functions object and put all the data functions into their own object.
Thanks!
In my opinion the second option is better, cause as you said, modifying native object is a bad practice.
Actually there is a third option, you can create a class, that will get the native data structure and expose method on it.
In that way, you will able to create multiple instances of the same class.
class DataSet {
constructor(data) {
this.data = data;
}
add(newData) {
if (newData === badData) {
console.log('bad data!');
return;
}
data.push(newData);
}
}
Same implementation but with ES5
var DataSet = (function () {
function DataSet(data) {
this.data = data;
}
DataSet.prototype.add = function (newData) {
if (newData === badData) {
console.log('bad data!');
return;
}
data.push(newData);
};
return DataSet;
}());
I am trying to read a cookie in plain javascript only. I'm not using any jquery cookie library.
Here's how my cookie looks:
var task_cookie = {
task1 : getTask('task1')
, task2 : getTask('task2')
, task3: getTask('task3')
, task4: getTask('task4')
, task5: getTask('task5')
};
document.cookie = "task_cookie=" + JSON.stringify(task_cookie)+";path=/;domain=.task.com";
Now, I'm trying to read the value of task_cookie later on a different page
I found this code on stackoverflow
function read_cookie(name) {
var result = document.cookie.match(new RegExp(name + '=([^;]+)'));
result && (result = JSON.parse(result[1]));
return result;
}
But this would give me the whole task_cookie.I however want to grab each key value inside the task_cookie. I want something like this:
$.cookie('task1')
$.cookie('task2')
However this is very easy in jquery after I stringify. But forsome reason I need to use pure javascript. How can I get individual values of task1 , task2 etc which are inside the task_cookie object? I'm having a hard time figuring this out :/
The function is returning the entire object, so you can just select an individual cookie from the function:
read_cookie('task_cookie')['task1'];
Or something similar. The above will just return the task1, but you can iterate through all the tasks.
Better yet, you can make the read_cookie function an object method and just return the cookie object to a property of the parent object.
var cookieHandler = {
get: function(name) {
//code to get cookies
//push to cookies array
},
cookies: []
};
That way you don't have to create a bunch of instances for a global function every time you iterate over a task.
So read the value from the cookie after you get the object.
function read_cookie(name) {
var result = document.cookie.match(new RegExp('tasj_cookie=([^;]+)'));
result && (result = JSON.parse(result[1]));
return result ? result[name] : null;
}
Better yet, use localstorage and not cookies.
localstorage.setItem("task1", getTask('task1'));
function read_storage (name) {
return localstorage.getItem(name); //might need to use JSON.parse() depending on the data
}
console.log(read_storage("task1"));
I'm attempting to create a function lookup in Javascript essentially mapping a data type to a function that does something for that data type. Right now I have something similar to:
var Namespace = Namespace || {};
Namespace.MyObj = function () {
var stringFunc = function(someData) {
//Do some string stuff with someData
};
var intFunc = function(someData) {
//Do some int stuff with someData
};
var myLookUp = {
'string': stringFunc,
'int' : intFunc
};
return {
PublicMethod: function (dataType, someData) {
myLookUp[dataType](someData);
}
};
} ();
When I invoke Namespace.MyObj.PublicMethod(dataType, someData) I get an error that myLookUp is not defined. I'm assuming I'm not going about setting up the function lookup object correctly, but not sure how to do so. Thanks for any help.
The problem might simply be incorrect case
myLookup[dataType](someData);
should be (notice the capital U)
myLookUp[dataType](someData);
Just looked at my post after I wrote it up, stupid oversight, I'm declaring the properties as strings, instead of just properties.
....
var myLookUp = {
string: stringFunc,
int: intFunc
};
....
Fixes the issue.
Some additional follow up, in my actual code dataType is the result of a jQuery select. Don't know why or if this would be browser dependant (I'm using FireFox), but using double quotes around the property definition works, single quotes does not, and no quotes works as well. :-\
I was tearing my hair out to get this done...particularly for an html5 detection script. I wanted a variable that is set only once and that can't be overwritten again. This is it:
var StaticConfiguration = {};
StaticConfiguration.Main = {
_html5: null
}
StaticConfiguration.getVariable = function(name) {
return StaticConfiguration.Main["_" + name];
}
StaticConfiguration.setVariable = function(name, value) {
if(StaticConfiguration.Main["_" + name] == null) {
StaticConfiguration.Main["_" + name] = value;
}
}
First, I define a global object StaticConfiguration containing all of these variables - in my case, just "html5". I set it to null, since I want to set it inside the application. To do so, I call
StaticConfiguration.setVariable("html5", "true");
It's set then. If I try to set it again, it fails - of course, since _html5 is not null anymore. So I practically use the underscore to "hide" the static variable.
This is helping me a lot. I hope it's a good approach - please tell me if not :)
First off, it's true, not "true" all strings (apart from the empty string) evaluate to true, including the string "false".
Second off, do you really need to protect data like this? There's not really any way to safely run a user's Javascript i your context anyway. There's always a way around protection like this. If offending code really cared, it could just replace the whole StaticConfiguration object anyway.
Matthew's code is a better approach to the problem, but it doesn't follow a singleton pattern, but is a class that needs to be instanciated. I'd do it more like this, if you wanted a single object with "static" variables.
StaticConfiguration = new (function()
{
var data = {}
this.setVariable = function(key, value)
{
if(typeof data[key] == 'undefined')
{
data[key] = value;
}
else
{
// Maybe a little error handling too...
throw new Error("Can't set static variable that's already defined!");
}
};
this.getVariable = function(key)
{
if (typeof data[key] == 'undefined')
{
// Maybe a little error handling too...
throw new Error("Can't get static variable that isn't defined!");
}
else
{
return data[key];
}
};
})();
Personal sidenote: I hate the "curly brackets on their own lines" formatting with a passion!
Take a look at Crockford's article on Private Members in JavaScript. You can do something like this:
var StaticConfiguration = (function() {
var html5; /* this is private, i.e. not visible outside this anonymous function */
return {
getVariable: function(name) {
...
},
setVariable: function(name, value) {
...
}
};
)();
How about:
var StaticConfiguration = new (function()
{
var data = {}
this.setVariable = function(key, value)
{
if(typeof data[key] == 'undefined')
{
data[key] = value;
}
};
this.getVariable = function(key)
{
return data[key];
};
})();
Similar to the other answer, but still allows arbitrary keys. This is truly private, unlike the underscore solution.
I'm a little curious as to why you think that you have to go to this extent to protect the data from being overwritten. If you're detecting the browser, shouldn't it only be done once? If someone's overwriting it with invalid data, then I would assume that it would be a problem in the client implementation and not the library code - does that make sense?
As a side note, I'm pretty big on the KISS principle, especially when it comes to client side scripting.
I know i'm a little late to the party but in situations like this i usually
var data;
if (data === undefined || //or some other value you expect it to start with{
data = "new static value"
};