Too Much Recursion Error When Trying to Find All HTML Comments - javascript

The script below is meant to find all html comments in the page (there are 4) and return them as one string. I ran the script below and received a "Too Much Recursion" error.
Have I created an infinite loop or did I do something else?
function findComment()
{
var olElement = document.getElementById("everything");//this is the id for my body element
var comments = new Array();
if (olElement.nodeType == 8)
{
comments[comments.length] = olElement;
} else if(olElement.childNodes.length>0)
{
for (var i = 0; i<olElement.childNodes.length; i++)
{
comments = comments.concat(findComment(olElement.childNodes[i]));
}
}
alert(comments);
}
//window.onload = countListItems;
//window.onload = countTagItems;
//window.onload = getElements;
window.onload = findComment;

This is a rough cut version of how you could do it with a recursion. It is not really elegant but will do the work:
function fico(el){
if (el.nodeType==8) return [el.textContent.trim()]
else return [...el.childNodes].map(fico);
}
console.log(fico(document.querySelector("#everything")).toString().replace(/,+/g,", "));
<body id="everything">
<div>something <!-- comment1 -->
<div>with something inside
<!-- comment2 -->
<div>and something further<div>
<span>inside
<!-- comment3 --></span>
it
</div>
more regular text
<!-- comment4 --> and enough.
</div></body>
Depending on the html input the function will return an array of subarrays with further levels of subarrays. To flatten it I used the Array-method toString() and then replace() with a regular expression to throw out the multiple commas in the result. There is still a superfluous one at the beginning ;-)
And here is an alternative version that uses a global comments array like you used in your code:
var comments=[];
function fico(el){
if (el.nodeType==8) comments.push(el.textContent.trim());
else [...el.childNodes].forEach(fico);
}
fico(document.querySelector("#everything")); // collect comments ...
console.log(comments.join(', ')); // ... and display them
<body id="everything">
<div>something <!-- comment1 -->
<div>with something inside
<!-- comment2 -->
<div>and something further<div>
<span>inside
<!-- comment3 --></span>
it
</div>
more regular text
<!-- comment4 --> and enough.
</div></body>

Move the olElements variable outside the function and pass in the element you want to search. The recursion you have is always starting with 'everything';
var comments = new Array();
function findComment(element)
{
if (element.nodeType == 8)
{
comments[comments.length] = element;
} else if(element.childNodes.length>0)
{
for (var i = 0; i<element.childNodes.length; i++)
{
comments = comments.concat(findComment(element.childNodes[i]));
}
}
return comments;
}
var olElement = document.getElementById("everything");//this is the id for my body element
alert(findComment(olElement));

Update: I tried both methods above and received error that either "element" or "el" is null. So...progress. I've pulled together my full code and html and posted below:
<!DOCTYPE html>
<html>
<head>
<title>A Simple Page</title>
<script>
var comments = new Array();
function findComment(element)
{
if (element.nodeType == 8)
{
comments[comments.length] = element;
} else if(element.childNodes.length>0)
{
for (var i = 0; i<element.childNodes.length; i++)
{
comments = comments.concat(findComment(element.childNodes[i]));
}
}
return comments;
}
//window.onload = countListItems;
//window.onload = countTagItems;
//window.onload = getElements;
var olElement = document.getElementById("everything");//this is the id for my body element
window.onload = alert(findComment(olElement));
</script>
</head>
<body>
<div id="everything">
<h1>Things to Do</h1><!--this is a title-->
<ol id="toDoList"><!--this is a list-->
<li>Mow the lawn</li><!--this is a list item-->
<li>Clean the windows</li>
<li>Answer your email</li>
</ol>
<p id="toDoNotes">Make sure all these things are done so you can get some rest.</p>
</div>
</body>
</html>

Related

Multiple Pages in one HTML file/page, with more than 2 pages

