Storage event not firing - javascript

Attaching the event:
$(window).on("storage", function (e) {
//callback not getting hit
});
Trying to fire the event:
localStorage.setItem("test", "123");
I have two tabs open, both listening to the storage event. I can see the localStorage getting updated correctly on both tabs. However when I change localStorage on one tab, the other never fires the event. Any ideas?
Tried on Chrome/Firefox. Domain format is https://www.xxx.yyy.zzz.

StorageEvent is fired in different page with the same domain.
From MDN
The StorageEvent is fired whenever a change is made to the Storage object.
This won't work on the same page that is making the changes — it is really a way for other pages on the domain using the storage to sync any changes that are made。

As others in the answers noted, the storage event only get picked up (by the listener) if the localStorage was changed in another browser's tab/window (of the same app), but not within the context of the current tab.
Detect storage changes in the current tab:
window.addEventListener('storage', console.log)
window.localStorage.setItem('test', '123')
window.dispatchEvent( new Event('storage') ) // <-----
A manual storage event is dispatched.
This will effectively trigger the storage event listener twice on other tabs/windows (of the same app), but in certain situations this shouldn't be a problem.

If even testing between different tabs/pages and still not seeing the event... I've found that the event will only fire if the key already exists.
It seems it's more like an onchange event.
Set a default value to the localStorage key, if even undefined and then test.
I'd call this a Chrome bug, as Firefox and Safari are firing correctly, but it is what it is.

An additional point to vsync's answer above is you can fire StorageEvent instead of Event and pass in an object so the fired event will match the browser default.
const oldValue = window.localStorage.getItem('test');
window.localStorage.setItem('test', newValue);
const event = new StorageEvent('storage', {
key: 'test',
oldValue,
newValue,
...
});
window.dispatchEvent(event);

Problem was caused by document.domain overriding in code. After I removed the document.domain setter, events worked correctly.
Seems this is caused by a bug in Chrome.
https://bugs.chromium.org/p/chromium/issues/detail?id=136356&q=label%3AOWP-Standards-Compatibility&colspec=ID%20Pri%20M%20Stars%20ReleaseBlock%20Cr%20Status%20Owner%20Summary%20OS%20Modified

window.addEventListener('storage', function (e) {
console.log("storage event occured here");
},false);
Storage Listner get called in other tabs , other than source tab. Just add debugger to other tabs.

You could always use a utility like localDataStorage to fire the events for you, in the same window/tab, whenever a key value changes, such as those made by the set or remove methods. You can also use it to transparently set/get any of the following "types": Array, Boolean, Date, Float, Integer, Null, Object or String.
[DISCLAIMER] I am the author of the utility [/DISCLAIMER]
Once you instantiate the utility, the following snippet will allow you to monitor the events:
function localStorageChangeEvents( e ) {
console.log(
"timestamp: " + e.detail.timestamp + " (" + new Date( e.detail.timestamp ) + ")" + "\n" +
"key: " + e.detail.key + "\n" +
"old value: " + e.detail.oldval + "\n" +
"new value: " + e.detail.newval + "\n"
);
};
document.addEventListener(
"localDataStorage"
, localStorageChangeEvents
, false
);

I was trying to logout from other tabs if logged out from one tab. The problem I faced was setting the same value over and over.
Thats why the Storage event not fired in other tabs. A simple hack was to add a salt/random stuff everytime to emit the event.
window.localStorage.setItem('logout-event',Math.random().toString())
Then the events are available in other tabs! A better way to handle events from other tabs is to use BroadcastChannel

Related

Why does my history.pushState call result in a null state?

