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.
Related
I have a simple javascript AJAX application that allows search and selection of records. Selection updates the location.hash and loads the associated record detail, and ideally vice-versa also (loading a record when the hash changes). Of course a careless implementation can cause loops and extra panel flashes.
I want a predictable and concise implementation of this bidirectional binding.
One approach is to only load a record on the hashchange event, and when a record is selected in the UI, set location.hash. This seems most concise, but I'd be concerned this would diminish record-click responsiveness in older browsers with a polled hashchange shim.
Another approach is to record a navigating (e.g.) state when selecting a record, and clear it when handling hashchange. That's covered in this question. However, that seems like certain event sequences, like tapping Back multiple times rapidly, might result in inconsistency between the displayed content and URL.
Have you seen an implementation that solves these problems?
Why not to use HTML5 history API instead? All modern browsers support it
To make things easier you can use history.js library to work with History/State APIs
Using that library you can subscribe to URL updates
History.Adapter.bind(window, 'statechange', function () {
// event handler code here
var state = History.getState();
}
or push new URL into history
History.pushState(null, "name", "http://newurl");
I'm not sure which JS framework you would like to use to get bidirectional binding, but with KnockoutJS you can create ko.computed object with read and write methods
I think there's a simple answer in my case, since it's a read-only/idempotent operation (well, it actually logs a view).
I'll just store the state displayed by the current content, and test it on each event that would load content (including the 'redundant' hashchange events), ignoring the event if it matches the currently-displayed state.
Seems cheap, for better or worse. :)
here's my approximate/pseudo-code:
var activeRecordId;
function loadHash() {
var idList = window.location.hash.substring(1).split(',');
if (idList.length > 1) loadSpecificRecordsToList(idList);
else if (idList != '') loadDetailRecord(idList[0]);
}
function loadDetailRecord(id) {
if (id != activeRecordId) {
activeRecordId = id;
doDetailLoadAjaxAndSuch(id);
}
}
$(function () {
loadHash();
$.bind('hashchange', loadHash);
});
It's pretty easy to unbind specific event handlers from model classes, but it seems the only way to unbind an event handler from an instance of a model is to unbind ALL event handlers attached to that instance using unbind() (takes no arguments).
Is there an equivalent to Model class' unbind([eventName, function]) function for model instances, or is there another better way to unbind specific handlers without unbinding them all?
I've looked through the Spine.js documentation but no luck so far. Adding arguments to the unbind() function has no effect - it just ignores them and unbinds all anyway.
In the end we switched to a more suitable framework for our needs, Backbone.js. Maintenance on the Spine.js GitHub repository seems to have ground to a halt in September last year and, though Spine supports use with JavaScript, the documentation for it is pretty poor.
If anyone can provide a working solution to the original problem I'll still accept it for the benefit of anyone else who stumbles across this.
I don't think this specifically answers your question but might get you to what you are going for.
Spine has a not so documented .one() event
from the source:
one: (ev, callback) ->
#bind ev, ->
#unbind(ev, arguments.callee)
callback.apply(this, arguments)
So it basically does the unbind for you behind the scenes, but it doesn't use a Model unbind which actually just triggers 'unbind'.
unbind: (ev, callback) ->
unless ev
#_callbacks = {}
return this
list = #_callbacks?[ev]
return this unless list
unless callback
delete #_callbacks[ev]
return this
for cb, i in list when cb is callback
list = list.slice()
list.splice(i, 1)
#_callbacks[ev] = list
break
this
vs.
unbind: ->
#trigger('unbind')
We use #item.one in a few places and have found it works okay.
example use:
#item.one 'awaitingPermit', (item) =>
#navigate('/document', item.id, 'show')
update:
We have worked on some solutions to this missing feature in Spine.js. see this issue thread https://github.com/spine/spine/issues/418
I tried looking for the answer, and this is my first post, so bear with me if I mess up in some way.
Basically my problem is this: I'm writing an extension for Chrome that uses jQuery. I have another extension that makes a timed $.ajax() request every 10 seconds. I need to find a way to run my code every time that timed ajax request and its callback function completes. Setting a timer for my own script can be done, although that's rather half-assed and doesn't work as well.
The problem can be illustrated thus:
//extension 1
function timedFunc() {
setTimeout(doStuff, 10000);
};
timedFunc();
//extension 2
//code to be run every time doStuff completes
I feel like there may be a very elementary solution to this problem but I appreciate the help.
There is (was) an event called DOMSubtreeModified.
But it has been deprecated so at tho moment there are really only workarounds available.
Why is the DOMSubtreeModified event deprecated in DOM level 3?
I can't advise on using this event as it hasn't even been implemented in all browsers.
But what you can do (easily) is just trigger you own event with all your ajax call!
Example:
fire your event when (any) ajax call completes:
$(document).ajaxComplete(function() {
$(document).trigger('domChanged');
}
and listen to it:
$(document).on('domChanged',function() {
alert("i changed the DOM tree!");
});
btw:
taken that you just want to react to ajax calls compleing... just use the .ajaxComplete() event:
http://api.jquery.com/ajaxComplete/
i didn't really understand what you are trying to say but i did understand your question in the title so here is my modest answer:
// a global variable for the documents content
var content=document.documentElement.innerHTML;
// return true if the document content has changed
function documentChanged(){
return content==document.documentElement.innerHTML;
}
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
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.