I try to get height of the parent div of children elements.
I have a Parent div with class="Parent" this have also n children element like <div data-elementid="el_ryz-E9a349" class="row">
Parent have a fix height: 220px and I need to know if children element (n) <div data-elementid="el_ryz-E9a349" class="row"> appear in parrent height if not execute scrollIntoView() to this children.
Important I can't delete this both elements, empty div and <div class="container" because affects my design.
...
const scrollToBottom = () => {
const elementNode = document.querySelector(`[data-elementid='${action.payload.id}']`);
const parentElementNode = elementNode.parentNode;
const elementsHeight = parentElementNode.offsetHeight;
const menuContainer = parentElementNode.parentNode.offsetHeight;
if (elementsHeight > menuContainer) {
elementNode.scrollIntoView({
behavior: 'smooth',
block: 'end',
});
}
};
setTimeout(scrollToBottom, 200);
...
It's obvious if I've n children elements it's redundant to make elementNode.parentNode.parentNode.parentNode to access Parent node to get height property.
Use this function to go up in your element parents and search for you parent classname:
const getParent = (element, cls) => {
if (element && element.parentElement) {
const parentClassName = element.parentElement.className;
if (element.parentElement && parentClassName && parentClassName.match(new RegExp(cls, 'g'))) {
return element.parentElement; // Found it
}
getParent(element.parentElement, cls);
} else {
return false; // No parent with such a className
}
}
const scrollToBottom = () => {
const elementNode = document.querySelector(`[data-elementid='${action.payload.id}']`);
const parentElementNode = getParent(elementNode, 'parent'); // second arg is the parent classname you looking for.
if (parentElementNode) {
const elementsHeight = parentElementNode.offsetHeight;
const menuContainer = parentElementNode.parentNode.offsetHeight;
if (elementsHeight > menuContainer) {
elementNode.scrollIntoView({
behavior: 'smooth',
block: 'end',
});
}
}
console.log('no parent found!')
};
setTimeout(scrollToBottom, 200);
Select with data-atttribute:
const getParentWithAttr = (element, attr) => {
if (element && element.parentElement) {
const parentElement = element.parentElement;
if (parentElement && parentElement.getAttribute('data-attr') === attr) {
return parentElement; // Found it
}
getParent(parentElement, attr);
} else {
return false; // No parent with such a className
}
}
Use case should be like this:
<div id="..." class="..." data-attr="parent">// parrent
... // chilren
</div>
getParentWithAttr(document.querySelector('.element'), 'parent');
Since the question's tag says React.js, I would instead refer to ReactJS how to scroll to an element. This uses React refs and makes your code much simpler. That being said, it looks like the question is actually using pure JavaScript on static HTML.
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class SampleComponent extends Component {
scrollToDomRef = () => {
const myDomNode = ReactDOM.findDOMNode(this.myRef.current)
myDomNode.scrollIntoView()
};
render() {
return <> // '<></>' is a React 16.3.0+ feature, use <div> if previous
<button onClick={this.scrollToDomRef}>Click me</div>
<div ref={ re => { this.myRef = re } }></div>
</>
}
}
Related
I have a code that adds or removes a class from elements. But it does this only for the menu, but not for the content (a huge white square in the middle), although it should for both at the same time. I need that when clicking on any button in the menu, the 'is-active' class changes both in the menu and in the content.
without clicking on the menu.png
with a click on the menu.png
const list = Array.from(document.querySelectorAll('.list'))
const play = Array.from(document.querySelectorAll('.play'))
const clearActiveClass = (element, className = 'is-active') => {
element.find(item => item.classList.remove(`${ className }`))
}
const setActiveClass = (element, index, className = 'is-active') => {
element[index].classList.add(`${ className }`)
}
const checkoutTabs = (item, index) => {
item.addEventListener('click', () => {
if (item.classList.contains('is-active')) return
console.log(item)
clearActiveClass(list)
clearActiveClass(play)
setActiveClass(list, index)
setActiveClass(play, index)
})
}
list.forEach(checkoutTabs)
The inner function to find needs to be something that finds an element containing className. Once you have found the element, you can remove the className class.
const clearActiveClass = (element, className = 'is-active') => {
element.find(item => item.classList.contains(className)).classList.remove(className);
}
In a React Component Passed a value, if Component Change state, How to return a new value from this component.
If ScrollDetecter state changed how to return value this component?
<ScrollDetector/>
export default class ScrollDetector extends React.Component {
//Most impotant function only metioned
handleScroll() {
const windowHeight = "innerHeight" in window ? window.innerHeight : document.documentElement.offsetHeight;
const body = document.body;
const html = document.documentElement;
const docHeight = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight);
const windowBottom = windowHeight + window.pageYOffset;
if (windowBottom >= docHeight) {
this.setState({
message:'bottom reached'
});
} else {
this.setState({
message:'not at bottom'
});
}
}
}
How to a particular value to parent component return if bottom reached message state.?
You want to change a parent component on event in child component(ScrollDetector)?
If so pass the callback function, that will update parent component, as a prop.
Let's say you want to change the state of parent component based on scroll. You would have something like this.
<ScrollDetector checkIfBottomReached={this.checkIfBottomReached} />
while the implementation of function would be
isBottomReached() {
this.setState({ isBottomReached: true });
}
or simply pass it shorthanded
<ScrollDetector checkIfBottomReached={() => this.setState({ isBottomReached: true })} />
and inside ScrollDetector component you would call this function when you reach the bottom, just by calling this.props.checkIfBottomReached()
You could use and event handler / callback function and send a prop from parent to child so it notifies when bottom has been reached:
class ScrollDetector extendsReactComponent {
handleScroll() {
...
if (windowBottom >= docHeight) {
this.props.onBottomReached()
} else {
...
}
}
}
and from your parent component:
class YourParent extends React.Component {
render() {
return (
<div>
<ScrollDetector
onBottomReached={() => console.log('bottom reached!') }
/>
</div>
)
}
}
In a React app, I have a component to display the results of a search called ResultsContainer. I am generating children by mapping a prop that has been passed down from a parent and creating a list element from each item in the array. Each of these children has a component that I (DetailsContainer) want to render when a button is clicked, and ONLY for the child that had the button clicked. As of now, the component renders for all of these children. I am not sure how to target the individual child so that the component only renders for that specific one.
ResultsContainer:
var React = require('react');
var DetailsContainer = require('./DetailsContainer.jsx');
var ResultsContainer = React.createClass ({
render: function () {
var queryType = this.props.queryType;
var details = this.props.details;
var that = this;
if (this.props.results == null) {
var searchResult = "Enter search terms"
} else {
var searchResult = this.props.results.map(function(result, index){
//if artist search
if (queryType == "artist") {
if (result.type == "artist") {
return <li className="collection-item" key={index}> {result.title} <button onClick={that.props.handleDetailClick}>Get details</button> <DetailsContainer details={details} /> </li> ;
}
}
});
};
return (
<div className="collection">
{searchResult}
</div>
);
},
});
module.exports = ResultsContainer;
When the button is clicked, I want to render the DetailsContainer component for that child ONLY, not all of them. How could I do this?
DetailsContainer:
var React = require('react');
var DetailsContainer = React.createClass({
render: function(){
if (this.props.details !== null) {
var detailsDisplay = "deets"
}
return (
<div className="detailsDisplay">
{detailsDisplay}
</div>
);
},
});
module.exports = DetailsContainer;
Here is handleDetailClick a function being passed down from the parent as a prop:
handleDetailClick: function() {
this.setState({details: "details"});
},
Also, in ResultsContainer, I could not figure out how to use .bind() to be able to use this.props.handleDetailClick from inside the callback of the .map, so i just created a variable "that" and set it equal to "this" outside the function. Is this bad practice? What is the proper way to do it using .bind()? Thanks so much.
First off map has an option to pass this. map(function(item){}, this); You have at least two options to limit the list. One is to use state like so;
render() {
let useList;
if (this.state.oneSelected)
useList = this.props.list[this.state.oneSelected];
else
useList = this.props.list;
let useListing = useList.map(function(item){}, this);
return ({useListing})
}
The other option is to pass the selected item to the parent this.props.selectOne(item.id) and have the parent return just the one item as a list.
I'm having trouble using mixpanel.track_links with links added dynamically (after page load).
For a general example, given this page:
<div id="link-div"></div>
<input type="button" id="add-link" />
<script type="text/javascript">
mixpanel.track_links(".mixpanel-event", "event name", function(ele) { return { "type": $(ele).attr("type")}});
</script>
At some user action, links are added to the page using jquery. For example:
$('#add-link).click(function() {
$('#link-div').html('<a class="mixpanel-event" type="event-type" href="#>Link to track</a>');
})
The problem is that track_links isn't triggered on click of the newly created link. I'm hoping someone can share their experience in enabling the track_link function to work for dynamically added links.
I was curious so I checked out their code and went ahead and did as they suggested. I tested it, and it worked fine. This requires jQuery though.
Example usage: mixpanel.delegate_links(document.body, 'a', 'clicked link');
// with jQuery and mixpanel
mixpanel.delegate_links = function (parent, selector, event_name, properties) {
properties = properties || {};
parent = parent || document.body;
parent = $(parent);
parent.on('click', selector, function (event) {
var new_tab = event.which === 2 || event.metaKey || event.target.target === '_blank';
properties.url = event.target.href;
function callback() {
if (new_tab) {
return;
}
window.location = properties.url;
}
if (!new_tab) {
event.preventDefault();
setTimeout(callback, 300);
}
mixpanel.track(event_name, properties, callback);
});
};
I had a somewhat hard time trying to get tracking links working as expected on react. The main caveat I noticed was that duplicated events may be sent to mixpanel in bursts.
I used a slightly modified version of #Kyle to solve my problem. Additionally, this accounts for properties being possibly a function as supported by the mixpanel API.
// mixpanelSetup.js
import md5 from "md5";
const setup = () => {
mixpanel.init(TOKEN);
// Sets ensure unique items
mixpanel.delegated_links = new Set();
mixpanel.delegate_links = (parent, selector, eventName, eventProperties, {ignoreUrl=false}) => {
// Hash by whatever thing(s) the use case considers 'unique' (e.g md5(`${selector}__${eventName}`))
const linkHash = md5(selector);
parent = parent || document.body;
parent = $(parent);
// Do not add additional trackers for an already tracked event.
if (mixpanel.delegated_links.has(linkHash)) {
return;
}
mixpanel.delegated_links.add(linkHash);
parent.on("click", selector, (event) => {
const newTab = event.which === 2 || event.metaKey || event.target.target === "_blank";
if (typeof eventProperties === "function") {
eventProperties = eventProperties(event.target) || {};
}
eventProperties.url = event.target.href;
// In case we don't want the url on the properties.
if (ignoreUrl) {
delete eventProperties.url;
}
const callback = () => {
if (newTab) {
return;
}
window.location = event.target.href;
};
if (!newTab) {
event.preventDefault();
setTimeout(callback, 300);
}
console.debug("Tracking link click!");
mixpanel.track(eventName, eventProperties, callback);
});
};
}
And can be used as:
// MyComponent.jsx
import React, { useEffect } from "react";
import { Link, useLocation } from "#reach/router";
const MyComponent = ({ moduleName, key, ...props }) => {
const id = `#${id}__${moduleName}`;
useEffect(() => {
mixpanel.delegate_links(document.parent, id, event => {
return {
module: event.id.split("__").pop(),
...props.otherPropsToTrack
};
})
}, [])
return <>
<Link {...props} to="/some/path" id={id}>My Page</Link>
</>
}
I want to check if a class exsits somewhere in one of the parent elements of an element.
I don't want to use any library, just vanilla JS.
In the examples below it should return true if the element in question resides somewhere in the childs of an element with "the-class" as the class name.
I think it would be something like this with jQuery:
if( $('#the-element').parents().hasClass('the-class') ) {
return true;
}
So this returns true:
<div>
<div class="the-class">
<div id="the-element"></div>
</div>
</div>
So does this:
<div class="the-class">
<div>
<div id="the-element"></div>
</div>
</div>
...but this returns false:
<div>
<div class="the-class">
</div>
<div id="the-element"></div>
</div>
You can use the closest() method of Element that traverses parents (heading toward the document root) of the Element until it finds a node that matches the provided selectorString. Will return itself or the matching ancestor. If no such element exists, it returns null.
You can convert the returned value into boolean
const el = document.getElementById('div-03');
const r1 = el.closest("#div-02");
console.log(Boolean(r1));
// returns the element with the id=div-02
const r2 = el.closest("#div-not-exists");
console.log(Boolean(r2));
<article>
<div id="div-01">Here is div-01
<div id="div-02">Here is div-02
<div id="div-03">Here is div-03</div>
</div>
</div>
</article>
You'll have to do it recursively :
// returns true if the element or one of its parents has the class classname
function hasSomeParentTheClass(element, classname) {
if (element.className.split(' ').indexOf(classname)>=0) return true;
return element.parentNode && hasSomeParentTheClass(element.parentNode, classname);
}
Demonstration (open the console to see true)
You can use some and contains to achieve the result:
function hasParentWithMatchingSelector (target, selector) {
return [...document.querySelectorAll(selector)].some(el =>
el !== target && el.contains(target)
)
}
// usage
hasParentWithMatchingSelector(myElement, '.some-class-name');
The fiddle
The code
function hasClass(element, className) {
var regex = new RegExp('\\b' + className + '\\b');
do {
if (regex.exec(element.className)) {
return true;
}
element = element.parentNode;
} while (element);
return false;
}
OR
function hasClass(element, className) {
do {
if (element.classList && element.classList.contains(className)) {
return true;
}
element = element.parentNode;
} while (element);
return false;
}
I'm ok with the function that Denys Séguret posted, it looks elegant and I like it.
I just tweaked a little bit that function since if the class specified in the parameter, is not present in the whole DOM, it fails when the recursion reaches the document object because is true that we control if the element has the parent node (in the last line, and when the document is the element the parent node is null) but before we execute the previous line, and when the element is the document, document.className is undefined and it fails, so the control must be moved to the top.
function hasSomeParentTheClass(element, classname) {
//
// If we are here we didn't find the searched class in any parents node
//
if (!element.parentNode) return false;
//
// If the current node has the class return true, otherwise we will search
// it in the parent node
//
if (element.className.split(' ').indexOf(classname)>=0) return true;
return hasSomeParentTheClass(element.parentNode, classname);
}
I believe if( $('#the-element').parents('.the-class').length ) to be more efficient, but perhaps not as human-readable; which, with querySelector in the picture, could be replaced with the following method:
function hasParent(element, parentSelector) {
var potentialParents = document.querySelectorAll(parentSelector);
for(i in potentialParents) if(potentialParents[i].contains(element))
return potentialParents[i];
return false;
}
That'd give you the ability to do:
var elm = document.getElementById('the-element');
if(hasParent(elm, '.the-class')) return true;
Try the closest() function - For each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree. Refer to the Official Docs here.
Another alternative for some those who like this style for modern/polyfilled browsers.
const hasClass = (element, className) => {
return element.classList.contains(className);
};
const hasParent = (element, className) => {
if (!element.parentNode) {
return false;
}
if (hasClass(element, className)) {
return true;
}
return hasParent(element.parentNode, className)
};
Working demo:
const hasClass = (element, className) => {
return element.classList.contains(className);
};
const hasParent = (element, className) => {
if (!element.parentNode) {
return false;
}
if (hasClass(element, className)) {
return true;
}
return hasParent(element.parentNode, className)
};
/* Demo Code, can ignore */
const child = document.getElementById('child');
const orphan = document.getElementById('orphan');
const output = document.getElementById('output');
const log = `child has parent? ${hasParent(child, 'list')}
orphan has parent? ${hasParent(orphan, 'list')}
`
output.innerText = log;
#output {
margin-top: 50px;
background: black;
color: red;
padding: 20px;
}
<div>
<ul class="list">
<li>
<a id="child" href="#">i have a parent</a>
</li>
</ul>
</div>
<div>
<ul>
<li>
<a id="orphan" href="#">im an orphan</a>
</li>
</ul>
</div>
<div id="output"></div>
My example for Vanilla JS, it's use a vanilla equivalent of parents() from jQuery
var htmlElement = <htmlElement>,
parents = [],
classExist;
while (htmlElement = htmlElement.parentNode.closest(<parentSelector>)) {
parents.push(htmlElement);
}
classExist = (parents > 0);
So your selector just to be a .className
And just check if parent is > 0
Because ID must be unique on document context, you could just use instead:
return !!document.querySelector('.the-class #the-element');
If you want to include element itself, you can use:
return !!document.querySelector('.the-class #the-element, #the-element.the-class');