I know that the <script> element can have function show(shown, hidden) on it. but with the 2 pages ({document.getElementById(shown).style.display='block'; document.getElementById(hidden).style.display='none'; return false;) in that, I can't figure out how to make that page count more. Any help?
P.S. I am open to almost anything. I can't guarantee your answers will help, but I might be able to figure it out using your suggestions.
I have tried more things on the function show(shown, hidden, hidden, hidden) but that does not help.
I am stuck. I have researched anything I could find. I can't figure it out.
Please help me.
My specific code I want suggestions on is this:
<script>
function show(shown, hidden) {
document.getElementById(shown).style.display='block';
document.getElementById(hidden).style.display='none';
return false;
}
</script>
with some <div>s.
I know this is probably not helping you figure out how to help me, but I need to know. (I hate full-on JavaScript!)
<!doctype html>
<html lang="en">
<head>
<title>Multi but Single Page</title>
<meta charset="utf-8">
<style>
.templates {
display: none;
}
</style>
<script>
// we save all templates in an global Variable
var templateStack = [];
// https://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript
function getParameterByName(name, url) {
url = url || window.location.href;
name = name.replace(/[\[\]]/g, '\\$&');
var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'),
results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, ' '));
}
window.addEventListener('load', function (e) {
// get all hidden template elements
var templates = document.getElementsByClassName('templates');
for (var i = 0, v; v = templates[i]; i++) {
// each Child Node is a new Page
for (var j = 0, x; x = v.childNodes[j]; j++) {
// at least if it's an element
if (x.nodeType === Node.ELEMENT_NODE) {
templateStack.push(x);
}
}
}
// uri support ?page=1 loads Page 2 and ?page=0 loads Page 1, default is 0
var pageIndex = getParameterByName('page') || '0';
// so we can test it with a Browser by just insert 'loadPage(1)'
loadPage(pageIndex);
});
function loadPage(index) {
// only valid indexes
if (index >= templateStack.length || index < 0) {
document.body.innerText = '404 Page not found';
return;
}
// clean everything in our page
document.body.innerHTML = '';
// append our fetched Page out of our Global Variable
document.body.appendChild(templateStack[index]);
}
</script>
</head>
<body>
<div class="templates">
<div>
<h3>Page 1</h3>
<p>
Welcome to Page 1
</p>
Load Page 2
</div>
<div>
<h1>Page 2</h1>
<p>
Our Page 2
</p>
Back to Page 1
</div>
</div>
</body>
</html>
I understand that you can use it with 2 pages but when you want to make more pages like 4-5 pages?
First you need an clear function (it will hide all the pages)
In the clear function get the body in dom and get all the childrens then make a foreach loop hiding all of them
Second you need an show function which will use the page as an parameter like "show('page1');" it will first call the clear function and then show the page1

How can I get user input in java script and display this input on page?

I am new to java and am trying to create a game that simulates hangman. I am trying to get the letters from the user after they input on keyboard. However, when I type something it doesn't make any difference, it doesn't output whether it is correct or incorrect. I think I may not be using the event in my guessLetter() function correctly, any help would be greatly appreciated.
window.addEventListener("DOMContentLoaded", function() {
var word = ['taco'];
let randNum = Math.floor(Math.random() * word.length);
let chosenWord = word[randNum];
let underScore = [];
let docUnderScore = document.getElementsByClassName('underScore');
let docRightGuess = document.getElementsByClassName('rightGuess');
let docWrongGuess = document.getElementsByClassName('wrongGuess');
console.log(chosenWord); //lets grader cheat
let generateUnderscore = () => {
for (let i = 0; i < chosenWord.length; i++) {
underScore.push('_');
}
return underScore;
}
document.onkeyup = function guessLetter(event) {
let letter = String.fromCharCode(event.keyCode || event.code).toLowerCase();
if (chosenWord.indexOf(letter) > -1) {
rightWord.push(letter);
underScore[chosenWord.indexOf(letter)] = letter;
docUnderScore[0].innerHTML = underScore.join(' ');
docRightGuess[0].innerHTML = rightWord;
if (underScore.join('') === chosenWord) {
alert('CONGRATS! YOU WIN!!!');
} else {
wrongWord.push(letter);
docWrongGuess[0].innerHTML = wrongWord;
}
}
underScore[0].innerHTML = generateUnderscore().join(' ');
}
});
<!DOCTYPE html>
<html>
<head>
<h1> Hangman </h1>
<div id="guesses">
<div class="letter" id="letter" </div>
</div>
</head>
<body>
</body>
<div class="container">
<div class="underScore">_ _ _ _</div>
<div class="rightGuess"> right guess </div>
<div class="wrongGuess"> wrong guess </div>
</div>
</html>
In the JS console, ReferenceErrors are being thrown as a result of the fact that the rightWord and wrongWord variables have not been defined.
You are writing the in head tag why ? it doesn't shown in your web page , place the html tags within body.

Split string and in some cases remove

