Cannot read property 'textContent' of undefined in JavaScript - javascript

What is the problem? I declared variable n
var n = 0;
while (true) {
var comment = document.getElementsByClassName('wall_reply_text')[n].textContent;
if (comment.indexOf("публикации") == 0) {
alert(comment);
}
n++;
}

the most likely explanation is that there are no elements with class name wall_reply_text when this code runs, so document.getElementsByClassName('wall_reply_text') returns an empty array. when you try to access index 0 in that empty array, you get undefined, and thus the error when trying to access undefined.textContent.

You need to break if there is no element
var n = 0, comment=""
while (true) {
const div = document.getElementsByClassName('wall_reply_text')[n]
if (div) comment = div.textContent;
else break
if (comment.indexOf("публикации") == 0) {
console.log(n,comment);
}
n++;
}
<div class="wall_reply_text">1</div>
<div class="wall_reply_text">публикации</div>
<div class="wall_reply_text">3</div>
<div class="wall_reply_text">4</div>
Here is a more elegant way
[...document.querySelectorAll('.wall_reply_text')]
.map(div => div.textContent)
.forEach((comment,i) => {
if (comment.indexOf("публикации") == 0) {
console.log(i,comment);
}
})
<div class="wall_reply_text">1</div>
<div class="wall_reply_text">публикации</div>
<div class="wall_reply_text">3</div>
<div class="wall_reply_text">публикации</div>
Alternative if you just need the one index
const comments = [...document.querySelectorAll('.wall_reply_text')]
.map(div => div.textContent)
console.log(comments.indexOf("публикации"))
<div class="wall_reply_text">1</div>
<div class="wall_reply_text">публикации</div>
<div class="wall_reply_text">3</div>
<div class="wall_reply_text">4</div>

Related

How to split html code from a page into nodes

I want to read the html from a site and then split it into nodes. I tried this code:
function load() {
$(document).ready(function () {
$.get("https://example.com/index.html", function (data) {
const loadpage = async function() {
var nodes = [...data.childNodes].slice(-3);
var cont = document.getElementById("container");
var msg = nodes;
});
if(cont.innerHTML='') {
cont.insertAdjacentHTML('afterbegin', msg);
} else {
cont.innerHTML=msg;
}
};
loadpage();
});
});
}
load();
html looks like this:
<main>
<div class="msg">something</div>
<div class="msg">something</div>
<div class="msg">something</div>
<div class="msg">something</div>
<div class="msg">something</div>
<div class="msg">something</div>
</main>
the expected output should be:
<div class="msg">something</div>
<div class="msg">something</div>
<div class="msg">something</div>
since I want only the last 3 nodes.
Thank you.
It is not necessary to use async await here and you are doing it wrong
Please read How to return values from async functions using async-await from function?
Your load is also wrong and too complex. You should not add a window event handler in a function and the test to insert after if cont is empty is not useful. Your test is also not a comparison (== '' or === '') but an assignment (= '').
Add the data to a partial element and slice the result
$(document).ready(function() {
const cont = document.getElementById("container");
$.get("https://example.com/index.html", function(data) {
const div = document.createElement('div')
div.innerHTML = data; // assuming HTML string?
[...div.querySelectorAll('.msg')]
.slice(-3)
.forEach(div => cont.innerHTML += div.outerHTML);
});
});

Search entire DOM for number

