JavaScript - Expand/Collapsing Issue While Using Multiple Classes - javascript

There a lot of similar questions and I've looked at a lot of them, but couldn't figure out or find one that addressed this problem.
The issue is I'm trying to apply event listeners to a particular class (success for the most part), and each of those classes have their own elements with a separate class.
group.addEventListener("click", toggle, false);
group is a class which will have the click event.
function toggle() {
var contents = this.getElementsByClassName('content');
for (i = 0; i < contents.length; i++) {
if (contents[i].style.display == "block") {
contents[i].style.display = "none";
}
else {
contents[i].style.display = "block";
}
}
}
contents is another class nested within group. There will be a lot of contents, but only a handful of group.
Using .addEventListener works, but the click event for it expands/collapses every contents, not just the ones under that particular group which is what I want. I click on any one of the group and all of the contents on the page will expand/collapse. How can I fix this? By the way, these are just snippets of the code (it's a lot to post). I tried to use this to provide scope, but I'm just doing it wrong since it's producing the same results anyway. Thank you in advance.
Edit:
More about the HTML
group.innerHTML += String.format("<div class='group' style='display: block;'>" +
"<h3 style='display: inline-block; color: #000; margin: 0px; padding: 0px;'>{0}</h3>" +
"<img src='https:\/\/my.blah.com\/_layouts\/15\/images\/ecbarw.png' style='margin: 5px;' alt='Click to expand-collapse.' />" +
"{1}</div>", trimmedKey, groups[key]);
group.addEventListener("click", toggle, false);
I'm using this for a SharePoint web part. It's within display templates so that's why there's a lot of unrelated code to post, but I'll include the relevant HTML for group and contents.
var content = String.format('<div class="content" style="display: none; margin: 30px 0px;"><span id="{0}" class="ms-srch-item-title">' +
'<h3 class="ms-srch-ellipsis">{1}</h3>' +
'</span>' +
'<span style="margin-right: 10px;"><span style="font-weight:bold;">Assigned To: </span>{2}</span>' +
'<span style="margin-right: 10px;"><span style="font-weight:bold;">Due Date: </span>{3}</span>' +
'<span style="margin-right: 10px;"><span style="font-weight:bold;">Task Status: </span>{4}</span></div>', $htmlEncode(id + Srch.U.Ids.title), titleHtml, assignedTo, dueDate, status);
search.Grouping.push(grouping, content);
The content is getting pushed to a function (not shown here) that sorts them into groups. The groups[key] is basically the same thing as content.

I am not sure why it is not working in your case but I have prepared a simple demo for you here:
// get all the elements with class 'group'
var groups = document.getElementsByClassName('group');
// add click event listener to each group
for (var i = 0; i < groups.length; i ++)
groups[i].addEventListener("click", toggle, false);
// function to toggle group contents
function toggle() {
// get all the elements within this group with class 'content'
var contents = this.getElementsByClassName('content');
// if the content is visible then hide it and vice-versa
for (i = 0; i < contents.length; i++) {
if (contents[i].style.display == "block") {
contents[i].style.display = "none";
}
else {
contents[i].style.display = "block";
}
}
}
.content {
display: None;
padding-left: 10px;
}
.group {
margin-top: 10px;
}
<div class="group">
Group1
<div class="content another">
Group 1 Content 1
</div>
<div class="content another">
Group 1 Content 2
</div>
</div>
<div class="group">
Group2
<div class="content another">
Group 2 Content 1
</div>
<div class="content another">
Group 2 Content 2
</div>
</div>

Related

How to add eventlistener to dynamically created divs

How to add event delegation to dynamically created divs, to change property of the target div? I've tried several suggestions that I found for event delegation but none of them work. I think I'm making some mistakes but I don't know how to fix.
I am trying to develop a file thumbnail list interface with HTML and JavaScript. I made a method that draws thumbnails dynamically from an Array. And now I want to add some functions to manipulate the thumbnails, ex. changing border color of the item(div) when it is clicked.
First I tried loop-method to add event listeners to the divs, but it didn't work well. And now I learned that event delegation is better way to add event listeners to dynamically created elements. But the problem is that though I'v tried codes but they didn't work at all.
I think I am making some mistakes or mis-using methods but I don't know what is the problem.
JavaScript
function drawThumbnails(area, list){
var j
var createdList = []
for (j=0; j<list.length; j++){
var thmb = document.getElementById("fileThumb");
var name = document.getElementById("itemName");
var date = document.getElementById("itemDate");
var thmbimg = document.getElementById("fileThumbImage");
var thmbicon = document.getElementById("file_icon_thumb");
name.innerHTML=list[j][0];
date.innerHTML=list[j][1];
if (list[j][2] == "folder"){
thmbimg.src = "thmb_folder.png";
thmbicon.style.display = "none";
}
else {
if (list[j][2] == "img"){
thmbimg.src=getthmbimgsample();
}
else{
thmbimg.src = getThmbimg(list[j][2]);
}
thmbicon.style.display = "block";
thmbicon.src = getThmbicon(list[j][2]);
}
var cln = thmb.cloneNode(true);
cln.style.display = "block";
document.getElementById(area).append(cln);
createdList.push(cln);
}
thmbLists.push(createdList);
}
drawThumbnails("folderArea", folders);
drawThumbnails("fileArea", files);
document.getElementById("folderArea").addEventListener('click',function(e){
if(e.target && e.target.className == "fileThumb"){
e.target.style.borderColor = "#408CFF";
}
});
HTML
<body>
<div class = "contentArea" id="contentArea">
<div class = "thumbArea" id="folderArea">
<div class = "fileThumb" id="fileThumb">
<img src="icon_thumb_folder.png" class="fileThumb_normal" id="fileThumbImage">
<div class="fileName">
<img src="icon_thumb_file.png" style="width: 20px;" id="file_icon_thumb">
<div class="fileNameLine" id = "itemName">File/FolderName</div>
<div class="fileNameDate" id="itemDate">Date</div>
</div>
</div>
</div>
<div class = "contentAreaSectionHeader">
<input type="checkbox" id="chTest2" name="chTest2">
<label for="chTest2"><span>Files</span></label>
</div>
<div class = "thumbArea" id="fileArea">
</div>
</body>
CSS
.fileThumb{
width: 213px;
height: 183px;
border-radius: 2px;
border-style: solid;
border-width: 1px;
border-color: #EEEEEE;
text-align: center;
position: relative;
float:left;
margin: 18px;
display: none;
overflow: hidden;
}

Affect an element when another element is hovered

How can I affect an element when another element is hovered, and the two elements are in this structure:
<div id="parent_element">
<div id="class-open-1"></div>
<div id="class-close-1"></div>
</div>
or they might be in this structure:
<div id="parent_element">
<div id="div1">
<div id="class-open-1"></div>
</div>
</div>
<div id="parent_element"></div>
<div id="div2">
<div id="class-close-1"></div>
</div>
</div>
I have tried this solution which works perfectly for the first case, but does not work for the second case:
_style.innerHTML = ".class-open" + j + ":hover{ background-color: cyan; } .class-open-" + j + ":hover ~ .class-close-" + j + " { background-color: cyan; }
the j changes , so I am only hovering the classnames that have the same j
this solution works for the case one, but doesnt work for both cases.
I have also tried this :
_style.innerHTML = ".class-open" + j + ":hover{ background-color: cyan; } .class-open-" + j + ":hover .class-close-" + j + " { background-color: cyan; }
But this changes the background-color, and doesn't only hover.
I only need css or javascript to solve this, any suggestions?
I am looking for a solution that works for BOTH cases.
You're going to need to use JavaScript mouseover or jQuery .hover(). This shows mouseover from the MDN.
myElement.addEventListener("mouseover", (event) => {
// do something to the other element.
}
Try this friend:
div#one
{
background-color:red;
}
div#one:hover ~ div#two
{
background-color:yellow;
}
<div id="one">
ONEE
</div>
<div>
SIMPLE DIV
</div>
<div>
SIMPLE DIV
</div>
<div>
SIMPLE DIV
</div>
<div>
SIMPLE DIV
</div>
<div>
SIMPLE DIV
</div>
<div id="two">
two
</div>
if (/\bclass-/.test(target.className)) {
var _style = document.createElement('style');
var j = target.className.match(/\d+$/)[0];
target.style.backgroundColor = "red";
_style.innerHTML = ".class-close-" + j + " { background-color: red; }";
setTimeout(function () {
target.style.backgroundColor = "";
_style.innerHTML = '.class-close-' + j + ' { background-color: ""; }';
}, 500);
document.head.appendChild(_style);
}
Here is the solution I made, but still looking for the "good effect" of Hover instead of just deleting the style after the 500ms..
Hope this helps someone.
If you can control the template code [which I believe we do in most cases], then if one element is inside other element, it can be solved just using css.
Else, if they have different parent, JS would be the direction which people have already suggested.

Best way to filter data on site

I write a small application which shows restaurants and pubs. I want to add a function to filter data by category. I get data in my script from external *.json file. There are only two categories - "bar" and "restaurant".
I would be very greatful if you can give me a hint about filtering in existing elements. After generation of cards with places you want to show only bars, so you click button "bars" and there are only bars on site, restaurants are hidden.
function updateData() {
var results = document.getElementById("results");
for (var i = 0; i < places.length; i++) {
results.innerHTML += '<div class="result text-center">' +
'<img class="rest-img img-thumbnail" width="236" height="236" src="img/img1.jpg">' +
'<h2 class="rest-name">' + places[i].name + '</h2>' +
'<p class="rest-category">' + places[i].category + '</p>' +
'<p class="rest-address">' + places[i].address + '</p>' +
'<i class="fa fa-beer" aria-hidden="true"></i><p class="rest-beer-price">' + places[i].price + ' PLN</p>';
}
}
function showBars() {
//Code to filter bars
}
var warsaw = document.getElementById("warsaw");
warsaw.addEventListener("click", updateData);
Many thanks in advance for help!
The simplest is with a filter function:
function filterByCategory(list, category) {
return list.filter(function(item) {
return (item.category === category);
});
}
The filter command expects a return of true to keep things and false to exclude.
Give the root of each item that needs to be filtered a class that represents their category. Then have the results element add or remove a class for each category that should be shown. Then it's just a matter of adding some CSS that only displays the categories of elements when the respective class exists on results.
Almost no JS needed with this approach. Updating the display is handled entirely by CSS. JS is only needed to toggle the class on results.
document.getElementById("foo").addEventListener("click", function() {
document.getElementById("results").classList.toggle("foo")
})
document.getElementById("bar").addEventListener("click", function() {
document.getElementById("results").classList.toggle("bar")
})
#results .item {
display: none;
}
#results.foo .fooCat {
display: block;
color: green;
}
#results.bar .barCat {
display: block;
color: blue;
}
<button id=foo>TOGGLE FOO</button>
<button id=bar>TOGGLE BAR</button>
<div id=results>
<p class="item fooCat">FOO ITEM</p>
<p class="item fooCat">FOO ITEM</p>
<p class="item barCat">BAR ITEM</p>
<p class="item fooCat">FOO ITEM</p>
<p class="item barCat">BAR ITEM</p>
<p class="item barCat">BAR ITEM</p>
<p class="item fooCat">FOO ITEM</p>
</div>

