-
-
Save juliocesar/926500 to your computer and use it in GitHub Desktop.
// I mean, seriously, localStorage is supported even by your mum. How about instead of | |
// casing the feature out, you give users in-memory (stale) storage instead? | |
// If they close your application, they deserve to lose data anyway. | |
// if (!('localStorage' in window)) { | |
if (!Modernizr.localstorage) { | |
window.localStorage = { | |
_data : {}, | |
setItem : function(id, val) { return this._data[id] = String(val); }, | |
getItem : function(id) { return this._data.hasOwnProperty(id) ? this._data[id] : undefined; }, | |
removeItem : function(id) { return delete this._data[id]; }, | |
clear : function() { return this._data = {}; } | |
}; | |
} |
@webninjataylor nice one. good find.
Thanks! That's most helpful.
The above isn't a polyfill, this is what I would call a polyfill : https://gist.github.com/remy/350433 :)
I have a beef with the polyfill mentioned twice in the comments (https://gist.github.com/remy/350433) It should preferably fallback to other forms of storage if possible (sqlite, userData, etc) before cookies. Cookies are really meant to be used as something to send back and forth to the server. localStorage is for the client only.
Change String(val)
to JSON.stringify(val)
, because vanilla objects’ toString
casts to "[object Object]"
. Then again, you might have to pollyfill JSON
, which defeats the purpose…
This would at least allow for direct setting and getting (and overwriting of localStorage methods, but as mentioned you can do that with the native implementation):
(function () {
function isSupported() {
try {
return 'localStorage' in window && window['localStorage'] !== null;
} catch(e) {
return false;
}
}
if (!isSupported()) {
function init(undef) {
var store = {
setItem: function (id, val) {
return store[id] = String(val);
},
getItem: function (id) {
return store.hasOwnProperty(id) ? String(store[id]) : undef;
},
removeItem: function (id) {
return delete store[id];
},
clear: function () {
init();
}
};
window.localStorage = store;
}
init();
}
}());
@carsonkahn, the native implementation does that too, at least in Chrome:
window.localStorage.setItem('foo', { a: 1, b: 2 });
// undefined
window.localStorage.getItem('foo');
// "[object Object]"
Garbage.
The point is to keep data across refreshes.
That's why people use cookies in polyfills.
See the spec or MDN.
Not really. Currently all browsers that are still relevant already support local storage. The only reason for the feature detect and fallback to memory use today is Safari and it's Private Browsing Mode. And when Private Browsing Mode is enabled, all hope of storing something longer-term is lost anyway...
The feature detect must perform a setItem
call wrapped in a try...catch
to be useful. I recommend also testing if you get back what you stored as it's just one extra line. Then, if it's not available, you either wrap all calls to localStorage in if
blocks, or you use some shim instead.
Here is how I deal with it in my code:
var DB;
try {
var x = 'test_localstorage_available_' + Date.now();
localStorage.setItem(x, x);
var y = localStorage.getItem(x);
localStorage.removeItem(x);
if (x !== y) {throw new Error();}
DB = localStorage;
}
catch(e) {
DB = new MemoryStorage('my-cool-app');
}
memorystorage is a little library I wrote that implements functional storage in memory that follows the Web Storage API. For more information check out my blog post on this topic: Introducing MemoryStorage.
@Download is correct, !Modernizr.localstorage returns true in Private Browsing Mode for Safari even if you can't use it.
However,if the user is using Private Browsing you can in most case use a combination of what you said and this polyfill.
// fix issue where localStorage or sessionStorage are not available , example : incognito Mode
try
{
window.sessionStorage.setItem('testKey', '1');
window.sessionStorage.removeItem('testKey');
}
catch (error)
{
window.sessionStorage = {
_data : {},
setItem : function(id, val) { return this._data[id] = String(val); },
getItem : function(id) { return this._data.hasOwnProperty(id) ? this._data[id] : undefined; },
removeItem : function(id) { return delete this._data[id]; },
clear : function() { return this._data = {}; }
};
}
Same thing for LocalStorage
And what would the difference be if we replace in getItem
:
return this._data.hasOwnProperty(id) // ...
With
return !(id in {}) // ...
Here's what I used with ES2015 arrow functions to let me "use" local storage while testing:
const ls = typeof localStorage !== 'undefined' && localStorage ?
localStorage :
{ _data: {}
, setItem: (id, val) => ls._data[id] = String(val)
, getItem: id => ls._data.hasOwnProperty(id) ? ls._data[id] : undefined
, removeItem: id => delete ls._data[id]
, clear: () => ls._data = {}
}
I then used ls
locally instead of localStorage
@morinted I don't think that supports the case when someone requests a property from localStorage directly e.g. localStorage.isItemThere
On the some recent browsers, like safari on iOS, window.localStorage
is not assignable, but Storage object is, which affects localStorage functions.
Here is my version of the pollyfill:
(function () {
function isSupported() {
var item = 'localStoragePollyfill';
try {
localStorage.setItem(item, item);
localStorage.removeItem(item);
return true;
} catch (e) {
return false;
}
}
if (!isSupported()) {
try {
Storage.prototype._data = {};
Storage.prototype.setItem = function (id, val) {
return this._data[id] = String(val);
};
Storage.prototype.getItem = function (id) {
return this._data.hasOwnProperty(id) ? this._data[id] : undefined;
},
Storage.prototype.removeItem = function (id) {
return delete this._data[id];
},
Storage.prototype.clear = function () {
return this._data = {};
}
} catch (e) {
console.error('localStorage pollyfill error: ', e);
}
}
}());
This is not a replacement of localstorage, because localstorage is persistent across page reloads
Shouldn't it return null
if key is not there?
@johny-gog is right.
It should be null
instead of undefined
when there is no id
.
not a good way.
I don't understand how window.localStorage
can be nil if the window.Storage
interface exists.
What is the versions of Safari and iOS with which you encountered this issue?
Please tag me on your response sir.
Shouldn't it return null if key is not there?
Thanks for pointing on it, it solve a bug for me.
the case of: JSON.parse(localStorage.getItem('foo'))
bfff you need to check this polyfill:
if (!window.localStorage) {
window.localStorage = {
getItem: function (sKey) {
if (!sKey || !this.hasOwnProperty(sKey)) { return null; }
return unescape(document.cookie.replace(new RegExp("(?:^|.*;\\s*)" + escape(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=\\s*((?:[^;](?!;))*[^;]?).*"), "$1"));
},
key: function (nKeyId) {
return unescape(document.cookie.replace(/\s*\=(?:.(?!;))*$/, "").split(/\s*\=(?:[^;](?!;))*[^;]?;\s*/)[nKeyId]);
},
setItem: function (sKey, sValue) {
if(!sKey) { return; }
document.cookie = escape(sKey) + "=" + escape(sValue) + "; expires=Tue, 19 Jan 2038 03:14:07 GMT; path=/";
this.length = document.cookie.match(/\=/g).length;
},
length: 0,
removeItem: function (sKey) {
if (!sKey || !this.hasOwnProperty(sKey)) { return; }
document.cookie = escape(sKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/";
this.length--;
},
hasOwnProperty: function (sKey) {
return (new RegExp("(?:^|;\\s*)" + escape(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=")).test(document.cookie);
}
};
window.localStorage.length = (document.cookie.match(/\=/g) || window.localStorage).length;
}
source: https://developer.mozilla.org/en-US/docs/Web/API/Storage/LocalStorage
Looks like this Gist needs a new name :o)
I just want to add, there are newer/better/shinier ways to detect if localStorage
is functional.
Many browser scenarios disable the features, even if window.localStorage
is defined.
A beautiful complete history and solution here:
https://gist.github.com/paulirish/5558557
try {
localStorage.setItem('mod', 'mod');
localStorage.removeItem('mod');
return true;
} catch(e) {
return false;
}
From @DmitryBaranovskiy 's snippet.
I just edit some. and can use localStorage['foo'] = 'bar';
(function (isStorage) {
if (!isStorage) {
function Storage() { };
Storage.prototype.setItem = function (id, val) {
return this[id] = JSON.stringify(val);
}
Storage.prototype.getItem = function (id) {
return this.hasOwnProperty(id) ? this[id] : null;
}
Storage.prototype.removeItem = function (id) {
return delete this[id];
}
Storage.prototype.clear = function () {
var self = this;
Object.keys(this).map(function (key) { self.removeItem(key) });
}
window.localStorage = new Storage();
}
})((function () {
try {
return "localStorage" in window && window.localStorage != null;
} catch (e) {
return false;
}
})());
2018-08-02 Updated
- return
null
instead ofundefined
- hasOwnProperty must passed
id
Just a observation getItem
returns null
when it doesn't find any value for the key, not undefined
. Returning undefined
may cause deserialization errors in any JSON.parse
expecting a storage value.
Updated
If you want the simplicity of @juliocesar gist but keep the data in a cookie:
window.localStorage = {
_data : JSON.parse(document.cookie.replace(/(?:(?:^|.*;\s*)localStorage\s*\=\s*([^;]*).*$)|^.*$/, "$1") || '{}'),
_save : function() { document.cookie = "localStorage=" + JSON.stringify(this._data) + "; expires=Tue, 19 Jan 2038 03:14:07 GMT; path=/"; },
setItem : function(id, val) { this._data[id] = String(val); this._save(); },
getItem : function(id) { return this._data.hasOwnProperty(id) ? this._data[id] : null; },
removeItem : function(id) { delete this._data[id]; this._save(); },
clear : function() { this._data = {}; this._save(); }
};
I swear this is hand crafted (not minified)
i made it by editing the first post in this thread, hacking it about until it acheived full functionality in both node.js and chrome.
caveat - it uses proxies to acheive localStorage.item = value functionality. this might mean older browsers choke at it. which probably makes this whole effort nothing but an academic exercise.
it works with Object.keys, and you can get the keys by calling localStorage.keys() also
to prove it works (in chrome at least), you need to do "delete window.localStorage" before loading the script, or edit the argument at the end of the file to give it a different name
in node.js use
var localStorage = require("./localStorage.js") - or whatever you called it.
data is writen to ./localStorage.json in real time
in the browser it will just auto populate window.localStorage when the script loads, and saves data to the cookie "localStorage' - of courese.
for what it's worth, it coverts keynames into a hash of sorts - more of an alphanumeic index. this means you can literally use any string as a key, provided you use the getItem and setItem mechanism.
enjoy.
(function (o,s,n,b,j,R,K,I,P,T,F,X,C,O,N,A) {
if (!o.k) {
var S=function(){},i,c,w=j.stringify[b](j),r=j.parse[b](j),u,
z=function(x){delete o[x];},D=o.d,
k=function(k){
i=K[I](k);if(i<0){i= K[n];K.push(k);}
return i[s](R);
},f=function(k,f){P[k]={enumerable:false,configurable:true,value:f};},
L=o.l,LS,ls={},p,h;
f("ikeys",function(){return P.keys.value().map(function(k,i){return i[s](R);});});
f("keys",function(){return K.filter(function(k,i){return !!o[i[s](R)];});});
f("setItem",function(i,v){c=k(i);o[c]=w(v);S();return o[c];});
f("getItem",function(i){c=K[I](i);h=c[s](R);return c<0||!o[h]?u:r(o[h]);});
f("removeItem",function(i){c=K[I](i);h=c[s](R);if(c>=0){i=o[h];z(c);S();return i;}});
f("clear",function(){c=K[n];K.splice(0,c);for(i=0;i<c;i++){z(i[s](R));}S();});
O.defineProperties(ls,P);
p={
get : function(x,k){ return P[k]?P[k].value:ls.getItem(k);},
set : function(x,k,v){ if(!P[k]){ls.setItem(k,v);return true;}},
ownKeys: function(){return K.filter(function(k,i){return !!o[i[s](R)];});},
getOwnPropertyDescriptor : function(k) {
return {enumerable: true,configurable: true,};
}
};
LS = new Proxy(ls,p);
o.x=function(i){O.keys(i).forEach(function(k){p.set(ls,k,i[k]);});};
if (o.n) {
module.exports=LS;
A="./"+L+".json";
N=require("fs");
C=S;
try{o.x(N.existsSync(A)?r(N.readFileSync(A)):{});}catch(e){}
S=function(){
c=w(LS);
i=c.length>2;
D=i?"writeFile":"unlink";
N[ D ]("./"+L+".json",i?c:C,i?C:u);
};
} else {
o.w[o.l]=LS;
c = D[C][X]( ';' ).map( function( x ) { return x.trim()[X]( '=' ); } ).reduce(
function( a, b ) { a[ b[ 0 ] ] = b[ 1 ]; return a; }, {} )[ L ];
if (c) {
o.x(r(atob(c)));
}
S=function(){
c=w(LS);i=c.length>2;
D[C]= L+"="+ (i ? btoa(c) : "")+"; ; expires="+ ( i ? N : A) +" UTC; path=/";
};
}
"lwndkx"[X]('').forEach(z);
}
})((function (o,k) {
try {
o.l=k;
o.w=window;
o.d=document;
o.k=(k in o.w&&o.w[k]!==null);
} catch (e) {
o.w={};
o.n=!0;
}
return o;
})({},"localStorage"),"toString","length","bind",JSON,36,[],"indexOf",{},!0,!1,"split","cookie",Object,"Tue, 19 Jan 2038 03:14:07","Thu, 01 Jan 1970 00:00:00");
I'm basically terrified looking at the traffic this has had over the years and I never received even a single notification for any of it.
Thanks, people! Yes, my original example is terrible.
Not sure if this addresses the fact that private Safari threw an error on setItem
in some versions... which is stupid, but still relevant. I'm just going to conveniently ignore it at this point though, and mute all my sentry/rollbar issues :-)
For those that want to "polyfill" the localStorage object due to it being blocked by an error like this: DOMException: Failed to read the 'localStorage' property from 'Window': Access is denied for this document.
(usually due to an embedded frame when your site is used in incognito), you'll need to use defineProperty
. For example, here's a super minimal polyfill that doesn't allow usage like localStorage.setItem("key", value)
, but does allow localStorage.key=value
:
Object.defineProperty(window, 'localStorage', {value: new Proxy({}, {set:(obj,k,v)=>obj[k]=String(v)}) });
@juliocesar, I guess I missed that. Well if it helps (and I know @timoxley will like this) a clean set of polyfills to get persistent local storage in IE7 is to grab this (https://github.com/douglascrockford/JSON-js/blob/master/json2.js) for the JSON polyfill, and then this (https://gist.github.com/350433) for the storage (in that order). Keep in mind 'getItem' and 'setItem' need to be used with this rather than the shorthand. Hope this is useful.