I want to redirect client to custom controller when he click back browser button.
Do you know any clear ways to catch back button event and force to call server?
Bests,
Thank you
Back button question is quite well answered on SO. A quick search will turn up lots of extra information. Here is a bit of a summary.
You have a few strategies to choose from.
1 - If you are developing an SPA (or not) you may find making use of the history api useful.
https://developer.mozilla.org/en-US/docs/Web/API/History
https://developer.mozilla.org/en-US/docs/Web/API/History_API
You will find plenty on SO about history api.
Try starting here Preserve dynamically changed HTML on back button
Basically, by adding a listener for popstate event which fires everytime the active history entry changes :
(in jQuery)
$(document).ready(function () {
$(window).on('popstate' , function (event) {
console.log('popstate');
console.log(event);
console.log(event.originalEvent);
});
};
http://caniuse.com/#search=history
2 - add a listner for the pageshow event, will fire when a page load is completed and when session history entry is used for navigation, so basically forward & back buttons.
https://developer.mozilla.org/en-US/docs/Web/Events/pageshow
$(window).on('pageshow' , function (event) {
console.log('pageshow');
console.log(event);
console.log(event.originalEvent);
});
http://caniuse.com/#search=pageshow
3 - Append a hashvalues to your urls with window.location.hash = 'pageHashValue'.
Listen for hashchange event and you can then act based on the #value if needed.
https://developer.mozilla.org/en-US/docs/Web/Events/hashchange
This is a common approach in single page applications.
$(window).on('hashchange' , function (event) {
console.log('hashchange');
console.log(event);
console.log(event.originalEvent);
console.log(window.location.hash);
});
http://caniuse.com/#search=hashchange
Finally take note that while you, as a developer, no doubt hate the browser back button (like me) our users tend to love it. If you change the expected behavior of the back button you can also expect your user experience to be negatively affected. The best strategy is to use these events to maintain the expected behavior of the back button rather than to try and change it.
Related
I've been maintaining a small project written in React. It's a browser extension which adds Slack-like emoji support to Facebook Messenger. Since Facebook is changing up thing once in a while, I need to make new changes to the browser extension, so they work just like before.
Facebook uses a <div> with the contenteditable attribute set to true. They have done this for as long as I remember. However, they changed something up, because I used to be able to do:
document.querySelector("div[contenteditable=true]").addEventListener(e => {
if(e.key == "Enter" && isActive) { //isActive means if the emoji picker is visible or not
e.preventDefault();
e.stopPropagation();
selectEmoji();
}
});
However, that event no longer fires, even though the element exists. It fires for EVERY key except Enter. So I started digging with a MutationObserver, and to my surprise, I noticed this:
removedNodes: NodeList[<div with contenteditable=true>]
addedNodes: NodeList[NEW <div with contenteditable=true>]
So once I hit enter, Facebook sends the message and then replaces the entire "chatbox" element (the div with contenteditable=true) with a new chatbox element.
What I don't understand, is how my addEventListener isn't firing, when I hit the Enter key. Even if I do something like this, it won't work:
setInterval(() => {
var chatbox = document.querySelector("div[contenteditable=true]");
chatbox.addEventListener("keydown", () => ........ });
}, 1000);
Even if Facebook is removing the element, shouldn't my event fire anyway? According to my test here, it should fire (at least the first time): https://jsfiddle.net/web4dyg1/
Since adding an event listener, and then cancel Facebook's request (previously form submission), doesn't work anymore, what other options do I have? I know this question is really niche, but I was hoping someone had an thinking-out-of-the-box idea.
EDIT: I need to clarify that adding the event listener works. Every single keypress fires the event EXCEPT for the Enter key.
I would like to update window.location.search without reloading the document and then execute a function based on the updated values in window.location.search.
When the user clicks a button, that button's onclick event fires the following function:
window.history.pushState({action : 'myAction'}, document.title, '?action=myAction');
So far so good. The window.location.search updates in the browser URL bar.
But I can't find a general background event listener which detects this update - something like onhashchange but for query strings.
The best I can come up with is:
window.history.pushState({action : 'myAction'}, document.title, '?action=myAction');
window.history.pushState({action : 'myAction'}, document.title, '?action=myAction');
window.history.back();
window.addEventListener('popstate', () => console.log(event.state.action));
This works but I'm convinced there must be a more elegant approach.
Final Answer:
I was mostly happy with the Second Attempt answer immediately below.
But I really didn't like:
history.back();
history.forward();
which I adopted to trigger the popstate event, manually.
In practice, I found this hack worked sometimes, it was slow at other times and on occasion it failed completely.
On paper, it feels too makeshift.
After repeatedly searching and experimenting (this was remarkably hard to find), I have finally found the correct syntax for manually firing a popstate event.
It's as simple as dispatching this:
new Event('popstate')
Like this:
let myEvent = new Event('popstate');
window.dispatchEvent(myEvent);
So the final code is:
window.history.pushState({action : 'myAction'}, document.title, '?action=myAction');
let queryStringChange = new Event('popstate');
window.dispatchEvent(queryStringChange);
Best Answer I can come up with (Second Attempt):
Building on the following:
I know for certain that the query-string will only need to be checked either:
when the page loads or reloads;
when window.history.pushstate is invoked;
or when (as #Kaiido astutely pointed out in the comments below) a page is accessed via the forward and back buttons
I also know that:
The window.load event listener covers Point 1.;
The window.popstate event listener covers Point 3.;
This only leaves Point 2.
Dealing with Point 2
As MDN reports:
Note that just calling history.pushState() or history.replaceState()
won't trigger a popstate event. The popstate event will be triggered
by doing a browser action such as a click on the back or forward
button (or calling history.back() or history.forward() in JavaScript).
Source: https://developer.mozilla.org/en-US/docs/Web/API/Window/popstate_event
But this means that rather than using (as in my first attempt, below):
window.history.pushState({action : 'myAction'}, document.title, '?action=myAction');
checkQueryString(); // DIRECT INVOCATION OF FUNCTION
I can use instead:
window.history.pushState({action : 'myAction'}, document.title, '?action=myAction');
history.back();
history.forward();
This means that rather than needing to invoke the function directly, I can now allow the same window.popstate event listener covering Point 3 to do its work, giving me cleaner, more logically separated code.
N.B. I find it... weird... that pressing the forward button alone will fire the window.popstate event listener, but invoking window.history.pushState() will not... (necessitating the immediately subsequent addition of history.back(); history.forward(); to duplicate the functionality of a forward button).
But, as above, this is the best, cleanest, most optimised (in terms of logical separation and future code maintenance) solution I can come up with. If anyone has a dramatically better idea, I'll be happy to transfer the green tick.
First Attempt at an Answer:
I am tentatively concluding that there is no way to achieve this effect using a background event listener which can detect when the query string updates.
Instead, since I know for certain that the query-string will only need to be checked either:
when the page loads or reloads
when window.history.pushstate is invoked
I can use the following:
window.addEventListener('load', checkQueryString);
and
window.history.pushState({action : 'myAction'}, document.title, '?action=myAction');
checkQueryString();
Tried to bind submit event (or click or whatever) to an element within a jQuery mobile page. What I wanted to do is get the value from an input within an form element within a jQuery page and store it in cookies/localStorage. Every time I come back to this page I want to restore the input field.
Currently I ended up in using this script:
$('.pageClassSelector').live('pageinit', function (e) {
$('.classSelector').submit(function () {
var q = $('.inputClassSelector').val();
// store to localStorage ...
});
// load from localStorage ...
$('.q').val(lastSearchString);
});
This approach is documented there http://jquerymobile.com/test/docs/api/events.html
Since it seems possible that identical pages are hold in the DOM, ID selectors are not quite useful. My problem now is that everytime I navigate to this page the submit event is bound again and thus results in storing DIFFERENT (!) values. The submit event seems to be fired multiple times and much more interesting with the value before last.
Am I doing anything completly wrong? Any hints how to do scripting in jquery mobile properly?
TRY1:
I placed now the submit event binding within the pagebeforeshow event like so:
$('#PageId').on('pagebeforeshow', function (e) {
$('.classSelector').on('submit', function () {
var q = $('.q').val();
alert('stored: ' + q);
}
$('.q').val(lastSearchString);
}
But the value going to be stored is still the value before last, when I was navigating the page before. The first time it works as it should.
TRY2:
This is what I have now and it looks like it does that what I want. I select now the current page and select only the form which is a child of this page.
Is this good practice?
$('div:jqmData(id="PageId")').on('pagebeforeshow', function (e) {
$(this).find('#form').on('submit', function () {
var q = $(this).find('#input').val();
localStorage.setItem("key", q);
return true;
});
lastSearchString = localStorage.getItem("key");
$(this).find('#input').val(lastSearchString);
});
Your requirement to load from local storage and store on the page needs to be done by binding to the pagebeforeshow event (look at the section "Page transition events") and not the pageinit event like you are currently doing.
$('.pageClassSelector').on('pagebeforeshow', function (e) {
// load from localStorage ...
$('.q').val(lastSearchString);
});
Furthermore generally each page element (where you have data-role='page') should have a unique ID so you can use that instead of the CSS selector.
Multiple events firing when navigating pages sounds like multiple bindings to me, which is a known problem with jQuery Mobile. Bindings are not unbound when navigating pages, because everything is loaded through AJAX. See for example this StackOverflow Question: Jquery mobile .click firing multiple times on new page visit or my solution.
$('.classSelector').on('submit', function(){})
Try to use the constraction to bind your event to element.
Look likes some data was loaded through ajax request
I would like to replace the Javascript confirm() function to allow custom buttons instead of Yes/Cancel. I tried searching but all the solutions are event driven such as jquery dialog(where the code does not wait for a response but it is event driven). Does anyone know of a non-event driven solution. It must work in Safari as well as IE (so no vbscript).
Here is sample code in many parts of my system. This is old code and was not designed with event driven windows in mind. I am trying to avoid a rewrite.
**
// Wait for users response
if (result>2000) {
if (confirm("Are you sure this is right?")){
... do stuff
}
}
... continue with other stuff
... lots of other code.
if (confirm("Did you double check your numbers?")){
... do more stuff
} else {
... do something
}
**
Like the others have said, this isn't possible. confirm is a blocking function - no more script is executed until the user has dismissed the dialog - and you can't simulate that with other methods of Javascript.
A better solution would be to structure your code for asynchronous execution. This is almost always a better idea -- firstly, it lets you decide how your dialogs should look, what buttons there are, etc; and secondly, it doesn't block the user. They might have the important information they need to double-check open in another tab, or elsewhere on the page. With confirm they'd have to answer your question before being able to get to either of these places.
Here's a snippet of what the code might look like. There's a lot of blank bits here, but it might put you on the right track:
if (result>2000) {
displayConfirm("Are you sure this is right?", {
"Yes": function () {
// ... do stuff
},
"No": function () {
// do.. nothing? up to you.
}
}
}
You'll see here that there are two functions defined, but none actually get executed. The displayConfirm function would have to construct a dialog box (in whichever way) and then create buttons, using those functions as the click handlers (or at least, calling them from the click handler).
What you're trying to do is impossible. You'll have to use an event driven custom dialog solution, or stick with the browsers default confirmation dialog.
You will not be able to do this w/o changing your calls. No custom code can stop execution like the confirm box can. Any solution will require editing code to an event model.
As others have mentioned you can't do it directly, but I managed to do it in a round about way. Assuming like me you have a HTML button which submits a form, and want that button to have a jQuery modal dialog...
add an onclick event to the HTML button
make the onlick event open a jQueryUI dialog, and have the onclick event return false to cancel the button's default action
On your jQueryUI dialog, get the Yes/Ok button to remove the onclick event from the button in step 1, and then call then trigger the button to fire the click event
How is this done?
This is a good question, and I actually don't think it can be done easily. (Some discussion on this)
If it is super duper important for you to have this functionality, you could hack it like so:
function singleClick(e) {
// do something, "this" will be the DOM element
}
function doubleClick(e) {
// do something, "this" will be the DOM element
}
$(selector).click(function(e) {
var that = this;
setTimeout(function() {
var dblclick = parseInt($(that).data('double'), 10);
if (dblclick > 0) {
$(that).data('double', dblclick-1);
} else {
singleClick.call(that, e);
}
}, 300);
}).dblclick(function(e) {
$(this).data('double', 2);
doubleClick.call(this, e);
});
And here is an example of it at work.
As pointed out in the comments, there is a plugin for this that does what I did above pretty much, but packages it up for you so you don't have to see the ugly: FixClick.
Raymond Chen has discussed some of the implications of single-versus-double clicking - although he's talking in the context of Windows, what he says is relevant to browser-based UI design.
Basically, the action taken on a double click should be a logical thing to do after a single click. So for example, in a desktop UI, single click selects an item, and double click opens it (e.g. opens the file, or launches the application). The user would have to select the file to open it anyway, so it doesn't matter that the single click action is taken before the double click action.
If you have a UI component whose double click action is completely unrelated to the single click action, such that it becomes necessary to prevent the single click action from occurring once the system realises it was actually a double click, then you really ought to rethink your design. Users will find it awkward and counter-intuitive, in that it will not act in the way they are used to things acting.
If you still want to go that way, then you will either have to use the debouncing technique (in which case all single click actions will be delayed) or else implement some mechanism whereby the double click handler undoes the work done by the single click handler.
You should also be aware that some users set a very long double click time. Somebody with, for example, arthritic hands might have a double click time of more than a second set in their system preferences, so the debouncing technique based on some arbitrary time period of your choosing is going to make your UI component inaccessible to those people if taking the single click action precludes taking the double click action. The "undo what just happened on single click" technique is the only viable workaround for this, as far as I know.
The technique outlined in the other answers is known as debouncing.
jQuery Sparkle provides a clean elegant solution for this, by implementing a singleclick custom event. By doing this, you can use it just like any other event, so:
$('#el').singleclick(function(){});
// or event
$('#el').bind('singleclick', function(){});
It also provides custom events for the last and first clicks of a series of clicks. And the lastclick custom event actually passes the amount of clicks back! So you could do this!
$('#el').lastclick(function(event,clicks){
if ( clicks === 3 ) alert('Tripple Click!');
});
You can find the source code for defining the custom event here.
It's open source under the AGPL licence, so you can feel free to grab what you need out of it worry free! :-) It's also actively developed on a day to day basis so you will never be short on support.
But most importantly it is a DRY Plugin/Effect Framework to allow you to develop plugins and extensions much more easily. So hope this helps to achieve that goal!
If this is for a button submitting a form (which is not necessarily the case for the original poster, but may be the case for other people getting here via Google), an easier option would be to disable the element that is being clicked on in your click event handler:
$(selector).click(function(e) {
$(this).prop('disable', true);
}