Add class to clicked button if statement - javascript

I'm trying to make hangaman game so if the user click the correct letter 'dont-draw' class will be added if he enter the wrong letter another class will added, but is seems this if condition never became false
else if(theClickedLetter !== wordGeuss){
e.target.classList.add('draw');
}
here is the code:
document.addEventListener('click', function(e) {
let theClickedLetter = e.target.textContent;
arrayGuess.forEach((wordGeuss, index) => {
if (theClickedLetter === wordGeuss) {
e.target.classList.add('dont-draw');
let n = Array.from(lettersGess.children);
n[index].textContent = theClickedLetter;
}
else if (theClickedLetter !== wordGeuss) {
e.target.classList.add('draw');
}
})
})

So first think you can do to test what's going on is that you add an else in the end and see where the logic is ending up at. Since you didn't provide a complete HTML, I couldn't do much on the testing end so I can only suggest you how to test and possible fixes. Here's your code with an else:
document.addEventListener('click', function(e) {
let theClickedLetter = e.target.textContent;
arrayGuess.forEach((wordGeuss, index) => {
if (theClickedLetter === wordGeuss) {
e.target.classList.add('dont-draw');
let n = Array.from(lettersGess.children);
n[index].textContent = theClickedLetter;
}
else if (theClickedLetter !== wordGeuss) {
e.target.classList.add('draw');
}
else {
console.log('ending up in else')
}
})
})
Then you can see what's going wrong. Possible issues and fixes are
The way you get the text from the event (try using other methods like e.target.innerText)
Try printing both of the variables before the if else block and see what's gone wrong (see if the values actually match and debug accordingly)
Just use an else at the end to put the code e.target.classList.add('draw');, that might be a possible solution

Related

How to change value of global var when it is being used as a parameter in a function? (Javascript)

