Currently my code is like so. As is, what this feature does, is when clicked, it turns an SVG element to a certain color depending upon what conditions are met (in this instance, sting.state.mi === 'und').
After that click, the SVG item it is linked to changes color, as seen in m[geography.id]. However, if I spam this button enough times then I can get the wrong colors to show up.
I would like to avoid users spamming my click button.
Using datamap.svg.select('.datamaps-subunit.MI).disabled = true does not really do the trick. However, I hope it gets the point across.
Below is my code.
datamap.svg.select('.datamaps-subunit.MI').on('click', function(geography) {
var m = {};
if (sting.state.mi === 'und'){
datamap.svg.select('.datamaps-subunit.MI').disabled = true;
sting.setState({trumpnums: sting.state.trumpnums + 12});
sting.setState({mi: 'right'});
m[geography.id] = 'tomato';
datamap.svg.select('.datamaps-subunit.MI').disabled = false;
}
});
This example uses Reactjs and Datamaps, though I don't imagine that's entirely relevant.
A simple flag should do the trick:
var waiting = false;
datamap.svg.select('.datamaps-subunit.MI').on('click', function(geography) {
// only run the logic if it isn't already being run
if (!waiting) {
waiting = true;
var m = {};
if (sting.state.mi === 'und'){
datamap.svg.select('.datamaps-subunit.MI').disabled = true;
sting.setState({trumpnums: sting.state.trumpnums + 12});
sting.setState({mi: 'right'});
m[geography.id] = 'tomato';
datamap.svg.select('.datamaps-subunit.MI').disabled = false;
waiting = false;
}
}
});
Also, you can set all of your state at once and setState will call a supplied callback once it's complete:
if (sting.state.mi === 'und'){
sting.setState({
trumpnums: sting.state.trumpnums + 12,
mi: 'right'
}, function() {
m[geography.id] = 'tomato';
waiting = false;
});
}
Related
I'm making a Blackjack game to exercise my Javascript skills. I had a bunch of alert() messages tied to the betting function to prevent invalid entries. In updating the code to have a more elegant message style than a browser alert, I wrote a function called alertModal() that pops up a message on the screen and then fades away. The message pops up the first time a user tries to enter an invalid bet, but does not pop up any other messages if the bet is invalid-- nothing happens. I know the placeBet() function is still running when the user clicks again, because if the bet is valid, dealFirstCards() runs and the game proceeds. So it seems to me that for some reason, the if/else portion of the placeBet() function is only running on the first click...
The game is live and running with this code at http://cnb-blackjack.netlify.com/game.html
Here is the javascript code in question:
// Player places a bet
$('div.bet').on('click', function() {
$(this).removeClass('glow');
$('.bet-button').addClass('glow');
});
$('.bet-button').on('click', function() {
event.preventDefault();
if (!placeBet.called) {
placeBet();
}
});
// PLACE BET
function placeBet() {
var $bet = parseInt($('.bet-input').val())
var $bank = parseInt($('.player-bank').text())
var $currentBet = $('.current-bet');
if (!isNaN($bet) && $bet <= $bank && $bet !== 0) {
$currentBet.text(' $' + $bet);
$('.bet input[type="text"]').val('');
$('.place-bet .hideaway').slideUp();
$('.player-bank').text($bank - $bet);
placeBet.called = true;
dealFirstCards();
} else if ($bet > $bank) {
var $message = 'Bet cannot exceed the amount in your Bank!';
alertModal($message);
} else if (isNaN($bet)) {
var $message = 'Enter a number, without "$".';
alertModal($message);
} else if ($bet === 0) {
var $message = "Betting nothing won't get you very far...";
alertModal($message);
}
}
// SHOW MODAL
function alertModal(message) {
$popUp = $('.alert-message');
$('.modal').removeClass('hide');
$popUp.text(message);
setTimeout(function() {
$('.modal').fadeOut(1000);
}, 1000);
}
function alertModal(message) {
$popUp = $('.alert-message');
$('.modal').show();
$popUp.text(message);
setTimeout(function() {
$('.modal').fadeOut(1000);
}, 1000);
}
As comments have explained, fadeOut is leaving the modal hidden after the first time it's clicked. Just call $(element).show(); on the modal to show it again and let fadeOut remove it.
var start = $('#start_img');
start.on('click', function(){
var piano = $('.piano');
piano.each(function(index){
$(this).hide().delay(700 * index).fadeIn(700*index);
start.off('click');
})
});
You can see that I have used the start.off('click') method, to stop the Event Listener from running again once it has been called. But the thing is, I only want the Event listener to be off during the time that the event is running. So that it cannot be called again while the event is still running. But once the event has finished, I want it to be 'callable' again. Does anyone know how t do this?
other way of doing this (doesn't work neither). Can anyone help me here. The other one is now clear.
var start = $('#start_img');
start.on('click', function() {
var q = 0;
var piano = $('.piano');
if (q === 1) {
return; // don't do animations
}
else{
piano.each(function(index) {
q = 1;
$(this).hide()
.delay(700 * index)
.fadeIn(700 * index, function() {
// remove from each instance when animation completes
q = 0
});
});}
});
You could toggle a class on active elements as well and then you can check for that class and not do anything if it exists
start.on('click', function() {
var piano = $('.piano');
if (piano.hasClass('active')) {
return; // don't do animations
}
piano.each(function(index) {
$(this).addClass('active')
.hide()
.delay(700 * index)
.fadeIn(700 * index, function() {
// remove from each instance when animation completes
$(this).removeClass('active')
});
});
});
For only one object, you could use a global variable for this, in my case, I'll be using isRunning:
var start = $('#start_img');
var isRunning = false;
start.on('click', function(){
if (!isRunning){
isRunning = true;
var piano = $('.piano');
piano.each(function(index){
$(this).hide().delay(700 * index).fadeIn(700*index, function(){
isRunning = false;
});
start.off('click');
});
}
});
This way your app shouldn't run the code until isRunning == false, which should happen after fadeIn is completed.
Syntaxes:
.fadeIn([duration] [,complete]);
.fadeIn(options);
.fadeIn([duration] [,easing] [,complete]);
For two or more objects, Charlietfl's answer should work perfectly.
I'm using the Microsoft Translation Widget, which I'd like to use to automatically translate a webpage without user interaction.
The problem is, I can't get rid of the widget that keeps popping up or hide it on document.ready because the CSS and JS get loaded from Microsoft's own script in the widget!
Does anyone know a way around this? I've looked everywhere and cannot find a solutuion for this.
Whoa, after some time playing around with that, I've finally achieved what you want.
It's kindda ugly, because of some needed workarounds, but it works, take a look at the fiddle.
The steps were:
Firstly, we must override the default addEventListener behavior:
var addEvent = EventTarget.prototype.addEventListener;
var events = [];
EventTarget.prototype.addEventListener = function(type, listener) {
addEvent.apply(this, [].slice.call(arguments));
events.push({
element: this,
type: type,
listener: listener
});
}
Then, we create a helper function removeEvents. It removes all the event listeners of an element.
var removeEvents = function(el, type) {
var elEvents = events.filter(function(ev) {
return ev.element === el && (type ? ev.type === type : true);
});
for (var i = 0; i < elEvents.length; i++) {
el.removeEventListener(elEvents[i].type, elEvents[i].listener);
}
}
When creating the script tag, in the way Microsoft says:
var s = d.createElement('script');
s.type = 'text/javascript';
s.charset = 'UTF-8';
s.src = ((location && location.href && location.href.indexOf('https') == 0) ? 'https://ssl.microsofttranslator.com' : 'http://www.microsofttranslator.com') + '/ajax/v3/WidgetV3.ashx?siteData=ueOIGRSKkd965FeEGM5JtQ**&ctf=True&ui=true&settings=Manual&from=';
var p = d.getElementsByTagName('head')[0] || d.dElement;
p.insertBefore(s, p.firstChild);
We must add a load event listener to that script, and the code below is fully commented:
s.addEventListener('load', function() {
// when someone changes the translation, the plugin calls the method TranslateArray
// then, we save the original method in a variable, and we override it
var translate = Microsoft.Translator.TranslateArray;
Microsoft.Translator.TranslateArray = function() {
// we call the original method
translate.apply(this, [].slice.call(arguments));
// since the translation is not immediately available
// and we don't have control when it will be
// I've created a helper function to wait for it
waitForTranslation(function() {
// as soon as it is available
// we get all the elements with an attribute lang
[].forEach.call(d.querySelectorAll('[lang]'), function(item, i) {
// and we remove all the mouseover event listeners of them
removeEvents(item, 'mouseover');
});
});
}
// this is the helper function which waits for the translation
function waitForTranslation(cb) {
// since we don't have control over the translation callback
// the workaround was to see if the Translating label is visible
// we keep calling the function, until it's hidden again
// and then we call our callback
var visible = d.getElementById('FloaterProgressBar').style.visibility;
if (visible === 'visible') {
setTimeout(function() {
waitForTranslation(cb);
}, 0);
return;
}
cb();
}
});
Update 1
After re-reading your question, it seems you want to hide all the widgets at all.
So, you must add the following code as soon as the translation is got:
waitForTranslation(function() {
document.getElementById('MicrosoftTranslatorWidget').style.display = 'none';
document.getElementById('WidgetLauncher').style.display = 'none';
document.getElementById('LauncherTranslatePhrase').style.display = 'none';
document.getElementById('TranslateSpan').style.display = 'none';
document.getElementById('LauncherLogo').style.display = 'none';
document.getElementById('WidgetFloaterPanels').style.display = 'none';
// rest of the code
});
I've created another fiddle for you, showing that new behavior.
Update 2
You can prevent the widget showing at all by adding the following CSS code:
#MicrosoftTranslatorWidget, #WidgetLauncher, #LauncherTranslatePhrase, #TranslateSpan, #LauncherLogo, #WidgetFloaterPanels {
opacity: 0!important;
}
And you can even prevent the before-translated text being showed, by hiding the document.body by default, and then showing it when the page is fully translated:
(function(w, d) {
document.body.style.display = 'none';
/* (...) */
s.addEventListener('load', function() {
var translate = Microsoft.Translator.TranslateArray;
Microsoft.Translator.TranslateArray = function() {
translate.apply(this, [].slice.call(arguments));
waitForTranslation(function() {
/* (...) */
document.body.style.display = 'block';
});
}
});
});
Take a look at the final fiddle I've created.
For me, this was the solution:
on your < style > section add this class
.LTRStyle { display: none !important }
Also, if you are invoking the translation widget this way:
Microsoft.Translator.Widget.Translate('en', lang, null, null, TranslationDone, null, 3000);
then add this to your callback (in this example is TranslationDone) function:
function TranslationDone() {
Microsoft.Translator.Widget.domTranslator.showHighlight = false;
Microsoft.Translator.Widget.domTranslator.showTooltips = false;
document.getElementById('WidgetFloaterPanels').style.display = 'none';
};
I've a task of building a modal prompt, that's been simple so far describing its methods like "show", "hide" when it comes down just to DOM manupulation.
Now comes the hardship for me... Imagine we have a page on which there are several immediate calls to construct and show several modals on one page
//on page load:
$("browser-deprecated-modal").modal();
$("change-your-city-modal").modal();
$("promotion-modal").modal();
By default my Modal (and other libraries i tried) construct all of these modals at once and show them overlapping each other in reverse order -
i.e $(promotion-modal) is on the top, while the
$("browser-deprecated-modal") will be below all of them. that's not what i want, let alone overlapping overlays.
I need each modal to show up only when the previous one (if there'are any) has been closed. So, first we should see "browser-deprecated-modal" (no other modals underneath), upon closing it there must pop up the second one and so on.
I've been trying to work it out with this:
$.fn.modal = function(options) {
return this.each(function() {
if (Modal.running) {
Modal.toInstantiateLater.push({this,options});
} else {
var md = new Modal(this, options);
}
});
}
destroy :function () {
....
if (Modal.toInstantiateLater.length)
new Modal (Modal.toInstantiateLater[0][0],Modal.toInstantiateLater[0][1]);
}
keeping a track of all calls to construct a Modal in a array and in the "destroy" method make a check of this array is not empty.
but it seems awkward and buggy me thinks.
i need a robust and clear solution. I've been thinking about $.Callbacks or $.Deferred,
kinda set up a Callback queue
if (Modal.running) { //if one Modal is already running
var cb = $.Callbacks();
cb.add(function(){
new Modal(this, options);
});
} else { //the road is clear
var md = new Modal(this, options);
}
and to trigger firing cb in the destroy method, but i'm new to this stuff and stuck and cannot progress, whether it's right or not, or other approach is more suitable.
Besides, I read that callbacks fire all the functions at once (if we had more than one extra modal in a queue), which is not right, because I need to fire Modal creation one by one and clear the Callback queue one by one.
Please help me in this mess.
My code jsfiddle
I got rid of the counter variable, as you can use toInstantiateLater to keep track of where you are, and only had to make a few changes. Give this a try...
Javscript
function Modal(el, opts){
this.el = $(el);
this.opts = opts;
this.overlay = $("<div class='overlay' id='overlay"+Modal.counter+"'></div>");
this.wrap = $("<div class='wrap' id='wrap"+Modal.counter+"'></div>");
this.replace = $("<div class='replace' id='replace"+Modal.counter+"'></div>");
this.close = $("<span class='close' id='close"+Modal.counter+"'></span>")
if (Modal.running) {
Modal.toInstantiateLater.push(this);
}
else {
Modal.running = true;
this.show();
}
}
Modal.destroyAll = function() {
Modal.prototype.destroyAll();
};
Modal.prototype = {
show: function() {
var s = this;
s.wrap.append(s.close);
s.el.before(s.replace).appendTo(s.wrap).show();
$('body').append(s.overlay).append(s.wrap);
s.bindEvents();
Modal.currentModal = s;
},
bindEvents: function() {
var s = this;
s.close.on("click.modal",function(e){
s.destroy.call(s,e);
});
},
destroy: function(e) {
var s = this;
s.replace.replaceWith(s.el.hide());
s.wrap.remove();
s.overlay.remove();
if (Modal.toInstantiateLater.length > 0) {
Modal.toInstantiateLater.shift().show();
}
else {
Modal.running = false;
}
},
destroyAll: function(e) {
Modal.toInstantiateLater = [];
Modal.currentModal.destroy();
}
}
Modal.running = false;
Modal.toInstantiateLater = [];
Modal.currentModal = {};
$.fn.modal = function(options) {
return this.each(function() {
var md = new Modal(this, options);
});
}
$("document").ready(function(){
$("#browser-deprecated-modal").modal();
$("#change-your-city-modal").modal();
$("#promotion-modal").modal();
$("#destroy-all").on("click", function() {
Modal.destroyAll();
});
});
jsfiddle example
http://jsfiddle.net/zz9ccbLn/4/
so im a little rusty with my JS, but here is my code...
basically i have an image, that on mouseover, it cycles through a hidden div full of other images... fading it out, replacing the src, and fading back in. it works great. but once it gets through all the images, i want it to start back over and keep looping through them until the mouseout event stops it.
i thought i could just call the function again from within the function cycle_images($(current_image));, but that leads to the browser freaking out, understandably. what is a good method to accomplish this?
$.fn.image_cycler = function(options){
// default configuration properties
var defaults = {
fade_delay: 150,
image_duration: 1500,
repeatCycle: true,
};
var options = $.extend(defaults, options);
this.each(function(){
var product = $(this);
var image = $('div.image>a.product_link>img', product);
var description = $('div.image>div.description', product);
var all_images = $('div.all_images', product);
var next_image = ($(all_images).find('img[src="' + $(image).attr('src') + '"]').next('img').attr('src')) ? $(all_images).find('img[src="' + $(image).attr('src') + '"]').next('img') : $(all_images).children('img').first();;
// mouseover
image.fadeOut(options.fade_delay, function(){
image.attr('src', next_image.attr('src'));
image.fadeIn(options.fade_delay);
});
if (options.repeatCycle){
var loop = function() {
product.image_cycler();
}
setTimeout(loop,options.image_duration);
}
});
};
$(document).ready(function() {
$('div.product').hover(function(){
$(this).image_cycler();
}, function(){
$(this).image_cycler({repeatCycle: false});
});
});
It sounds like you want it to re-cycle and stop on mouseout.
After you define the cycle_images() function, add a flag, repeatCycle
cycle_images.repeatCycle = true;
At the end of the cycle_images function, add a recursive call with a timeout
if (this.repeatCycle) {
var f = function() {
return cycle_images.apply(this, [$current_image]);
}
setTimeout(f,50);
}
Then in mouseout,
cycle_images.repeatCycle = false;