I've got a product title which I'm splitting and inserting a linebreak using javascript like this:
<script>
function myFunction() {
var str = "How are you - doing today?";
var res = str.split("-").join('<br>');
document.getElementById("demo").innerHTML = res;
}
</script>
This works for most case scenarios, however in some cases I will need to remove the second line completely. So everything after the - will need to be removed. Only within that element though, so if I've got this for example
<h3>This is a product - title</h3>
the result should be
<h3>This is a product</h3>
Again this only needs to apply to elements with a certain class. Anybody got any idea ow to do this?
Why not us a simple replace,
string = string.replace(/-/g, '<br>');
or for complete deletion, take
string = string.replace(/-.*$/g, '');
Check className of the element:
function myFunction() {
const str = `How are you - doing today?`
const first = str.split(`-`)[0]
const all = str.split(`-`).join(`<br/>`)
const el = document.getElementById(`demo`)
const el.innerHTML = el.className === `any-name` ? first : all
}
Try this:
(function() {
// For splitted titles
var split = document.querySelectorAll(".dash-split");
var splits = [];
split.forEach(function(spl) {
splits.push(spl.innerHTML.split("-").join("<br>"));
});
console.log(splits); // Outputs ["This is <br> split!"]
// For removed titles
var removedEls = document.querySelectorAll(".dash-split");
var removed = [];
removedEls.forEach(function(rem) {
removed.push(rem.innerText.split("-")[0].trim());
});
console.log(removed); // Outputs ["This is"]
})();
<!DOCTYPE html>
<html>
<head>
<title>Welcome!</title>
</head>
<body>
<div>
<h1 class="dash-split">This is - split!</h1>
<h1 class="dash-remove">This is - removed!</h1>
</div>
</body>
</html>
This should get you what you want, provided the script runs at the end of the document. For wrapping, it keys off of the class title-wrap.
<html>
<head></head>
<body>
<h3>This is a product - title</h3>
<h3 class="title title-wrap">This is a product - with a wrapped title</h3>
<h3>This is a product - with another title</h3>
<script>
(function() {
var titles = document.querySelectorAll('h3');
titles.forEach(function(o, i) {
var title = o.innerHTML;
if (/title-wrap/ig.test(o.className)) {
o.innerHTML = title.replace(/-/g, '<br />');
} else {
o.innerHTML = title.replace(/-.*$/g, '');
}
});
})();
</script>
</body>
</html>

Unable to access object property of state object in state modification method (TypeError)

I'm working on a text analyzer in jQuery that returns word count, unique word count, average word length, and average sentence length.
I had it working (at least halfway, up to the unique word count functionality) before I realized my structure was horrible. So I refactored it...and now I'm having trouble getting it to work at all.
On line 65, I'm getting TypeError: Cannot read property 'length' of null. This is in reference to state.sentences, which when I console.log, I get null. I just noticed that when I type in a full sentence as my input, that doesn't come up (and it logs the sentence correctly), but it's still not rendering the content to the DOM.
What am I doing wrong here? Something about the way I'm trying to access the state object, obviously -- but what, exactly, is beyond me.
Here is the index.js:
'use strict'
// state object
var state = {
text: "",
words: [],
uniqueWords: [],
sentences: [],
wordLengths: [],
sentenceLengths: [],
wordCount: 0,
uniqueWordCount: 0,
averageWordLength: 0,
averageSentenceLength: 0
}
//state modification functions
var getText = function(state) {
state.text = $('#user-text').val()
}
var getWords = function(state) {
state.words = state.text.match(/[^_\W]+/g)
//need to also change all uppercase to lowercase
}
var getSentences = function(state) {
state.sentences = state.text.match( /[^\.!\?]+[\.!\?]+/g )
}
var getUniqueWords = function(state) {
for (var i = 0; i < state.words.length; i++) {
if (state.uniqueWords.indexOf(state.words[i]) < 0) {
state.uniqueWords.push(state.words[i])
}
}
}
var getWordCount = function(state) {
state.wordCount = state.words.length
}
var getUniqueWordCount = function(state) {
state.uniqueWordCount = state.uniqueWords.length
}
var getWordLengths = function(state) {
for (var i = 0; i < state.words.length; i++) {
state.wordLengths.push(state.words[i].length)
console.log(state.wordLengths)
}
}
var getAverageWordLength = function(state) {
var sum = state.wordLengths.reduce(function(a, b) {
return a + b
}, 0)
state.averageWordLength = sum/state.wordLengths.length
}
var getSentenceLengths = function(state) {
for (var i = 0; i < state.sentences.length; i++) {
state.sentenceLengths.push(state.sentences[i].length)
}
}
var getAverageSentenceLength = function(state) {
var sum = state.sentenceLengths.reduce(function(a,b) {
return a + b
}, 0)
state.averageSentenceLength = sum/state.sentenceLengths.length
}
// render functions
var renderWordCount = function(state, element) {
$("dl").toggleClass('hidden')
return element.append(state.wordCount)
}
var renderUniqueWordCount = function(state, element) {
$("dl").toggleClass('hidden')
return element.append(state.uniqueWordCount)
}
var renderAverageWordLength = function(state, element) {
$("dl").toggleClass('hidden')
return element.append(state.averageWordLength)
}
var renderAverageSentenceLength = function(state, element) {
$("dl").toggleClass('hidden')
return element.append(state.averageSentenceLength)
}
// event listener functions
$(function() {
$('button').click(function() {
event.preventDefault()
getText(state)
getWords(state)
getSentences(state)
getUniqueWords(state)
getWordCount(state)
getUniqueWordCount(state)
getAverageWordLength(state)
getSentenceLengths(state)
getAverageSentenceLength(state)
renderWordCount(state, $('.wordCount'))
renderUniqueWordCount(state, $('.uniqueWordCount'))
renderAverageWordLength(state, $('.averageWordLength'))
renderAverageSentenceLength(state, $('.averageSentenceLength'))
})
})
And here is the index.html:
<!DOCTYPE html>
<html>
<head>
<title>Text analyzer</title>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/4.2.0/normalize.min.css">
<link rel="stylesheet" type="text/css" href="main.css">
</head>
<body>
<div class="container">
<main>
<h1>Text analzyer</h1>
<p>Paste in text below, submit, and get some basic stats back.</p>
<form class="js-form">
<div>
<label for="user-text">Text to analyze</label>
<textarea cols="60" rows="20" id="user-text" name="user-text" placeholder="What have you got to say?" required></textarea>
</div>
<div>
<button type="submit">Analyze it!</button>
</div>
</form>
<dl class="hidden text-report">
<dt>Word count</dt>
<dd class="wordCount"></dd>
<dt>Unique word count</dt>
<dd class="uniqueWordCount"></dd>
<dt>Average word length</dt>
<dd class="averageWordLength"></dd>
<dt>Average sentence length</dt>
<dd class="averageSentenceLength"></dd>
</dl>
</main>
</div>
<script src="jquery-3.1.1.js"></script>
<!-- <script src="app.js"></script> -->
<script src="index.js"></script>
</body>
</html>
Thank you!
P.S. If you have any ideas about structuring the app, all thoughts are welcome; I especially am concerned about the way I'm calling all the functions one after another in the ready function at the end. That seems kinda messy for some reason.
Your problem lies in here:
$(function() {
$('button').click(function() {
event.preventDefault()
getText(state) // <---at this point you are passing an object to set the text in.
// state object but getText has some-thing which is not correct
//.....other too
})
})
var getText = function(state) {
state.text = $('user-text').val() // <-----Here `user-text` is not a valid
// html element and jquery doesn't recognize it.
// So, You should change it to a valid css selector.
}
So, eventually you should use a Id selector:
$('#user-text').val()
As your html element has Id attribute:
<textarea cols="60" rows="20"
id="user-text"
name="user-text"
placeholder="What have you got to say?" required></textarea>