I need to search my entire document for a phone number, and compile a list of elements which have this phone number in them.
However I have encountered afew snags.
I can't simply do document.body.innerHTML and replace the numbers, as this messes up third party scripts.
The following will match the elements, but ONLY if they have the number within them, and nothing else:
let elements = document.querySelectorAll("a, div, p, li");
let found = [];
for (let elm in elements) {
if (elements.hasOwnProperty(elm)) {
if (elements[elm].textContent !== undefined && elements[elm].textContent.search("00000 000000") != -1) {
found.push(elements[elm]);
}
}
}
So the following element will not match:
<li class="footer__telephone">
<i class="fa fa-phone" aria-hidden="true"></i>00000 000000
</li>
Due to having the i tag in there.
Using textContent instead of text also does not work as the parent of an element will then match, but I don't want the parent.
Edit:
<div class="row-block hmpg-text">
<div class="wrapper">
<div class="container">
<div class="row">
<div class="twelvecol">
00000 000000
</div>
</div>
</div>
</div>
</div>
Lets say the above is my HTML, if I loop through all the elements and test them with testContent then the first is going to be returned as true, to containing my number, but I need the element with the class of twelvecol on it, not the parent which is 4 levels up.
Managed to find an answer, similar to what Phylogenesis said however couldn't get any of them examples working.
function replaceText(el, regex_display, regex_link) {
// Replace any links
if (el.tagName === "A") {
if (regex_link.test(el.getAttribute("href"))) {
el.setAttribute("href", el.getAttribute("href").replace(regex_link, replacement.replace(/\s/g, '')));
}
}
if (el.nodeType === 3) {
if (regex_display.test(el.data)) el.data = el.data.replace(regex_display, replacement);
if (regex_link.test(el.data)) el.data = el.data.replace(regex_link, replacement);
} else {
let children = el.childNodes;
for (let i = 0; i < children.length; i++) {
replaceText(children[i], regex_display, regex_link);
}
}
}
let bodyChildren = document.body.childNodes;
let search_display = new RegExp(search, "g");
let search_link = new RegExp(search.replace(/\s/g, ''), "g");
for (let i = 0; i < bodyChildren.length; i++) {
replaceText(bodyChildren[i], search_display, search_link);
}

Simplify function for removing duplicate array

