Sudden error in js to get first element by class - javascript

I used this for ages:
function findFirstChildByClass(element, className) {
var foundElement = null, found;
function recurse(element, className, found) {
for (var i = 0; i < element.childNodes.length && !found; i++) {
var el = element.childNodes[i];
var classes = el.className != undefined? el.className.split(" ") : [];
for (var j = 0, jl = classes.length; j < jl; j++) {
if (classes[j] == className) {
found = true;
foundElement = element.childNodes[i];
break;
}
}
if(found)
break;
recurse(element.childNodes[i], className, found);
}
}
recurse(element, className, false);
return foundElement;
}
But suddenly, this line throws an error:
var classes = el.className != undefined? el.className.split(" ") : [];
Uncaught TypeError: el.className.split is not a function
I can't see right now what's wrong.

It's related of the use of svg complex class name. Before refactoring everything, I quickly fixed the issue replacing the wrong line.
if (el.className != undefined && typeof(el.className) === 'string') {
classes = el.className.split(" ")
} else {
classes = []
}
The className property on svg elements is an Object type SVGAnimatedString and therefore doesn't have the split method.

Related

JavaScript - Uncaught TypeError when trying to get info from DOM elements

Very new to javascript and can't figure out what I'm doing wrong. Trying to assign the className of a <div> element to a var, and I get this error.
scripts.js:30 Uncaught TypeError: Cannot read property '0' of undefined
at checkWinner (scripts.js:30)
at HTMLDivElement.buttonClick (scripts.js:25)
When I try to figure out if the property even exists, the console is leading me to believe it does. This seems like conflicting info to me.
winLines[0][0].className
"xButt"​
Any help is appreciated. I'm sure it's something basic. Here's the code just in case.
var turns = 0;
var gameButtons = Object.values(document.querySelectorAll('.gameButton'));
var winLines= [
[gameButtons[0], gameButtons[1], gameButtons[2]]
/* other arrays go hear */
];
for (let i = 0; i < gameButtons.length; i++) {
gameButtons[i].textContent = null;
gameButtons[i].addEventListener('click', buttonClick);
}
function buttonClick(e) {
console.log(e.target.id + ": You clicked me!");
if (turns % 2 == 0) {
e.target.className = 'xButt';
e.target.textContent = 'X';
e.target.style.backgroundColor = 'green';
} else {
e.target.className = 'oButt';
e.target.textContent = 'O';
e.target.style.backgroundColor = 'blue';
}
turns++;
checkWinner();
}
function checkWinner() {
for (let i = 0; i <= winLines.length; i++) {
let markOne = winLines[i][0].className;
let markTwo = winLines[i][1].className;
let markThree = winLines[i][2].className;
if (markOne === markTwo && markOne === markThree) {
alert("Awww sh********t!");
}
}
}
Your loop has more iterations than your array has elements.
Change the loop like so:
for (let i = 0; i < winLines.length; i++)
The undefined error comes because you're trying winLines[1][0] which doesn't exist because winLines only has one element (at index 0)

error: Cannot read property 'split' of undefined

I am getting a value as a string from cookie which has multiple values stored in it.
I am separating these values with the use of the split() function, but I am getting an error continuously. Here is my code. It would be a great help if anyone can help me out with this.
var sourcez = jQuery.cookie("Source");
var mediumz = jQuery.cookie("Medium");
function utmze(eutmz) {
var utmz_val = jQuery.cookie("__utmzz");
for (var o = utmz_val, r = o.split("|"), a = 0; (a < r.length); a++) {
var t = r[a].split("=");
if (t[0] == eutmz) {
return t[1];
}
}
}
Make sure that string is not empty , null and undefined before you are performing the split action
function isValidString(input){
if(input != null && input != '' && input != undefined){
return true;
}
return false;
}
if(isValidString(input)){
input.split('=');
}
Make the following changes to avoid the error:
var sourcez = jQuery.cookie("Source");
var mediumz = jQuery.cookie("Medium");
function utmze(eutmz) {
var utmz_val = jQuery.cookie("__utmzz");
for (var o = utmz_val, r = o.split("|"), a = 0; (a < r.length); a++) {
if (typeof r[a] != "undefined") { // Checking if the variable is defined.
var t = r[a].split("=");
if (t[0] == eutmz) {
return t[1];
}
}
}
}

How to get elements that have at least one direct child text node

