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.
Related
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();
});
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 have a piece of javascript code here. When a hyperlink is clicked, the load_button() function is called which just sets the variable load_switch to true. I have a piece of code inside $(window).scroll(function() { which will fire the code when the user scrolls. So at the moment, the user clicks the hyperlink to set the variable to true, and then my load_posts function (which I omitted from the code I included to make it easier to read, see below) fires when the user scrolls.
I would like to know how I can make the function fire without the user having to scroll first to activate it. I am editing a previously programmed function which used to be an infinite scroll (hence the function being called when the user scrolls). Here is my javascript:
<script language="javascript">
var load_switch = false;
function load_button(){
load_switch = true;
}
$(document).ready(function() {
$('.loader').hide();
var load = 0;
$(window).scroll(function() {
if(load_switch) {
//load_posts function goes here
}
});
});
</script>
Name your function
var load_switch = false;
function load_button(){
load_switch = true;
}
// Name your function instead of defining it inline
function onScroll() {
if(load_switch) {
//load_posts function goes here
}
}
$(document).ready(function() {
$('.loader').hide();
var load = 0;
$(window).scroll(onScroll);
// Call it whenever you'd like
onScroll();
});
You can use triggerHandler:
$(window).triggerHandler('scroll');
Note this will run all event handlers. If you don't want that, you need to store the desired event handler in a variable:
function handleScroll() {
//load_posts function goes here
}
function load_button(){
$(window).on('scroll', handleScroll);
}
$(document).ready(function() {
$('.loader').hide();
handleScroll(); // Call it manually
});
Also note your approach was bad. Instead of running the event handler always and exiting if a boolean flag is false, get rid of that flag and add or remove the event handler instead of setting the flag to true or false.
I have a problem with event object passed to the function in drop event. In my code, div#dropArea has it's drop event handled by firstDrop function which does some animations and then calls the proper function dropFromDesktop which handles the e.dataTransfer.files object. I need this approach in two separate functions because the latter is also used further by some other divs in the HTML document (no need to duplicate the code). First one is used only once, to hide some 'welcome' texts.
Generally, this mechanism lets you drag files from desktop and drop them into an area on my website.
Here's, how it looks (in a shortcut):
function firstDrop(ev) {
var $this = $(this);
//when I call the function here, it passes the event with files inside it
//dropFromDesktop.call($this, ev);
$this.children('.welcomeText').animate({
opacity: '0',
height: '0'
}, 700, function() {
$('#raw .menu').first().slideDown('fast', function() {
//when I call the function here, it passes the event, but 'files' object is empty
dropFromDesktop.call($this, ev);
});
});
}
function dropFromDesktop(ev) {
var files = ev.originalEvent.dataTransfer.files;
(...) //handling the files
}
$('#dropArea').one('drop', firstDrop);
$('some_other_div').on('drop', dropFromDesktop);
The problem is somewhere in jQuery.animation's callback - when I call my function inside it, the event object is passed correctly, but files object from dataTransfer is empty!
Whole script is put inside $(document).ready(function() { ... }); so the order of function declarations doesn't matter, I guess.
I suspect your problem is related with the lifetime of the Event object. Unfortunately, I have no clue about the cause of it. But, there is a way to workaround it that I can think of and it is keeping a reference to Event.dataTransfer.files instead.
var handleFileList = function(fn) {
return function(evt) {
evt.preventDefault();
return fn.call(this, evt.originalEvent.dataTransfer.files);
};
};
var firstDrop = function(fileList) { ... }
var dropFromDesktop = function(fileList) { ... }
$('#dropArea').one('drop', handleFileList(firstDrop));
$('some_other_div').on('drop', handleFileList(dropFromDesktop));
I have a single html page with multiple div elements on it. Each time a user clicks on on a div, it is replaced with an CKEditor on the fly:
$('.editable').click(function() {
editor = CKEDITOR.replace(this);
});
Now, if the CKEditor instance loses its focus (blur event), I need to post the content to a separate script via ajax (if something changed) and destroy this instance:
$('.editable').click(function() {
editor = CKEDITOR.replace(this);
editor.on('blur', function(e)
{
if (e.editor.checkDirty())
// get data with e.editor.getData() and do some ajax magic
e.editor.destroy();
});
});
But this example won't work because, I don't know why, destory() will be called before checkDirty(). How can I get this working?
How about if you put the destroy() inside the if() statement? You could have an else clause that invokes destroy if nothing has changed. If something has changed, you can invoke destroy() within the if clause once the data has been transfered.
$('.editable').click(function() {
editor = CKEDITOR.replace(this);
editor.on('blur', function(e)
{
if (e.editor.checkDirty()) {
// get data with e.editor.getData() and do some ajax magic
if ( dataTransferComplete ) {
e.editor.destroy();
}
} else {
e.editor.destroy();
}
});
});
Or you could check a variable before invoking destroy(). Set that variable to true after the data transfer has been completed and in the else clause, that way destroy() won't be invoked until you've checked for changes and transfered any updated data.
$('.editable').click(function() {
editor = CKEDITOR.replace(this);
editor.on('blur', function(e)
{
var okToDestroy = false;
if (e.editor.checkDirty()) {
// get data with e.editor.getData() and do some ajax magic
okToDestroy = true;
} else {
okToDestroy = true;
}
if (okToDestroy )
e.editor.destroy();
});
});
This is an outline, I haven't tested the code, but if shows the concept.
Be Well,
Joe