Multiple event handlers only firing on first element, not the target - javascript

So I am trying to make a more/less content toggle feature, it worked just fine until I started adding more boxes to be toggled. I read up on using multiple event handlers and the suggestion was to use document.getElementsByClassName(); and iterate through them, but the second button when clicked does not show/hide the correct content, only the first one.
Any help is appreciated. The simpler way would be to use jQuery or assign different IDs to the buttons and call the function on each one but I'm sure it must be possible to just create/call the event listener once and have it work on every subsequently added element.
Here is my code:
HTML
<div class="card">
<div class="cardLessContent">
<h1>Here is a card</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent auctor mi sollicitudin, tristique tortor eget, tempus elit. Vestibulum ante leo, aliquam ac dapibus at, auctor vitae ligula.</p>
</div>
<div id="cardMoreContent" class="cardMoreContent">
<p> Nam finibus nec augue at semper. Quisque sit amet ex eu augue rutrum aliquet. Suspendisse dui enim, gravida quis turpis a, auctor pellentesque velit.</p>
</div>
<button id="toggleContent" class="toggleContent toggledOff">More</button>
</div>
<div class="card">
<div class="cardLessContent">
<h1>Here is a card</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent auctor mi sollicitudin, tristique tortor eget, tempus elit. Vestibulum ante leo, aliquam ac dapibus at, auctor vitae ligula.</p>
</div>
<div id="cardMoreContent" class="cardMoreContent">
<p> Nam finibus nec augue at semper. Quisque sit amet ex eu augue rutrum aliquet. Suspendisse dui enim, gravida quis turpis a, auctor pellentesque velit.</p>
</div>
<button id="toggleContent" class="toggleContent toggledOff">More</button>
</div>
JavaScript
const btnToggleContent = document.getElementsByClassName("toggleContent");
const cardMoreContent = document.getElementById("cardMoreContent");
var toggleContent = function() {
cardMoreContent.classList.toggle("showing");
this.classList.toggle("toggledOff");
if(this.classList.contains("toggledOff")) {
this.innerHTML = "More";
}
else {
this.innerHTML = "Less";
}
}
for (var i = 0; i < btnToggleContent.length; i++) {
btnToggleContent[i].addEventListener("click", toggleContent);
}
JSFiddle: https://jsfiddle.net/jw3qe9xf/6/

Id attribute should be unique in HTML.Otherwise it will only pick the first occurence of that Id.In your case you will always toggle the first cardMoreContent
Here is a simple solution for you by using event.target
Javascript
const btnToggleContent = document.getElementsByClassName("toggleContent");
const cardMoreContent = document.getElementById("cardMoreContent");
var toggleContent = function(e) {
e.target.previousElementSibling.classList.toggle("showing");
this.classList.toggle("toggledOff");
if(this.classList.contains("toggledOff")) {
this.innerHTML = "More";
}
else {
this.innerHTML = "Less";
}
}
for (var i = 0; i < btnToggleContent.length; i++) {
btnToggleContent[i].addEventListener("click", toggleContent);
}
JSFiddle : https://jsfiddle.net/c1gdjk25/

Yes , works only for first. This is reason :
you have multiply use ' id="toggleContent" ' same id value for more than one element it is wrong in basic.
Id attribute must be uniq !
Use example from this answer :
How to get child element by class name?
You will need only childNodes and loop trow it.
Count on that you can search child from child element also ...

after you remove all ids attributes change your javascript code to :
const btnToggleContent = document.getElementsByClassName("toggleContent");
var toggleContent = function(e) {
/*e.target will get the clicked element which is the button clicked and parentElement
gives you the parent element of this button which is the card you want to modify*/
var card = e.target.parentElement;
/*when you have the card you can use on its getElementsByClassName to retreive all
elements that have the class (cardMoreContent) in this case theres only one so you take
the first element from the array returned with [0]*/
var cardMoreContent = card.getElementsByClassName("cardMoreContent")[0];
//then rest of the code stays the same
cardMoreContent.classList.toggle("showing");
if(this.classList.contains("toggledOff")) {
this.innerHTML = "More";
}
else {
this.innerHTML = "Less";
}
}
for (var i = 0; i < btnToggleContent.length; i++) {
btnToggleContent[i].addEventListener("click", toggleContent);
}