I want to find div element that contain custom attribute mod than append that div to list item. But first I have to remove divs that contain duplicate mod value. Here's what I have done
<div class="list"></div>
<div class="container">
<div mod="dog"></div>
<div mod="man"></div>
<div mod="woman"></div>
<div mod="dog"></div>
<div mod="bird"></div>
<div mod="insects"></div>
<div mod="dog"></div>
</div>
this is my script
modArr($('.container').find('[mod]'))
function modArr(el){
var filterArray = [] // store mod
, modNames = [] // store mod value
, arrIndex = [] // store non duplicate index
, li = [] // store
modArray = el
// store mod value
for(var i=0; i < modArray.length; i++){
modNames.push($(modArray[i]).attr('mod')) // get mod value from div
}
// search for non duplicate mod value and get the index of none duplicate mod
for(var i=0; i < modArray.length; i++){
if(filterArray.indexOf(modNames[i]) === -1){
filterArray.push(modNames[i])
arrIndex.push(i) // push non duplicate index value
}
}
filterArray = [] // reset filterArray
// push module from modArray to filterArray using index in arrIndex
for(var i=0; i < arrIndex.length; i++){
filterArray.push(modArray[arrIndex[i]])
}
// push to li array
$.each(filterArray,function(i,el){
li[i] = '<li>'+ el.outerHTML +'</li>'
})
$('<ul></ul>')
.append(li.join(''))
.appendTo('.list')
}
What you can see is that I've used to many loops, is there any simple way to do this. Thanks!
We can use an object as a map for checking duplicates, see comments (I've added text to the mod divs so we can see them):
modArr($('.container').find('[mod]'));
function modArr(elements) {
// A place to remember the mods we've seen
var knownMods = Object.create(null);
// Create the list
var ul = $("<ul></ul>");
// Loop the divs
elements.each(function() {
// Get this mod value
var mod = this.getAttribute("mod");
// Already have one?
if (!knownMods[mod]) {
// No, add it
knownMods[mod] = true;
ul.append($("<li></li>").append(this.cloneNode(true)));
}
});
// Put the list in the .list element
ul.appendTo(".list");
}
<div class="list"></div>
<div class="container">
<div mod="dog">dog</div>
<div mod="man">man</div>
<div mod="woman">woman</div>
<div mod="dog">dog</div>
<div mod="bird">bird</div>
<div mod="insects">insects</div>
<div mod="dog">dog</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
We can also just use the DOM to check for duplicates, but it's a bit slower (not that it matters for the number of elements here):
modArr($('.container').find('[mod]'));
function modArr(elements) {
// Create the list
var ul = $("<ul></ul>");
// Loop the divs
elements.each(function() {
// Get this mod value
var mod = this.getAttribute("mod");
// Already have one?
if (ul.find('div[mod="' + mod + '"]').length == 0) {
// No, add it
ul.append($("<li></li>").append(this.cloneNode(true)));
}
});
// Put the list in the .list element
ul.appendTo(".list");
}
<div class="list"></div>
<div class="container">
<div mod="dog">dog</div>
<div mod="man">man</div>
<div mod="woman">woman</div>
<div mod="dog">dog</div>
<div mod="bird">bird</div>
<div mod="insects">insects</div>
<div mod="dog">dog</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Note: I used this.cloneNode(true) rather than outerHTML because there's no need to take a roundtrip through markup. If you want more jQuery there, it's $(this).clone(); ;-) Similarly, if you don't like this.getAttribute("mod"), there's $(this).attr("mod").
I'd be remiss if I didn't point out that mod is an invalid attribute name for div elements. You can use any name you want starting with data-, though, so perhaps use <div data-mod="dog"> instead.
Try this, only adds if an element with mod is not already in list:
$('.list').append('<ul>');
$('.container [mod]').each(function(index, el) {
if($('.list [mod=' + $(el).attr('mod') + ']').length === 0) {
$('.list ul').append($('<li>' + el.outerHTML + '</li>'));
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<div class="list"></div>
<div class="container">
<div mod="dog">Dog1</div>
<div mod="man">Man1</div>
<div mod="woman">Woman1</div>
<div mod="dog">Dog2</div>
<div mod="bird">Bird1</div>
<div mod="insects">Insect1</div>
<div mod="dog">Dog3</div>
</div>

Angular JS Ng-Repeat Issue

I have a scenario like I want to display the contents which are available in only one array, if its present in another array there is no need to display it.
My html is like
<div ng-repeat="array1Value in array1">
<div ng-repeat="array2Value in array2">
<div ng-if="isNotFound(array1,array2Value.id)">
<span>{{array2Value.name}}</span>
</div>
</div>
</div>
My js class is like
var app = angular.module("MyApp",{});
app.controller("MyController",function($scope) {
$scope.array1 = [
{
id:"1",name:"one"
},
{
id:"2",name:"two"
},
{
id:"3",name:"three"
}
];
$scope.array2 = [
{
id:"1",name:"one"
},
{
id:"2",name:"two"
},
{
id:"4",name:"four"
}
];
$scope.alreadyPrinted = [{}];
$scope.isNotFound = function(array,value){
for(i = 0; i < array.length; i++){
if (value === array[i].id) {
return false;
}
}
if($scope.alreadyPrinted.indexOf(value) > -1){
return false;
} else {
$scope.alreadyPrinted.push(value);
return true;
}
}
});
I need only four as my output. But as of now nothing is coming. Please help me to correct the issue.
You need to remove external loop... try this:
<div ng-repeat="array2Value in array2">
<div ng-if="isNotFound(array1,array2Value.id)">
<span>{{array2Value.name}}</span>
</div>
</div>
If I understand you right, you want True to be returned if the value is not in the first array.
Try this for your isnotFound function:
$scope.isNotFound = function(array,value){
for(i = 0; i < array.length; i++){
if (value !== array[i].id) {
return true;
}
}
return fase;
}
Or maybe even better:
$scope.isNotFound = function(array,value){
angular.forEach(array, function(item){
if (value !== item.id) {
return true;
}
});
return fase;
}
I'm not sure why you are repeating over the first array and the allreadyPrinted part. Your html can just be:
<div ng-repeat="array2Value in array2">
<div ng-if="isNotFound(array1,array2Value.id)">
<span>{{array2Value.name}}</span>
</div>
</div>
I think there is problem in your alreadyPrinted code. Your code is displaying 'four' as output. Try to insert one alert box in your code like
for (i = 0; i < array.length; i++) {
if (value === array[i].id) {
alert('is not found function called');
return false;
}
}
You will see 'four' as output on your browser but for one iteration only.
Correct your alreadyPrinted code.alert box will help you to isolate the problem.

How do you test for equivalence between two HTML elements or jQuery selectors?

Can you write a function that sees if two pieces of DOM are equivalent? It should ignore irrelevant whitespace. Ideally, it should also ignore empty attributes.
So the following should be equal:
<div style="">
<div>a</div>
</div>
and
<div><div>a</div></div>
I've created a function that will compare the lengths of the jQuery objects and compare the tag names of each child element. So far it seems to work fairly well in the basic fiddle I've created for it. Here is the code, let me know how it works for you and if there are any bugs/improvements needed to be made. This does not compare the inner text though, however if you need that I think it should be easy enough to tweak.
UPDATE 1:
Changed to use recursion instead of a for loop, and it now compares the inner text of each element as well. If you don't need the text checked just delete that comparison.
UPDATE 2:
Per the OP comment, I've found and edited a few extra functions that can be utilized to compare CSS of the elements and check that they are the same. If not then the element are not the same and will return false.
jQuery:
This first function is a jQuery function that will return an object of the CSS properties.
(function ($) {
$.fn.getStyleObject = function () {
var dom = this.get(0);
var style;
var returns = {};
if (window.getComputedStyle) {
var camelize = function (a, b) {
return b.toUpperCase();
}
style = window.getComputedStyle(dom, null);
for (var i = 0; i < style.length; i++) {
var prop = style[i];
var camel = prop.replace(/\-([a-z])/g, camelize);
var val = style.getPropertyValue(prop);
returns[camel] = val;
}
return returns;
}
if (dom.currentStyle) {
style = dom.currentStyle;
for (var prop in style) {
returns[prop] = style[prop];
}
return returns;
}
return this.css();
}
})(jQuery);
Next function defined is used to compare the objects returned by the previous function.
function arrays_equal(a, b) {
var result = true;
$.each(a, function (key, value) {
if (!b.hasOwnProperty(key) || b[key] !== a[key]) {
result = false;
}
});
return result;
}
This is the primary function that checks for equality between the two elements. It will make a call to the added jQuery function to retrieve an object made of the CSS properties and then compares the two objects the second function that was added.
function equalElements(a, b) {
if (a.length != b.length) {
return false;
}
if (a.children().length != b.children().length) {
return false;
} else {
if (a.children().length > 0) {
var x = a.children().first();
var y = b.children().first();
equalElements(x, y);
}
if (a.get(0).tagName != b.get(0).tagName) {
return false;
}
if (a.text() != b.text()) {
return false;
}
var c = a.getStyleObject();
var d = b.getStyleObject();
if (!arrays_equal(c, d)) {
return false;
}
}
return true
}
HTML:
(Used to compare and test function)
<div id="div1">
<div>a</div>
</div>
<div id="div2" style="">
<div>a</div>
</div>
<div id="div3" style=""></div>
<div id="div4" style="">
<div>a</div>
<div>a</div>
</div>
<div id="div5" style="">
<div>b</div>
</div>
<div id="div6" style="width: 50px;">
<div>a</div>
</div>
<div id="div7" style="width: 50px;">
<div>a</div>
</div>
<div id="div8" style="width: 25px;">
<div>a</div>
</div>
<div id="div9" style="width: 50px; height: 10px;">
<div>a</div>
</div>
<ul id="list1">
<li>a</li>
</ul>
Test Code:
(Sample see fiddle for full test)
var a = $("#div1");
var b = $("#div2");
var same = equalElements(a, b);
if (same) {
alert("div1 is equal to div2");
}

Categories

Resources