when dynamically creating an element, when clicking does not see attributes - javascript

I have an array of data from which I am creating elements. And when I hang the event handler, then the dataset.container is undefined. How do I get to the attributes, or how do I create elements differently to get the data-category?
const cards = [
[],
[],
[]
]
function renderCategory(card) {
let str = '';
for (let i = 0; i < card.length; i++) {
str += `<div class="card card-category" data-category="${i}">
<div class="card-image">
<img src="${card[i].image}">
</div>
<div class="card-desc">
<div class="card-text">${card[i].name}</div>
</div>
</div>`;
}
return str;
}
let categoryCard = cards[0];
function createCard(cards) {
const main = document.getElementById('category');
main.innerHTML += renderCategory(cards);
}
createCard(categoryCard)
document.addEventListener('DOMContentLoaded', () => {
document.querySelector('#category').addEventListener('click', ({
target: {
dataset
}
}) => {
console.log(dataset.category) // undefined
})
})

So several things
For one, I could not recognise your addEventListener('click', ({ target: { dataset } construction as valid JavaScript.
I have tried to not change a lot but I
return complete set of cards
delegate from container correctly - clicking anywhere in the card will return the category
let categoryCard;
const cards = [
{ image : "https://via.placeholder.com/128x90.png?text=Card1", name:"Card 1"},
{ image : "https://via.placeholder.com/128x90.png?text=Card2", name:"Card 2"},
{ image : "https://via.placeholder.com/128x90.png?text=Card3", name:"Card 3"}
]
function renderCategory(cards) {
return cards.map((card,i) => (
`<div class="card card-category" data-category="${i}">
<div class="card-image">
<img src="${card.image}">
</div>
<div class="card-desc">
<div class="card-text">${card.name}</div>
</div>
</div>`)).join("");
}
function createCard(cards) {
const main = document.getElementById('category');
main.innerHTML += renderCategory(cards);
categoryCard = main.querySelector(".card-category"); // first card
}
window.addEventListener('load', () => {
document.querySelector('#category').addEventListener('click', e => {
const tgt = e.target;
const parent = tgt.classList.contains("card-category") ? tgt : tgt.closest(".card-category")
console.log(parent && parent.dataset.category ? parent.dataset.category : "clicked somewhere else")
})
createCard(cards)
})
<div id="category"></div>

event.target refers to the lowest Element of the DOM tree on which the event was triggered. In other words, depending on where you click the target can be the card-category DIV, the card-image DIV, the card-desc DIV, the card-text DIV or the IMG element. That's why you're not able to access the data-attribute which is only set on the parent.
If you want to always get the Element to which you bound the event, use event.currentTarget instead. But in your case, you bound the event to the main #category wrapper. If you want to bind the event to the element that has the data-category attribute, you'll have to reorganize your code to use document.createElement instead of producing HTML strings.
Keep in mind you should append elements to the page before binding events to them.

Related

Jquery code is not working in next js . Showing unexpected results but working on react

i am trying to implement an ui requirement. I want to add a active class name to the children div one at a time. 1st it will add the class in first child, and then the class will be removed and to be added in the 2nd child div. And it will infinitly itereate.
Here is my code in next js
$(".softwares_container").each(function () {
(function ($set) {
setInterval(function () {
var $cur = $set
.find(`.${st.active}`)
.removeClass(`${st.active}`);
//store inner html of current item
var $next = $cur.next().length
? $cur.next()
: $set.children().eq(0);
$next.addClass(`${st.active}`);
//store inner element of next item
//set inner html of current item to inner html of next item
var $next_inner = $next.children().eq(0);
setValue({
name: $next_inner.attr('alt'),
description: $next_inner.attr('data-info')
})
// setImage($next_inner.attr('src'))
}, 1000);
})($(this));
});
<div className={`softwares_container ${st.left_container}`}>
<div className={` ${st.img}`} alt="1">
<img src={ae.src} data-info="this is aftereffects" alt="After effects" />
</div>
<div className={st.img} alt="2">
<img src={pr.src} alt="Adobe Premiere pro" />
</div>
<div className={st.img}>
<img src={ps.src} alt="Adobe Photoshop" />
</div>
<div className={st.img}>
<img src={xd.src} alt="Adobe Xd" />
</div>
</div>
But it is not working.it is showing unexpected behaviour. It works fine in react .
Can anyone please give me an alternative solution or tell me how to fix the issue?
Here's the link where you can see the unexpected behaviour.
https://diptnc.ml/about
You can write an effect that sets the classname for elements in an array in a round-robin manner.
// Keep the interval id around so that
// it can be cleared when unsubscribing the effect.
let activeFxId;
/*
Applies active class to an array of HTMLElement in a round-robin manner.
*/
function activeFx(elements) {
activeFxId = setInterval(() => {
const elementsArr = [...elements];
let idx = elementsArr.findIndex((element) => {
return element.classList.contains('active');
});
if (idx === -1) {
idx = 0;
}
elementsArr[idx].classList.remove('active');
elementsArr[(idx + 1) % elementsArr.length].classList.add('active');
}, 2000);
return () => {
clearInterval(activeFxId);
};
}
How you provide this array of elements is left to you. An approach is to store a ref to the parent element containing them and pass that on to the function.
For example,
/* Example component */
import React, {useEffect, useRef} from 'react';
export default () => {
const ref = useRef();
useEffect(() => {
if (ref.current && ref.current.children) {
return activeFx(ref.current.children);
}
});
return (
<div ref={ref}>
<div>One</div>
<div>Two</div>
<div>Three</div>
</div>
);
};

JavaScript classes, how does one apply "Separation of Concerns" and "Don't repeat Yourself" (DRY) in practice

I'm just in the process of learning how JavaScript classes work and I'm just looking for some advice on how to achieve something quite simple I hope regarding animating some elements.
I have created a class named myAnimation, the constructor takes in 1 argument which is an element. All its doing is fading a heading out and in, all very simple. It works fine when there is just one heading element on the page, I'm just not to sure how I go about getting it to work with more than one heading.
Please excuse my naivety with this; it's all very new to me, this is just a basic example I have managed to make myself to try and help myself understand how it works.
class myAnimation {
constructor(element) {
this.element = document.querySelector(element);
}
fadeOut(time) {
if (this.element.classList.contains('fadeout-active')) {
this.element.style.opacity = 1;
this.element.classList.remove('fadeout-active');
button.textContent = 'Hide Heading';
} else {
this.element.style.opacity = 0;
this.element.style.transition = `all ${time}s ease`;
this.element.classList.add('fadeout-active');
button.textContent = 'Show Heading';
}
}
}
const heading = new myAnimation('.heading');
const button = document.querySelector('.button');
button.addEventListener('click', () => {
heading.fadeOut(1);
});
<div class="intro">
<h1 class="heading">Intro Heading</h1>
<p>This is the intro section</p>
<button class="button">Hide Heading</button>
</div>
<div class="main">
<h1 class="heading">Main Heading</h1>
<p>This is the main section</p>
<button class="button">Hide Heading</button>
</div>
After my comment I wanted to make the script run in a way I thought it might have been intended by the OP.
Even though it demonstrates what needs to be done in order to run properly, the entire base design proofs to be not fitting to what the OP really might need to achieve.
The class is called Animation but from the beginning it was intermingling element-animation and changing state of a single somehow globally scoped button.
Even though running now, the design does not proof to be a real fit because one now passes the element that is going to be animated and the button it shall interact with altogether into the constructor.
The functionality is grouped correctly, just the place and the naming doesn't really fit.
The OP might think about a next iteration step of the provided code ...
class Animation {
constructor(elementNode, buttonNode) {
this.element = elementNode;
this.button = buttonNode;
// only in case both elements were passed ...
if (elementNode && buttonNode) {
// couple them by event listening/handling.
buttonNode.addEventListener('click', () => {
// - accessing the `Animation` instance's `this` context
// gets assured by making use of an arrow function.
this.fadeOut(1);
});
}
}
fadeOut(time) {
if (this.element.classList.contains('fadeout-active')) {
this.element.style.opacity = 1;
this.element.classList.remove('fadeout-active');
this.button.textContent = 'Hide Heading';
} else {
this.element.style.opacity = 0;
this.element.style.transition = `all ${time}s ease`;
this.element.classList.add('fadeout-active');
this.button.textContent = 'Show Heading';
}
}
}
function initializeAnimations() {
// get list of all elements that have a `heading` class name.
const headingList = document.querySelectorAll('.heading');
// for each heading element do ...
headingList.forEach(function (headingNode) {
// ... access its parent element and query again for a single button.
const buttonNode = headingNode.parentElement.querySelector('.button');
// if the related button element exists ...
if (buttonNode) {
// ... create a new `Animation` instance.
new Animation(headingNode, buttonNode);
}
});
}
initializeAnimations();
.as-console-wrapper { max-height: 100%!important; top: 0; }
<div class="intro">
<h1 class="heading">Intro Heading</h1>
<p>This is the intro section</p>
<button class="button">Hide Heading</button>
</div>
<div class="main">
<h1 class="heading">Main Heading</h1>
<p>This is the main section</p>
<button class="button">Hide Heading</button>
</div>
... new day, next possible iteration step ...
The 2nd iteration separates concerns.
It does so by renaming the class and implementing only class specific behavior. Thus a FadeToggle class provides just toggle specific functionality.
The code then gets split into two functions that handle initialization. For better reuse the initializing code and the html structure need to be refactored into something more generic. The data attribute of each container that features a trigger-element for fading a target element will be used as a configuration storage that provides all necessary information for the initializing process. (One even can provide individual transition duration values.)
Last there is a handler function that is implemented in a way that it can be reused by bind in order to generate a closure which provides all the necessary data for each trigger-target couple.
class FadeToggle {
// a clean fade-toggle implementation.
constructor(elementNode, duration) {
duration = parseFloat(duration, 10);
duration = Number.isFinite(duration) ? duration : 1;
elementNode.style.opacity = 1;
elementNode.style.transition = `all ${ duration }s ease`;
this.element = elementNode;
}
isFadeoutActive() {
return this.element.classList.contains('fadeout-active');
}
toggleFade(duration) {
duration = parseFloat(duration, 10);
if (Number.isFinite(duration)) {
this.element.style.transitionDuration = `${ duration }s`;
}
if (this.isFadeoutActive()) {
this.element.style.opacity = 1;
this.element.classList.remove('fadeout-active');
} else {
this.element.style.opacity = 0;
this.element.classList.add('fadeout-active');
}
}
}
function handleFadeToggleWithBoundContext(/* evt */) {
const { trigger, target } = this;
if (target.isFadeoutActive()) {
trigger.textContent = 'Hide Heading';
} else {
trigger.textContent = 'Show Heading';
}
target.toggleFade();
}
function initializeFadeToggle(elmNode) {
// parse an element node's fade-toggle configuration.
const config = JSON.parse(elmNode.dataset.fadeToggleConfig || null);
const selectors = (config && config.selectors);
if (selectors) {
try {
// query both the triggering and the target element
const trigger = elmNode.querySelector(selectors.trigger || null);
let target = elmNode.querySelector(selectors.target || null);
if (trigger && target) {
// create a `FadeToggle` target type.
target = new FadeToggle(target, config.duration);
// couple trigger and target by event listening/handling ...
trigger.addEventListener(
'click',
handleFadeToggleWithBoundContext.bind({
// ... and binding both as context properties to the handler.
trigger,
target
})
);
}
} catch (exception) {
console.warn(exception.message, exception);
}
}
}
function initializeEveryFadeToggle() {
// get list of all elements that contain a fade-toggle configuration
const configContainerList = document.querySelectorAll('[data-fade-toggle-config]');
// do initialization for each container separately.
configContainerList.forEach(initializeFadeToggle);
}
initializeEveryFadeToggle();
.as-console-wrapper { max-height: 100%!important; top: 0; }
<div class="intro" data-fade-toggle-config='{"selectors":{"trigger":".button","target":".heading"},"duration":3}'>
<h1 class="heading">Intro Heading</h1>
<p>This is the intro section</p>
<button class="button">Hide Heading</button>
</div>
<div class="main" data-fade-toggle-config='{"selectors":{"trigger":".button","target":".heading"}}'>
<h1 class="heading">Main Heading</h1>
<p>This is the main section</p>
<button class="button">Hide Heading</button>
</div>
... afternoon, improve the handling of state changes ...
There is still hard wired data, written directly into the code. In order to get rid of string-values that will be (re)rendered every time a toggle-change takes place one might give the data-based configuration-approach another chance.
This time each triggering element might feature a configuration that provides state depended values. Thus the initialization process needs to take care of retrieving this data and also of rendering it according to the initial state of a fade-toggle target.
This goal directly brings up the necessity of a render function for a trigger element because one needs to change a trigger's state not only initially but also with every fade-toggle.
And this again will change the handler function in a way that in addition it features bound state values too in order to delegate such data to the render process ...
class FadeToggle {
// a clean fade-toggle implementation.
constructor(elementNode, duration) {
duration = parseFloat(duration, 10);
duration = Number.isFinite(duration) ? duration : 1;
elementNode.style.opacity = 1;
elementNode.style.transition = `all ${ duration }s ease`;
this.element = elementNode;
}
isFadeoutActive() {
return this.element.classList.contains('fadeout-active');
}
toggleFade(duration) {
duration = parseFloat(duration, 10);
if (Number.isFinite(duration)) {
this.element.style.transitionDuration = `${ duration }s`;
}
if (this.isFadeoutActive()) {
this.element.style.opacity = 1;
this.element.classList.remove('fadeout-active');
} else {
this.element.style.opacity = 0;
this.element.classList.add('fadeout-active');
}
}
}
function renderTargetStateDependedTriggerText(target, trigger, fadeinText, fadeoutText) {
if ((fadeinText !== null) && (fadeoutText !== null)) {
if (target.isFadeoutActive()) {
trigger.textContent = fadeinText;
} else {
trigger.textContent = fadeoutText;
}
}
}
function handleFadeToggleWithBoundContext(/* evt */) {
// retrieve context data.
const { target, trigger, fadeinText, fadeoutText } = this;
target.toggleFade();
renderTargetStateDependedTriggerText(
target,
trigger,
fadeinText,
fadeoutText
);
}
function initializeFadeToggle(elmNode) {
// parse an element node's fade-toggle configuration.
let config = JSON.parse(elmNode.dataset.fadeToggleConfig || null);
const selectors = (config && config.selectors);
if (selectors) {
try {
// query both the triggering and the target element
const trigger = elmNode.querySelector(selectors.trigger || null);
let target = elmNode.querySelector(selectors.target || null);
if (trigger && target) {
// create a `FadeToggle` target type.
target = new FadeToggle(target, config.duration);
// parse a trigger node's fade-toggle configuration and state.
const triggerStates = ((
JSON.parse(trigger.dataset.fadeToggleTriggerConfig || null)
|| {}
).states || {});
// get a trigger node's state change values.
const fadeinStateValues = (triggerStates.fadein || {});
const fadeoutStateValues = (triggerStates.fadeout || {});
// get a trigger node's state change text contents.
const fadeinText = fadeinStateValues.textContent || null;
const fadeoutText = fadeoutStateValues.textContent || null;
// rerender trigger node's initial text value.
renderTargetStateDependedTriggerText(
target,
trigger,
fadeinText,
fadeoutText
);
// couple trigger and target by event listening/handling ...
trigger.addEventListener(
'click',
handleFadeToggleWithBoundContext.bind({
// ... and by binding both and some text values
// that are sensitive to state changes
// as context properties to the handler.
target,
trigger,
fadeinText,
fadeoutText
})
);
}
} catch (exception) {
console.warn(exception.message, exception);
}
}
}
function initializeEveryFadeToggle() {
// get list of all elements that contain a fade-toggle configuration
const configContainerList = document.querySelectorAll('[data-fade-toggle-config]');
// do initialization for each container separately.
configContainerList.forEach(initializeFadeToggle);
}
initializeEveryFadeToggle();
.as-console-wrapper { max-height: 100%!important; top: 0; }
<div class="intro" data-fade-toggle-config='{"selectors":{"trigger":".button","target":".heading"},"duration":3}'>
<h1 class="heading">Intro Heading</h1>
<p>This is the intro section</p>
<button class="button" data-fade-toggle-trigger-config='{"states":{"fadeout":{"textContent":"Hide Heading"},"fadein":{"textContent":"Show Heading"}}}'>Toggle Heading</button>
</div>
<div class="main" data-fade-toggle-config='{"selectors":{"trigger":".button","target":".heading"}}'>
<h1 class="heading">Main Heading</h1>
<p>This is the main section</p>
<button class="button">Toggle Heading</button>
</div>
This is happening because document.querySelector(".button") only returns the first element with class .button (reference).
You might want to try document.querySelectorAll(".button") (reference) to add your event listeners.
(Though this will only toggle your first heading - for the very same reason. ;))

DOM getElementById as getting the whole element

why doesn't the gift just get the div id?
clicking modal is returning the whole div instead of just the id, why is that?
jsfidle
function Modals(id, bt, show) {
const modal = document.getElementById(id)
modal.classList.add(show)
modal.addEventListener("click", (elemento) => {
if (elemento.target.id === modal) {
modal.classList.remove(show)
console.log(modal)
}
})
}
const comentarios = document.querySelector(".bt_comentarios")
comentarios.addEventListener("click", () => Modals("modal_comentarios",
"bt_comentarios", "show"))
<div id="modal_comentarios" class="modal fix"></div>
document.getElementById will return an instance of HTMLElement. To get the id of the element, you'll need to use the id property.
const main = document.getElementById('main');
console.log(main);
console.log(main.id);
<div id="main">
</div>
Using your snippet
function Modals(id, bt, show) {
const modal = document.getElementById(id)
modal.classList.add(show)
modal.addEventListener("click", (elemento) => {
// EDIT: I used modal.id
if (elemento.target.id === modal.id) {
modal.classList.remove(show)
// EDIT: I used modal.id
console.log(modal.id)
}
})
}
const comentarios = document.querySelector(".bt_comentarios")
comentarios.addEventListener("click", () => Modals("modal_comentarios",
"bt_comentarios", "show"))
<div id="modal_comentarios" class="modal fix">
<button id="modal_comentarios" class="bt_comentarios">Click Me!</button>
</div>
What I changed
I added a few pieces to make the code go down the path of your if statement, and referenced the modal.id property instead of the instance of the HTMLElement itself. Let me know if you have more questions.

Click event on div with img and text not firing when clicking img

I wrote toggle script in ES6/vanilla JS. The intended functionality is super simple, you click on the toggle div and it adds an active class to another div that matches the toggle div's data-toggle property. In my toggle div, I need there to be both text and an image. It works great when you click on the text within the div, but when you click on the image within the div, the toggle is not firing. Is there something specific I need to do to include all of the children within the div?
For some reason, I can't even get this working via this code snippet editor, but it is working in my project.
const setActive = (toggles, panels, id) => {
let activePanel = panels.filter(panel => panel.getAttribute('data-toggle') == id)
let activeToggle = toggles.filter(toggle => toggle.getAttribute('data-toggle') == id)
activePanel.forEach(panel => panel.classList.add('active'))
activeToggle.forEach(toggle => toggle.classList.add('active'))
}
const removeActive = (nodes) => {
nodes.forEach(node => node.classList.remove('active'))
}
const handler = (event) => {
event.preventDefault()
let id = event.target.getAttribute('data-toggle')
let panels = Array(...document.querySelectorAll('.js-toggle-panel'))
let toggles = Array(...document.querySelectorAll('.js-toggle'))
removeActive(panels)
removeActive(toggles)
setActive(toggles, panels, id)
}
let toggles = Array(...document.querySelectorAll('.js-toggle'))
toggles.forEach(toggle => toggle.addEventListener('click', handler))
.toggle-panel {
display: none;
}
.toggle-panel .active {
display: block;
}
<div class="js-toggle toggle" data-toggle="toggle-1">
<img src="https://via.placeholder.com/50"> First toggle
</div>
<div class="js-toggle toggle" data-toggle="toggle-2">
Second toggle
</div>
<div class="js-toggle-panel toggle-panel" data-toggle="toggle-1">
<h1>Toggle 1</h1>
</div>
<div class="js-toggle-panel toggle-panel" data-toggle="toggle-2">
<h1>Second toggle!</h1>
</div>
I changed two things that I believe will resolve your issue:
I changed the selector .toggle-panel .active to .toggle-panel.active-- without that, even in the cases where the JS was working as you intended nothing was actually be made visible.
I moved your code from using event.target to event.currentTarget -- the former always points to the clicked element, whereas the latter refers to the element on which the listener has been placed.
See the snippet below.
const setActive = (toggles, panels, id) => {
let activePanel = panels.filter(panel => panel.getAttribute('data-toggle') == id)
let activeToggle = toggles.filter(toggle => toggle.getAttribute('data-toggle') == id)
activePanel.forEach(panel => panel.classList.add('active'))
activeToggle.forEach(toggle => toggle.classList.add('active'))
}
const removeActive = (nodes) => {
nodes.forEach(node => node.classList.remove('active'))
}
const handler = (event) => {
event.preventDefault()
let id = event.currentTarget.getAttribute('data-toggle')
let panels = Array(...document.querySelectorAll('.js-toggle-panel'))
let toggles = Array(...document.querySelectorAll('.js-toggle'))
removeActive(panels)
removeActive(toggles)
setActive(toggles, panels, id)
}
let toggles = Array(...document.querySelectorAll('.js-toggle'))
toggles.forEach(toggle => toggle.addEventListener('click', handler))
.toggle-panel {
display: none;
}
.toggle-panel.active {
display: block;
}
<div class="js-toggle toggle" data-toggle="toggle-1">
<img src="https://via.placeholder.com/50"> First toggle
</div>
<div class="js-toggle toggle" data-toggle="toggle-2">
Second toggle
</div>
<div class="js-toggle-panel toggle-panel" data-toggle="toggle-1">
<h1>Toggle 1</h1>
</div>
<div class="js-toggle-panel toggle-panel" data-toggle="toggle-2">
<h1>Second toggle!</h1>
</div>
Instead of event.target you should use event.currentTarget in your handler function to return node to which event listener is attached. event.target is returning <img> node, not <div> with data-toggle in your case.

JavaScript recursive array to select html elements

I'm currently trying to make a recursive function that takes html elements as an array so I can take html elements like the querySelector function
The reason i'm doing this is because I can't use getElementsByTagName() or querySelector()
Here is my code:
function flatten(items)
{
const flat = [];
items.forEach(item => {
if (Array.isArray(item)) {
flat.push(...flatten(item));
}
else {
flat.push(item);
}
});
return flat;
}
var button = flatten(footer).flatten(div);
count = 0;
button.onclick = function() {
count += 1;
button.innerHTML = count;
};
I get the following error: ReferenceError: footer is not defined
Thanks
Here is my HTML code:
<div class="wrapper">
<div class="container">
<footer>
<div>
</div>
</footer>
</div>
</div>
Edit:
footer is defined in my HTML, I want to select footer in my function
Also, I can't add class or id to my html, I can't edit it
If, for the sake of practice (or a lost bet), you'd want to write your own querySelectorAll, you could write a recursive function that walks the DOM tree... The only thing you rely on is an entrance to the DOM: window.document.
Note that this will never be able to compete with the performance of your browser's default query implementations. We're just doing it to show we can.
Step 1: recursively walking the document (depth-first)
const walk = (el) => {
console.log(el.nodeName);
Array.from(el.children).forEach(walk);
};
walk(document);
<div class="wrapper">
<div class="container">
<footer>
<div>
</div>
</footer>
</div>
</div>
As you can see, this function loops over each element in the document and its children.
Step 2: Adding the filter logic
If you want it to actually find and return elements, you'll have to pass some sort of filtering logic. querySelectorAll works with string inputs, which you could try to recreate... Since we're redoing this for fun, our select will work with functions of HTMLElement -> bool.
const selectIn = (pred, el, result = []) => {
if (pred(el)) result.push(el);
Array.from(el.children)
.filter(e => e)
.map(el2 => selectIn(pred, el2, result));
return result;
}
// EXAMPLE APP
// Define some selectors
const withClass = className => el =>
el && el.classList && el.classList.contains(className);
const withTag = tagName => el =>
el && el.nodeName === tagName.toUpperCase();
// Select some elements
const footer = selectIn(withTag("footer"), document)[0];
const container = selectIn(withClass("container"), document)[0];
const divsInFooter = selectIn(withTag("div"), footer);
// Log the results
console.log(`
footer:
${footer.outerHTML}
container:
${container.outerHTML}
divsInFooter:
${divsInFooter.map(d => d.outerHTML)}
`);
<div class="wrapper"><div class="container"><footer><div></div></footer></div></div>

Categories