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
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)
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];
}
}
}
}
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.
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);
}
});
}
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