I'm following some tutorials to learn how to use the history events in JS to deal with what is essentially paging on one of my forms. I have added the following code to an onClick event that changes the page:
state = {'actionCode': 'pageChange', 'pageNum': pageNum};
window.history.pushState(state, 'Page ' + pageNum, '/subF/fileName.cfm#page' + pageNum);
console.log(state);
I tried a number of other variations originally, including blank or null title and url arguments.
I then added this to the bottom of my JS file to see what I have to work with:
function checkState(e) {
console.log(e);
console.log(history.state);
}
$(function() {
window.onpopstate = checkState;
});
What I expected to see after changing 'pages' (running the first snippet of code) and then clicking the back button was a e.state object containing actionCode and pageNum variables. Instead, I see the state appear as null even though the object itself appears to hold the data immediately after passing said object to pushState:
I get the same null value when dumping history.state, so I assume the problem is with the push and not the get, or that I'm completely misunderstanding how these functions work.
What I expected to be able to do was add code to checkState that looks at the 'actionCode' and takes appropriate action based on that, reference the variables I know will exist in the state object for that particular actionCode.
In order for the onpopstate event to get triggered, you have to perform an actual "change history" action, i.e. clicking the browser BACK/FORWARD button or manually calling history.back() / history.forward() / history.go(). Simply pushing/replacing a state won't trigger an event.
You can try this:
<script>
window.onpopstate = function(event) {
console.log(event);
};
const state1 = {'actionCode': 'pageChange', 'pageNum': 1};
const state2 = {'actionCode': 'pageChange', 'pageNum': 2};
history.pushState(state1, 'Page ' + state1.pageNum, '/subF/fileName.cfm#page' + state1.pageNum);
history.pushState(state2, 'Page ' + state2.pageNum, '/subF/fileName.cfm#page' + state2.pageNum);
history.go(-1);
</script>
Here, the invocation of history.go(-1) will load the previous page from the session history thus firing an onpopstate event and you will see the state is there.
You can learn more about the peculiarities here: MDN page.
If you're trying to simulate handling of location/state change for new entries, you'll have to manually fire a PopStateEvent(or any custom one with a respective handler).
const event = new PopStateEvent('popstate', { state: state });
window.dispatchEvent(event);
or simply:
window.dispatchEvent(new Event('popstate'));

Give rise to an event on all tabs on same namespace (browser/domain), but not on the tab that triggered it

