The code I am working on displays some messages on different pages.
page is the page number.
ActionGetMessages is an ajax request.
PushMessage just manage the content.
Here is the code:
function ShowMessages ( page ) {
var oMessages = ActionGetMessages ( page );
for ( var key in oMessages ) {
if ( key == "end" ) continue;
PushMessage ( oMessages [key] );
}
var previous_page_button = document.getElementById ( "previous_page_button");
var next_page_button = document.getElementById ( "next_page_button" );
if ( page > 0 ) {
previous_page_button.style.display = "block";
previous_page_button.addEventListener ( "click",
function () {
ShowMessages ( page - 1 );
} );
}
if ( !oMessages ["end"] ) {
next_page_button.style.display = "block";
next_page_button.addEventListener ( "click",
function () {
ShowMessages ( page + 1 );
} );
}
}
Debugging this code I noticed that after the user have navigated back and forth the pages some times clicking (for example) the next page button cause multiple calls to ShowMessages or something like that.
Any help would be really appreciated.
It's not totally clear to me what you're doing with the above code but what you describe seems to me like there are some event listeners attached multiple times that you don't want to have.
In fact when you click a button which calls ShowMessages() it will attach another event listener to the button everytime the button is clicked. So before you attach the event, try to remove the existing listener first. Have a look at:
removeEventListener
if you make sure that there are no events attached to the button before you add your event listener it should work and have the code executed just one time.
Keep in mind, that you will need to have a reference to your listener to remove it. You can do it like this:
//Define this listener globally (anywhere outside your function)
// so you can reference it anytime or keep track of the last
// added listener somewhere. Otherwise you won't be able to
// remove it.
var myPreviousPageListener = function () {
ShowMessages ( page - 1 );
}
previous_page_button.removeEventListener("click", myPreviousPageListener);
previous_page_button.addEventListener ( "click", myPreviousPageListener);
Related
I'm having this webpage
http://pocolocoadventures.be/reizen/
And it should filter (with isotope.js) the travelboxes on the page.It does in safari, chrome, firefox, opera, .. but in IE, the filter doesn't work. Even worse, JS doesn't react at all at a click event on te span.
This is the piece of js
// Travel Isotope
var container = $('#travel-wrap');
container.isotope({
animationEngine : 'best-available',
itemSelector: '.travel-box ',
animationOptions : {
duration : 200,
queue : false
},
});
$(".filters span").click(function(){
var elfilters = $(this).parents().eq(1);
if( (elfilters.attr("id") == "alleReizen") && elfilters.hasClass("non-active") )
{
$(".label").each(function(){
inActive( $(this) );
});
setActive(elfilters);
}
else{
//set label alleReizen inactive
inActive( $("#alleReizen") );
if( elfilters.hasClass("non-active") ){
setActive(elfilters);
}
else{
inActive(elfilters);
}
}
checkFilter();
var filters=[];
$(".search.filters").children().each(function(){
var filter = $(this).children().children().attr("data-filter");
if( $(this).hasClass("non-active") ){
filters = jQuery.grep(filters, function(value){
return value != filter;
});
}
else{
if(jQuery.inArray(filter,filters) == -1){
filters.push(filter);
}
}
});
filters = filters.join("");
filterItems(filters);
});
function filterItems(filters){
console.log("filter items with filters:" + filters);
container.isotope({
filter : filters,
}, function noResultsCheck(){
var numItems = $('.travel-box:not(.isotope-hidden)').length;
if (numItems == 0) {
$("#no-results").fadeIn();
$("#no-results").css("display", "block");
}
else{
$("#no-results").fadeOut();
$("#no-results").css("display", "none");
}
});
}
function setActive(el){
el.removeClass("non-active");
var span = el.find('i');
span.removeClass("fa-check-circle-o").addClass("fa-ban");
}
function inActive(el){
el.addClass("non-active");
var span = el.find('i');
span.removeClass("fa-ban").addClass("fa-check-circle-o")
}
function checkFilter(){
var filterdivs = $('.filters span').parent().parent();
if( filterdivs.not('.non-active').length == 0 ){
setActive( $("#alleReizen") );
}
var filterLabels = $(".filters .label");
if( filterLabels.not('.non-active').length == 0){
setActive( $("#alleReizen") );
}
}
function noResultsCheck() {
var numItems = $('.item:not(.isotope-hidden)').length;
if (numItems == 0) {
//do something here, like turn on a div, or insert a msg with jQuery's .html() function
alert("There are no results");
}
}
Probably something small and stupid; but I can't find it..
Thanks in advance!
On your website you've build the buttons like this:
<button>
<span>
</span>
</button>
Now the button element is designed to be a button. It differs from the input button. In the latter you'd set the caption using value. In the button element you set it as a text node. The button element can contain elements like a span. The spec isn't very clear about whether or not you should have event handlers on the children of the button element. It's a browser developers interpretation of allowing it or not.
This problem has been posted here before (a few times)
span inside button, is not clickable in ff
Missing click event for <span> inside <button> element on firefox
It seems that Firefox is allowing it, based upon your findings. IE isn't. So to be on the safe side: use the button the way it was intended.
Wrap the button inside a span (not really logical)
Put the click handler on the button.
$(".filters button").click(...);
played around in the console a bit, and this seemed to work well.
$(".filters").on('click', 'span', function(){
// foo here
console.log('foo');
});
Maybe the filters are manipulated by one of your js files after page load?
.on will allow you to select a container which listens on changes that happen inside it, passing the element you want the actual action to work on.
If it's ok for you, I'd suggest to use the <button> element, instead of the <span>.
Let me know if that works for you.
I am using the jQuery $(window).on('hashchange') method to hide irrelevant sections except the one that the URI-hash points to. It works beautifully, back button and everything. Except for the fact that I can't seem to prevent the default behaviour of the browsers, that is to scroll down to the section with the matching id.
Here is my function.
var AddHashNav = function (hashmatch, container) {
$(window).on('hashchange', function (e) {
if ( !window.location.hash ) {
// empty hash, show only the default header
change_preview(container, container + ' > header');
return false;
}
// Don't do anything to hash links who's ids don't match
else if ( window.location.hash.match(hashmatch) ) {
change_preview(container, window.location.hash);
return false;
}
});
}
var changePreview = function (container, preview) {
$(container + ' >').addClass('hidden');
$(preview).removeClass('hidden');
}
And the caller is simply
$(document).ready(function () {
// applay AddHashNav to all sections who's id ends in 'preview'
AddHashNav(/preview?/, '.hash-nav-container');
});
I've tried both e.preventDefault(); and return false; and neither seem to work.
Note that I'm trying to prevent the behaviour of the hashChange event, not the click event, here it seems not to be possible, but I'm sure at least somebody has managed to figure out how to do this.
I fixed it by running the change_preview() on the first call of the AddHashNav constructor, and therefor hiding the sections on the $(document).load() call.
var AddHashNav = function (hashmatch, container) {
// hide all sections except header on load
change_preview()
$(window).on('hashchange', function (e) {
if ( !window.location.hash ) {
// empty hash, show only the default header
change_preview(container, container + ' > header');
return false;
}
// Don't do anything to hash links who's ids don't match
else if ( window.location.hash.match(hashmatch) ) {
change_preview(container, window.location.hash);
return false;
}
});
}
It is not the most beautiful solution. I'm not sure why it works (I assume it is because the sections have no position in the window object, and hence can't be scrolled into), but it works for now.
TL;DR;
Is there a way to force the focus to be inside a modal box in a web page ?
Here's the problem: I have a classic web page, containing text, links and forms. When I click one specific link in the page, a modal box appear (something similar to fancybox or jQuery.ui.dialog). This modal contains also links and form elements. If the user use its "tab" key, he can focus every element on the page, elements which are inside the modal, but also elements which are outside it. I would like to force the focus to say inside the modal box, but I can't find a way to do it. I would like to do this in CSS or JavaScript if possible.
I know this is possible, because jQuery.ui.dialog can do it using the modal option, here's an example http://jsfiddle.net/pomeh/QjLJk/1/show/. I tried to look at the source code but I'm not figuring how it works precisely. Here's some code I found in the jQuery UI source code which sounds like resolving this issue:
this.document.bind( "focusin.dialog", function( event ) {
if ( !that._allowInteraction( event ) ) {
event.preventDefault();
$(".ui-dialog:visible:last .ui-dialog-content")
.data( widgetFullName )._focusTabbable();
}
});
_allowInteraction: function( event ) {
if ( $( event.target ).closest(".ui-dialog").length ) {
return true;
}
// TODO: Remove hack when datepicker implements
// the .ui-front logic (#8989)
return !!$( event.target ).closest(".ui-datepicker").length;
},
_focusTabbable: function() {
// Set focus to the first match:
// 1. First element inside the dialog matching [autofocus]
// 2. Tabbable element inside the content element
// 3. Tabbable element inside the buttonpane
// 4. The close button
// 5. The dialog itself
var hasFocus = this.element.find("[autofocus]");
if ( !hasFocus.length ) {
hasFocus = this.element.find(":tabbable");
}
if ( !hasFocus.length ) {
hasFocus = this.uiDialogButtonPane.find(":tabbable");
}
if ( !hasFocus.length ) {
hasFocus = this.uiDialogTitlebarClose.filter(":tabbable");
}
if ( !hasFocus.length ) {
hasFocus = this.uiDialog;
}
hasFocus.eq( 0 ).focus();
}
keydown: function( event ) {
if ( this.options.closeOnEscape && !event.isDefaultPrevented() && event.keyCode &&
event.keyCode === $.ui.keyCode.ESCAPE ) {
event.preventDefault();
this.close( event );
return;
}
// prevent tabbing out of dialogs
if ( event.keyCode !== $.ui.keyCode.TAB ) {
return;
}
var tabbables = this.uiDialog.find(":tabbable"),
first = tabbables.filter(":first"),
last = tabbables.filter(":last");
if ( ( event.target === last[0] || event.target === this.uiDialog[0] ) && !event.shiftKey ) {
first.focus( 1 );
event.preventDefault();
} else if ( ( event.target === first[0] || event.target === this.uiDialog[0] ) && event.shiftKey ) {
last.focus( 1 );
event.preventDefault();
}
}
I wont get into the coding as you already have the code, I'll explain you the logic behind it.
If your page has the following elements,
element0(tabindex 1) --> element1(tabindex 2) --> element2(tabindex 3)
To prevent focus going out, you basically create a cycle.
When tab key is pressed on element0, it goes to element1 as it would normally go.
But when the tab key is pressed on element2, you need to prevent the browser's default behaviour (by event.preventDefault()) i.e going to an element with a higher tabindex and give focus to the element0.
Same ways when shift+ tab is pressed on element0, you need to prevent the browser's default behaviour (event.preventDefault()) and manually give focus to element2.
In this way, you create a cycle such that focus never goes outside.
I'm building a small adventure to help me understand some concepts of JQuery/Javascript and I've hit a snag.
What I'm trying to do is by clicking on a "take" button and then on an item on the screen to move it to the inventory. Good news is, that all works. Bad news is that if I press another item without using the "take" button first it goes straight to my inventory.
Same problem with "look". You should have to press "look" first and then the item to get a description, but once it's pressed it'll give every item you click a description straight away.
You can imagine that once you've pressed them both every item you click without clicking another button will give you a description and end up in your inventory straight away.
This is how I'm trying to get it to unbind:
$("#take").click(function () {
$("#zilverenKogel").click(function () {
$("#zilverenKogel").fadeOut(1200, 0);
$("#invKogel").fadeTo(1500, 1);
$("#take").unbind("click");
Any help will be greatly appreciated!
Mofx solved it for me. End, and working result, is:
var state = null;
$( "#take" ).click(function () {state = "take";})
$( "#look" ).click(function () {state = "look";})
$( "#zilverenKogel" ).click(function () {
if (state == "take") {
$("#zilverenKogel").fadeOut(1200, 0);
$("#invKogel").fadeTo(1500, 1);
state=null;
} else if (state == "look") {
alert("blablabla");
state=null;
}
});
thanks a lot Mofx!
You unbind the wrong event. You want to unbind the event on "#zilverenKogel", because that is the one doing the "take-action".
I also suspect, you connect events to other elements as well within "$("#take").click(function () {". You would have to disconnect them all. After the action was taken or another action was selected.
Maybe a statemachine would be a better solution here. Something like:
var state = null;
$("#take").click(function () {state = "take";}
$("#look").click(function () {state = "look";}
$("#zilverenKogel").click(function () {
if (state == "take") {
moveToInventory();
} else if (state == "look") {
showDescription();
}
}
In Gmail, clicking on the checkbox shown below selects all messages and I'm making a userscript (for personal use and I need it to work in Chrome) that'll select the unread messages only (only the first 2 messages in the screenie below are unread) instead of the default behavior of that checkbox.
My first idea is to simulate click events and although I could access the "unread" menuitem fine using the code...
var unread_menuitem = document.getElementById('canvas_frame').contentWindow.document.getElementById(':s2');
$(unread_menuitem).css({'border':'thin red solid'});
and dispatch the click event to it using the code...
var clickEvent = document.createEvent('MouseEvents');
clickEvent.initEvent( 'click', true, true );
unread_menuitem.dispatchEvent(clickEvent); // Chrome's console returned 'true'
the unread messages don't get selected.
My second idea was to brute force the selection by checking the checkbox $('#canvas_frame').contents().find('tr.zE input').prop('checked', true) and apply the css styles that Gmail applies on a manual click event, but while I was able to match the manual click event both visually as well as DOM-wise (afaik)...
Gmail says "No conversations selected" while performing some action, in this case I did a "Mark as Read". I also want to note that manually clicking on the checkboxes that were put in this state using my brute force method did not "uncheck" them as you'd expect. They needed one additional manual click to get unchecked.
Both my ideas have bombed and I want to know if there are others ways to tackle this, or if there are ways to improve upon my ideas above that can solve the problem.
There's a script here that looks it does what you're trying to do. Is that the whole script you needed to create or was that just part of the functionality?
According to the discussions, they did create it for Firefox which it works in, some people have commented it doesn't work in Chrome, so you might be looking at a solution that needs to target different browsers (your question doesn't specify if it must work in Chrome, just that you were using it).
This is what they are using to select the unread messages, it looks like they are simulating the mousedown and mouseup events on each item:
var handler = function(type,e) {
e.preventDefault();
e.stopPropagation();
var e2 = document.createEvent('MouseEvents');
e2.initEvent('mousedown',true,false);
var el = anchor.wrappedJSObject;
el.dispatchEvent(e2);
setTimeout(function() {
var el = document.querySelectorAll('div[selector='+type+'] > div')[0].wrappedJSObject;
var e2 = document.createEvent('MouseEvents');
e2.initEvent('mouseup',true,false);
el.dispatchEvent(e2);
},100);
}
They are calling this by setting up a click event further down which calls handler('unread',e);
Solved it, easy as pie and now that I think about it, it's the solution listed in user PirateKitten's answer - instead of "checking off" the checkboxes besides the unread messages like my second idea, simulate clicks on those checkboxes instead. Works like a charm and here's the code, which you can run in Chrome's console while using Gmail (doesn't need jQuery btw):
var unreadMessages = document.getElementById('canvas_frame').contentWindow.document.querySelectorAll('tr.zE input');
var numMessages = unreadMessages.length;
while ( numMessages-- ) {
var clickEvent = document.createEvent('MouseEvents');
clickEvent.initEvent( 'click', true, true );
unreadMessages[numMessages].dispatchEvent(clickEvent);
}
Here's my full script that you can run inside your Chrome console (or turn it into an extension/userscript) to change the default behavior of the checkbox from selecting ALL messages to just the unread messages only:
var hasUILoaded = setInterval( function() {
if( document.getElementById('canvas_frame').contentDocument.getElementsByClassName('J-Zh-I J-J5-Ji J-Pm-I L3') ) {
clearInterval( hasUILoaded );
setTimeout( function() {
var content_frame = document.getElementById('canvas_frame').contentDocument;
var chkbox = content_frame.getElementsByClassName( 'J-Zh-I J-J5-Ji J-Pm-I L3' );
var chkbox = chkbox[0].childNodes[0];
var unreadMessages = content_frame.getElementsByClassName('zE'); // DOM structure: <tr class=zE> <td> <img><input> </td> </tr> so we add ".childNodes[0].childNodes[1]" whenever we want to access the check boxes of each message.
var allMessages = content_frame.getElementsByClassName('zA');
chkbox.onclick = function() {
if( chkbox.checked ) {
var numUnread = unreadMessages.length;;
var numAll = allMessages.length;
setTimeout(function () {
if( allMessages[0].childNodes[0].childNodes[1].checked ) {
while ( numAll-- ) {
var clickEvent = document.createEvent('MouseEvents');
clickEvent.initEvent( 'click', true, true );
allMessages[numAll].childNodes[0].childNodes[1].dispatchEvent(clickEvent);
}
}
}, 10);
setTimeout(function (){
if(!unreadMessages[0].childNodes[0].childNodes[1].checked) {
while ( numUnread-- ) {
var clickEvent = document.createEvent('MouseEvents');
clickEvent.initEvent( 'click', true, true );
unreadMessages[numUnread].childNodes[0].childNodes[1].dispatchEvent(clickEvent);
}
}
}, 30);
}
}
}, 100);
}
}, 300);