I'm using a script that counts how many times the user clicks each link and sort their order based on that number...
It works fine as is, however it doesn't work if the <a> tags is nested inside a div. It's ignoring the divs and the content/image/icon inside, while showing only the links..
How to change it so that it will show and reorder the entire div instead of just the link?
Please see what I have so far:
function updateClicks(ele) {
const storage = window.localStorage.getItem(ele.innerHTML + " clicks");
if (storage === null) {
window.localStorage.setItem(ele.innerHTML + " clicks", "1");
} else {
var clicks = parseInt(window.localStorage.getItem(ele.innerHTML + " clicks")) + 1;
localStorage.removeItem(ele.innerHTML + " clicks");
window.localStorage.setItem(ele.innerHTML + " clicks", clicks);
}
}
function orderItems() {
var order = [];
var href = [];
var links = document.getElementById("links-list");
var link = links.getElementsByTagName("a");
for (i = 0; i < link.length; i++) {
href.push(link[i].href);
}
links = links.innerHTML.split("</a>");
document.getElementById("links-list").innerHTML = "";
for (i = 0; i < links.length - 1; i++) {
var lastChar = links[i].charAt(links[i].length - 1);
var clicks = parseInt(window.localStorage.getItem(lastChar + " clicks"));
if (isNaN(clicks)) {
clicks = 0;
}
order.push([lastChar, clicks, href[i]]);
}
order.sort(function(a, b) {
return a[1] - b[1]
});
order.reverse();
console.log(order)
for (i = 0; i < order.length; i++) {
document.getElementById("links-list").innerHTML += "<a href='" + order[i][2] + "' onclick='updateClicks(this)'>" + order[i][0] + "</a>";
}
}
.link-container {
display: inline-block;
}
.link-container a {
padding: 10px;
background-color: #c0c0c0;
}
.link-img {
width: 16px;
height: 16px;
}
<body onload="orderItems();">
<div id="links-list">
<div class="card link-container">
<img class="link-img" src="https://i.imgur.com/6ZpMxiG.png" />
A
</div>
<div class="card link-container">
<img class="link-img" src="https://i.imgur.com/sFUFOyO.png" />
B
</div>
<div class="card link-container">
<img class="link-img" src="https://i.imgur.com/M5a2gh8.png" />
C
</div>
<div class="card link-container">
<img class="link-img" src="https://i.imgur.com/mbrEuvR.png" />
D
</div>
</div>
</body>
I'm still trying to learn JS and I need this feature for a project I'm building.. Thank you in advance for any help.
Here is one way of keeping track of the click counts and sorting the divs accordingly:
// The cnt values in the divs array could be initialised from local storage:
const
list=document.getElementById("links-list"),
divs=[...list.querySelectorAll(".card")];
divs.forEach(e=>e.cnt=0)
list.onclick=ev=>{
if (ev.target.tagName!=="A") return;
ev.target.closest(".card").cnt++;
divs.sort((a,b)=>a.cnt-b.cnt)
.forEach(el=>list.append(el))
console.log(divs.map(e=>e.textContent.trim()+e.cnt).join(","))
}
.link-container {
display: inline-block;
border:1px solid grey;
}
.link-img {
width: 16px;
height: 16px;
}
<body">
<div id="links-list">
<div class="card link-container">
<img class="link-img" src="https://i.imgur.com/6ZpMxiG.png" />
A
</div>
<div class="card link-container">
<img class="link-img" src="https://i.imgur.com/sFUFOyO.png" />
B
</div>
<div class="card link-container">
<img class="link-img" src="https://i.imgur.com/M5a2gh8.png" />
C
</div>
<div class="card link-container">
<img class="link-img" src="https://i.imgur.com/mbrEuvR.png" />
D
</div>
</div>
</body>
I'm a beginner in javascript with HTML and CSS. I want to try is there a way to access child container class via parent container class. or can I add a new class("second_new") to "second" class via "first" class.
/* CSS */
.first {
background-color: red;
}
.first_new {
background-color: pink;
}
.second {
background-color: blue;
}
.second_new {
background-color: purple;
}
<!-- HTML -->
<div class="row">
<div class="first">
<h1>This is first class</h1>
<div class="second"> <!-- I want to change this -->
<h2>This is Second class</h2>
</div>
</div>
<div class="first">
<h1>This is first class</h1>
<div class="second"> <!-- I want to change this -->
<h2>This is Second class</h2>
</div>
</div>
</div>
<!-- JAVASCRIPT -->
<script>
var firstClass = document.getElementsByClassName("first");
function Mousein() {
this.classList.add("first_new");
};
function Mouseout() {
this.classList.remove("first_new");
};
for (var i = 0; i < firstClass.length; i++) {
firstClass[i].addEventListener('mouseover', Mousein);
firstClass[i].addEventListener('mouseout', Mouseout);
}
</script>
yes you can
Method 1
document.querySelector('.first .second');
Medthod 2
let parent = document.querySelector('.first');
parent.querySelector('.second');
Thanks Guys I found the answer this
/* CSS */
.first {
background-color: red;
}
.first_new {
background-color: pink;
}
.second {
background-color: blue;
}
.second_new {
background-color: purple;
}
<!-- HTML -->
<div class="row">
<div class="first">
<h1>This is first class</h1>
<div class="second"> <!-- I want to change this -->
<h2>This is Second class</h2>
</div>
</div>
<div class="first">
<h1>This is first class</h1>
<div class="second"> <!-- I want to change this -->
<h2>This is Second class</h2>
</div>
</div>
</div>
<!-- JAVASCRIPT -->
<script>
var firstClass = document.getElementsByClassName("first");
var child;
function Mousein() {
this.classList.add("first_new");
child = this.querySelector(".second");
child.classList.add("second_new")
};
function Mouseout() {
this.classList.remove("first_new");
child.classList.remove("second_new")
};
for (var i = 0; i < firstClass.length; i++) {
firstClass[i].addEventListener('mouseover', Mousein);
firstClass[i].addEventListener('mouseout', Mouseout);
}
</script>
Yes you can access bey selector. document.querySelector('parent child') . In your case would be: const childEl = document.querySelector('.first .second');
You can use getElementsByTagName() on any type of element.
This would be
var parents = document.getElementsByClassName('parent');
var child = [];
for (let i = 0; i < parents.length; i++) {
var child = parents.getElementsByTagName('div')[0];
children.push(child);
}
Or, Even Simpler:
var parents = document.querySelectorAll('.parent');
var children = document.querySelectorAll('.parent > div');
Note: Elements selected by querySelectorAll() are like arrays and array methods can be applied.
Note: To select one element use querySelector() method.
I have a div setup like this:
<input id="search">
<div class="entry">
<div class="title">hello world test 123</div>
<div class="description">lorem ipsum test test1 testing</div>
</div>
<div class="entry">
<div class="title">attack on titan</div>
<div class="description">fullmetal alchemist</div>
</div>
And I allow the user to search the divs with:
jQuery(document).ready(function() {
jQuery("#search").on("keyup click input", function () {
var val = jQuery(this).val();
if (val.length) {
jQuery(".entry").hide().filter(function () {
return jQuery('.title, .description',this).text().toLowerCase().indexOf(val.toLowerCase()) != -1;
}).show();
}
else {
jQuery(".entry").show();
}
});
});
Works great, try jsFiddle.
My question is, how can I highlight the search terms? For example, if the user searches for test, I want to wrap the text test into <span> tags.
EDIT: Note that I know how to search/replace text, but I can't seem to make it work properly with my search function.
Optimised solution
After all the issues discussed in comments and trying to optimise the solution so it won't have any lack for eventual bugs, I refactored the code and optimised it:
$(document).ready(function() {
$("#search").on("keyup click input", function() {
var val = jQuery(this).val();
var regExp = new RegExp(val, 'ig');
var reg = new RegExp('<span class="highlight">(.+)<\/span>', 'ig');
if (val.length) {
$(".entry").hide().filter(function() {
var found = $('.title, .description', this).text().toLowerCase().indexOf(val.toLowerCase()) != -1;
if (val.length > 3) {
$('.title, .description', this).each(function(k, v) {
if ($(v).text().match(regExp)) {
$(v).html($(v).text().replace(regExp, '<span class="highlight">$&</span>'));
} else {
$(v).html($(v).text().replace(reg, '$&'));
}
});
} else {
$('.title, .description', this).each(function(k, v) {
$(v).html($(v).text().replace(reg, '$&'));
});
}
return found;
}).show();
} else {
$('.title, .description').each(function(k, v) {
$(v).html($(v).text().replace(reg, '$&'));
});
$(".entry").show();
}
});
});
.highlight {
background-color: blue
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="search">
<div class="entry">
<div class="title">hello world test 123</div>
<div class="description">lorem ipsum test test1 testing</div>
</div>
<div class="entry">
<div class="title">attack on titan</div>
<div class="description">fullmetal alchemist</div>
</div>
It loops over the elements and use a RegExp with a matching group and if the iterated element content matches the Regex replace the matched text with the same content wrapped in a span, otherwise just set the content to its original form.
Original Answer
This is how you should do it:
var val = jQuery(this).val();
if (val.length) {
$(".entry").hide().filter(function() {
var found = $('.title, .description', this).text().toLowerCase().indexOf(val.toLowerCase()) != -1;
var regExp = new RegExp(val, 'ig');
$('.title, .description', this).each(function(k, v) {
if ($(v).text().toLowerCase().indexOf(val.toLowerCase()) != -1) {
var newHTML = $(v).text().replace(regExp, '<span class="highlight">$&</span>');
$(v).html(newHTML);
}
});
return found;
}).show();
} else {
$(".entry").show();
}
You need to loop over the elements and use a RegExp with a matching group and if this element content matches your Regex replace the matched text with the same content wrapped in a span.
Demo:
This is a working Demo:
$(document).ready(function() {
$("#search").on("keyup click input", function() {
var val = jQuery(this).val();
if (val.length) {
$(".entry").hide().filter(function() {
var found = $('.title, .description', this).text().toLowerCase().indexOf(val.toLowerCase()) != -1;
var regExp = new RegExp(val, 'ig');
$('.title, .description', this).each(function(k, v) {
if ($(v).text().toLowerCase().indexOf(val.toLowerCase()) != -1) {
var newHTML = $(v).text().replace(regExp, '<span class="highlight">$&</span>');
$(v).html(newHTML);
}
});
return found;
}).show();
} else {
$('.title, .description').each(function(k, v) {
var reg = new RegExp('<span class="highlight">(.+)<\/span>', 'ig');
var newHTML = $(v).text().replace(reg, '$&');
$(v).html(newHTML);
});
$(".entry").show();
}
});
});
.highlight {
background-color: blue
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="search">
<div class="entry">
<div class="title">hello world test 123</div>
<div class="description">lorem ipsum test test1 testing</div>
</div>
<div class="entry">
<div class="title">attack on titan</div>
<div class="description">fullmetal alchemist</div>
</div>
Edit:
This is a Demo that highlights sentences only if more than 2 letters are typed:
$(document).ready(function() {
$("#search").on("keyup click input", function() {
var val = jQuery(this).val();
if (val.length) {
$(".entry").hide().filter(function() {
var found = $('.title, .description', this).text().toLowerCase().indexOf(val.toLowerCase()) != -1;
var regExp = new RegExp(val, 'ig');
if (val.length > 2) {
$('.title, .description', this).each(function(k, v) {
if ($(v).text().toLowerCase().indexOf(val.toLowerCase()) != -1) {
var newHTML = $(v).text().replace(regExp, '<span class="highlight">$&</span>');
$(v).html(newHTML);
}
});
} else {
$('.title, .description').each(function(k, v) {
var reg = new RegExp('<span class="highlight">(.+)<\/span>', 'ig');
var newHTML = $(v).text().replace(reg, '$&');
$(v).html(newHTML);
});
}
return found;
}).show();
} else {
$('.title, .description').each(function(k, v) {
var reg = new RegExp('<span class="highlight">(.+)<\/span>', 'ig');
var newHTML = $(v).text().replace(reg, '$&');
$(v).html(newHTML);
});
$(".entry").show();
}
});
});
.highlight {
background-color: blue
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="search">
<div class="entry">
<div class="title">hello world test 123</div>
<div class="description">lorem ipsum test test1 testing</div>
</div>
<div class="entry">
<div class="title">attack on titan</div>
<div class="description">fullmetal alchemist</div>
</div>
Try with contains(text) instead of filter() .initially hide the All div .Then Show only the text contains div .And apply the span element to matching letter in the children using new RegExp()
For ignore case sensitive match ig in regex and also added code for case insensitive for contains
Updated Fix with .title, .description on children
jQuery(document).ready(function() {
jQuery("#search").on("input", function() {
var val = jQuery(this).val()
jQuery(".entry").hide()
jQuery(".entry:contains(" + val + ")").show()
jQuery(".entry").each(function() {
if ($(this).find(".title, .description:contains(" + val + ")")) {
$(this).find(".title, .description:contains(" + val + ")").html(function() {
return $(this).text().replace(new RegExp('('+val+')', 'ig'), '<span>$1</span>')
})
}
})
});
})
jQuery.expr[':'].contains = function(a, i, m) {
return jQuery(a).text().toUpperCase()
.indexOf(m[3].toUpperCase()) >= 0;
};
.entry {
background: #fff;
margin-bottom: 10px;
width: 300px;
}
span {
color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="search">
<div class="entry">
<div class="title">hello world test 123</div>
<div class="description">lorem ipsum test test1 testing</div>
</div>
<div class="entry">
<div class="title">Attack on titan</div>
<div class="description">fullmetal alchemist</div>
</div>
<div class="entry">
<div>
<div class="title">For nested element on titan</div>
<div>
<div class="description">fullmetal alchemist nested</div>
</div>
</div>
</div>
Try this:
document.getElementById('search').onkeyup = userInput;
document.getElementById('search').onclick = userInput;
document.getElementById('search').oninput = userInput;
var allEntries = document.querySelectorAll('.entry');
function userInput () {
var val = this.value;
for (var i = 0; i < allEntries.length; i++) {
var entryElement = allEntries[i];
var title = entryElement.querySelector('.title');
var description = entryElement.querySelector('.description');
var noHtmlSearchStr = '';
if (title) noHtmlSearchStr += title.innerText;
if (description) noHtmlSearchStr += description.innerText;
if (noHtmlSearchStr.length > 0) {
if (noHtmlSearchStr.toLowerCase().indexOf(val.toLowerCase()) != -1) {
// Remove existing <b> tags.
var regexp1 = new RegExp('(<b>|<\/b>)', 'gi');
if (title) title.innerHTML = title.innerHTML.replace(regexp1, '');
if (description) description.innerHTML = description.innerHTML.replace(regexp1, '');
if (val.length > 3) {
var regexp2 = new RegExp('(' + val + ')(?!>)', 'gi');
if (title) title.innerHTML = title.innerHTML.replace(regexp2, '<b>$1</b>');
if (description) description.innerHTML = description.innerHTML.replace(regexp2, '<b>$1</b>');
}
entryElement.style.display = 'block';
} else {
entryElement.style.display = 'none';
}
}
}
}
.entry {
background: #fff;
margin-bottom: 10px;
width: 300px;
}
<input id="search">
<div class="entry">
<div class="title">hello world test 123</div>
<div class="description">div lorem <span>ipsum</span> test <div>test1</div> testing span</div>
</div>
<div class="entry">
<div class="title">attack on titan</div>
<div class="description">fullmetal alchemist</div>
</div>
<div class="entry"></div>
<div class="entry">
<div class="title">attack on titan</div>
</div>
<div class="entry">
<div class="description">Let's not go to Camelot, 'tis a silly place</div>
</div>
Explanation of JS code
Bind all events to the userInput() function.
Get all elements with the .entry class and store them in allEntries.
Get the user's input and store in val.
Iterate through allEntries.
Get the text to search on from title and description and store in noHtmlSearchStr.
If val matches some part of noHtmlSearchStr then show the entryElement, otherwise hide it.
Remove <b> tags from title and description.
If the length of the user's search (val) is longer than three characters, highlight the matches on the text, otherwise, don't highlight anything.
I want to create a pair game that if the same textnode is matched it will set the background in white to reveal the matched textnode if not it will set a timeout and get back in original state.
The Problem of this is if I use the childNodes.nodeValue in match it saids that ChildNodes.nodeValue is not a function. And I try another code. I declare a variable that calls the element tag name of div which is I append a textNode in div. I want to compare two consecutive childNodes of div and if it is the same node, I change the color of the background to white. and I use the setTimout method, if not the color of background will go back again in original state which is black, I am pretty confused about this.
can you scan my code and help me to figure out what is the problem of this code?
here is the code.
<html>
<head>
<style>
div.row {
clear : left;
margin: auto;
width: 520px;
}
div.col {width:100px;
height:100px;
border: 3px solid black;
float : left;
margin: 10px;
font-size: 75px;
text-align: center;
background-color: black;
}
</style>
</head>
<body>
<div class="row">
<div id="00" class="col"></div>
<div id="01"class="col"></div>
<div id="02"class="col"></div>
<div id="03"class="col"></div>
</div>
<div class="row">
<div id="10" class="col"></div>
<div id="11"class="col"></div>
<div id="12"class="col"></div>
<div id="13"class="col"></div>
</div>
<div class="row">
<div id="20" class="col"></div>
<div id="21"class="col"></div>
<div id="22"class="col"></div>
<div id="23"class="col"></div>
</div>
<div class="row">
<div id="30" class="col"></div>
<div id="31"class="col"></div>
<div id="32"class="col"></div>
<div id="33"class="col"></div>
</div>
<script>
var size = 4;
var player = 0;
var board = new Array(size);
for (var i = 0; i < size; i++) {
board[i] = new Array(size);
for (var j = 0; j < size; j++) {
board[i][j] = 0;
}
}
var div_elements = document.getElementsByClassName("col");
for (var i = 0; i < div_elements.length;i++) {
div_elements[i].addEventListener("click", function() {mclick(this);});
}
var count=0;
function mclick(obj) {
if(match(div_elements.childNodes[0].nodeValue) == match(div_elements.childNodes[1].nodeValue)
{
obj.style.backgroundColor="white";
}
else{
setTimeout(function(){ obj.style.backgroundColor="white" }, 1000);
}
}
function shuffle() {
var value;
var text;
var text_node;
for (var i = 0; i < (size * size) ; i++) {
value = Math.ceil(Math.random() * 8);
board[Math.floor(i/4)][i %4] = value;
}
for (var i = 0; i < div_elements.length; i++)
{
text = board[Math.floor(i/4)][i%4];
text_node = document.createTextNode( text);
div_elements[i].appendChild(text_node);
}
}
shuffle();
</script>
</body>
</html>
You must be more specific. What kind of problem are you having? What are the error messages? What do you do that triggers the problem?
At least, put the code in a pastebin.com or something similar so that others don't need to setup a project for testing your whole stuff.
Take the following code:
<div id="work">
<div class="large-{{columns}} large-offset-{{columns}} columns projects">
</div>
</div>
The idea is that <div class="large-{{columns}} large-offset-{{columns}} columns projects"> can be generated an indefinite amount of times inside #work, and {{columns}} generates a number between 0 and 12.
What I want to do is run some JavaScript that goes through the numbers generated by {{columns}} and every time the sum is about to surpass 12, the associated divs get wrapped inside a new div with class "row".
The resulting HTML might look like this:
<div id="work">
<div class="row">
<div class="large-8 large-offset-4 columns projects"></div>
</div>
<div class="row">
<div class="large-6 large-offset-0 columns projects></div>
<div class="large-6 large-offset-0 columns projects"></div>
</div>
<div class="row">
<div class="large-4 large-offset-0 columns projects"></div>
<div class="large-8 large-offset-0 columns projects"></div>
</div>
<div class="row">
<div class="large-12 large-offset-0 columns projects"></div>
</div>
</div>
How can I accomplish this?
You can extract the {{columns}} values from each div's class name with the following regular expression:
/large-(\d+)\s* large-offset-(\d+)/
This computes the delta that should be added to the running sum:
var matches = /large-(\d+)\s* large-offset-(\d+)/.exec(item.className),
delta = parseInt(matches[1], 10) + parseInt(matches[2], 10);
You can make new row divs with document.createElement and fill them with clones of the original divs.
Demonstration:
function makeRowDiv(buildRow) {
var row = document.createElement('div');
row.className = 'row';
for (var i = 0; i < buildRow.length; ++i) {
row.appendChild(buildRow[i]);
}
return row;
}
window.onload = function () {
var work = document.getElementById('work'),
items = work.getElementsByTagName('div'),
newWork = document.createElement('div');
var buildRow = [],
count = 0;
for (var i = 0; i < items.length; ++i) {
var item = items[i];
if (item.className.indexOf('columns') == -1) {
continue;
}
// Extract the desired value.
var matches = /large-(\d+)\s* large-offset-(\d+)/.exec(item.className),
delta = parseInt(matches[1], 10) + parseInt(matches[2], 10);
if (count + delta > 12 && buildRow.length != 0) {
newWork.appendChild(makeRowDiv(buildRow));
count = 0;
buildRow = [];
}
buildRow.push(item.cloneNode(true));
count += delta;
}
if (buildRow.length != 0) {
newWork.appendChild(makeRowDiv(buildRow));
}
// Replace work with newWork.
work.parentNode.insertBefore(newWork, work);
work.parentNode.removeChild(work);
newWork.id = 'work';
};
body {
font-family: sans-serif;
font-size: 14px;
color: #444;
}
#work .row {
padding: 1px;
margin: 8px;
background: #deedff;
border: 1px solid #c4d1e1;
}
#work .row div {
/* display: inline; */
padding: 1px 4px 2px 4px;
margin: 4px;
background: #fff3fc;
border: 1px solid #ded3dc;
}
#work .row div div {
/* display: inline; */
padding: 1px 4px 2px 4px;
margin: 4px;
background: #eee;
border: 1px solid #ddd;
}
p {
padding: 0;
margin: 0;
}
<div id="work">
<div class="large-8 large-offset-4 columns projects">
<div class="child-div"><p>8</p></div>
<div class="child-div"><p>4</p></div>
</div>
<div class="large-6 large-offset-0 columns projects">
<div class="child-div"><p>6</p></div>
</div>
<div class="large-3 large-offset-3 columns projects">
<div class="child-div"><p>3</p></div>
<div class="child-div"><p>3</p></div>
</div>
<div class="large-4 large-offset-0 columns projects">
<div class="child-div"><p>4</p></div>
</div>
<div class="large-8 large-offset-0 columns projects">
<div class="child-div"><p>8</p></div>
</div>
<div class="large-6 large-offset-6 columns projects">
<div class="child-div"><p>6</p></div>
<div class="child-div"><p>6</p></div>
</div>
</div>
If you have enough horizontal space, you can uncomment the CSS line /* display: inline; */ to see the children of each row div arranged side by side.
I would use split or replace to get your integers and sum them up as suggested here.
Example:
var str = 'large-8 large-offset-6';
var large = str.replace(/.*large-(\d+)/, '$1');
var offset = str.replace(/.*large-offset-(\d+)/, '$1');
Then use a solution such as this to get your wrappers.
Example:
var divs = $("#work > .columns");
var count = <count how many cols are need to reach sum>
for(var i = 0; i < divs.length; i+=count) {
divs.slice(i, i+count).wrapAll("<div class='new'></div>");
}
I'm sure you can clean it up and finish it off but should give you the idea. I will complete when I get time tonight.