window.onstorage = () => {
alert("A storage event from another tab!")
};
A storage event never executes within the tab that triggered it, instead it gets called in all other tabs on the same "namespace" (browser/domain. See: https://developer.mozilla.org/en-US/docs/Web/API/Window/storage_event).
How can I do the same but with my own custom event (x happens and then all other tabs on same namespace give rise to an event)?
Using localStorage along with the onstorage event is one way of broadcasting messages amongst tabs. However, you are enforced to encode/parse the message using JSON.stringify/JSON.parse. This prevents you from sending non-stringifiable objects like Blobs, Files etc. Also, the size of localStorage is limited (~10MB).
There is a better approach for this purpose, which is very similar to the postMessage API, called Broadcast Channel API. An example on how you can create your own event notification system:
// Window 1
const bc = new BroadcastChannel('my_channel'); // connect to my_channel
bc.postMessage({eventType: "myEvent", eventData: "Hi, this is an event"});
// Window 2
const bc = new BroadcastChannel('my_channel'); // connect to my_channel
bc.onmessage = function (ev) {
if(ev.data.eventType == "myEvent"){ // only listen for myEvent
console.log("Event myEvent received.");
console.log("Information of the event: %s", ev.data.eventData);
}
}
Notice that both windows must be connected to the same channel (in this case "my_channel"), and both windows must have the same origin.

JS / Prototype - Detect SELECT element collapsed or option actually selected

I have functionality setup to execute whenever a select element changes its value. Instead of using the normal onchange event, it executes every 300ms and only calls the handler if it detects a changed value. I'm using prototype's Form.Element.Observer, if this means anything to you.
Now, in Firefox, when the user hovers over the option and the timed value check function fires, the handler is called because the browser says that the value has changed. What I would like to do is be able to detect when the option has actually been selected by the user instead of simply hovered over. I realize in the Prototype documentation, they explicitly note this bug (or feature, depending how you look at it) but I would like to be able to normalize the behavior.
In a way, this is somewhat similar to a question I've seen on StackOverflow before ( Detect if an HTML select element is expanded (without manually tracking state) ) but I was hoping someone out there would be able to tell me how to do this for only a specific select element and when it's closed instead of expanded.
To get a better idea of what I mean, check out http://jsfiddle.net/KxQd6/ , mess with the select element and check out the console logs.
Here is an example showing how to control SELECT element changes through the different stages you mentioned:
(user, dom-load, and js update)
This code utilizes event.simulate which provides a cross browser way of firing/simulating events.
CODE (fiddle)
document.observe('dom:loaded', function() {
var select = $('box_1'),
button_1 = $('change_via_ajax'),
button_2 = $('change_via_script'),
results = $('results');
// create observer on our select
select.observe('change', function(e) {
var d = new Date()
t = d.getHours() + ':' + d.getMinutes() + ':' + d.getSeconds();
results.insert(new Element('div')
.update(t + ' - Change was fired, value is now [' + this.value + ']'));
});
button_1.observe('click', function() {
new Ajax.Request('/echo/json/', {
onComplete: function(j) {
select.options[Math.round(Math.random() * 2)].selected = true;
select.simulate('change');
}
});
});
button_2.observe('click', function() {
select.options[0].selected = true;
select.simulate('change');
});
});​
Look at https://github.com/harvesthq/chosen (especially at https://github.com/harvesthq/chosen/blob/master/coffee/chosen.proto.coffee lines 68-89). There is no way to determine current state of SELECT element - only to track it more or less reliably with a bunch of event observers for mouse, keyboard and UI events.

Using addProgressListener and onStatusChange to obtain DNS lookup times for pages

I'm trying to add functionality to a firefox extension to time how long it takes a webpage to perform DNS lookup. Looking at Firebug, I figured it's possible to do so by adding a web progress listener to the browser object and listening for events.
First I register an event listener when a page is loaded:
window.addEventListener("load", function(e) { myObj.onLoad(e); }, false);
Inside myObj.onLoad() I register my web progress listener as such:
gBrowser.addProgressListener(this, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
Finally I implement 'onStatusChange' inside myObj, along with QueryInterface and others:
onStatusChange: function(aWebProgress, aRequest, aStatus, aMessage) {
this.logInfo("onStatusChange: " + aMessage + ". Time = " + (new Date().getTime()));
}
However, when onStatusChange is called, aStatus is always 0 even though aMessage displays the correct event. I've spent hours trying to figure this out. Any ideas why??
Also it seems that onStatusChange with status of 'Ci.nsISocketTransport.STATUS_RESOLVING' is only being called on some components, without being called for others, even though they may have a different domain name that needs to be translated and the DNS has not been cached. Need help plz!
If you attach a progress listener to the tabbed browser you only get a filtered view of progress events. You might find that you need to attach your progress listener to the inner browser instead. (If you want to watch multiple tabs/windows you might find it better to attach your progress listener to the window itself, or, for a service, to the root document loader.)

Backbone.js not registering Model.hasChanged event

I'm trying to get a View in Backbone.js to save when there's a 'change' event if (and only if) the data has changed.
Long story short, I've a 'change' event set on the View that calls this:
function changed_event() {
log.debug("Before: " + this.model.get('name')) // not 'contrived!'
this.model.set({'name': 'contrived!'});
log.debug("After: " + this.model.get('name')) // 'contrived!'
if (this.model.hasChanged()) {
alert("This is never called.");
}
}
I'd like to know why the model.hasChanged() is false when clearly the model has been changed.
I'm not sure what other information is necessary, but if there is more information that may be helpful please comment and I'll elaborate.
Thank you for reading.
By calling model.set, you are firing a change event. .hasChanged() returns true iff the model has changed since the last change event. So it will return false since nothing changed since that change event. To get the behaviour you want, call .set with the silent option: this.model.set({'name': 'contrived!'}, {silent: true})

Categories