I'm trying to write a function that returns all elements with an immediate child text node -
function getAllElementsWithDirectTextNode() {
var matchingElements = [];
var allElements = document.body.getElementsByTagName('*');
for (var i = 0, n = allElements.length; i < n; i++) {
if (allElements[i].childNodes !== null) {
matchingElements.push(allElements[i]);
}
}
console.log(allElements);
}
However, this is not working. I think it has to do with the 5th line (the if statement). If the element has no direct text node children but does have a direct element child that has its own child text, I do not want to include it.
EDIT: I tried Praveen Kumar's solution with the following DOM, and the console prints all the elements in the body. However, I was expecting to only see div.div1, span.span2, div.div3, and button. Am I doing something wrong?
<div class="div1">
this is direct text
<span class="span1"></span>
</div>
<div class="div2">
<span class="span2">this is span text</span>
</div>
<article></article>
<div class="div3">
<span class="span3"></span>
this is direct text, child #2
</div>
You need to check if they have a childNode with nodeType of Node.TEXT_NODE:
function getAllElementsWithDirectTextNode() {
var matchingElements = [];
var allElements = document.body.getElementsByTagName('*');
for (var i = 0, n = allElements.length; i < n; i++) {
if (allElements[i].childNodes !== null) {
for (var j in allElements[i].childNodes)
if (allElements[i].childNodes[j].nodeType == Node.TEXT_NODE) {
matchingElements.push(allElements[i]);
break;
}
}
}
console.log(allElements);
}
jsfiddle with NodeIterator
MDN Reference
$(function () {
var textNodeIterator = document.createNodeIterator(
document.body,
NodeFilter.SHOW_TEXT,
function (node) {
if (is_ignorable(node)) return NodeFilter.FILTER_REJECT;
return NodeFilter.FILTER_ACCEPT;
});
var matchingElements = [];
var currentNode;
while (currentNode = textNodeIterator.nextNode()) {
//console.log(currentNode);
if (currentNode) {
//console.log(currentNode.nodeType + "-" + currentNode.textContent);
if (!isParentAlreadyMatched(currentNode.parentNode)) matchingElements.push(currentNode.parentNode);
}
}
console.log(matchingElements);
function is_all_ws(nod) {
// Use ECMA-262 Edition 3 String and RegExp features
return !(/[^\t\n\r ]/.test(nod.textContent));
}
function is_ignorable(nod) {
return (nod.nodeType == 8) || // A comment node
((nod.nodeType == 3) && is_all_ws(nod)); // a text node, all ws
}
function isParentAlreadyMatched(parentNode) {
for (var i = 0; i < matchingElements.length; i++) {
if (matchingElements[i] === parentNode) return true;
}
return false;
}
});
You need to test whether any of the children are text nodes. So you need to loop through all the children, testing their type.
function getAllElementsWithDirectTextNode() {
var matchingElements = [];
var allElements = document.body.getElementsByTagName('*');
for (var i = 0, n = allElements.length; i < n; i++) {
if (allElements[i].childNodes !== null) {
var children = allElements[i].childNodes;
for (var j = 0, m = children.length; j < m; j++) {
if (children[j].nodeType == Node.TEXT_NODE) {
matchingElements.push(allElements[i]);
break; // don't need to check the remaining children
}
}
}
}
console.log(allElements);
}
jsfiddle with a for loop solution
function getAllElementsWithDirectTextNode() {
var matchingElements = [];
var allElements = document.body.getElementsByTagName('*');
for (var i = 0; i < allElements.length; i++) {
for (var j=0; j < allElements[i].childNodes.length; j++) {
if (allElements[i].childNodes[j].nodeType === Node.TEXT_NODE) {
if (is_all_ws(allElements[i].childNodes[j])) continue;
matchingElements.push(allElements[i]);
break;
}
}
}
console.log(matchingElements);
}
function is_all_ws(nod) {
// Use ECMA-262 Edition 3 String and RegExp features
return !(/[^\t\n\r ]/.test(nod.textContent));
}
The answers provided so far are fine, but a bit dated.
There are some modern ECMAScript and DOM features you can use nowadays:
querySelectorAll
Array.from
Destructuring assignment
filter
some
const getAllElementsWithDirectTextNode = () => Array.from(document.body.querySelectorAll("*"))
.filter(({ childNodes }) => Array.from(childNodes)
.some(({ nodeType, textContent }) => nodeType === Node.TEXT_NODE && textContent.trim()))
The .trim check is there to test if removing white space from both ends of the text node still produces a non-empty string.
Remove it if you also want to include white space only results:
const getAllElementsWithDirectTextNode = () => Array.from(document.body.querySelectorAll("*"))
.filter(({ childNodes }) => Array.from(childNodes)
.some(({ nodeType }) => nodeType === Node.TEXT_NODE))
The Node.prototype.childNodes getter will never return null, so any comparison with null is superfluous.

Can this function be re-written recursively or more cleanly?

