I have a local storage service I've created which looks like this
import Ember from 'ember';
import ENV from 'bidr/config/environment';
const stringify = JSON.stringify;
const parse = JSON.parse;
function setItem(key, value) {
localStorage.setItem(key, stringify(value));
}
function getItem(key) {
var result = localStorage.getItem(key);
if (result) {
return parse(result);
}
}
export default Ember.Service.extend({
namespace: ENV.APP.LocalStorageKey,
user: null,
init: function() {
this.set('user',this.getItem('user'));
},
setItem: function (key, object) {
var ttlOptions = arguments[2];
if (ttlOptions) {
this._setTTLKey(key, ttlOptions);
}
this.set(key,object);
setItem(this._namespacedKey(key), object);
},
getItem: function (key) {
return getItem(this._namespacedKey(key));
},
keyExpired: function (key, now) {
var ttl = this.getItem(`_ttl_${key}`);
if (ttl) {
var expiry = new Date(ttl.lastUpdated)
.setTime(new Date(ttl.lastUpdated)
.getTime() + ttl.ttl);
now = now || new Date();
return now > expiry;
}
return true;
},
_setTTLKey: function (key, ttlOptions) {
var dateTime = new Date();
setItem(this._namespacedKey(`_ttl_${key}`),
{ttl: ttlOptions.ttl, lastUpdated: dateTime}
);
},
_namespacedKey: function (key) {
return this.get('namespace') + `.${key}`;
}
});
The user object is so I can access it directly in a template or as a computed property value.
The problem is another computed property that depends on a property of user in the service is not being notified when it gets changed. In my application controller I have an action that updates a property on the user property in the service
updateActiveEvent(eventInfo) {
var currentUserInfo = this.get('localStorage').getItem('user');
currentUserInfo.active_auction = eventInfo.eid;
this.get('localStorage').setItem('user',currentUserInfo);
},
(this comes in from a socket service) this is working properly as after this action fires I can check the browsers localstorage object and see the active_auction property of it has changed. But in my template where I am displaying that (or a computed property based on that) it's not updating.
I saw there is possibly a way I can force ember to notify of property changes but couldn't seem to get this to work, not sure if I was doing it right.
Related
I work over a small React app that should store some data in local storage. And if I understand it correctly, it should keep them till clearly ordered to clear. The reason for this post is that while app data perfectly survives the refreshing page action, it disappears after closing/opening Chrome.
Here is the function which creates the storage:
export function initGlobalStorage() {
var Storage = function (options) {
options = options || {};
for (var i in options) {
this[i] = options[i];
}
};
const prefix = window.location.href;
Storage.prototype = {
storage: window.localStorage,
addPrefix: function(key){return (prefix + key); },
set: function (key, value) {
this.storage.setItem(this.addPrefix(key), JSON.stringify(value));
},
get: function (key) {
return this.storage.getItem(this.addPrefix(key));
},
remove: function (key, value) {
this.storage.remove(key, value);
},
clear: function () {
this.storage.clear();
},
key: function (index) {
return this.storage.key(index);
},
each: function (fn) {
if (typeof fn === "function") {
for (var i = 0, key; i < this.storage.length; ++i) {
key = this.storage.key(i);
fn(this.storage.getItem(key), key, i);
}
}
},
getAll:function(){
let result =[];
for(var key in this.storage){
if (key.includes(prefix)){result.push(JSON.parse(this.storage.getItem(key)))};
}
return result;
},
hasItems:function(){
console.log(this.getAll().length);
return this.getAll().length? true:false;
},
};
window.Storage = {
local: new Storage({ storage: window.localStorage }),
session: new Storage({ storage: window.sessionStorage }),
};
};
window.local.href is for distinguish 'my items' in localStorage from others that possibly exists there on client computer. BTW, currently I only test this app on localhost.
Here is how above function is applied
export function checkSupportForCache() {
return dispatch => {
if (storageAvailable('localStorage')) {
dispatch(cacheSupported());
console.warn("Storage available");
initGlobalStorage();
if (window.Storage.local.hasItems()){
console.log('Storage contains items');
dispatch(cacheNotEmpty());
}else{
console.warn("No items in storage");
}
} else {
console.warn("Storage not available");
}
};
}
storageAvailable('localStorage') is a function that checks support for localStorage in a certain browser.
As I have written, when I refresh page localStorage is still there - then I suppose code is OK.
But what happens when do close browser then? I do not consciously request for any sort of purging action. Do I unconsciously? I have checked Chrome settings and there is nothing that looks suspicious. Do I not understand anything at all as per subject? Maybe, just give me a hint.
I am mocking localStorage in unitests like
function storageMock() {
var storage = {};
....
}
and setting localStorage like
window.localStorage = localStorageMock()
It was working fine until, I have updated Node to 10.15.1.
It is throwing error that TypeError: Cannot set property localStorage of #<Window> which has only a getter.
Any idea that how I can mock localStorage and set it to window.localStorage.
P.S I am getting answers like setItem and getItem on localStorage, is there any way that I can set whole localStorage at once.
Add
Object.defineProperty(window, 'localStorage', {
value: storageMock
});
Full example:
const localStorageMock = (() => {
let store = {};
return {
getItem(key) {
return store[key] || null;
},
setItem(key, value) {
store[key] = value.toString();
},
removeItem(key) {
delete store[key];
},
clear() {
store = {};
}
};
})();
Object.defineProperty(window, 'localStorage', {
value: localStorageMock
});
window.localStorage.setItem("KEY", "INPUT")
I'm using spyOn, in a jasmine test, to "listen" to a function call from a service,
that function returns an Observable.
I'm getting the error unexpected token U JSON;
The error is generated from the component line:
this.config = JSON.parse(localStorage.getItem('configuration'));
The localStorage item was JSON.stringified;
I understand that this error is usually thrown when JSON.parse = undefined,
So I tried to set the variable within my test i.e.
component.config = mockConfig;
So..
// Storage Mock
function storageMock() {
var storage = {};
return {
setItem: function(key, value) {
storage[key] = value || '';
},
getItem: function(key) {
return key in storage ? storage[key] : null;
},
removeItem: function(key) {
delete storage[key];
},
get length() {
return Object.keys(storage).length;
},
key: function(i) {
var keys = Object.keys(storage);
return keys[i] || null;
}
};
}
let mockConfig = JSON.stringify({
base_url:"http://image_url/",
poster_sizes:['w9', 'w100']
})
//Set storage
let m = storageMock()
m.setItem('configuration', mockConfig)
it('Should set items array with values from MoviesService', () => {
component.config = JSON.parse(m.getItem('configuration'));
let spy = spyOn(moviesService, 'getPreview').and.callFake(()=>{
return Observable.from([[{id1: 1, title: 'a'}, {id1: 2, title: 'b'}]])
})
component.ngAfterViewInit();
expect(component.items.length).toBeGreaterThan(0);
});
For anyone who come across this problem I got this working by by placing this code in the beforeEach wrapper
Object.defineProperty(window, 'localStorage', { value: m });
Basically it uses my mock localStorage variable instead of the one from the window object.
How can I set additional data in an action function in a Meteor Application that uses IronRouter ? See comments in emailWelcome and emailContract functions below...
Code:
EmailController = RouteController.extend({
template: 'emailPage',
waitOn: function() {
return [
Meteor.subscribe('customers'),
];
},
data: function() {
var request = Requests.findOne(this.params._id);
if (!request)
return;
var customer = Customers.findOne({'_id': request.customerId});
if (!customer)
return;
return {
sender: Meteor.user(),
recipient: Customers.findOne({_id:Session.get('customerId')})
};
},
emailWelcome: function() {
// Set var in the context so that emailTemplate = 'welcomeEmail' here
this.render('emailPage');
},
emailContract: function() {
// Set var in the context so that emailTemplate = 'contractEmail' here
this.render('emailPage');
}
});
You can get access to the data with this.getData() in your action functions:
emailWelcome: function() {
var data = this.getData(); // get a reference to the data object
data.emailTemplate = 'welcomeEmail';
this.render('emailPage');
},
emailContract: function() {
var data = this.getData(); // get a reference to the data object
data.emailTemplate = 'contractEmail';
this.render('emailPage');
}
be careful not to call this.data(), as that will regenerate the
data instead of getting you a reference to the already generated data
object.
also be careful not to call this.setData(newData) within an action as that will invalidate the old data object, initiating a reactivity reload, and lead to an infinite loop!
I'd like to create a Javascript object that can save and load its state (to local storage).
This is the basic pattern I'm using:
var obj = function () {
// private members
//
return {
// public members
load: function () {
this.state = JSON.parse(localStorage.getItem('obj'));
if (this.state === null) {
this.state = {
name: 'foo'
};
}
},
save: function () {
localStorage.setItem('obj', JSON.stringify(this.state));
}
};
}();
// load state
obj.load();
console.log(obj.state.name);
// save state
obj.state.name = 'bar';
obj.save();
But there's one thing that annoys me about this pattern: I have to access the object's persistent properties through the 'state' property.
How can I rewrite this so I can use the object in a more natural way, like:
// load state
obj.load();
console.log(obj.name);
// save state
obj.name = 'bar';
obj.save();
This is a very simple 'state', but the solution has to work for a complex state object with nested objects, arrays etc., so simply adding a 'name' property to my object is not what I'm after.
If you don't care which properties get loaded/saved then you can simply copy all from state into self. For example, after reading into var state (instead of this.state since you don't want state to be a part of this anymore): for(x in state) this[x] = state[x];
similarly, you'd save out: var state = {}; for(x in this) state[x] = this[x]
However, if you want to have a pre-defined list, then I'd recommend: var fields = ['name', 'zip', 'age'];
And then use for(x in fields) this[x] = state[x] to load and for(x in fields) state[x] = this[x]; to save.
Sorry it's a bit pieced together, but I hope you can follow what I mean :)
EDIT: Added full example per OPs request.
An example of a full solution using this technique is as follows:
var obj = function () {
// private members
//
return {
// public members
load: function () {
var state = JSON.parse(localStorage.getItem('obj'));
if(state == null) state = { name: 'foo' };
for(x in state) this[x] = state[x];
},
save: function ()
{
var state = {};
// check if it's a function. This version taken from underscorejs
var isFunction = function(obj) {
return !!(obj && obj.constructor && obj.call && obj.apply);
};
for(x in this)
{
if(isFunction(this[x])) continue; // skip functions
state[x] = this[x];
}
localStorage.setItem('obj', JSON.stringify(state));
}
};
};
You can also accomplish a direct save when a property changes,
by using ES5 getters/setters or by using Watch.js
Watch.js example:
var obj = (function () {
// private members
//
var self = {
// Some properties
name: '',
otherName: '',
// Try to load state or use "foo state"
state: JSON.parse(localStorage.getItem('obj')) || {
name: 'foo'
},
save: function () {
localStorage.setItem('obj', JSON.stringify(this.state));
}
};
// Watch the object and save it to local storage, when a property changes
// (Of course, you don't need to call the save method here...)
watch(self, function(property, value) {
console.log('saving state!');
self.state[property] = value;
self.save();
});
return self;
}());
// Update some properties and see that it is saved to local storage.
obj.name = "Some name";
obj.otherName = "Some other name";
console.log(JSON.parse(localStorage.getItem('obj')));
Example on JsFiddle.
You could make the state internal and surface getters and setters:
var obj = function () {
// private members
var state = {};
return {
// public members
load: function () {
var loadedState = JSON.parse(localStorage.getItem('obj'));
if (loadedState === null) {
state = {
name: 'foo'
};
} else {
state = loadedState;
}
},
save: function () {
localStorage.setItem('obj', JSON.stringify(state));
},
getState: function (key) {
return state[key];
},
setState: function (key, value) {
state[key] = value;
}
};
};
Using jQuery's extend():
var obj = (function () {
return {
load: function () {
var stored = localStorage.getItem("obj");
var state = stored ? JSON.parse(stored) : {
name: 'foo'
};
$.extend(this, state);
},
save: function () {
localStorage.setItem("obj", JSON.stringify(this));
}
};
})();
// load state
obj.load();
console.log(obj);
// save state
obj.name = 'bar';
obj.save();
jsfiddle
All credit to pimvdb.