Related

JS. How to Break selection/range into fragments per containing blocks and remove inline element formatting?

Lets say we have a div
<div id="my-div">
<p>Lorem ipsum dolor sit <strong>amet |consequat pharetra auctor </strong>condimentum lacus purus tortor habitasse molestie.</p>
<p>Auctor luctus lacus imperdiet <strong>pharetra| consequat</strong> egestas faucibus.</p>
</div>
And user selects a portion similar the one between "|".
function getSelectionPortion(){
const div = document.getElementById('my-div');
const selection = document.getSelection().getRangeAt(0).cloneContents(); // or extractContents();
return selection;
}
getSelectionPortion() will return the following:
<p><strong>consequat pharetra auctor </strong>condimentum lacus purus tortor habitasse molestie.</p>
<p>Auctor luctus lacus imperdiet <strong>pharetra</strong></p>
now we can get portions of the range by paragraphs, simply by e.g. fragment.querySelectorAll('p').
And if we extract the range contents and loop over each portion, we can take each strong inside each p and replace with its innerHTML
let blocks = getSelectionPortion().querySelectorAll('p');
for (let block of blocks){
let inlines = block.querySelectorAll('strong');
for (let inline of inlines){
inline.replaceWith(inline.innerHTML);
}
}
However, this outputs with additional paragraphs, because extracted fragment closes the paragraph tags. So we end up with
<div id="my-div">
<p>Lorem ipsum dolor sit <strong>amet</strong></p>
<p>consequat pharetra auctor condimentum lacus purus tortor habitasse molestie.</p>
<p>Auctor luctus lacus imperdiet pharetra</p>
<p><strong>consequat</strong> egestas faucibus.</p>
</div>
How do you remove the inline tag from the selection without creating additional block level elements with fragment? Is there a way to divide the range into peaces instead of its extract?

Is there an option in CSS or JS to force single-character words at the end of a line to move to the next one?