This function match sets an attribute (collapsed) to true or false depending on
the value of a string :
function match(children) {
var data = $scope.treeData
for (var i = 0; i < data.length; i++) {
var s = data[i]
for (var i2 = 0; i2 < s.children.length; i2++) {
var s2 = s.children[i2]
for (var i3 = 0; i3 < s2.children.length; i3++) {
for (var i4 = 0; i4 < s2.children[i3].children.length; i4++) {
var text = "";
if ($scope.searchText == undefined) {
text = ""
} else {
text = $scope.searchText
}
if (s2.children[i3].children[i4].label
.toLowerCase() === text.toLowerCase()) {
s2.children[i3].collapsed = false
}
}
}
}
}
}
Excluding the bad use of variable names i3,i2 etc is there a cleaner method ?
As the inner most loop requires access to the outer loop can recursion still be used ?
Update :
Data structure :
[{"label":"test","collapsed":false,"children":[{"label":"test","collapsed":false,"children":[],"$$hashKey":"002"}],"$$hashKey":"001"}]
Update 2 :
Using a recursive function but the string 'test' is not being matched :
http://jsfiddle.net/U3pVM/19196/
fiddle src :
<div ng-app>
<h2>Todo</h2>
<div ng-controller="TodoCtrl">
</div>
</div>
function TodoCtrl($scope) {
var json = [{"label":"test","collapsed":false,"children":[{"label":"test","collapsed":false,"children":[],"$$hashKey":"002"}],"$$hashKey":"001"}]
var searchText = 'test'
function match(node, searchText){
angular.forEach(node.children, function(idx, child){
node.collapsed = child.label.toLowerCase === searchText.toLowerCase
console.log(node.collapsed)
if(child.children.length > 0){
match(child, searchText);
}
});
}
match(json, searchText);
}
Please try this :
function match2(obj) {
if (obj.children) {
for (var i = 0; i < obj.children.length; i++) {
match2(obj.children[i]);
}
}
else {
var text = $scope.searchText ? $scope.searchText : "";
if (obj.label
.toLowerCase() === text.toLowerCase()) {
obj.collapsed = false
}
}
Your JSFiddle is very nearly there. I made a couple of changes for this working JSFiddle.
First, you were passing an array into match and not an object. I changed your json variable to be json instead by removing the outer [], but you could also have fixed this by passing in json[0].
The other change was that you had the two parameters, child and idx, were the wrong way round.
function match(node, searchText){
angular.forEach(node.children, function(child, idx){
node.collapsed = child.label.toLowerCase === searchText.toLowerCase
console.log(node.collapsed)
if(child.children.length > 0){
match(child, searchText);
}
});
}
Here's another way of doing it, using some:
function match (node, searchText) {
node.collapsed = node.children.some(function(child) {
return child.label.toLowerCase === searchText.toLowerCase;
});
angular.forEach(node.children, function(child, idx){
match(child, searchText);
})
}
I think something like this may work for you. I don't know anything about angular there might be something there that would make it easier.
var searchText = ($scope.searchText == undefined) ? "": $scope.searchText;
match($scope.treeData, searchText);
function match(node, searchText){
$.each(node.children, function(idx, child){
node.collapsed = child.label.toLowerCase === searchText.toLowerCase
if(child.children.length > 0){
match(child, searchText);
}
});
}

Can I use the 'in' keyword to test a property in an (tree) object

Let's say I've got the following object:
Variables.settings.backend.url = 'http://localhost';
What I would do to test is url is available, is do to the following:
if ('settings' in Variables && 'backend' in Variables.settings && 'url' in Variables.settings.backend) {
// true
}
This is quite cumbersome.
If this was PHP, i could just do
if (!empty($variables['settings']['backend']['url']) {
//true
}
Can this be done any simpler in JS?
I wrote a function to test this :
var isModuleDefined = function(moduleName) {
var d = moduleName.split(".");
var md = d[0];
for (var i = 1, len = d.length; i <= len; i++) {
if (eval("typeof " + md) !== "undefined") {
md = md + "." + d[i];
continue;
}
break;
}
return i === len + 1;
};
isModuleDefined("Variables.settings.backend.url");
but i really don't know about the cost-efficiency of that method, using eval.
Edit (Without eval..):
var isModuleDefined = function(moduleName) {
var d = moduleName.split(".");
var base = window;
for (var i = 0, len = d.length; i < len; i++) {
if (typeof base[d[i]] != "undefined") {
base = base[d[i]];
continue;
}
break;
}
return i === len;
};
Yet another variant
function isFieldExist(expression){
var fields = expression.split('.'),
cur = this;
for(var i=0; i<fields.length; i++){
if(typeof cur[fields[i]] === "undefined") return false;
cur = cur[fields[i]];
}
return true;
}
for using
isFieldExist("Variables.settings.backend.url"); // find in global scope
isFieldExist.call(Variables, "settings.backend.url"); // find in Variables

Categories

Resources