JavaScript function doesn't recognize my HTML element

I have a problem with my JavaScript function, in the "for" part it doesn't recognize the HTML elements when I use i to refer to the list position, but when I use [0] or [1], for example, it does recognize it. So there must be a problem with the loop part but I can't figure out what is it, here is the code:
(function () {
"use strict";
window.animacion_click_menu = function (id) {
var i;
var menu = document.getElementById('menu').getElementsByTagName('LI');
var bloqueActual = document.getElementById(id);
for (i = 0; i <= menu.length; i++) { //recorre los LI devolviendolos a su posicion original
menu[i].style.marginLeft = -40;
menu[i].style.opacity = 1;
}
bloqueActual.style.marginLeft = 200;
bloqueActual.style.opacity = 0;
};
})();
and here's my html:
<head>
<meta charset="UTF-8">
<title>Mario Luque Marchena</title>
<meta charset="utf-8">
<link rel="stylesheet" href="css/estilos.css">
</head>
<body>
<center>
<h1 class="titulo">Bienvenid#</h1>
</center>
<div id="main-screen">
<ul id="menu">
<center>
<li id="sobremi" onclick="window.animacion_click_menu('sobremi');">Sobre mi</li>
<li id="portafolios" onclick="animacion_click_menu('portafolios');">Portafolios</li>
<li id="animacion" onclick="animacion_click_menu('animacion');">Animacion</li>
<li id="back-end" style="border-bottom-style: dashed;" onclick="animacion_click_menu('back-end');">Back-End</li>
</center>
</ul>
</div>
<script type="text/javascript" src="js/animaciones.js"></script>
</body>
if you have any suggestions to make the code better, are welcome too, i'm learning to code. thank you!, and sorry for the bad english in case it was
Your error is really on the for loop.
Take a look on:
for (i = 0; i <= menu.length; i++) {
it should be:
for (i = 0; i <= menu.length-1; i++) {
Otherwise, it will try to iterate from 0 to 5 while your menu array has only 4 items.
The result is that in the last iteration, when you try to access the element menu with the inexistent index (menu[5]) you get the error:
Uncaught TypeError: Cannot read property 'style' of undefined
Other possibility to overcome this is to change <= to < and work with the loop as:
for (i = 0; i < menu.length; i++) {
use window.onload() or
$('document').ready(function(){
//Put your code here
})
I think your code is getting executed before DOM creation.

Categories

Resources