While creating a responsive web design it often happens that a one-letter word remains as the last word in a paragraph's line:
This is a
paragraph
I would like to move such a single-letter word as follows:
This is
a paragraph
Is there any built-in property in CSS which allows to achieve this effect?
If there is none, how can this be done in JavaScript?
If you want to ALWAYS keep "a" and "paragraph" together, add a "non-breaking-space" between them:
<div style="width:100px;border:1px solid #333;padding:5px">
<p>
This is a paragraph
</p>
</div>
In order to keep single-letter words (like 'a' or 'I') on the same line as the following word, you can replace the space character between them with the non-breaking space Unicode character \u00A0.
This could be automated with a little JavaScript. For example, this code replaces [space][letter][space] with [space][letter][non-breaking space]:
const modifySingleChars = str => str.replace(/ ([a-zA-Z]) /g,
' $1' + '\u00A0');
To change all instances on a page, first collect all the text nodes within the body (skipping anything inside a <script> tag.
Then simply iterate through the text nodes, making the appropriate substitutions.
A working example:
// form array of all text nodes in parentNode
function allTextNodes(parentNode) {
let arr = [];
if (!parentNode) {
return arr;
}
let nodes = parentNode.childNodes;
nodes.forEach(node => {
if (node.nodeName === 'SCRIPT') {
return;
}
if (node.nodeType === Node.TEXT_NODE) {
arr.push(node);
} else {
arr = arr.concat(allTextNodes(node));
}
});
return arr;
}
// convert [space][letter][space] to [space][letter][non-breaking space];
const modifySingleCharWords = str => str.replace(/ ([a-zA-Z]) /g,
' $1' + '\u00A0');
function fixAllSingleCharWordsInBody() {
let tNodes = allTextNodes(document.body);
tNodes.forEach(tNode => {
tNode.nodeValue = modifySingleCharWords(tNode.nodeValue);
});
}
<body>
<div id="wrapper" style="width:20rem">
<h4>Prevent single character words at end of line</h4>
<button type="button" onclick="fixAllSingleCharWordsInBody();">Fix All Words
</button>
<p>Lorem ipsum dolor i amet, consectetur a dipiscing elit, sed o eiusmod tempor incididunt u labore et dolore magna aliqua. <span>Nisl purus i mollis</span> nunc.
</p>
<p>In vitae turpis massa e elementum tempusus a sed. Eget mi proin e libero enim i faucibus. Quis lectus nulla a volutpat diam ut.
</p>
<p>Pharetra e ultrices neque ornare. Donec a tristique risus e feugiat in fermentum. Consectetur adipiscing e u aliquam purus sit amet.
</p>
<p>Vitae congue mauris rhoncus aenean e elit scelerisque mauris pellentesque. Mauris u eros i cursus turpis a tincidunt dui.
</p>
<p>At volutpat diam u venenatis tellus. Tellus integer feugiat scelerisque varius morbi i nunc faucibus at.</p>
<script>
const b = 'Do not modify anything inside a script tag';
</script>
</div>
</body>
from few years we have ES6 so...
[...document.body.querySelectorAll('*')]
.map(n => n.firstChild)
.filter(n => (n != null && n.nodeType === Node.TEXT_NODE && !['SCRIPT', 'STYLE'].includes(n.parentNode.nodeName)))
.forEach(el => {
el.nodeValue = el.nodeValue.replace(/ ([a-zA-Z]) /g, ` $1\u00A0`)
});
document.body can be replaced by any node, here is for simplicity

Can you 'customize' non built-in elements?

In the MDN documentation for using custom elements, they detail an example for customizing built-in elements:
customElements.define('expanding-list', ExpandingList, { extends: "ul" });
Allowing this usage:
<ul is="expanding-list">
...
</ul>
I am wondering if it is possible to customize another custom element in the same way? For example, if I have created an element called custom-element, and I want to have variants of it, I might want to create a new special-custom-element class, and define it in the same way, so as to be able to use it like so:
<custom-element is="special-custom-element">
...
</custom-element>
However, I am prompted with an error stating:
Uncaught DOMException: Failed to execute 'define' on 'CustomElementRegistry': "custom-element" is a valid custom element name
When attempting to run the following:
customElements.define('special-custom-element', SpecialCustomElement, { extends: 'custom-element' });
Is this something I am doing wrong, or is this behaviour strictly limited to built-in elements? I'm finding it rather difficult to find any information about this behaviour other than what is on the page I linked to, so I was hoping for some advice from someone who knows the specs better.
You can not do it in the sense that you want since:
There are two types of custom elements you can create:
Autonomous custom element: Standalone elements; they don't inherit from built-in HTML elements.
Customized built-in element: These elements inherit from — and extend — built-in HTML elements.
customElements.define('word-count2', WordCount2, {extends: 'p'});
Is for extending the built-in elements.
You have to go the Autonomous custom element route as per the docs
Here is an idea:
// Create a class for the element
class MyElement extends HTMLElement {
constructor(text) {
// Always call super first in constructor
super();
// Create a shadow root
var shadow = this.attachShadow({
mode: 'open'
});
// Create the span
var wrapper = document.createElement('span');
wrapper.textContent = !!text ? text : 'foo';
shadow.appendChild(wrapper);
}
}
class MyElement2 extends MyElement {
constructor() {
// Always call super first in constructor
super('bar');
}
}
// Define the new element
customElements.define('my-element', MyElement);
customElements.define('my-element2', MyElement2);
<div>
<my-element text="">
</div>
<div>
<my-element2 text="">
</div>
Now you still could extend your class (and do customization of a build-in element) and access the output of the super so that might be useful to you and to some extend might allow you to get what you need:
// Create a class for the element
class WordCount extends HTMLParagraphElement {
constructor() {
// Always call super first in constructor
super();
// count words in element's parent element
const wcParent = this.parentNode;
function countWords(node) {
const text = node.innerText || node.textContent;
return text.split(/\s+/g).length;
}
const count = `Words: ${countWords(wcParent)}`;
// Create a shadow root
const shadow = this.attachShadow({
mode: 'open'
});
// Create text node and add word count to it
const text = document.createElement('span');
text.textContent = count;
// Append it to the shadow root
shadow.appendChild(text);
// Update count when element content changes
text.textContent = count;
}
}
class WordCount2 extends WordCount {
constructor() {
// Always call super first in constructor
super();
console.log(this.shadowRoot.textContent)
}
}
// Define the new element
customElements.define('word-count', WordCount, {
extends: 'p'
});
customElements.define('word-count2', WordCount2, {
extends: 'p'
});
<div>
<h2>Sample heading</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc pulvinar sed justo sed viverra. Aliquam ac scelerisque tellus. Vivamus porttitor nunc vel nibh rutrum hendrerit. Donec viverra vestibulum pretium. Mauris at eros vitae ante pellentesque bibendum.
Etiam et blandit purus, nec aliquam libero. Etiam leo felis, pulvinar et diam id, sagittis pulvinar diam. Nunc pellentesque rutrum sapien, sed faucibus urna sodales in. Sed tortor nisl, egestas nec egestas luctus, faucibus vitae purus. Ut elit nunc,
pretium eget fermentum id, accumsan et velit. Sed mattis velit diam, a elementum nunc facilisis sit amet.</p>
<p is="word-count"></p>
</div>
<div>
<h3>Sample heading</h3>
<p>Lorem ipsum dolor sit amet, consec Sed tortor nisl, egestas nec egestas luctus, faucibus vitae purus. Ut elit nunc, pretium eget fermentum id, accumsan et velit. Sed mattis velit diam, a elementum nunc facilisis sit amet.</p>
<p is="word-count2"></p>
</div>
You can also do this
class SpecialCustomElement extends CustomElement
{
...
}
customElements.define('special-custom-element', SpecialCustomElement, { extends: 'ul' });
and can use it as
...

add value to existing class in jQuery

First of all, I have searched quite some hours to find this.. I presume it has an easy fix but I'm really new to jQuery and Javascript, so I'm here for your help.
The problem
I'm working with multiple divs and jQuery ToggleSlide(). I want the script to find the right div to open when I click the corresponding div.
For example, when I click the div 'hackandfly', I want it to open the div 'hackandfly-open'.
The code
$(document).ready(function() {
$('.project-open').hide();
$('.hackandfly, .scanergy, .connecting-food').click(function() {
$slidah = $(this);
$slidah.slideToggle();
$('div.project-open').not($slidah).slideUp();
});
});
HTML
<div class="content projects">
<h3>Projects</h3>
<div class="project project-big hackandfly">
<h3>Hack and Fly</h3>
</div>
<div class="hackandfly-open project-open" style="display: none;">
<img src="images/schiphol-logo.png" class="img-project-open"> Proin nec elit ac sapien facilisis ultrices. Integer pellentesque ex a luctus fringilla. Aenean in quam quam. Integer gravida quam eget mauris laoreet hendrerit. Vestibulum feugiat ipsum id.
<br>
<br>
Metus aliquet iaculis. Proin massa justo, maximus in tortor et, Proin massa justo, maximus in tortor et. In aliquam laoreet magna et iaculis. Vestibulum vel orci lobortis, elementum nulla eget, porta eros. Interdum et malesuada fames ac ante ipsum primis in faucibus.
<br>
<br>
Proin massa justo, maximus in tortor et, tincidunt efficitur nibh. Mauris vulputate euismod lorem, vel rutrum ipsum iaculis eu.
</div>
So what I'm looking for, is that when I push 'hackandfly' div, 'scanergy' div or the 'connecting-food' div, I want it to slideToggle the corresponding div that has -open behind it (I have 3 divs with info called hackandfly-open, scanergy-open, connecting-food-open).
I tried some things like:
$slidah = $(this).after('-open');
And some other stuff but it didn't work. Who can help me?
Cheers!
Use attr() like
$(document).ready(function() {
$('.project-open').hide();
$('.hackandfly, .scanergy, .connecting-food').click(function() {
$slidah = $($(this).attr('class')+'-open');
$slidah.slideToggle();
$('div.project-open').not($slidah).slideUp();
});
});
However, the above will fail if you have multiple classes.
A workaround would be to add data-* to the clicked elements like
<div class="hackandfly other-class" data-class-target="hackandfly-open"></div>
and then
$(document).ready(function() {
$('.project-open').hide();
$('.hackandfly, .scanergy, .connecting-food').click(function() {
$slidah = $('.'+$(this).attr('data-class-target'));
$slidah.slideToggle();
$('div.project-open').not($slidah).slideUp();
});
});
I would generate a unique click handler for each class. That way, you can store all the applicable class names in an array:
// Creates a new unique click function for each class name
function generateClickHandler(className) {
return function(e) {
// select the open class here
$slidah = $('.'+className+'-open');
$slidah.slideToggle();
$('div.project-open').not($slidah).slideUp();
};
}
// Iterate over all the class names and add a new function for each
var clsList = ["hackandfly", "scanergy", "connecting-food"];
$.each(clsList, function(className) {
$("."+className).click(generateClickHandler(className));
});
Use:
$('.hackandfly, .scanergy, .connecting-food').click(function() {
$slidah = $("."+$(this).attr('class')+"-open");
$slidah.slideToggle();
});
If you added a wrapper around each section like so:
<div class="content projects">
<h3>Projects</h3>
<div class="project-wrapper">
<div class="project project-big hackandfly">
<h3>Hack and Fly</h3>
</div>
<div class="hackandfly-open project-open" style="display: none;">
{...}
</div>
</div>
</div>
you could use parent and find to get the corresponding element as follows:
$('.hackandfly, .scanergy, .connecting-food').click(function() {
$(this).parent().find('.project-open').eq(0).slideToggle();
}

Adding View Details Button with JS

I'm trying to add a view details button to each product using JS. I want it to look for each instance of the class "clsItemBlock" and then append the html inside of clsItemPublished" The code below is adding multiple buttons on the first product, instead of one one each.
In a PERFECT world, since I know there are only 15 products per page, I could have it only check 15 times to prevent it from looping constantly.
MY JS:
var divs = document.getElementsByClassName('clsItemBlock');
for (var i = 0; i < divs.length; i++)
{
var iDiv = document.createElement('div');
iDiv.id = 'detail_button';
document.getElementsByClassName('clsItemPublished')[0].appendChild(iDiv);
iDiv.innerHTML = 'View Details';
}
FIDDLE:
http://jsfiddle.net/ptc6ws9o/1/
Or, if it is appropriate you can just use details and summary tags: http://jsfiddle.net/dorgLr71/
<details>
<summary>View Details</summary>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin
dapibus porttitor libero, eget rutrum nibh laoreet et.
Pellentesque porttitor erat ligula, id elementum est egestas
eget. Mauris gravida dui ut leo tempor, nec placerat tortor
hendrerit.
</p>
</details>
GOT IT!
var divs = document.getElementsByClassName('clsItemBlock');
for (var i = 0; i < divs.length; i++)
{
var iDiv = document.createElement('div');
iDiv.id = 'detail_button';
document.getElementsByClassName('clsItemPublished')[i].appendChild(iDiv);
iDiv.innerHTML = 'View Details';
}

Categories

Resources