HTML - How do I insert a <span></span> tag into each line of a <pre></pre> block without hard coding?

I was just trying to add line numbers at the beginning of source code using CSS.
I realized the effect I wanted, as follows:
However, the HTML code required continual use of <span>...</span> tags:
<pre class="code">
<span>var links = document.getElementsByClassName("link");</span>
<span>for(var i = 0; i < links.length; i++){</span>
<span> links[i].onclick=function(){</span>
<span> alert(i+1);</span>
<span> };</span>
<span>}</span>
</pre>
With the span tags positioned at home/end of lines I can let the line numbers show as expected.
But I think there must be another better solution to prevent me adding all these span tags hard-coded, maybe using Javascript, or jQuery I don't mind but don't know how. Please help.
NOTE:
My problem is not how to display line numbers when the <span> tags are already there. Instead, I wanted to know if the origin HTML code contains NO <span> tags, how can I automatically add them into the suitable places and so I can apply the CSS styles.
This can be achieved by using CSS counters
This does not require any JavaScript (or jQuery) which means no need for each libraries or scripts and was introduced way back in CSS 2.1 so has great browser support across the board.
pre {
background: #eee;
counter-reset: section; /* Reset the counter to 0 for each new pre */
}
pre span:before {
counter-increment: section; /* Increment the section counter*/
content: counter(section); /* Display the counter */
padding: 0 5px;
border-right: 1px solid #777;
margin-right: 5px;
color: #777
}
<pre class="code">
<span>var links = document.getElementsByClassName("link");</span>
<span>for(var i = 0; i < links.length; i++){</span>
<span> links[i].onclick=function(){</span>
<span> alert(i+1);</span>
<span> };</span>
<span>}</span>
</pre>
<pre class="code">
<span>var links = document.getElementsByClassName("link");</span>
<span>for(var i = 0; i < links.length; i++){</span>
<span> links[i].onclick=function(){</span>
<span> alert(i+1);</span>
<span> };</span>
<span>}</span>
</pre>
I have combined #Stewartside answer with what you have actually asked for.
Below you can see a simple plain JavaScript to replace any line in element with code class to be wrapped in span which applies #Stewartside css.
var codeElement = document.getElementsByClassName("code"); //array of code blocks
var formattedCode = codeElement[0].textContent.replace("\r\n", "\n").split("\n");
var codeLength = formattedCode.length;
formattedCode.forEach(function(line, index, array) {
if (codeLength - 1 == index) return;
array[index] = "<span>" + line + "</span>";
});
codeElement[0].innerHTML = formattedCode.join("\n");
$(".code-jquery").each(function(index, codeElement) {
var formattedCode = $(codeElement).html().replace("\r\n", "\n").split("\n");
var codeLength = formattedCode.length;
$(codeElement).text("");
$.each(formattedCode, function(index, line) {
if (codeLength - 1 == index) return;
$(codeElement).append("<span>" + line + "</span>\n")
});
});
pre {
background: #eee;
counter-reset: section; /* Reset the counter to 0 for each new pre */
}
pre span:before {
counter-increment: section; /* Increment the section counter*/
content: counter(section); /* Display the counter */
padding: 0 5px;
border-right: 1px solid #777;
margin-right: 5px;
color: #777
}
pre.code-jquery span {
color: green;
}
<pre class="code">
var links = document.getElementsByClassName("link");
for(var i = 0; i < links.length; i++) {
links[i].onclick = function() {
alert(i+1);
};
}
</pre>
//jQuery version
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<pre class="code-jquery">
var links = document.getElementsByClassName("link");
for(var i = 0; i < links.length; i++) {
links[i].onclick = function() {
alert(i+1);
};
}
</pre>
So you basically need to append a span before each line. Here is the codepen link. I am currently using jQuery's insertBefore() method for it.
See below for the explanation of the code :-
$('.code') will give you the pre tag. Now the jQuery .find() method will give you all the spans inside the pre tag. Now jQuery .each() function is basically a for loop ( in simple terms ) which will loop for all the span tags inside the pre tag.
.insertBefore() function simply inserts whatever there is in the selector to the element mentioned inside the function.
var iCount = 1;
$('.code').find('span').each(function(){
$( "<span>"+iCount+"| </span>" ).insertBefore( this);
iCount++;
});
pre{
background: #eee
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<pre class="code">
<span>var links = document.getElementsByClassName("link");</span>
<span>for(var i = 0; i < links.length; i++){</span>
<span> links[i].onclick=function(){</span>
<span> alert(i+1);</span>
<span> };</span>
<span>}</span>
</pre>

Custom html tab implementation problems

my use case : create tab like experience. clicking on add button creates a (horz tab button) and a corresponding div, which is linked via onclick listener, dynamically.
problems :
on clicking add button, values from previous tabs are reset (which is obvious wrt to the way $tabs_prev & $menu_prev is populated) and
their respective js goes away (which I can't understand, why?)
a remove tab implementation (because the way I've coded these tabs, removing a tab and corresponding div isn't really simple, so, any clues in this direction, maybe?)
code : fiddle : http://jsfiddle.net/g58fzs75/1/
HTML:
<body>
<input id="hidden" type="hidden" value="1"></input>
<div id="template_tabBtn" style="display:none">
<input type="button" value="add" onclick="addTab()"></input>
</div>
<ul id="menu">
</ul>
<div id="tabs">
</div>
<div id="template_tabBar" style="display:none">
<li>
<input type="button" id="tab_btn" class="template_tabBar" value="Tab" onclick="tabClick(this)"></input>
</li>
</div>
<div id="template_tabs" style="display:none">
<div id="tabs" class="template_tabs tab_div" value="1">
<input type="text" id="txt" class="template_tabs" value="alert"></input>
<input type="button" id="btn" class="template_tabs" value="alert"></input>
</div>
</div>
</body>
CSS:
<style>
ul#menu {
padding: 0;
}
ul#menu li {
display: inline;
}
ul#menu li input {
background-color: black;
color: white;
padding: 10px 20px;
text-decoration: none;
border-radius: 4px 4px 0 0;
}
ul#menu li input:hover {
background-color: orange;
}
</style>
jQuery :
<script src="http://code.jquery.com/jquery-1.9.1.js" type="text/javascript"></script>
<script>
$tabs_prev = "";
$menu_prev = "";
$add_btn = "";
$current_tabID = "";
function tabClick(id) {
showCurrent($(id).attr('id'));
}
function addTab() {
var tabCount = parseInt($('#hidden').val()) + 1;
$('#hidden').val(tabCount);
run(tabCount);
showCurrent($('#tabs-' + tabCount).attr('id'));
}
$(document).ready(function() {
$add_btn = "<li>" + $('#template_tabBtn').html() + "</li>";
run(1);
});
function run(tabCount) {
//$tabs_prev += main($('#template_tabs'),tabCount);//alert("tabs\n"+$tabs_prev);
$menu_prev += main($('#template_tabBar'), tabCount); //alert("menu\n"+$menu_prev);
$('#tabs').html($('#tabs').html() + main($('#template_tabs'), tabCount));
$('#menu').html($menu_prev + $add_btn);
logic(tabCount);
}
function main(target, tabCount) {
$htmlBackup = $(target).html();
$('.' + $(target).attr('id')).each(function() {
$(this).attr('id', $(this).attr('id') + "-" + tabCount).removeClass($(target).attr('id'));
$(this).attr('value', $(this).attr('value') + "-" + tabCount);
});
$html = $(target).html();
$(target).html($htmlBackup);
return $html;
}
function logic(tabCount) {
$('#btn-' + tabCount).click(function() {
alert($('#txt-' + tabCount).val());
});
}
function showCurrent(current_id) {
$('.tab_div').each(function() {
var id = $(this).attr('id');
var id_num = id.substr(id.lastIndexOf('-') + 1, id.length);
var current_id_num = current_id.substr(current_id.lastIndexOf('-') + 1, current_id.length);
if (id_num == current_id_num) {
$("#tabs-" + id_num).show();
$('#tab_btn-' + id_num).css({
"background-color": "orange"
});
} else {
$("#tabs-" + id_num).hide();
$('#tab_btn-' + id_num).css({
"background-color": "black"
});
}
});
}
</script>
The reason why your javascript is disappearing is because resetting the innerHTML deletes the onclick handlers on the elements. Why: the original elements are destroyed, including references to events and new elements are created.
The code responsible for this:
$('#tabs').html($('#tabs').html() + main($('#template_tabs'), tabCount));
Please use jQuery's appending of an element by cloning the template tab:
$('#tabs').append($('#template_tabs').clone(true));
Append appends htmlstrings or elements to an parent element. It's a buffed up version of the documents native 'appendChild'.
clone clone the template element (makes a copy). You can do this in your function main and return it to the append function.
function main(tabCount)
{
var node = $('#template_tabs').clone(true));
//do things with the node, like setting an onclick handler, or id.
//example
node.setAttribute("id", "tab" + tabCount);
}
Removing can be done also:
function removeNode(node)
{
//provide a node via jQuery
//example: removeNode($("#tab2")) <-- now tab2 will be removed from the DOM.
node.remove();
}

Categories