I am new to javascript, so this is a basic question. I created a simple mp3 player that loads the first song, then plays the next couple songs in an array. I modified this code that I found on stack, and it works:
audioPlayer.onended = function() {
if(currentSong < nextSong.length-1) {
audioPlayer.src = nextSong[++currentSong];
document.getElementById('songTitle').innerHTML
= songTitle[currentSong];
}
}
However, if I try to put the implementation in its own function and call the function that way it doesn't work:
audioPlayer.onended = nextSong();
function nextSong() {
if(currentSong < nextSong.length-1) {
audioPlayer.src = nextSong[++currentSong];
document.getElementById('songTitle').innerHTML
= songTitle[currentSong];
}
}
I don't want to rewrite the code every time I want to use the function nextSong(). I have tried calling the nextSong() function from a button inside the tag, for example this post, but cannot get the function to call. Thanks for your help.
This is a common confusion. What your second example is actually doing is running the nextSong function and assigning its return value to onended.
Instead, you could change your code to:
function nextSong() {
if(currentSong < nextSong.length-1) {
audioPlayer.src = nextSong[++currentSong];
document.getElementById('songTitle').innerHTML
= songTitle[currentSong];
}
}
// Assign the function (nextSong) not its return value (nextSong())
audioPlayer.onended = nextSong;
Related
I'm making a simple browser game to practice JS where you kill a bad guy. There are 2 modes, easy and hard. I am trying to set up a reset button that will reset information fields depending on which of the modes the game is in.
example
playing easy, click reset and the game resets by running easyMode function
playing hard, click reset and the game resets by running hardMode function
apologies if this is simple, this is why I'm making practice games
I've tried to make the function that currently has a specific class selected run when running reset function
var resetButton = document.querySelector("#new");
var easy = document.querySelector("#easy");
var hard = document.querySelector("#hard");
var playerHealth = document.querySelector("#php");
var playerFocus = document.querySelector("#pfocus");
var bossHealth = document.querySelector("#bhp");
var attack = document.querySelector("#attack");
var strong = document.querySelector("#strong");
var regenerate = document.querySelector("#regen");
var modeButtons = document.querySelectorAll(".mode");
var defenseLog = document.querySelector("#defenselog");
var offenseLog = document.querySelector("#offensivelog")
var boss = {}
var player = {}
setupModeButtons();
easyMode();
reset();
function hardMode(){
player.health = 12;
player.focus = 15;
boss.health = 25;
update()
};
function easyMode(){
player.health = 10;
player.focus = 10;
boss.health = 12;
update();
}
function update (){
playerFocus.textContent = player.focus;
playerHealth.textContent = player.health;
bossHealth.textContent = boss.health;
};
function setupModeButtons(){
for(var i = 0; i < modeButtons.length; i++) {
modeButtons[i].addEventListener("click", function(){
modeButtons[0].classList.remove("selected");
modeButtons[1].classList.remove("selected");
this.classList.add("selected");
});
}
}
function reset (){
if(easyMode.classList=="selected"){
easyMode();
} else if(hardMode.classList=="selected") {
hardMode();
}
}
The reset button works but always resets with function easyMode no matter which has had the class "selected" applied with function setupModeButtons
element.classList is a DomTokenList (https://developer.mozilla.org/en-US/docs/Web/API/Element/classList), so it never be "selected". Use the contains method instead:
function reset (){
if(easyMode.classList.contains("selected")){
easyMode();
} else if(hardMode.classList.contains("selected")) {
hardMode();
}
}
I hope this will help!
I am assuming you have a DOM tree that containing something like this
<button class='mode'>Easy</button>
<button class='mode'>Hard</button>
The code might be more readable if you explicitly named the buttons, rather than using an implicit array modeButtons[i] for your difficulty scale.
<button class='mode mode-easy'>Easy</button>
<button class='mode mode-hard'>Hard</button>
Your reset() function is trying to reference the prototype object chain of the functions easyMode.__proto__.classList and hardMode.__proto__.classList rather than the state of the DOM button. Also note that .classList is an array and not a string, so needs .contains()
function reset(){
if( modeButtons[0].classList.contains("selected") ) {
easyMode();
} else if( modeButtons[1].classList.contains("selected") ) {
hardMode();
}
}
I am trying to write a javascript program which stores the value from an input element in an array when a button is clicked. The array is the split and each individual letter added to a span element and then appended to the document. The idea is to create a typing effect using setTimeout.
I am running into an issue creating a closure within the loop, so currently the setTimeout function always returns the final value of the iteration.
The function in question is at the bottom of the code block and called addTextToBoard();
var noteButton = document.querySelector('[data-js="button"]');
noteButton.addEventListener("click",function() {
var messageIn = document.querySelector('[data-js="input"]');
var message = messageIn.value;
postToBoard(message);
});
function postToBoard(val) {
var noteBoard = document.querySelector('[data-js="noteboard"]');
var newElement = document.createElement('div');
newElement.classList.add('noteboard__item');
noteBoard.appendChild(newElement);
setTimeout(function(){
newElement.classList.add('active');
}, 200);
addTextToBoard(newElement, val);
}
function addTextToBoard(el, val) {
var wordArray = val.split('');
for(i = 0; i < wordArray.length; i++) {
var letter = document.createElement('span');
letter.innerHTML = wordArray[i];
setTimeout(function(x){
return function() {}
el.appendChild(letter);
}(i),1000);
}
}
I believe I am close, I'm just not fully understanding the syntax for creating the closure. If someone could give poke in the right direction, without necessarily giving the full solution that would be great.
I essentially tried to paste in the following code snippet from here but I've missed something somehwere along the way!
setTimeout(function(x) { return function() { console.log(x); }; }(i), 1000*i);
Best,
Jack
You are close.
Since the "letter" variable changes, you'll add only the last letter over and over again. You need to "save" the current letter on the setTimeout() callback function, One way to go is like this:
function appendMyLetter(letter) {
return(function() {
el.append.Child(letter);
});
}
function addTextToBoard(el, val) {
var wordArray = val.split('');
for(i = 0; i < wordArray.length; i++) {
var letter = document.createElement('span');
letter.innerHTML = wordArray[i];
setTimeout(appendMyLetter(letter), 1000);
}
}
This way, the appendMyLetter() function gets called with a different parameter (one for each letter) and returns a function with the correct "stored" value to be called by setTimeout().
EDIT
Looking at your setTimeout() code closely
setTimeout(function(x){
return function() {}
el.appendChild(letter);
}(i),1000);
It would work fine, if you used the proper parameters and used the appendChild() inside the returned function, like so:
setTimeout(function(x){
return(function() {
el.appendChild(x);
});
}(letter),1000);
You can create an immediately-invoked function expression IIFE to create a closure
function addTextToBoard(el, val) {
var wordArray = val.split('');
for(i = 0; i < wordArray.length; i++) {
(function(index) {
var letter = document.createElement('span');
letter.innerHTML = wordArray[i];
setTimeout(function(){
el.appendChild(letter);
},1000);
})(i);
}
}
I dont know if this will work but here you go a slight change in operator:
letter.innerHTML += wordArray[i];
if you dont get the effect you imagined you will get you better try to increment the timer by i like this
setTimeout(function(){
...
},1000*i);
I have a function which "types" out a header title as though it is being typed on the screen.
The typer only starts typing once a particular section of my site is "active" or is seen on the screen.
At present, it takes the outputID aka the area where this text will be typed into. There are two instances of this function being run, each with different outputIDs - I only want the function to run once per outputID.
This is how the function is initially called.
<h2 id="typer-get-in-touch" class="typer" data-text="Get in Toche^^^^^ Touch"></h2>
if(anchorLink == 'contact'){
var outputID = $("#typer-get-in-touch");
textTyping(outputID);
}else if(anchorLink == 'expertise'){
var outputID = $("#typer-expertise");
textTyping(outputID);
}
This is the textTyping function
function textTyping(outputID){
$(outputID).show();
var textString = $(outputID).data("text");
var textArray = textString.split("");
var texttypeing = setInterval(
function() {
typeOutText(outputID,textArray);
}, 170);
function typeOutText(outputID,textArray) {
if (textArray[0] == "^"){
outputID.text(function(index, text){
return text.replace(/(\s+)?.$/, '');
});
textArray.shift();
}else {
if (textArray.length > 0) {
outputID.append(textArray.shift());
} else {
clearTimeout(texttypeing);
}
}
}
}
My issue at present is that the function runs multiple types, and continues to type each time the original anchorLink trigger is achieved. The result is that is writes the title many times e.g:
Get In TouchGet In TouchGet In Touch
Each time the section is navigated to, the typing starts again.
How can I run this function only ONCE per outputID? So once the outputID has been used, the function can no longer run for that data?
JSFiddle of non-working example: https://jsfiddle.net/qLez8zeq/
JSFiddle of mplungjan's solution: https://jsfiddle.net/qLez8zeq/1/
Change
function textTyping(outputID){
$(outputID).show();
var textString = $(outputID).data("text");
to
function textTyping(outputID){
var textString = $(outputID).data("text");
if (textString=="") return;
$(outputID).data("text","");
$(outputID).show();
FIDDLE
What you need to do is to bind the event handler for each ID and then unbind it after it's been triggered the first time. Since you're already using jQuery, you can use the "one" method to do exactly this for each outputID:
$( "#typer-get-in-touch" ).one( "click", function() {
textTyping(outputID);
});
I suppose you could store your processed outputIds into an array and then check if the given outputId is present in the array before starting?
Define your array, check for the existence, if not found, do code example:
var processedIds = [];
function textTyping(outputID) {
var foundItem = false;
for (var i = 0; i < processedIds.length; i++)
{
if (processedIds[i] == outputID) {
foundItem = true;
break;
}
}
if (!foundItem) {
//the rest of your code goes here
}
}
You can add some check at the beginning of your function:
var called = {};
function textTyping(outputID) {
if (called[outputID]) {
return;
}
called[outputID] = true;
// your code
}
Basically I have some event listeners and their handling function defined as follows:
<div id="postTextBlock"/>
<div id="postImageBlock"/>
<div id="postQuoteBlock"/>
<div id="postLinkBlock"/>
document.getElementById('postTextBlock').addEventListener('click', function() { showPostType(postTextBlock) }, false);
document.getElementById('postImageBlock').addEventListener('click', function() { showPostType(postImageBlock) }, false);
document.getElementById('postQuoteBlock').addEventListener('click', function() { showPostType(postQuoteBlock) }, false);
document.getElementById('postLinkBlock').addEventListener('click', function() { showPostType(postLInkBlock) }, false);
var showPostType = (function () {
var postTypes = new Array('postTextBlock', 'postImageBlock', 'postQuoteBlock', 'postLinkBlock')
return function(type) {
for (var i = 0; i < postTypes.length; i++) {
(function(index) { alert(document.getElementById(postTypes[index])) })(i)
}
}
})()
When I run this I will get 5 alerts. One for each of the postTypes defined in my array and a final null for what I'm guessing is postTypes[5]. Why is it executing the code with i = 5 when I have set the for loop to terminate when i = 5 (postTypes.length = 4).
Edit:
I added the html that it references as well as the full array values. Hopefully this clears some stuff up about the code not working.
You know your code sample doesn't work? I took a stab at what it's --supposed-- to do.
http://jsfiddle.net/8xxQE/1/
document.getElementById('postTextBlock').addEventListener('click', function() {
showPostType('postTextBlock'); //Argument does nothing
}, false);
document.getElementById('postImageBlock').addEventListener('click', function() {
showPostType('postImageBlock'); //Argument does nothing
}, false);
The arguments passed above were not included, based on the function code they did nothing anyways.
var showPostType = (function() {
var postTypes = new Array('postTextBlock', 'postImageBlock')
return function(/*type argument removed isn't referenced*/) {
var l = postTypes.length;
for (; l--;) {
(function(index) {
console.log(index, postTypes[index]);
alert(document.getElementById(postTypes[index]))
})(l);
}
}
})()
I added some trickery as just an example of a better way to write a for loop. Your closure works fine, I think you are doing something else to cause this code to not work as expected. Why would this error run 4 times, there's only two items in the array. My example ran exactly twice every time I clicked a div, as you can see on JSFiddle.
The div's id is "postLInkBlock", but you're searching for "postLinkBlock". That's the null.
My code looks something like:
$(document).ready(function(){
var cont = 0;
function func1(cont)
{
//Some code here
search.setSearchCompleteCallback(this, searchComplete, null);
//Some other code
}
func1(cont);
function searchComplete()
{
//Some code
cont += 1;
if (cont < length ) {
func1(cont);
} else {
// Other code
}
}
});
So what I want to do is delay the execution of func1(cont); inside of the searchComplete() function. The reason for this is that all the code does is to work with the Google search API and PageRank checks and I need to slow down the script so that I won't get banned. (Especially for the requests it makes regarding the PR check).
If I simply use setTimeout() on func1(cont); it says there is no func1() defined, if I try to get the function outside $(document).ready() it sees the function but the Google code won't for for it needs the page completely loaded.
How can I fix setTimeout or how can I pause the script for a number of seconds ?
Thanks!
Write
func1(cont);
as
window.setTimeout(function() {
func1(cont);
}, 1000);
Instead of declaring the function like this:
function func1(cont) {}
declare it like this:
var func1 = function(cont) {}
You'll need to rearrange your code a little:
$(document).ready(function(){
var cont = 0;
var func1;
var searchComplete = function()
{
//Some code
cont += 1;
if (cont < length ) {
func1(cont);
} else {
// Other code
}
}
func1 = function(cont)
{
//Some code here
search.setSearchCompleteCallback(this, searchComplete, null);
//Some other code
}
func1(cont);
});
I'd try something like this. I prefer to declare the vars and functions inside the jquery namespace, but you could equally move the cont variable and the functions outside of the document ready function and have them available globally.
$(document).ready(function(){
$.cont = 0;
$.func1 = function() {
//Some code here
search.setSearchCompleteCallback(this, $.searchComplete, null);
//Some other code
}
$.searchComplete = function() {
//Some code
$.cont += 1;
if (cont < length ) {
setTimeout($.func1,1000);
} else {
// Other code
}
}
setTimeout($.func1,1000); // delay the initial start by 1 second
});
Hopefully I've got your description correct:
document.ready() event fires
Inside document.ready() you want a function to be called after X milliseconds
This function wires up the Google object search.setSearchCompleteCallback() to another function (which it looks like it needs a parent object from the this)
If this is the case, why do you need any of the functions declared inside the document.ready() scope? Can you't simply make all 3 global? e.g.
var search = null; // initialise the google object
var cont = 0;
function timedSearch()
{
search.setSearchCompleteCallback(this, searchComplete, null);
}
function searchComplete()
{
if (++cont < length) // postfix it below if this is wrong
setTimeout(timedSearch,1000);
}
$(document).ready(function()
{
setTimeout(timedSearch,1000);
}
Hit me with the downvotes if I've misunderstood.