I've created a custom modal dialog that is added and removed from the screen when it is called. However, when I'm trying to remove it, the remove function doesn't seem to be working in certain circumstances.
This is the close function from the modal (triggered by clicking on the close button):
function modal_close() {
$('.custom_block_page').fadeOut().remove();
$(this).parent().fadeOut().remove();
};
This is how I call that function from the button inside the modal dialog:
MatchGame.closeWin = function() {
$('.custom_modal_close').trigger('click');
MatchGame.playGame();
};
If I just click the close button, the dialog is removed and everything works as expected. But when I trigger the close, the dialog fades to nothing, but remains in the body so it displays again the next time it is called.
Checking the console between I get:
$('.custom_block_page').length
1 // displayed the first time
$('.custom_block_page').length
0 // during the 2nd game (expected)
$('.custom_block_page').length
2 // displayed after the 2nd game; I expect this to be 1
I've tried putting a timeout on my playGame, but that didn't seem to help either.
Thanks for the help!
The issue you observe is due to .fadeOut(), which is implemented asynchronously in a whole series of later event threads.
Meanwhile, in the original event thread .remove(), subsequent statements, returning from the function and subsequent statements in the function's caller, ALL execute synchronously - well before .fadeOut() has completed.
The solution is to exploit .promise(), which will return a jQuery promise, from which you can chain .then() :
function modal_close() {
return $('.custom_block_page').add($(this).parent()).fadeOut().promise().then(function() {
$(this).remove();
});
};
In the caller, .trigger() returns jQuery but you now need to work with the returned promise, therefore use .triggerHandler().
MatchGame.closeWin = function() {
$('.custom_modal_close').triggerHandler('click').then(function() {
MatchGame.playGame();
});
};
Edit:
Code from add_block_page() and add_popup_box() can be safely rolled into show_modal_box() to make one larger function.
By doing so, you will benefit from being able to access the variables $block_page, $pop_up, $close, $inner from the close button's click handler.
function show_modal_box() {
var $block_page = $('<div class="custom_block_page"></div>').appendTo('body'); // dark background
var $pop_up = $('<div class="custom_modal_box"></div>').appendTo($block_page);
var $close = $('').appendTo($pop_up);
var $inner = $('<div class="custom_inner_modal_box">loading...</div>').appendTo($pop_up);
if(options.name != '') {
$pop_up.attr('id', options.name);
}
// Add the content - if url, load the page otherwise use the text
if (options.url != '') {
$inner.load(options.url);
} else {
var innerHTML = '';
if(options.title[0] === "<") { // assume formatting
innerHTML += options.title;
} else {
innerHTML += '<h2>' + options.title + '</h2>';
}
if(options.description[0] === "<") {
innerHTML += options.description;
} else {
innerHTML += '<p>' + options.description + '</p>';
}
$inner.html(innerHTML);
}
$close.click(function() {
// for example
return $pop_up.fadeOut().promise().then(function() {
$block_page.remove();
});
});
$(window).off('resize.popup').on('resize.popup', add_styles).trigger('resize.popup'); // prevent accumulation of resize handlers
// checkNeedScroll();
$pop_up.fadeIn();
}
EDIT 2
I think I have it!
In the custom_modal_box plugin, the code below causes a click handler to be appended to this:
return this.click(function(e) {
show_modal_box();
});
That's fine if the plugin is invoked just once on any particular element however in this game's code it is invoked on the same element, $('.win'), every time a game is completed.
To prevent an accumulation of click handlers on $('.win'), change that code to :
return this.off('click.popup').on('click.popup', function(e) {
show_modal_box();
});
Related
I'm having some problems with users clicking buttons multiple times and I want to suppress/ignore clicks while the first Ajax request does its thing. For example if a user wants add items to their shopping cart, they click the add button. If they click the add button multiple times, it throws a PK violation because its trying to insert duplicate items into a cart.
So there are some possible solutions mentioned here: Prevent a double click on a button with knockout.js
and here: How to prevent a double-click using jQuery?
However, I'm wondering if the approach below is another possible solution. Currently I use a transparent "Saving" div that covers the entire screen to try to prevent click throughs, but still some people manage to get a double click in. I'm assuming because they can click faster than the div can render. To combat this, I'm trying to put a lock on the Ajax call using a global variable.
The Button
<span style="SomeStyles">Add</span>
Knockout executes this script on button click
vmProductsIndex.AddItemToCart = function (item) {
if (!app.ajaxService.inCriticalSection()) {
app.ajaxService.criticalSection(true);
app.ajaxService.ajaxPostJson("#Url.Action("AddItemToCart", "Products")",
ko.mapping.toJSON(item),
function (result) {
ko.mapping.fromJS(result, vmProductsIndex.CartSummary);
item.InCart(true);
item.QuantityOriginal(item.Quantity());
},
function (result) {
$("#error-modal").modal();
},
vmProductsIndex.ModalErrors);
app.ajaxService.criticalSection(false);
}
}
That calls this script
(function (app) {
"use strict";
var criticalSectionInd = false;
app.ajaxService = (function () {
var ajaxPostJson = function (method, jsonIn, callback, errorCallback, errorArray) {
//Add the item to the cart
}
};
var inCriticalSection = function () {
if (criticalSectionInd)
return true;
else
return false;
};
var criticalSection = function (flag) {
criticalSectionInd = flag;
};
// returns the app.ajaxService object with these functions defined
return {
ajaxPostJson: ajaxPostJson,
ajaxGetJson: ajaxGetJson,
setAntiForgeryTokenData: setAntiForgeryTokenData,
inCriticalSection: inCriticalSection,
criticalSection: criticalSection
};
})();
}(app));
The problem is still I can spam click the button and get the primary key violation. I don't know if this approach is just flawed and Knockout isn't quick enough to update the button's visible binding before the first Ajax call finishes or if every time they click the button a new instance of the criticalSectionInd is created and not truely acting as a global variable.
If I'm going about it wrong I'll use the approaches mentioned in the other posts, its just this approach seems simpler to implement without having to refactor all of my buttons to use the jQuery One() feature.
You should set app.ajaxService.criticalSection(false); in the callback methods.
right now you are executing this line of code at the end of your if clause and not inside of the success or error callback, so it gets executed before your ajax call is finished.
vmProductsIndex.AddItemToCart = function (item) {
if (!app.ajaxService.inCriticalSection()) {
app.ajaxService.criticalSection(true);
app.ajaxService.ajaxPostJson("#Url.Action("AddItemToCart", "Products")",
ko.mapping.toJSON(item),
function (result) {
ko.mapping.fromJS(result, vmProductsIndex.CartSummary);
item.InCart(true);
item.QuantityOriginal(item.Quantity());
app.ajaxService.criticalSection(false);
},
function (result) {
$("#error-modal").modal();
app.ajaxService.criticalSection(false);
},
vmProductsIndex.ModalErrors);
}
}
you could use the "disable" binding from knockout to prevent the click binding of the anchor tag to be fired.
here is a little snippet for that. just set a flag to true when your action starts and set it to false again when execution is finished. in the meantime, the disable binding prevents the user from executing the click function.
function viewModel(){
var self = this;
self.disableAnchor = ko.observable(false);
self.randomList = ko.observableArray();
self.loading = ko.observable(false);
self.doWork = function(){
if(self.loading()) return;
self.loading(true);
setTimeout(function(){
self.randomList.push("Item " + (self.randomList().length + 1));
self.loading(false);
}, 1000);
}
}
ko.applyBindings(new viewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.0.0/knockout-min.js"></script>
Click me
<br />
<div data-bind="visible: loading">...Loading...</div>
<br />
<div data-bind="foreach: randomList">
<div data-bind="text: $data"></div>
</div>
I'm doing an application with Phonegap and I'm using a self-built slide transition to change the pages.
It works like this:
Every page is a div with 100% height and width, so if I change the Page, I set the next div right to the currently active and slide both to the left side.
Now to the Problem: the sliding works fine, but it's executed before the content of the right div is completely loaded. So the right div slides in empty, and only after a few hundred miliseconds the content will appear.
I tried it with document.ready, but as I've read this event is only executed the first time the DOM is loaded.
Does anybody know how I can wait for the DOM to be completely rendered again after I've manipulated the DOM with Javascript?
In your case, you can pick one element in the content of the next div and keep checking it with $(...).length. If the value is > 0, the DOM is loaded and you can change the page.
You may want to try this function:
Function.prototype.deferUntil = function(condition, timeLimit) {
var ret = condition();
if (ret) {
this(ret);
return null;
}
var self = this, interval = null, time = ( + new Date());
interval = setInterval(function() {
ret = condition();
if (!ret) {
if (timeLimit && (new Date() - time) >= timeLimit) {
// Nothing
} else {
return;
}
}
interval && clearInterval(interval);
self(ret);
}, 20);
return interval;
};
Usage:
(function() {
console.log('Next page loaded');
}).deferUntil(function() {
return $('#nextDiv').length > 0;
}, 3000);
The above example will check the div that has id="nextDiv" in every 20ms (not longer than 3 seconds). When the div is loaded, it will show 'Next page loaded' in the console.
You can try on this fiddle
There is a DOMNodeInserted event that is supposed to work like document.ready but for individual DOM nodes. But it is deprecated and has lots of issues. StackOverflow users found a good alternative to it that works quite well in all mobile browsers: Alternative to DOMNodeInserted
Here is a function that will trigger a callback once all images matching a jquery selector have finished loading
Js Fiddle Sample
//css
input {width: 420px;}
//html
<div id="container"></div>
<input type="text" value="http://goo.gl/31Vs" id="img1">
<br><input type="text" value="http://wall.alafoto.com/wp-content/uploads/2010/11/Fractal-Art-Wallpapers-09.jpg" id="img2">
<br><input type="text" value="http://pepinemom.p.e.pic.centerblog.net/ssg8hv4s.jpg" id="img3">
<br><input type="button" value="Load images" name="loadImages" id="btn">
<div id="message"></div>
//javascript
//Call a function after matching images have finished loading
function imagesLoadedEvent(selector, callback) {
var This = this;
this.images = $(selector);
this.nrImagesToLoad = this.images.length;
this.nrImagesLoaded = 0;
//check if images have already been cached and loaded
$.each(this.images, function (key, img) {
if (img.complete) {
This.nrImagesLoaded++;
}
if (This.nrImagesToLoad == This.nrImagesLoaded) {
callback(This.images);
}
});
this.images.load(function (evt) {
This.nrImagesLoaded++;
if (This.nrImagesToLoad == This.nrImagesLoaded) {
callback(This.images);
}
});
}
$("#btn").click(function () {
var c = $("#container"), evt;
c.empty();
c.append("<img src='" + $("#img1").val() + "' width=94>");
c.append("<img src='" + $("#img2").val() + "' width=94>");
c.append("<img src='" + $("#img3").val() + "' width=94>");
evt = new imagesLoadedEvent("img", allImagesLoaded);
});
function allImagesLoaded(imgs) {
//this is called when all images are loaded
$("#message").text("All images loaded");
setTimeout(function() {$("#message").text("");}, 2000);
}
You could use jQuery ajax to load the content, and on success run a function with the slide.
$("#page1").load('page2.html', function() {
//do your custom animation here
});
Althoug I'm not completely sure how you're loading the content. Is it static (Already there but just not visible?) Or is it loaded with ajax?
EDIT: You could just do a small .delay() or setTimeout with a few millisec, and then animate the sliding.
I had a similar problem making a masonry site responsive. I use window.onload which waits for all elements to complete loading before initialising masonry.js. I also placed the window.onload inside .onchange function and it fired everytime the viewport resized.
I am sure applying similar principles will solve your problem.
try once
$(window).bind('load',function(){
//code
});
Maybe you can set an event on your div.
myDiv.onafterupdate = myHandler;
function myHandler() {
// Do here what you want to do with the updated Div.
}
Does this help you?
In jquery you could use $() just after your DOM manipulation code.
$(function(){
//code that needs to be executed when DOM is ready, after manipulation
});
$() calls a function that either registers a DOM-ready callback (if a function is passed to it) or returns elements from the DOM (if a selector string or element is passed to it)
You can find more here
difference between $ and $() in jQuery
http://api.jquery.com/ready/
Initially, I had a problem that a click event was firing multiple times, but I have managed to overcome that with a probably over use of unbind() and one() as you'll see in my code below!
What I have here is some code which opens up a universally usable Modal window which I use for various things, including, in some cases a password form.
I don't think you need the HTML so I won't post that.
When a button, or an action causes the window to be required, I call the function like this:
showModalAlert(type, theWidth, theHeight, title, html, confirmThis, denyThis)
The first three variables determine how the window will look, title and html determine the content and confirmThis and denyThis are functions set immediately prior to calling this function and determine what the action should be if this is a confirm window and the confirm or deny buttons are press.
In the case of a security window, the confirm button is replace by a "sign it" button which submits a simple password form and returns a User Id from database. If a User Id is successfully returned, the script programatically presses the confirm button and in turn runs it's function as per the call to the inital opening of the modal window.
My problem is that if an incorrect password is entered, or a user cancels the window and then later without refreshing the browser window, re-enters the password correctly, the confirmThis() function is performed twice (or as many times as the incorrect password/cancel action was performed).
So, clearly, what it is doing is "remembering" the confirmThis function each time.
As I said, initially, the password success function was clicking confirmIt twice, copious use of one() has fixed this, it is now definitely only clicking confirmIt once, but it is still performing the function multiple time.
How can I clear this function and ensure it is only performed once?
The function from which I am calling the modal window looks like this:
$('#saveDelivery').click(function () {
function confirmIt() {
formData = (JSON.stringify($('#delDetail').serializeObject()));
saveData(formData);
$('#saveDelivery').removeClass('centreLoader');
};
showModalAlert('security', '300px', '185px', 'Security!', 'You need to "Sign" this action.', confirmIt, '');
});
It's simply a click on the saveDelivery element, the confirmThis function is declared at this point and submits an AJAX form
the actual showModalAlert function is below:
function showModalAlert(type, theWidth, theHeight, title, html, confirmThis, denyThis) {
// stuff that opens the alert window \\
if (confirmThis == '') {
$('#confirmIt').one('click', function () { $('#closeAlert').one('click').click(); });
} else {
$('#confirmIt').one('click', function () { confirmThis(); $('#closeAlert').one('click').click(); });
};
if (denyThis == '') {
$('#denyIt').one('click', function () { $('#closeAlert').one('click').click(); $('#signIt').unbind(); });
} else {
$('#denyIt').one('click', function () { denyThis(); $('#closeAlert').one('click').click(); $('#signIt').unbind(); });
};
if (type == "confirm") {
$('.closeAlert, .signItForm').hide();
};
if (type == "alert") {
$('.alertConfirm, .signItForm').hide();
};
if (type == "fixedAlert") {
$('.closeAlert, .alertConfirm, .signItForm').hide();
};
if (type == "security") {
$('.signItForm').show();
$('.closeAlert').hide();
$('#confirmIt').hide();
$('#signIt').unbind().fadeTo('fast',1);
};
};
$('#signIt').live('click', function () {
var formData = (JSON.stringify($('.secureSign').serializeObject()));
var signitPwd = $('#signItpwd').val();
var jsonURL = "/jsonout/getdata.aspx?sql=SELECT id, password FROM users WHERE password ='" + signitPwd + "' LIMIT 1&output=json&usedb=new&labelName=any&fileName=";
$.getJSON(jsonURL, function (data) {
if (data.length > 0) {
$('.savingUserID').val(data[0].id);
$('#confirmIt').one('click').click();
$('#signIt').fadeTo('fast', 0);
$('#confirmIt').show();
} else {
$('#signIt').fadeTo('fast', 0);
$('#confirmIt').one('click').show();
$('.closeAlert').show();
$('.alertConfirm, .signItForm').hide();
$('#alertTitle').html("Error!");
$('#alertContent').css({ 'text-align': 'center' }).html("Password Denied");
};
});
});
From my understanding of $.one, it merely runs the event ONCE. If you bind it twice to the event, it will run twice instantaneously, but no more.
Example: http://jsfiddle.net/qCwMH/ (click the button, and it will run the event 4 times).
Each time you click saveDelivery, you are infact, binding another $.one event to #confirmIt.
What you could do is unbind your events from confirmIt and denyIt at the start of the modal function (i.e. $('#confirmIt, #denyIt').unbind('click');, and then you will assign them fresh each time that function is called, rather than building on top of them. Not ideal, as binding/unbinding uses more resources than other options, but just give that a try to start with perhaps?
I'm a newbie to Javascript & HTML5. I'm iterating through a set of objects called requests and creating divs for them. I'm trying to have it so that if any of the items are hovered over the style class changes, and if they are clicked on that I will later invoke a function but for now just want an alert. Only the last item gets it.
I've looked at what seemed like similar issues other people have had, but I can't see where I am going wrong.
for (i= 0; i<reqs.length; i++) {
var requestID = "request"+i;
// Build the DIV for each request
element.innerHTML += "<div id="+requestID+" class=request><img class=requestImage src=images/"
+reqs[i].image+" alt=Face /> "+reqs[i].name+"</div>";
var requestElement = $('#'+requestID);
requestElement.hover(
function() {
$(this).removeClass().addClass("requestHover");
},
function() {
$(this).removeClass().addClass("request");
}
);
requestElement.click(
// if the request is clicked, then alert me - testing
function() {
alert('Handler for .click() called.');
}
);
}// end for
From my understanding the $('#request1') should reference the first div item, and $('#request2') the second, etc. It behaves like each .hover and .click assignment overwrites the previous one.
I'd write it this way
for (i= 0; i<reqs.length; i++) {
var requestID = "request"+i;
// Build the DIV for each request
element.innerHTML += "<div id="+requestID+" class=request><img class=requestImage src=images/"
+reqs[i].image+" alt=Face /> "+reqs[i].name+"</div>";
}// end for
var requestElement = $(".request");
requestElement.hover(
function() {
$(this).removeClass().addClass("requestHover");
},
function() {
$(this).removeClass().addClass("request");
}
);
requestElement.click(
// if the request is clicked, then alert me - testing
function() {
alert('Handler for .click() called.');
}
);
Edit to answer to your comment:
No, it's a matter of closure. At the end of the loop, requestID is always the same, so $("#"+requestID); is always the same. It's has if there is only one bind.
To overcome such a thing, you have some option:
using $.each to loop (see http://forum.jquery.com/topic/binding-event-to-element-dynamically , http://forum.jquery.com/topic/binding-click-event-in-a-loop, http://api.jquery.com/jQuery.each/ )
using the live() method (see http://api.jquery.com/live/, http://jsfiddle.net/VrzUb/1/ (used for the click))
assigning the events thanks to a selector common to each object (e.g. using classes, as above)
You can assign the event listener after the for loop, maybe that solves your problem:
for (...) {
}
$('div.request').hover( ... );
I'm trying to have 'help' tooltips which appear when you click on labels of certain class, and go away when you click away. Nothing appears. I can set breakpoints in firebug and see the 'loading' tooltip, and then when the ajax returns the tooltip updates correctly, then removeTooltip gets called phantomly (stack trace just says F() F() in jquery). So the tooltip gets set and removed so fast that it's never seen.
HelpText.removeTooltip = function() {
$('#activeHelpTip').remove();
$('body').unbind('click', HelpText.removeTooltip);
}
HelpText.initToolTip = function(clickedElement) {
$('body').click(HelpText.removeTooltip);
$(clickedElement).append('<span id="activeHelpTip" class="helpTip">Loading help...</span>');
}
HelpText.updateTooltip = function(helpString, clickedElement, methodName) {
if (helpString == null) { helpString = "Help text has not been defined for selected field"; }
$('#activeHelpTip').html(helpString);
}
$(document).ready(function() {
$('.helpText').click(function() {
var helpRequested = $(this).html();
var path = window.location.pathname;
var fullPage = path.substring(path.lastIndexOf('/') + 1);
var page = fullPage.substring(0, fullPage.indexOf('.'));
var label_helpRequested = $(this).html();
HelpText.initToolTip(this);
HelpText.getHelpText(page, label_helpRequested, this);
});
HelpText.getHelpText = function(pageNameParam, fieldNameParam, element) {
var params = { pageName: pageNameParam, fieldName: fieldNameParam };
if (this._webRequest) {
// abort the previous web service call if we
// are issuing a new one and the previous one is
// active.
this._webRequest.get_executor().abort();
this._webRequest = null;
}
// this._webRequest is a handler on the async request
this._webRequest = Sys.Net.WebServiceProxy.invoke(HelpTextServiceURL,
"GetHelpText",
false, /* use GET */
params, /* parameters to the Ajax service method - case and type sensitive */
HelpText.updateTooltip, /* success callback */
null, /* failure callback */
element); /* user context - preserved info - accessed in the success callback - in this case will contain SPAN */
}
Your initToolTip function sets an on-click handler for the entire body of the page to call removeToolTip(). I imagine that what is happening is that when the click event fires on $('.helpText'), the tooltip is added, and then the click event bubbles up to the body element, at which point removeToolTip() is being called.
you haven't cancelled bubbling on the event that shows the tooltip, and the first thing you do is attach a remove handler to body. So when your init handler ends, jQuery and the browser delegate that event up the chain, where your remove handler is seen and processed.
The solution is to cancel bubbling in your init handler.