I want to be able to change the value of a global variable when it is being used by a function as a parameter.
My javascript:
function playAudio(audioFile, canPlay) {
if (canPlay < 2 && audioFile.paused) {
canPlay = canPlay + 1;
audioFile.play();
} else {
if (canPlay >= 2) {
alert("This audio has already been played twice.");
} else {
alert("Please wait for the audio to finish playing.");
};
};
};
const btnPitch01 = document.getElementById("btnPitch01");
const audioFilePitch01 = new Audio("../aud/Pitch01.wav");
var canPlayPitch01 = 0;
btnPitch01.addEventListener("click", function() {
playAudio(audioFilePitch01, canPlayPitch01);
});
My HTML:
<body>
<button id="btnPitch01">Play Pitch01</button>
<button id="btnPitch02">Play Pitch02</button>
<script src="js/js-master.js"></script>
</body>
My scenario:
I'm building a Musical Aptitude Test for personal use that won't be hosted online. There are going to be hundreds of buttons each corresponding to their own audio files. Each audio file may only be played twice and no more than that. Buttons may not be pressed while their corresponding audio files are already playing.
All of that was working completely fine, until I optimised the function to use parameters. I know this would be good to avoid copy-pasting the same function hundreds of times, but it has broken the solution I used to prevent the audio from being played more than once. The "canPlayPitch01" variable, when it is being used as a parameter, no longer gets incremented, and therefore makes the [if (canPlay < 2)] useless.
How would I go about solving this? Even if it is bad coding practise, I would prefer to keep using the method I'm currently using, because I think it is a very logical one.
I'm a beginner and know very little, so please forgive any mistakes or poor coding practises. I welcome corrections and tips.
Thank you very much!
It's not possible, since variables are passed by value, not by reference. You should return the new value, and the caller should assign it to the variable.
function playAudio(audioFile, canPlay) {
if (canPlay < 2 && audioFile.paused) {
canPlay = canPlay + 1;
audioFile.play();
} else {
if (canPlay >= 2) {
alert("This audio has already been played twice.");
} else {
alert("Please wait for the audio to finish playing.");
};
};
return canPlay;
};
const btnPitch01 = document.getElementById("btnPitch01");
const audioFilePitch01 = new Audio("../aud/Pitch01.wav");
var canPlayPitch01 = 0;
btnPitch01.addEventListener("click", function() {
canPlayPitch01 = playAudio(audioFilePitch01, canPlayPitch01);
});
A little improvement of the data will fix the stated problem and probably have quite a few side benefits elsewhere in the code.
Your data looks like this:
const btnPitch01 = document.getElementById("btnPitch01");
const audioFilePitch01 = new Audio("../aud/Pitch01.wav");
var canPlayPitch01 = 0;
// and, judging by the naming used, there's probably more like this:
const btnPitch02 = document.getElementById("btnPitch02");
const audioFilePitch02 = new Audio("../aud/Pitch02.wav");
var canPlayPitch02 = 0;
// and so on
Now consider that global data looking like this:
const model = {
btnPitch01: {
canPlay: 0,
el: document.getElementById("btnPitch01"),
audioFile: new Audio("../aud/Pitch01.wav")
},
btnPitch02: { /* and so on */ }
}
Your event listener(s) can say:
btnPitch01.addEventListener("click", function(event) {
// notice how (if this is all that's done here) we can shrink this even further later
playAudio(event);
});
And your playAudio function can have a side-effect on the data:
function playAudio(event) {
// here's how we get from the button to the model item
const item = model[event.target.id];
if (item.canPlay < 2 && item.audioFile.paused) {
item.canPlay++;
item.audioFile.play();
} else {
if (item.canPlay >= 2) {
alert("This audio has already been played twice.");
} else {
alert("Please wait for the audio to finish playing.");
};
};
};
Side note: the model can probably be built in code...
// you can automate this even more using String padStart() on 1,2,3...
const baseIds = [ '01', '02', ... ];
const model = Object.fromEntries(
baseIds.map(baseId => {
const id = `btnPitch${baseId}`;
const value = {
canPlay: 0,
el: document.getElementById(id),
audioFile: new Audio(`../aud/Pitch${baseId}.wav`)
}
return [id, value];
})
);
// you can build the event listeners in a loop, too
// (or in the loop above)
Object.values(model).forEach(value => {
value.el.addEventListener("click", playAudio)
})
below is an example of the function.
btnPitch01.addEventListener("click", function() {
if ( this.dataset.numberOfPlays >= this.dataset.allowedNumberOfPlays ) return;
playAudio(audioFilePitch01, canPlayPitch01);
this.dataset.numberOfPlays++;
});
you would want to select all of your buttons and assign this to them after your html is loaded.
https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName
const listOfButtons = document.getElementsByClassName('pitchButton');
listOfButtons.forEach( item => {
item.addEventListener("click", () => {
if ( this.dataset.numberOfPlays >= this.dataset.allowedNumberOfPlays ) return;
playAudio("audioFilePitch" + this.id);
this.dataset.numberOfPlays++;
});

my functions are mixed and the engine executing what ever he wants

so i was told to Present a loader to the user when a call is made to the server (indicating the server is calculating) Present an error to the user if the input number is more than 50, and do not send a server request Try passing the number 42 to the server. The server will send back an error, present this error to the user.
now what i did is everything besides the last error to present it to the user.
i have tried everything i could think of, and no matter what the user types, it displays both of the messages.
this is my code:
const clcBtn = document.getElementById("calcButton");
let clcInput = document.getElementById("calcInput");
const result = document.getElementById('paragraph');
const loader = document.getElementById('spinner');
const error = document.getElementById('error-message');
const serverError = document.getElementById('server-error');
clcBtn.addEventListener("click", calcFunc);
function numValidate(value) {
if(value > 50) {
return false;
}
return true;
}
function calcFunc() {
if (!numValidate (clcInput.value)) {
error.style.display = "block"
setTimeout(() => {
error.style.display = "none";
}, 5000);
console.error("Can't be larger than 50") // only bec it's a cool feature :D
return;
}
loader.classList.add("spinner-border");
fetch(`http://localhost:5050/fibonacci/${clcInput.value}`).then(function (response) {
return response.json().then(function (data) {
result.innerHTML = data.result;
});
});
setTimeout(() => {
loader.classList.remove("spinner-border");
}, 1000);
}
this is my code with what i have tried to add (on of the things i have tried.. this is the best output i could come with)
code:
// additional code to present to the user the error message if the input value is equal to 42.
clcBtn.addEventListener("click", errMsg);
function numValidateTwo(value) {
if(value === 42) {
return true;
}
return false;
}
function errMsg() {
if (!numValidateTwo (clcInput.value)) {
serverError.style.display = "block";
}
return;
}
a little bit more about what i am trying to achieve:
i want to present this error message to the user, whenever the input value is equal to 42.
is it related to async or callback? which i need to go through the lectures again.. but right now i need to solve this bec i have no time.
what did i do wrong ?
and how i can make it work?
can someone explain this to me?
thanks in advance!

Office ContentControls List Inaccurate

I can do this a couple of ways, but let's stick to the AddIn itself. If I create a ContentControl:
Word.run((context) => {
let control = context.document.getSelection().insertContentControl();
control.tag = 'example';
control.insertOoxml('<xml here>');
context.sync();
});
Then later (with proper async handling of course) I delete the control:
Word.run((context) => {
let contentControls = context.document.contentControls;
context.load(contentControls, 'tag');
context.sync().then(() => {
for (let c = 0; c < contentControls.items.length; ++c) {
if (contentControls.items[c].tag === 'example') {
contentControls.items[c].delete(false); // delete the contentControl with the matching tag
}
}
context.sync()
});
});
Then I check the list of content controls for that control I just deleted:
Word.run((context) => {
let contentControls = context.document.contentControls;
context.load(contentControls, 'tag');
context.sync().then(() => {
for (var i = 0; i < contentControls.items.length; ++i) {
if (contentControls.items[c].tag === 'example') {
// this tag list still includes the deleted content control until I close and reopen the document
}
}
});
});
The tags still show the deleted content control. I have to close and reopen the document for the context to refresh. Am I missing a step for proper syncing with the current state of the document? Is context.sync() not enough?
NOTE: delete() does work: the content disappears from the document as expected. It's just still in the list of controls when I search the document.
Further research has determined the cause of this.
When TrackChanges is on, the ContentControls in the document are not ACTUALLY deleted. This still feels like a word bug, but you can check for the deleted flag on the contentcontrol (not sure offhand if that's really possible because it might be at some arbitrary level of the ancestry), manage your deletions manually (as we're doing), or turn off track changes to resolve this issue.
All that said, I am going to leave this up as a question in case someone has a better resultion.
Try to check list after you sync your context - in .then callback:
Word.run((context) => {
let contentControls = context.document.contentControls;
context.load(contentControls, 'tag');
context.sync().then(() => {
for (let c = 0; c < contentControls.items.length; ++c) {
if (contentControls.items[c].tag === 'example') {
contentControls.items[c].delete(false); // delete the contentControl with the matching tag
}
}
context.sync().then(()=> {
//your updated list
})
});
});

Display All ().html in Javascript

Sorry for the lack of description in title, it's difficult to explain.
So I have a simple signup page and I made a bunch of functions in my code that check things such as the username length, make sure the passwords match, etc..
The problem is, if there is more than one error in the users input, it only displays one error at the bottom.
HEre is the JSfiddle: http://jsfiddle.net/LCBradley3k/xqcJS/19/
Javascript:
$('#join').on('click', function () {
var correct = true;
$('input[type="text"], input[type="password"]').each(function (indx) {
var $currentField = $(this);
if ($currentField.val() === '') {
$currentField.addClass('empty');
correct = false;
$currentField.one('keydown', function () {
$currentField.removeClass('empty');
});
} else {
$currentField.removeClass('empty');
}
});
function userLength() {
var x = $('input[name="user"]').val();
if (x.length < 6) {
$('#answer').html('Less than six characters.');
$('input[name="user"]').addClass('empty');
return false;
} else {
return true;
}
}
function passwordCheck() {
var x = $('input[name="password"]').val();
var y = $('input[name="passwordcheck"]').val();
if (x === y) {
return true;
} else {
$('#answer').html('Two different passwords');
$('input[name="password"], input[name="passwordcheck"]').addClass('empty');
return false;
}
}
function validateForm() {
var x = $('input[name="email"]').val();
if (x.indexOf('#') !== -1 && x.lastIndexOf(".") !== -1) {
return true;
} else {
$('#answer').html('Not a valid email');
$('input[name="email"]').addClass('empty');
return false;
}
}
if (correct) {
if (userLength()) {
if (passwordCheck()) {
if (validateForm()) {
$('#answer').html('Thank You!');
setTimeout(function () {
$('.inputs').hide("slide", {
direction: "up"
}, 1000);
}, 2000);
}
}
}
} else {
$('#answer').html('Please fill highlighted fields.');
}
});
You can see that all of them edit the #('#answer') div with .html(). But only one is displayed when there is more than one error. Once that error is fixed and the button is pressed, it will then display the next error. I want them all to be displayed in a list.
I created a fiddle that may be of some help. The idea is to create an array with the errors in it like so:
var errors = [];
errors.push("Error 1");
errors.push("Error 2");
As you step through the validation, every time an error is encountered you simply push the error string onto the array. When you get to the end of the validation you need to compile these errors into html like that can be appended to your $('#answer') element. In this case the items are compiled into an unordered list. You can change this to fit your needs.
var content = "<ul>";
for(var a = 0, len = errors.length; a < len; a++) {
content += "<li>" + errors[a] + "</li>";
}
content += "</ul>";
$('#answer').html(content);
The html is built dynamically and stored in the variable content. content is then appended to your html element that displays the errors (in your case answer).
You have 2 issues with doing what you want.
First, you are only continuing your checks if the first one passes, due to your nested if statements.
Second, you are replacing the #answer html with the message, which means even if you do each check, you will only see the results of the last one.
A simple fix would be to un-nest your if statements, and keep a variable that tracks the overall pass state. Secondly, instead of using .html(), use .append(), but make sure to clear out #answer before starting your checks.
correct &= checkFilled();
correct &= userLength();
correct &= passwordCheck();
correct &= validateForm();
if (correct) {
// ...
}
Fiddle: http://jsfiddle.net/jtbowden/9cFKW/
Note: I made your form filled check it's own function to work better with this method.
You can do some more fancy things, like pushing error messages on an array, and then checking the array for errors at the end and appending all of the messages, but this should get you started.

Javascript Array Losing an Element At Random

I have a very strange issue that I am running into. I am using jsTree from JQueryUI on one of my sites, and I have different implementations of it used in different .js files. One of them seems to work, which is very confusing as it uses almost identical code (only the variable names are different) to the implementation that is broken. The problem comes from the contextmenu function. The code I am using is as follows:
$(document).ready(function () {
if(typeof dryerList == 'undefined' || dryerList.length == 0) {
var dryerList = [];
$.ajax({
url:'../TrackingApp/getGrainBins.php?t=234.23423452353',
async: false,
success: function(text) {
try {
dryerList = $.parseJSON(text);
} catch (e) {
alert('ERROR: ' + e);
}
if(dryerList.length == 0) {
alert('ERROR: No fleet data received.')
}
}
});
}
$("#dryerListTree").jstree({
plugins : ['json_data', 'ui', 'themes', 'contextmenu'],
contextmenu: {items: customBinMenu},
json_data : { data: binNodes }
});
$('#dryerListTree').bind("dblclick.jstree", function (event) {
var node = $(event.target).closest("li");
var id = node[0].id;
for(i=0; i < dryerList.length; i++) {
if(id == dryerList[i].id) {
centerMap(dryerList[i].y, dryerList[i].x);
break;
}
}
});
});
function customBinMenu(node) {
if ($(node).hasClass("folder")) {
return;
}
var items = {
centerItem: {
label: "Locate",
action: function () {
// Centers map on selected bin
var id = node[0].id;
for(i=0; i < dryerList.length; i++) {
if(id == dryerList[i].id) {
centerMap(dryerList[i].y, dryerList[i].x);
break;
}
}
}
},
dashboardItem: {
label: "Dashboard",
action: function () {
// Opens dryer info window over map
var id = node[0].id;
var dryerIndex = -1;
for(i=0; i < dryerList.length; i++) {
if(id == dryerList[i].id) {
dryerIndex = i;
break;
}
}
}
}
};
return items;
}
The strange bit is, the double-click handler works just fine. When I get to the customBinMenu() function, the dryerList array is there, and dryerList[0] contains 4 of the 5 values that it should- but somehow the 'id' element has been dropped from that object. I have been looking at this for quite some time, and I can't figure out how it can drop a single element from the object without losing any other data, especially when identical code is working for a similar list. Any suggestions?
Ok, I read in your question: 'and dryerList[0] contains 4 of the 5 values that it should- but somehow the 'id' element has been dropped from that object'
So by 'element' and 'value' I assume you mean 'attribute': the node's 'id'-attribute to be precise ??
I see in your code: var id = node[0].id;
That should be: var id = node[0].getAttribute("id");
Good luck!
UPDATE 1:
Ok, if (as per your comment) var id = node[0].id; (getting id from node[0]) is ok, then if(id == dryerList[i].id) looks wrong, since you just (re-)defined id to be the value of node[0]'s id.
Actually I would not use 'id' as a var-name (in this case).
So what if you did: var idc = node[0].getAttribute("id");
and then: if(idc === dryerList[i].getAttribute("id"))
UPDATE 5: You still have some errors by the way:
You forgot a semi-colon to close the alert in:
if(dryerList.length == 0) {
alert('ERROR: No fleet data received.')
}
You should use '===' to compare with '0' on line 2 and 14
naturally in real life you would define function customBinMenu(node) before it was used in your document.ready function.
Fixed by swapping code order.
The same goes for this document.ready function where you used var dryerList before it was defined.
Fixed by: var dryerList = dryerList || []; if(dryerList.length === 0){//ajax & json code}
Could you please confirm if this fiddle, which is now valid javascript, represents your intended baseline-code that still results in your problem of the 'id'-attribute being 'undefined' in dryerList's node-collection (since the code you posted contained some simple errors that are fixed in this jsfiddle, excluding the things mentioned in update 1, since you commented that this is not the problem) ?
May I ask (since you start at document.ready), why do you (still) check if dryerList already exists?
May I ask if you could update that corrected fiddle with some demo-data for us to toy around with?

Categories