I am trying to create an array that acts like the contents page of a book. Here is what I'm trying:
var section = [someFunction(pInput.value), anotherFunction(pInput.value), function3(pInput.value)];
var sectionNum = 0;
var playerInput = function () {
var pInput = document.getElementById('input');
section[sectionNum];
};
EDIT:
My functions wont execute, so when I run the code nothing happens.
To sum up, I want to know if I'm coding it wrong or if there is another way to do it.
Thanks for the help,
~Gateway
You should restructure your code along these lines:
var section = [someFunction, anotherFunction, function3];
var sectionNum = 0;
var playerInput = function () {
var pInput = document.getElementById('input');
section[sectionNum](pInput.value);
};
But please be sure to put in some nice checks that sectionNum has a legal value at all times.
The section array is storing the values returned by each function rather than the functions themselves.
You probably meant to do something like this:
var section = [someFunction, anotherFunction, function3];
var sectionNum = 0;
var playerInput = function () {
var pInput = document.getElementById('input');
section[sectionNum](pInput.value);
};
Instead of putting your functions inside of an array and trying to call them from there based on the sectionNum. You could use a switch statement; which in my opinion makes it easier to read:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch
In your case it would be something like:
switch (sectionNum) {
case 0:
someFunction(pInput.value)
break;
case 1:
anotherFunction(pInput.value)
break;
case 2:
function3(pInput.value)
break;
default:
alert("I don't know that number")
break;
}
The only downside is that you can't add more functions on the go as you can with the array.
Related
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);
This may seem like a duplicate question, and to some extent, it is, but I have already been through many similar questions, and sadly, none have suited my need. I would really appreciate problem-specific advice.
My main problem in the JavaScript code here is that I cannot access the values in the variables RememberText20 and RememberFullText, in function TextLimiter, from function ReadMoreLessText. The "Message" is an argument for the ReadMoreLessText function, which essentially matches the element clicked to the correct value in the aforementioned variables, which are themselves arrays.
*I know there is nothing wrong with the arrays themselves, as they retain their values as they are supposed to, because a simple alert() proves this. Similarly, there is nothing wrong with the Message argument, as the function ReadMoreLessText works fine with other values.
My simple problem is that I cannot access the values in the aforementioned variables, from the ReadMoreLessText function, although they are global variables, as they should be.
I would really appreciate a problem-specific answer here. Thank you in advance.
// JavaScript Document
//Start Text250
window.onload = function TextLimiter() {
for (y = 0; y < 6; y++) {
FullText = document.getElementsByClassName("Introduction")[y].innerHTML;
TextLength = FullText.length;
RememberFullText = [];
RememberFullText[y] = FullText;
var Text250 = FullText.substr(0, 250) + "...";
RememberText250 = [];
RememberText250[y] = Text250;
if (TextLength > 250) {
document.getElementsByClassName("Read_More")[y].innerHTML = "Read More→";
document.getElementsByClassName("Introduction")[y].innerHTML = Text250;
} else {
document.getElementsByClassName("Read_More")[y].innerHTML = "";
}
}
};
//End Text250
//Start ReadMoreLessText
var ReadMore = function(Message) {
var ScreenText = document.getElementsByClassName("Introduction")[Message].innerHTML;
if (ScreenText === RememberText250[Message]) {
document.getElementsByClassName("Introduction")[Message].innerHTML = RememberText250[Message];
} else {
document.getElementsByClassName("Introduction")[Message].innerHTML = RememberText250[Message];
}
};
//End ReadMoreLessText
Try defining RememberFullText and RememberText250 outside the enclosing for loop.
window.onload = function TextLimiter() {
RememberFullText = [];
RememberText250 = []
for (y = 0; y < 6; y++) {
...
As written they are set to an empty array in each iteration of the loop. Hence only the last entry of each array will be retained after the loop has finished.
I don't see your variables declared as globals. Do you have a var RememberText20, RememberFullText; outside any function?
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
}
I have a function which works just fine the first if I call it just once, but when I call it repeatedly within a for loop, I get the following error:
TypeError: getNamedRange is not a function, it is string.
Doing a search on this error gives me a clue that this is a javascript error, not a Google Apps Script error. I haven't worked much with javascript, but I suspect it may have something to do with how I return the value from the function.
This is the code which calls the function:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var baseSheet = ss.getSheetByName("Base");
var catCol = 9;
var riskAreaColumn = 10;
var numRows = baseSheet.getDataRange().getNumRows();
// I am not using this var, should I be?
var data = baseSheet.getDataRange().getValues();
var cell;
var rangeName;
var range;
var rule;
for(var i=2; i<numRows; i++){
cell = baseSheet.getRange(i, riskAreaColumn);
rangeName = getNamedRange("CategoryRiskRange",baseSheet.getRange(i, catCol).getValue());
range = SpreadsheetApp.getActiveSpreadsheet().getRangeByName(rangeName);
rule = SpreadsheetApp.newDataValidation().requireValueInRange(range).build();
cell.setDataValidation(rule);
}
SpreadsheetApp.flush();
}
This is the function being called:
function getNamedRange(categoryRange, category) {
var categoryList = SpreadsheetApp.getActive().getRangeByName(categoryRange).getValues();
for (var i = 0; i < categoryList.length; i++) {
if (categoryList[i][0] == category) {
getNamedRange = categoryList[i][1];
return getNamedRange;
}
}
}
The first time through the for loop works, the second time gives me the aforementioned error. Thank you for reading this, I hope it's clear.
you are overwriting the function definition here:
getNamedRange = categoryList[i][1];
this will work:
if (categoryList[i][0] == category) {
return categoryList[i][1];
}
Javascript doesn't interpret things until it gets to them, and is very happy to redefine things when you tell it to.
The first time through, it sees
function getNamedRange(categoryRange, category)
and says "oh, a function! Cool!" But in that function, you have the line
getNamedRange = categoryList[i][1];
and it says "Oh, so getNamedRange is something else now. Okay, I'm fine with that."
Rename your variable, and you should be fine.
My last question asked for running once without the use of booleans. I decided I needed booleans but not a lot of variables since that would be messy.
So let's say I have this:
var counter = 0;
$(function() {
$('#typing').keyup(function() {
switch($(this).val().toLowerCase()) {
case 'test':
// DO THIS EVERYTIME TEST CASE IS CALLED
$('#test').fadeOut("fast", function() {
$(this).html("<span class='green'>That's correct!</span>").fadeIn("fast");
});
// DO THIS ONCE AND ONLY ONCE
count++;
}
});
});
Basically, it's part of 'percent complete' application where a user tries to type all cases I make available. So if a user types test twice... on the first run of case test it would add 1 to the counter but on the SECOND run of case test it would NOT add to counter.
I don't want to make it to add a lot of booleans for each case because that would be messy.
Get it? :)
My idea is to make an array and per each case that I add it would add alltogther. So array[0] would be case test. Then I on my first try I would set array[0] to 1.. then create a FOR-LOOP on each case array to add up for total. This is a good idea?
The only problem is I don't know how to make a per case array.
A simple way would be to use an object literal as the counter variable, and once a case is hit assign the value true:
var counter = {};
...
case 'test' : counter[$(this).val()] = true;
Alternatively, check if the 'case' has already been stored in a plain old boring array:
var counter = [];
...
case 'test' :
if(!$.inArray($(this).val(), counter)) {
counter.push($(this).val());
}
Doing exactly what you asked:
var validKeys = ['test', 'testing', '1337'];
var checkedKeys = {};
function getCount() {
var counter = 0;
$.each(validKeys, function(index, value) { //jQuery.each, for neat array iteration
if(checkedKeys[value])
counter++;
});
return counter;
}
$(function() {
$('#typing').keyup(function() {
var val = $(this).val().toLowerCase();
if(val == 'test') { //neater than a switch statement, often
// DO THIS EVERYTIME TEST CASE IS CALLED
$('#test').fadeOut("fast", function() {
$(this).html("<span class='green'>That's correct!</span>").fadeIn("fast");
});
}
checkedKeys[val] = true;
});
});