My question is simple; why don't my class styles apply to dynamically created elements?
I am creating a search bar here where I generate an li per matching result, and append it to my ul. When I inspect the page, I see the classes are applied to the li's correctly, but the styles from the class itself aren't present. I hard coded a test li and it had the expected styles. What am I missing here in order to have my styles applied to these dynamically generated elements? Surely I don't have to assign every style for the li's in my typescript? Any explanation would be lovely, thank you all! (:
My HTML:
<div class="section">
<h2>Step 1: Choose an Identity Provider (IDP)</h2>
<div class="search">
<input
class="focusable"
(focusout)="handleFocusOut()"
(input)="debounce(search, 300, $event)"
placeholder="Select Identity Provider"
autocomplete="off"
/>
<i class="icon fas fa-search"></i>
<ul id="search-options">
<li class="focusable testing">IMG Salesforce</li>
</ul>
</div>
<!-- <i class="fa fa-plus"></i>-->
</div>
My scss:
.section {
...
.search {
position: relative;
width: 300px;
.icon {
position: absolute;
right: 5px;
top: 3px;
}
input {
width: 300px;
}
ul {
color: red;
li {
cursor: pointer;
&:hover {
background-color: grey;
color: red;
}
.testing {
cursor: pointer;
&:hover {
background-color: grey;
color: red;
}
}
}
}
}
}
My TS:
let ul = document.getElementById('search-options');
this.displayServices.forEach((service) => {
let li = document.createElement('li');
li.classList.add('focusable', 'testing');
li.addEventListener('focusout', this.handleFocusOut);
const img = document.createElement('img');
img.src = this.getImgUrl(service);
img.width = 20;
img.height = 20;
img.style.margin = '0 10px';
li.innerHTML = `${service.name}`;
li.style.display = 'flex';
li.style.alignItems = 'center';
li.style.border = '.5px solid black';
li.style.padding = '8px 0';
li.prepend(img);
ul.appendChild(li);
});
It's hard to be precise without seeing the whole tamale, but generally you should be getting your data in the .TS file and sending that data directly to the view. Your view should be creating those elements on the fly. Not shown in the answer here is the inline styles you were adding to the image and the LI tag - just do those in CSS.
Something like this:
TS:
this.someService.getData.subscribe(displayServices => {
this.displayServices = displayServices;
})
HTML:
<div class="section">
<h2>Step 1: Choose an Identity Provider (IDP)</h2>
<div class="search">
<input
class="focusable"
(focusout)="handleFocusOut($event)"
(input)="debounce(search, 300, $event)"
placeholder="Select Identity Provider"
autocomplete="off" />
<i class="icon fas fa-search"></i>
<ul id="search-options">
<li *ngFor="service in displayServices"
class="focusable testing"
(focusout)="handleFocusOut($event)">
<img [src]="getImgUrl(service)" />
{{service.name}}</li>
</ul>
</div>
<!-- <i class="fa fa-plus"></i>-->
</div>
the classes are applied to the li's correctly, but the styles from the class itself aren't present
If you mean the focusable and testing classes, I don't see them in your SCSS.
Related
I have a form (which I am incidentally generating in PHP from a database) that is using CSS to replace checkboxes. When a checkbox is checked, the containing <li> should have an outline added, and when unchecked, the outline should be removed. Using onchange events works to change these at a click, but the outlines remain when the form is reset. I added onreset events, to try to force the removal, but that doesn't seem to change anything.
I've recreated this behavior in the snippet. (I have not hidden the checkboxes, as the snippet system apparently does not duplicate the normal browser behavior of clicking on the <label> to set or clear the checkbox. [EDIT: This was my mistake; I set the wrong for on the labels, and now that behavior works. The rest stands.])
How do I make this work? I could have a function that manually sets each outline in a reset function, but, as I said, the checkboxes are created from a database, so I'd have to write the PHP to write the js function, which seems like the wrong way to go.
function doCheckboxes(clicked_id) {
if (document.getElementById(clicked_id).checked) {
document.getElementById(clicked_id).parentNode.style.outline = "2px solid black";
} else {
document.getElementById(clicked_id).parentNode.style.outline = "0 none black";
}
}
function clearCheckboxes(clicked_id) {
document.getElementById(clicked_id).parentNode.style.outline = "0 none black";
}
li {
display: inline-block;
margin: 10px;
text-align: center;
font-weight: 600;
}
.imageholder {
display: block;
height: 60px;
width: 60px;
background-repeat: no-repeat;
background-clip: content-box;
background-size: cover;
margin: auto;
}
.has-thing1 .imageholder {
background-image: url(path/to/image.png);
}
.has-thing2 .imageholder {
background-image: url(path/to/image.png);
}
.has-thing3 .imageholder {
background-image: url(path/to/image.png);
}
<form action="." method="get">
<fieldset class="subcategory">
<ul>
<li class="has-x has-thing1">
<input type="checkbox" onChange="doCheckboxes(this.id)" onReset="clearCheckboxes(this.id)" id="x_thing1" name="has[]" value="thing1">
<label for="x_thing1">
<div class="imageholder"> </div>
Thing1
</label>
</li>
<li class="has-x has-thing2">
<input type="checkbox" onChange="doCheckboxes(this.id)" onReset="clearCheckboxes(this.id)" id="x_thing2" name="has[]" value="thing2">
<label for="x_thing2">
<div class="imageholder"> </div>
Thing2
</label>
</li>
<li class="has-x has-thing3">
<input type="checkbox" onChange="doCheckboxes(this.id)" onReset="clearCheckboxes(this.id)" id="x_thing3" name="has[]" value="thing3">
<label for="x_thing3">
<div class="imageholder"> </div>
Thing3
</label>
</li>
</ul>
</fieldset>
<div>
<button type="submit">Submit</button></div>
<button type="reset">Clear Selection</button>
</form>
Create function clearAllCheckboxes
function clearAllCheckboxes(evt) {
const formEl = evt.closest('form')
const allCheckbox = formEl.querySelectorAll('[type=checkbox]')
allCheckbox.forEach(checkbox => {
checkbox.parentNode.style.outline = "0 none black"
})
}
Add an onClick handler to the button "Clear Selection"
<button type="reset" onClick="clearAllCheckboxes(this)">Clear Selection</button>
I have an unordered list in which every list item has a span and inside of it a picture.
I'm trying to set as background-image of every span, the image that their inner img block contains and then put the opacity of that image block to 0.
Here's the code I wrote. The problem is that this seems not work correctly, even if I coomment the line where I set the background image, I still can see the pictures, while I wouldn't be supposed of, since the opacity of the img block is set to 0. That makes me think that the code isn't setting the background image correctly.
Can you help me understand what I am doing wrong?
thank you!
var myUl = $('.my-ul');
[...myUl.children].forEach(childLi => {
const span_list = childLi.querySelector('span');
const img_list = childLi.querySelector('img');
var path_picture = img_list.src;
$(span_list).css("background-image", "url(${path_picture})");
$(span_list).css("background-size", "contain");
img_list.style.opacity = 0;
});
.my-ul li span {
display: block;
width: 200px;
height: 200px;
}
.my-ul li img {
width: 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<ul class="my-ul">
<li>
<span>
<img src="https://www.trudellanimalhealth.com/sites/default/files/documents/tmdi-cat-athma-concern_2x.png" />
</span>
</li>
<li>
<span>
<img src="https://img.webmd.com/dtmcms/live/webmd/consumer_assets/site_images/article_thumbnails/other/cat_relaxing_on_patio_other/1800x1200_cat_relaxing_on_patio_other.jpg" />
</span>
</li>
<li>
<span>
<img src="https://undark.org/wp-content/uploads/2020/02/GettyImages-1199242002-1-scaled.jpg" />
</span>
</li>
</ul>
There are two issues:
$(".myul") returns a jquery collection, which as a .children() function (not a property) but this is not an array, so can't be iterated using [...].forEach
"url(${path_picture})" looks like it uses string interpolation, so needs to use backtick ` not quote "
Giving:
//var myUl = $('.my-ul');
var myUl = document.querySelector(".my-ul");
[...myUl.children].forEach(childLi => {
const span_list = childLi.querySelector('span');
const img_list = childLi.querySelector('img');
var path_picture = img_list.src;
$(span_list).css("background-image", `url(${path_picture})`);
$(span_list).css("background-size", "contain");
img_list.style.opacity = 0;
});
.my-ul li span {
display: block;
width: 200px;
height: 200px;
}
.my-ul li img {
width: 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<ul class="my-ul">
<li>
<span>
<img src="https://www.trudellanimalhealth.com/sites/default/files/documents/tmdi-cat-athma-concern_2x.png" />
</span>
</li>
<li>
<span>
<img src="https://img.webmd.com/dtmcms/live/webmd/consumer_assets/site_images/article_thumbnails/other/cat_relaxing_on_patio_other/1800x1200_cat_relaxing_on_patio_other.jpg" />
</span>
</li>
<li>
<span>
<img src="https://undark.org/wp-content/uploads/2020/02/GettyImages-1199242002-1-scaled.jpg" />
</span>
</li>
</ul>
The alternative is to use jquery
var myUl = $('.my-ul');
myUl.children().each((i, e) => {
var path_picture = $("img", e).attr("src");
$("span", e)
.css("background-image", `url(${path_picture})`)
.css("background-size", "contain");
$("img", e).hide();
});
.my-ul li span {
display: block;
width: 200px;
height: 200px;
}
.my-ul li img {
width: 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<ul class="my-ul">
<li>
<span>
<img src="https://www.trudellanimalhealth.com/sites/default/files/documents/tmdi-cat-athma-concern_2x.png" />
</span>
</li>
<li>
<span>
<img src="https://img.webmd.com/dtmcms/live/webmd/consumer_assets/site_images/article_thumbnails/other/cat_relaxing_on_patio_other/1800x1200_cat_relaxing_on_patio_other.jpg" />
</span>
</li>
<li>
<span>
<img src="https://undark.org/wp-content/uploads/2020/02/GettyImages-1199242002-1-scaled.jpg" />
</span>
</li>
</ul>
Not sure how you handle changing styles in vanilla js or jquery, but it could be this img_list.style.opacity = 0; is wrong. Maybe img_list is a group of elements/nodes so it won't work?
I am pretty new to JS and jQuery and trying to build a plugin for some form controls to dynamically add and remove elements containing inputs and values which should be stored in a database later on.
I am using a list of elements while the first of those elements serves as a template. In preparation to the cloning, a button triggering on click and containing some data is added to remove newly added or already existing elements. Those elements mainly contain input fields with IDs relating to an index.
This issue happens only when altering the HTML which is copied to create a new element in the DOM.
Here is a fiddle displaying the behavior:
$('ul').sortable({
handle: '.handle',
});
$('<button>remove</button>')
.appendTo($('li'))
.click(function() {
$(this).closest('li').remove();
})
const $template = $('ul>li').first().clone(true);
$('<button>another one</button>')
.appendTo($('#add-btn-holder'))
.click(function() {
$clone = $template.clone(true).html(function(i, html) {
return html.replace(/id-\d-/g, 'id-X-');
});
$('ul').append($clone);
});
ul {
list-style-type: none;
}
li {
background: #f1f1f1;
margin: .4em;
}
[id^="id-X-"] {
background: #ddd;
}
input {
height: 3em;
}
.handle {
cursor: move;
font-size: 1.2em;
padding: 1em;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.0/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<ul>
<li id="id-0-holder">
<span class="handle"><b>=</b></span>
<input id="id-0-field_0" value="val_0_0" />
<input id="id-0-field_1" value="val_0_1" />
</li>
<li id="id-2-holder">
<span class="handle"><b>=</b></span>
<input id="id-1-field_0" value="val_1_0" />
<input id="id-1-field_1" value="val_1_1" />
</li>
<li id="id-2-holder">
<span class="handle"><b>=</b></span>
<input id="id-2-field_0" value="val_2_0" />
<input id="id-2-field_1" value="val_2_1" />
</li>
</ul>
<div id="add-btn-holder">
</div>
After adding an new element and altering the html, the event handlers and data will be lost. For simplicity I did not include any data but the click event to remove the new row will be lost.
Commenting out line 17 in the script code (string#replace) leaves all events and data intact.
I can store the data values in a temporary variable before altering the html and re-apply them afterwards. I don't know how to deal with events though and hopefully there is an easier solution without temporary variables.
Unfortunately I didn't find anything useful here because most headers sound promising but are solved with a simple $.clone(true) or $.clone(true, true) (which I am already using).
My real world code is a bit more complex. The remove-button is holding the corresponding to-be-removed item within a data attribute and the query is also build around data, therefore I pretty much have to rely on those values. Unfortunately the classes and IDs may alter why I cannot query with conventional methods.
If this was already asked somewhere else, feel free to guide me there, I wasn't able to find anything useful :(
Don't update the HTML, modify the attributes directly.
When you update the HTML, it's reparsed from scratch and any dynamic modifications to the DOM are lost.
$('ul').sortable({
handle: '.handle',
});
$('<button>remove</button>')
.appendTo($('li'))
.click(function() {
$(this).closest('li').remove();
})
const $template = $('ul>li').first().clone(true);
$('<button>another one</button>')
.appendTo($('#add-btn-holder'))
.click(function() {
$clone = $template.clone(true);
$clone.find("[id^=id-]").attr('id', function(i, id) {
return id.replace(/^id-\d+-/, 'id-X-');
});
$('ul').append($clone);
});
ul {
list-style-type: none;
}
li {
background: #f1f1f1;
margin: .4em;
}
[id^="id-X-"] {
background: #ddd;
}
input {
height: 3em;
}
.handle {
cursor: move;
font-size: 1.2em;
padding: 1em;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.0/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<ul>
<li id="id-0-holder">
<span class="handle"><b>=</b></span>
<input id="id-0-field_0" value="val_0_0" />
<input id="id-0-field_1" value="val_0_1" />
</li>
<li id="id-2-holder">
<span class="handle"><b>=</b></span>
<input id="id-1-field_0" value="val_1_0" />
<input id="id-1-field_1" value="val_1_1" />
</li>
<li id="id-2-holder">
<span class="handle"><b>=</b></span>
<input id="id-2-field_0" value="val_2_0" />
<input id="id-2-field_1" value="val_2_1" />
</li>
</ul>
<div id="add-btn-holder">
</div>
Just .clone() the .last() <li> and increment #id and value by 1. Delegate the click event by using the .on() method. BTW I shortened the #ids to save myself some unneeded typing, feel free to use whatever you prefer.
$('ul').sortable({
handle: '.handle',
});
$('<button>Remove</button>')
.appendTo('li')
.on('click', function() {
if ($('li').length === 1) {
return;
} else {
$(this).closest('li').remove();
}
});
let idx;
$('<button>Add</button>')
.appendTo('.add-box')
.on('click', function() {
const $template = $('ul>li').last();
let prev = Number($template[0].id.split('-').pop());
idx = prev + 1;
let $clone = $template.clone(true, true);
$clone[0].id = `i-${idx}`;
$clone.find('input').each(function(i) {
$(this)[0].id = `i-${idx}-f_${i}`;
$(this).val(`v_${idx}_${i}`);
});
$('ul').append($clone);
});
ul {
list-style-type: none;
}
li {
background: #f1f1f1;
margin: .4em;
}
[id^="id-X-"] {
background: #ddd;
}
input {
height: 3em;
}
.handle {
cursor: move;
font-size: 1.2em;
padding: 1em;
}
<ul>
<li id="i-0">
<label class="handle"><b>=</b></label>
<input id="i-0-f_0" value="v_0_0" />
<input id="i-0-f_1" value="v_0_1" />
</li>
<li id="i-1">
<label class="handle"><b>=</b></label>
<input id="i-1-f_0" value="v_1_0" />
<input id="i-1-f_1" value="v_1_1" />
</li>
<li id="i-2">
<label class="handle"><b>=</b></label>
<input id="i-2-f_0" value="v_2_0" />
<input id="i-2-f_1" value="v_2_1" />
</li>
</ul>
<div class="add-box"></div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.0/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
So with some help from your answers I came up with this solution to dynamically handle all attributes matching the regex. thanks.
$('<button>remove</button>')
.appendTo($('li'));
$('<button>another one</button>')
.appendTo($('#add-btn-holder'));
// remove-button handler
$('li').on('click', 'button', function() {
$(this).closest('li').remove();
});
const $template = $('li').first().clone(true, true);
// add-button handler
$('#add-btn-holder').on('click', function() {
$clone = $template.clone(true, true);
var re = /id-\d-/,
repl = 'id-X-';
$clone.find('*').each(function() {
var $el = $(this);
$($el.get(0).attributes).each(function() {
$el.prop(this.name, this.value.replace(re, repl));
})
});
$('ul').append($clone);
});
ul {
list-style-type: none;
}
li {
background: #f1f1f1;
margin: .4em;
}
[id^="id-X-"] {
background: #ddd;
}
input {
height: 3em;
}
.handle {
cursor: move;
font-size: 1.2em;
padding: 1em;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.0/jquery.min.js"></script>
<ul>
<li id="id-0-holder">
<input id="id-0-field_0" value="val_0_0" />
<input id="id-0-field_1" value="val_0_1" />
</li>
<li id="id-2-holder">
<input id="id-1-field_0" value="val_1_0" />
<input id="id-1-field_1" value="val_1_1" />
</li>
<li id="id-2-holder">
<input id="id-2-field_0" value="val_2_0" />
<input id="id-2-field_1" value="val_2_1" />
</li>
</ul>
<div id="add-btn-holder">
</div>
I'm wanting to create a variation of Javascript tabs using data attributes rather than IDs to link the tab and the content.
Here's how it should work:
Clicking a <button class="tab" data-tab-trigger="1"> adds a class of is-active and removes any is-active classes from all other button elements
The value of data-tab-trigger matches the value of data-tab-content on the corresponding <div class="tab-content" data-tab-content="1"> and should add a class of is-open to it
The is-active class highlights the active tab and the is-open class shows the related tab content
Here's the JS I'm currently working which isn't working as expected:
var tabTriggerBtns = document.querySelectorAll('.tabs li button');
tabTriggerBtns.forEach(function(tabTriggerBtn, index){
tabTriggerBtn.addEventListener('click', function(){
var tabTrigger = this;
var tabTriggerData = tabTrigger.getAttribute('data-tab-trigger');
var tabContent = document.querySelector('.tab-content');
var currentTabData = document.querySelector('.tab-content[data-tab-content="' + tabTriggerData + '"]').classList.add('is-open');
if(tabContent !== currentTabData) {
tabContent.classList.toggle('is-open');
}
if(tabTrigger.classList.contains('is-active')) {
tabTrigger.classList.remove('is-active');
}
else {
tabTriggerBtn.classList.remove('is-active');
tabTrigger.classList.add('is-active');
}
});
});
Here's a Codepen with my ongoing script: https://codepen.io/abbasarezoo/pen/752f24fc896e6f9fcce8b590b64b37bc
I'm having difficulty finding what's going wrong here. I'm relatively comfortable writing jQuery, but quite raw when it comes to vanilla JS so any help would be very much appreciated.
One of your main issue is in this line:
tabContent !== currentTabData
You may use dataset in order to access data attributes.
Moreover, you may simplify your code in few steps:
remove classess
add classess
The snippet:
var tabTriggerBtns = document.querySelectorAll('.tabs li button');
tabTriggerBtns.forEach(function(tabTriggerBtn, index){
tabTriggerBtn.addEventListener('click', function(){
var currentTabData = document.querySelector('.tab-content[data-tab-content="' + this.dataset.tabTrigger + '"]');
// remove classess
document.querySelector('.tab-content.is-open').classList.remove('is-open');
document.querySelector('.tabs li button.is-active').classList.remove('is-active');
// add classes
currentTabData.classList.add('is-open');
this.classList.add('is-active');
});
});
* {
margin: 0;
padding: 0;
}
body {
display: flex;
}
.tabs {
width: 25%;
border: 2px solid red;
}
button.is-active {
background-color: red;
}
.tab-content__outer {
width: 75%;
}
.tab-content {
display: none;
}
.tab-content.is-open {
display: block;
background-color: yellow;
}
<ul class="tabs">
<li>
<button class="tab is-active" data-tab-trigger="1">First</button>
</li>
<li>
<button class="tab" data-tab-trigger="2">Second</button>
</li>
<li>
<button class="tab" data-tab-trigger="3">Third</button>
</li>
</ul>
<div class="tab-content__outer">
<div class="tab-content is-open" data-tab-content="1">
First
</div>
<div class="tab-content" data-tab-content="2">
Second
</div>
<div class="tab-content" data-tab-content="3">
Third
</div>
</div>
I have a simple bit of JS used to show / hide DIVs:
function HideShow(e, itm_id) {
var tbl = document.getElementById(itm_id);
if (tbl.style.display == ""){
e.innerHTML = "<i class='fa fa-plus' aria-hidden='true'></i>";
tbl.style.display = "none"; }
else {
e.innerHTML = "<i class='fa fa-minus' aria-hidden='true'></i>";
tbl.style.display = ""; }
}
This is a working example of the code on Codepen: Show Hide Divs without jQuery
This is an example of one section:
<div id="activities" style="margin-bottom:50px;">
<div style="color: #000; background: #eee; border-bottom:1px solid #ccc; padding:5px;">
<h1 class="heading"><i class='fa fa-minus' aria-hidden='true'></i> Activities <span style="color:#ccc;"></span></h1>
</div>
<div id="parent_activities" style="background: #fff; padding:20px;">
<div id="activities__award-medal" style="background: #fff; padding-left:10px; background:#f1f1f1; border-top:1px solid #fff; font-size:30px;"><i class='fa fa-minus' aria-hidden='true'></i> award-medal <span style="color:#ccc;"></span></div>
<div id="child_award-medal" style="background: #fff; padding:20px;">
<ul class="gallery grid">
<li>
<a href="#">
<img title="military medal - 🎖️" src="https://cdn.jsdelivr.net/emojione/assets/svg/1f396.svg" style="width:64x; height:64px" role="presentation">
</a>
</li>
</ul>
</div>
<div id="activities__event" style="background: #fff; padding-left:10px; background:#f1f1f1; border-top:1px solid #fff; font-size:30px;"><i class='fa fa-minus' aria-hidden='true'></i> event <span style="color:#ccc;"></span></div>
<div id="child_event" style="background: #fff; padding:20px;">
<ul class="gallery grid">
<li>
<a href="#">
<img title="jack-o-lantern - 🎃" src="https://cdn.jsdelivr.net/emojione/assets/svg/1f383.svg" style="width:64x; height:64px" role="presentation">
</a>
</li>
</ul>
</div>
</div>
</div>
The top level example has an id of parent_activities and then there are two child values:
child_award-medal
child_event
I'd like to work out how to add two links:
A link to toggle the HideShow function for the parents so that all divs with an ID starting with parent_ are shown / hidden
A link to toggle the HideShow function for the children so that all divs with an ID starting with child_ are shown / hidden
I'm not sure how I'd go about that though.
Any advice much appreciated. Thanks
Note: this isn't a fully complete solution. The intention is to assist you in the parts that are giving you pause.
Try not to embed JavaScript in your HTML body; it's unnecessary markup and makes it difficult to track down and debug errors. I did not change your existing calls, but demonstrate how it can be done by using addEventListener with the newer code
You can target your elements using document.querySelectorAll and looking for the prefix you're interested in (e.g., parent_, child_). Which prefixes to use have been added to the links in the data-selector attributes
because the toggling action is not going to another page, these should be buttons or spans
to hide elements, you can use the Bootstrap display classes, as I have used d-none which stands for display none. The Bootstrap library provides these to make it especially easier for responsive layouts
many of your inline-CSS should be in classes, this is to both reduce your markup and make it more organized
// So forEach can be used on 'querySelectorAll' and 'getElementsByClassName' collections
HTMLCollection.prototype.forEach = NodeList.prototype.forEach = Array.prototype.forEach;
function HideShow(e, itm_id) {
var tbl = document.getElementById(itm_id);
if (tbl.style.display == "") {
e.innerHTML = "<i class='fa fa-plus' aria-hidden='true'></i>";
tbl.style.display = "none";
} else {
e.innerHTML = "<i class='fa fa-minus' aria-hidden='true'></i>";
tbl.style.display = "";
}
}
// -----------------------------------------------------------
// NEW Code
// New toggle links
let toggles = document.getElementsByClassName('toggler');
// Attach click event
toggles.forEach(link => link.addEventListener('click', fnToggleElement))
// Event handler definition
function fnToggleElement() {
let elements = document.querySelectorAll(`[id^="${this.dataset.selector}"]`)
let className = 'd-none'
elements.forEach(el => {
let fas = el.parentElement.closest('.item,.sub-container,.menu-container').querySelectorAll('.fa')
if (el.classList.contains(className)) {
el.classList.remove(className)
fas.forEach(fa => {
fa.classList.remove('fa-plus')
fa.classList.add('fa-minus')
})
} else {
el.classList.add(className)
fas.forEach(fa => {
fa.classList.remove('fa-minus')
fa.classList.add('fa-plus')
})
}
})
}
.menu-container {
margin-bottom: 50px;
}
.sub-container {
padding: 20px;
}
.heading {
color: #000;
background: #eee;
border-bottom: 1px solid #ccc;
padding: 5px;
}
.indent {
background: #fff;
padding: 20px;
}
.icon {
width: 64px;
height: 64px;
}
.item {
background: #fff;
padding-left: 10px;
background: #f1f1f1;
border-top: 1px solid #fff;
font-size: 30px;
}
.toggler {
cursor: pointer;
display: inline-block;
}
.gallery {
width: 100%;
*width: 99.94877049180327%;
margin: 0;
padding: 0;
}
.gallery.grid li {
margin: 2px 5px;
}
.gallery.grid li {
margin: 2px 5px;
display: block;
}
.gallery.grid li:hover {
background: #ccc;
}
.gallery.grid li {
display: inline-block;
border-top: 1px solid #eee;
border-right: 1px solid #ccc;
border-bottom: 1px solid #ccc;
border-left: 1px solid #eee;
padding: 6px;
position: relative;
-moz-box-sizing: border-box;
border-radius: 3px 3px 3px 3px;
background: #fff;
}
.gallery a {
display: block;
}
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css" rel="stylesheet" />
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet" />
<div class="container-fluid">
<div class="row">
<div class="col-sm"><span class="toggler btn-link" data-selector="parent_">Toggle Parents</span></div>
<div class="col-sm"><span class="toggler btn-link" data-selector="child_">Toggle Children</span></div>
</div>
</div>
<div class="container-fluid">
<div id="activities" class="menu-container">
<h1 class="heading">
<a href="javascript:;" onclick="HideShow(this,'parent_activities')">
<i class='fa fa-minus' aria-hidden='true'></i>
</a> Activities
<span style="color:#ccc;"></span>
</h1>
<div id="parent_activities" class="sub-container">
<div id="activities__award-medal" class="item">
<a href="javascript:;" onclick="HideShow(this,'child_award-medal')">
<i class='fa fa-minus' aria-hidden='true'></i>
</a> award-medal
<span style="color:#ccc;"></span>
</div>
<div id="child_award-medal" class="indent">
<ul class="gallery grid">
<li>
<a href="# ">
<img title="military medal - 🎖️" src="https://cdn.jsdelivr.net/emojione/assets/svg/1f396.svg " class="icon" role="presentation">
</a>
</li>
</ul>
</div>
<div id="activities__event " class="item">
<a href="javascript:; " onclick="HideShow(this, 'child_event') ">
<i class='fa fa-minus' aria-hidden='true'></i>
</a> event
<span style="color:#ccc; "></span>
</div>
<div id="child_event " class="indent">
<ul class="gallery grid ">
<li>
<a href="# ">
<img title="jack-o-lantern - 🎃" src="https://cdn.jsdelivr.net/emojione/assets/svg/1f383.svg" class="icon" role="presentation">
</a>
</li>
</ul>
</div>
</div>
</div>
<div id="animals-nature" class="menu-container">
<h1 class="heading"><i class='fa fa-minus' aria-hidden='true'></i> Animals & Nature <span style="color:#ccc;"></span></h1>
<div id="parent_animals-nature" class="sub-container">
<div id="animals-nature__animal-amphibian " class="item ">
<a href="javascript:;" onclick="HideShow(this, 'child_animal-amphibian')">
<i class='fa fa-minus' aria-hidden='true'></i>
</a> animal-amphibian
<span style="color:#ccc;"></span>
</div>
<div id="child_animal-amphibian" class="indent">
<ul class="gallery grid">
<li>
<a href="# ">
<img title="frog face - 🐸 " src="https://cdn.jsdelivr.net/emojione/assets/svg/1f438.svg " style="width:64x; height:64px " role="presentation ">
</a>
</li>
</ul>
</div>
<div id="animals-nature__animal-bird " class="item">
<a href="javascript:;" onclick="HideShow(this, 'child_animal-bird')">
<i class='fa fa-minus' aria-hidden='true'></i>
</a> animal-bird
<span style="color:#ccc;"></span>
</div>
<div id="child_animal-bird" class="indent">
<ul class="gallery grid">
<li>
<a href="# ">
<img title="turkey - 🦃 " src="https://cdn.jsdelivr.net/emojione/assets/svg/1f983.svg " style="width:64x; height:64px " role="presentation ">
</a>
</li>
</ul>
</div>
</div>
</div>
Try the following selector and apply:
document.querySelectorAll('[id^="child_"]')
See the below snippet for an example:
function toggleIdStartingWith( prefix = 'parent_' ){
// Select all IDs starting with prefix and turn this NodeList into an array
// so we can loop through it easily later.
var all = [...document.querySelectorAll(`[id^="${prefix}"]`)];
// Determine whether we want to turn them on or off by
// checking the first element. You might want to also check
// if any elements are found at all before doing this.
var hidden = all[ 0 ].style.display === 'none';
// Apply the display style to all.
all.forEach(element => {
element.style.display = hidden ? '' : 'none';
});
// Return the inverted hidden value, which is what we applied.
// Useful if you want to toggle stuff, and then see what the result
// was in the code that called the function.
return !hidden;
}
// For testing purposes I am hooking two buttons up for testing this.
document.getElementById('hideshow_parents').addEventListener( 'click', event => {
event.preventDefault()
event.target.textContent = toggleIdStartingWith( 'parent_' )
? 'Show all Parents'
: 'Hide all Parents'
})
document.getElementById('hideshow_children').addEventListener( 'click', event => {
event.preventDefault()
event.target.textContent = toggleIdStartingWith( 'child_' )
? 'Show all Children'
: 'Hide all Children'
})
<div id="parent_1">Parent</div>
<div id="child_1">Child</div>
<div id="parent_2">Parent</div>
<div id="child_2">Child</div>
<div id="parent_3">Parent</div>
<div id="child_3">Child</div>
<div id="parent_4">Parent</div>
<div id="child_4">Child</div>
<div id="parent_5">Parent</div>
<div id="child_5">Child</div>
<button id="hideshow_parents">Hide/Show Parents</button>
<button id="hideshow_children">Hide/Show Children</button>
As you asked in the comment, switching the classes depending on the toggle state is easy too. I personally think you shouldn't mix html and interactivity, so I am going to use addEventListener in my example:
function toggleIdStartingWith( prefix = 'parent_' ){
var all = [...document.querySelectorAll(`[id^="${prefix}"]`)];
var hidden = all[ 0 ].style.display === 'none';
all.forEach(element => {
element.style.display = hidden ? '' : 'none';
});
return !hidden;
}
document.querySelector('h1').addEventListener( 'click', event => {
event.preventDefault()
if( toggleIdStartingWith( 'parent_' ) ){
event.target.textContent = 'Show';
event.target.classList.remove( 'fa-minus' )
event.target.classList.add( 'fa-plus' )
} else {
event.target.textContent = 'Hide';
event.target.classList.add( 'fa-minus' )
event.target.classList.remove( 'fa-plus' )
}
})
.fa-minus:before { content: '-'; }
.fa-plus:before { content: '+'; }
<div id="parent_1">Parent</div>
<div id="parent_2">Parent</div>
<div id="parent_3">Parent</div>
<div id="parent_4">Parent</div>
<div id="parent_5">Parent</div>
<h1 class="fa-minus">Hide</h1>
If you are insistent on getting it as an onclick in your html, just wrap it in a function:
function toggle( target, prefix ){
if( toggleIdStartingWith( prefix ) ){
target.textContent = 'Show';
target.classList.remove( 'fa-minus' )
target.classList.add( 'fa-plus' )
} else {
target.textContent = 'Hide';
target.classList.add( 'fa-minus' )
target.classList.remove( 'fa-plus' )
}
}
And call it as such:
<h1 onclick="toggle( this, 'parent_); return false;'"></h1>
Also, just so you know, it might be good to return false if you are going to use onclick handlers in HTML to prevent the default events from occuring. Then you can just leave your link set to # instead of the ugly javascript:;.
You should use querySelectorAll() to select "IDs starting with...". This can be done like document.querySelectorAll('[id^="start_"]') and then you iterate through the elements applying the style to hide or show.
Check out this fiddle: https://jsfiddle.net/1c38dezk/
You should use querySelectorAll() to select "IDs starting with...".
Have a nice day