History.js window.onstatechange doesn't fire - javascript

I'm just a js beginner, so maybe I just don't understand it right. But isn't the window.onstatechange supposed to fire when you hit the back/forward button of your browser when you previously changed the state with History.stateObj ?
I actually see the object changing in the Firebug console, but window.onstatechange just won't fire! Also - very confusing - when I use window.onpopstate instead, the object isn't changing anymore (when using the back/forward button).
Here's what I do:
$('.container').click(function(e) {
e.preventDefault();
var title = $(this).data('title');
stateObj = { show: title }
History.pushState(stateObj, document.title, '?show=' + title);
}
window.onstatechange = function() {
var title = History.getState().data['show'];
alert('title');
}
I already found out from here, that I have to use
History.Adapter.bind(window,'statechange',function(){
var title = History.getState().data['show'];
alert('title');
});
...which works, but I still don't really understand why window.onstatechange won't fire?!
// EDIT: Opened a ticket on Github
Any suggestions ?

Lucian Poston answered this question on GitHub in April 2012:
This seems to be the design of the History.Adapter event model. Have a look at the adapters' implementations e.g. https://github.com/balupton/history.js/blob/master/scripts/uncompressed/history.adapter.native.js
When history.js raises a 'statechange' event, it invokes History.Adapter.trigger(), which iterates over a list of event handlers setup via prior calls to History.Adapter.bind(). window.onstatechange() is not invoked as you expected.
Actually, History.Adapter.bind(window, 'statechange', function(){ alert('oi'); }) sets up window.onstatechange so that if fired, it would invoke History.Adapter.trigger(window, 'statechange'), which in turn would invoke the event handler, function(){ alert('oi'); }. If you redefine window.onstatechange as in your example, it would break that behavior.

Related

Is it possible to bind to events on a child window from a parent?

Is it possible to bind functions to events on child windows?
document.getElementById('foo').onclick = function() {
var newWindow= window.open('other.html', "_blank");
newWindow.document.addEventListener("onreadystatechange", function(){
console.log('foo'); // This is never run. Can I construct the new window so that it is run "onreadystatechange"?
});
return false;
};
Note that I would like to bind an event to onreadystatechange. I wish to avoid a race condition, can I create a window, bind the events and then load the URL to avoid the race condition?
.addEventListener("onreadystatechange", ...)
Event properties start with "on". The event names on the other hand do not. I.e. it should be
.addEventListener("readystatechange", ...)
I have not tried avoiding the race condition because I know of no way to do so.
Ok, I'm not entirely sure how events and auxiliary browsing context initialization work with window.open(), the spec is quite complex there.
I'd just try setting DOM event breakpoints (chrome debugger has those) and see which events are fired in which order and then check if that works in other browsers.
That said, I think the simplest option here might to read the document.readyState property. If it's "complete" then the site is already fully loaded and no further state change event will be fired and you can execute your script directly instead of waiting for the event.
If you do this should be good to go.
var newwindow = window.open('other.html', "_blank");
var $ = newwindow.$; // add if needed
$(newwindow).bind('someEvent', function() { FunctionThatDoesSomethingInTheNewWindow });
return false;

inline javascript onclick event

This is my html code
Hit
This is my javascript file
function clickHandler(evt) {
var thisLink = (evt)?evt.target:Window.event.srcElement;
alert(thisLink.innerHTML);
return false;
}
But when i click the Hit Link, it redirects.
you need to pass in the event if you wish to preventDefault.
html:
Hit
script:
function runFunction (evt) {
evt.preventDefault();
evt.stopPropagation();
}
To tie both of the very-correct answers together, what's happened is you've inlined a function where you've written onclick="return runFunction();"
If you look at that, what it's really doing is going like this:
var link = document.getElementById("myLink");
link.onclick = function () { runFunction(); };
See the problem?
My runFunction is being called without any event object passed in, at all.
...which means that var thisLink = (evt) ? is going to return false, which means that it's going to try to run in oldIE-mode.
By writing onclick="runFunction", that's the same as saying:
link.onclick = runFunction;
Which means that when the onclick event happens, runFunction will be called, and in W3C-compliant browsers, it will be sent an event object.
Which is why that solution works.
The best way to avoid a lot of this confusion is to deal with JavaScript from inside of JavaScript, and to deal with HTML inside of HTML, so that you don't have to worry about how strings translate into code.
Now, to get all of this to work, AND prevent redirection, you want to do this:
for W3C browsers (the ones that pass the event parameter):
function runFunction (evt) {
// stops the default-action from happening
// means you need to find another way to fire it, if you want to later
evt.preventDefault();
// stops higher-up elements from hearing about the event
// like if you stop a submit button from "clicking", that doesn't stop the form
// from submitting
evt.stopPropagation();
//the oldIE versions of both of these are
event.cancelBubble = true;
event.returnValue = false;
}
When I plugged your code into chrome, I got this as the error in the console:
Uncaught TypeError: Cannot read property 'srcElement' of undefined
IF the javascript bombs out while processing, it never gets a chance to return at all so the browser tends to disregard what is in the onclick handler after the exception.
Since it bombed out... default behavior of anchor tags, which is to send you off to wherever the href says to go.
Try wrapping the contents of the function in a try/catch block and see what turns up if this kind of thing plagues you.

Callback for a popup window in JavaScript

I hope I did my homework well, searching the Internets for the last couple of hours and trying everything before posting here, but I'm really close to call it impossible, so this is my last resort.
I want a simple thing (but seems like hard in JavaScript):
Click button -> Open Window (using window.open)
Perform an action in the popup window and return the value to parent (opener)
But I want to achieve it in a systematic way, having a callback defined for this popup; something like:
var wnd = window.open(...)
wnd.callback = function(value) {
console.log(value);
};
I've tried defining the callback property in popup window JS code:
var callback = null;
Unfortunately, that does not work, as...
$('#action').click(function() {
console.log(callback);
});
... returns just that "null" I set initially.
I've also tried setting the callback in a parent window after window load (both thru window.onload=... and $(window).ready()), none worked.
I've also tried defining some method in child window source code to register callback internally:
function registerCallback(_callback)
{
callback = _callback; // also window.callback = _callback;
}
But with the same result.
And I don't have any more ideas. Sure, it would be simple setting the value using window.opener, but I'll loose much of a flexibility I need for this child window (actually an asset selector for DAM system).
If you have some ideas, please share them.
Thank you a million!
HTML5's postMessage comes to mind. It's designed to do exactly what you're trying to accomplish: post messages from one window and process it in another.
https://developer.mozilla.org/en/DOM/window.postMessage
The caveat is that it's a relatively new standard, so older browsers may not support this functionality.
http://caniuse.com/#feat=x-doc-messaging
It's pretty simple to use:
To send a message from the source window:
window.postMessage("message", "*");
//'*' is the target origin, and should be specified for security
To listen for messages in a target window:
window.addEventListener
("message", function(e) {
console.log(e.data); //e.data is the string message that was sent.
}, true);
After few more hours of experiments, I think, I've found a viable solution for my problem.
The point is to reference jQuery from parent window and trigger a jQuery event on this window (I'm a Mac user but I suppose, jQuery has events working cross-platform, so IE compatibility is not an issue here).
This is my code for click handler on anchor...
$(this).find('a[x-special="select-asset"]').click(function() {
var evt = jQuery.Event('assetSelect', {
url: 'this is url',
closePopup: true,
});
var _parent = window.opener;
_parent.jQuery(_parent.document).trigger(evt);
});
... and this is the code of event handler:
$(document).bind('assetSelect', function (evt) {
console.log(evt);
});
This solution is fine, if you don't need to distinguish between multiple instances of the asset selection windows (only one window will dispatch "assetSelect" event). I have not found a way to pass a kind of tag parameter to window and then pass it back in event.
Because of this, I've chosen to go along with (at the end, better and visually more pleasant) solution, Fancybox. Unfortunately, there is no way - by default - to distinguish between instances either. Therefore, I've extended Fancybox as I've described in my blog post. I'm not including the full text of blog post here, because is not the topic of this question.
URL of the blog post: http://82517.tumblr.com/post/23798369533/using-fancybox-with-iframe-as-modal-dialog-on-a-web

Statechange is firing whenever i do a push-state

I am using history.js to handle back button. In history.js statechange is firing whenever i do a pushstate. Why?
Wanted to add, yes this is the expected behaviour of History.js.
At the same time there are more discussions that critize this behaviour as it is not the W3C standard and does create some confusion.
In short, to answer your question: In the History.js pushState() function is a call to statechange at the end.
Upside of this solution is that you can just change (push) your new state and let the onstatechange()-function handle the transition. Downside is that you are not able to handle exceptions/or have to write them into the onstatechange event-handler.
I personally prefer the W3C way of handling this, as you can distinguish between back/forward button and pushState. The History.js maintainers are working on an internal flag solution, that enables you to change this behaviour:
Notice how the above calls [pushstate-calls] trigger statechange events, if for some
reason you do not want this to happen then inside your statechange
handler you can use the following:
if ( History.getState().internal ) { return; }
*This feature is currently in development and can only be used with the 'dev' version of History.js! Hope this will help some other people in the future :)
After trying to accomplish this for a day now, I finally found the solution here: https://github.com/browserstate/history.js/issues/47#issuecomment-25750285
The code is pretty damn simple, the following is quoted from the link:
When you push your state
History.pushState({
_index: History.getCurrentIndex(),
someData: ...
}, someTitle, someUrl);
and then in the event binding
History.Adapter.bind(window, 'statechange', function () {
var currentIndex = History.getCurrentIndex();
var internal = (History.getState().data._index == (currentIndex - 1));
if (!internal) {
// your action
}
});
According to this discussion on github, it's expected behaviour of history.js
This pull request claims to have modified history.js to be more inline with W3C Specs.

Prevent single attribute from firing this.bind("change")

In Backbone.js, I have a model I am binding a change event to, but I want to prevent this from happening on specific attribute changes. For example, I want it to fire for every single time model.set() is called, except when calling model.set({arbitraryName: value}).
Here's what I have:
this.bind("change", function() {
this.update();
});
But I have no clue how to determine what is being set--any ideas?
EDIT
It looks like I can call
model.set({arbitraryName: value}, {silent: true})
to prevent the change event from firing (which works for what I need), but what if I have something bound like:
this.bind("change:arbitraryName", functionName)
You can consider using hasChanged in the event handler?
var self = this;
this.bind("change", function() {
if(!self.hasChanged("someAttribute")){
self.update();
}
});
I'm not sure I understand your question completely. Please notice the difference of the above, and the below.
this.bind("change:someAttribute", function(){
self.update();
});
The first one will fire update on any change where someAttribute remains constant. The second one will fire update on any change to someAttribute.
Hope this helps.

Categories

Resources