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');
Related
$("*").click(function(){
$(this); // how can I get selector from $(this) ?
});
Is there an easy way to get selector from $(this)? There is a way to select an element by its selector, but what about getting the selector from element?
Ok, so in a comment above the question asker Fidilip said that what he/she's really after is to get the path to the current element.
Here's a script that will "climb" the DOM ancestor tree and then build fairly specific selector including any id or class attributes on the item clicked.
See it working on jsFiddle: http://jsfiddle.net/Jkj2n/209/
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<script>
$(function() {
$("*").on("click", function(e) {
e.preventDefault();
var selector = $(this)
.parents()
.map(function() { return this.tagName; })
.get()
.reverse()
.concat([this.nodeName])
.join(">");
var id = $(this).attr("id");
if (id) {
selector += "#"+ id;
}
var classNames = $(this).attr("class");
if (classNames) {
selector += "." + $.trim(classNames).replace(/\s/gi, ".");
}
alert(selector);
});
});
</script>
</head>
<body>
<h1><span>I love</span> jQuery</h1>
<div>
<p>It's the <strong>BEST THING</strong> ever</p>
<button id="myButton">Button test</button>
</div>
<ul>
<li>Item one
<ul>
<li id="sub2" >Sub one</li>
<li id="sub2" class="subitem otherclass">Sub two</li>
</ul>
</li>
</ul>
</body>
</html>
For example, if you were to click the 2nd list nested list item in the HTML below, you would get the following result:
HTML>BODY>UL>LI>UL>LI#sub2.subitem.otherclass
::WARNING:: .selector has been deprecated as of version 1.7, removed as of 1.9
The jQuery object has a selector property I saw when digging in its code yesterday. Don't know if it's defined in the docs are how reliable it is (for future proofing). But it works!
$('*').selector // returns *
Edit: If you were to find the selector inside the event, that information should ideally be part of the event itself and not the element because an element could have multiple click events assigned through various selectors. A solution would be to use a wrapper to around bind(), click() etc. to add events instead of adding it directly.
jQuery.fn.addEvent = function(type, handler) {
this.bind(type, {'selector': this.selector}, handler);
};
The selector is being passed as an object's property named selector. Access it as event.data.selector.
Let's try it on some markup (http://jsfiddle.net/DFh7z/):
<p class='info'>some text and <a>a link</a></p>
$('p a').addEvent('click', function(event) {
alert(event.data.selector); // p a
});
Disclaimer: Remember that just as with live() events, the selector property may be invalid if DOM traversal methods are used.
<div><a>a link</a></div>
The code below will NOT work, as live relies on the selector property
which in this case is a.parent() - an invalid selector.
$('a').parent().live(function() { alert('something'); });
Our addEvent method will fire, but you too will see the wrong selector - a.parent().
In collaboration with #drzaus we've come up with the following jQuery plugin.
jQuery.getSelector
!(function ($, undefined) {
/// adapted http://jsfiddle.net/drzaus/Hgjfh/5/
var get_selector = function (element) {
var pieces = [];
for (; element && element.tagName !== undefined; element = element.parentNode) {
if (element.className) {
var classes = element.className.split(' ');
for (var i in classes) {
if (classes.hasOwnProperty(i) && classes[i]) {
pieces.unshift(classes[i]);
pieces.unshift('.');
}
}
}
if (element.id && !/\s/.test(element.id)) {
pieces.unshift(element.id);
pieces.unshift('#');
}
pieces.unshift(element.tagName);
pieces.unshift(' > ');
}
return pieces.slice(1).join('');
};
$.fn.getSelector = function (only_one) {
if (true === only_one) {
return get_selector(this[0]);
} else {
return $.map(this, function (el) {
return get_selector(el);
});
}
};
})(window.jQuery);
Minified Javascript
// http://stackoverflow.com/questions/2420970/how-can-i-get-selector-from-jquery-object/15623322#15623322
!function(e,t){var n=function(e){var n=[];for(;e&&e.tagName!==t;e=e.parentNode){if(e.className){var r=e.className.split(" ");for(var i in r){if(r.hasOwnProperty(i)&&r[i]){n.unshift(r[i]);n.unshift(".")}}}if(e.id&&!/\s/.test(e.id)){n.unshift(e.id);n.unshift("#")}n.unshift(e.tagName);n.unshift(" > ")}return n.slice(1).join("")};e.fn.getSelector=function(t){if(true===t){return n(this[0])}else{return e.map(this,function(e){return n(e)})}}}(window.jQuery)
Usage and Gotchas
<html>
<head>...</head>
<body>
<div id="sidebar">
<ul>
<li>
Home
</li>
</ul>
</div>
<div id="main">
<h1 id="title">Welcome</h1>
</div>
<script type="text/javascript">
// Simple use case
$('#main').getSelector(); // => 'HTML > BODY > DIV#main'
// If there are multiple matches then an array will be returned
$('body > div').getSelector(); // => ['HTML > BODY > DIV#main', 'HTML > BODY > DIV#sidebar']
// Passing true to the method will cause it to return the selector for the first match
$('body > div').getSelector(true); // => 'HTML > BODY > DIV#main'
</script>
</body>
</html>
Fiddle w/ QUnit tests
http://jsfiddle.net/CALY5/5/
Did you try this ?
$("*").click(function(){
$(this).attr("id");
});
Try this:
$("*").click(function(event){
console.log($(event.handleObj.selector));
});
Well, I wrote this simple jQuery plugin.
This checkes id or class name, and try to give as much exact selector as possible.
jQuery.fn.getSelector = function() {
if ($(this).attr('id')) {
return '#' + $(this).attr('id');
}
if ($(this).prop("tagName").toLowerCase() == 'body') return 'body';
var myOwn = $(this).attr('class');
if (!myOwn) {
myOwn = '>' + $(this).prop("tagName");
} else {
myOwn = '.' + myOwn.split(' ').join('.');
}
return $(this).parent().getSelector() + ' ' + myOwn;
}
Just add a layer over the $ function this way:
$ = (function(jQ) {
return (function() {
var fnc = jQ.apply(this,arguments);
fnc.selector = (arguments.length>0)?arguments[0]:null;
return fnc;
});
})($);
Now you can do things like $("a").selector and will return "a" even on newer jQuery versions.
http://www.selectorgadget.com/ is a bookmarklet designed explicitly for this use case.
That said, I agree with most other people in that you should just learn CSS selectors yourself, trying to generate them with code is not sustainable. :)
I added some fixes to #jessegavin's fix.
This will return right away if there is an ID on the element. I also added a name attribute check and a nth-child selector in case a element has no id, class, or name.
The name might need scoping in case there a multiple forms on the page and have similar inputs, but I didn't handle that yet.
function getSelector(el){
var $el = $(el);
var id = $el.attr("id");
if (id) { //"should" only be one of these if theres an ID
return "#"+ id;
}
var selector = $el.parents()
.map(function() { return this.tagName; })
.get().reverse().join(" ");
if (selector) {
selector += " "+ $el[0].nodeName;
}
var classNames = $el.attr("class");
if (classNames) {
selector += "." + $.trim(classNames).replace(/\s/gi, ".");
}
var name = $el.attr('name');
if (name) {
selector += "[name='" + name + "']";
}
if (!name){
var index = $el.index();
if (index) {
index = index + 1;
selector += ":nth-child(" + index + ")";
}
}
return selector;
}
I've released a jQuery plugin: jQuery Selectorator, you can get selector like this.
$("*").on("click", function(){
alert($(this).getSelector().join("\n"));
return false;
});
I was getting multiple elements even after above solutions, so i extended dds1024 work, for even more pin-pointing dom element.
e.g. DIV:nth-child(1) DIV:nth-child(3) DIV:nth-child(1) ARTICLE:nth-child(1) DIV:nth-child(1) DIV:nth-child(8) DIV:nth-child(2) DIV:nth-child(1) DIV:nth-child(2) DIV:nth-child(1) H4:nth-child(2)
Code:
function getSelector(el)
{
var $el = jQuery(el);
var selector = $el.parents(":not(html,body)")
.map(function() {
var i = jQuery(this).index();
i_str = '';
if (typeof i != 'undefined')
{
i = i + 1;
i_str += ":nth-child(" + i + ")";
}
return this.tagName + i_str;
})
.get().reverse().join(" ");
if (selector) {
selector += " "+ $el[0].nodeName;
}
var index = $el.index();
if (typeof index != 'undefined') {
index = index + 1;
selector += ":nth-child(" + index + ")";
}
return selector;
}
Taking in account some answers read here I'd like to propose this:
function getSelectorFromElement($el) {
if (!$el || !$el.length) {
return ;
}
function _getChildSelector(index) {
if (typeof index === 'undefined') {
return '';
}
index = index + 1;
return ':nth-child(' + index + ')';
}
function _getIdAndClassNames($el) {
var selector = '';
// attach id if exists
var elId = $el.attr('id');
if(elId){
selector += '#' + elId;
}
// attach class names if exists
var classNames = $el.attr('class');
if(classNames){
selector += '.' + classNames.replace(/^\s+|\s+$/g, '').replace(/\s/gi, '.');
}
return selector;
}
// get all parents siblings index and element's tag name,
// except html and body elements
var selector = $el.parents(':not(html,body)')
.map(function() {
var parentIndex = $(this).index();
return this.tagName + _getChildSelector(parentIndex);
})
.get()
.reverse()
.join(' ');
if (selector) {
// get node name from the element itself
selector += ' ' + $el[0].nodeName +
// get child selector from element ifself
_getChildSelector($el.index());
}
selector += _getIdAndClassNames($el);
return selector;
}
Maybe useful to create a jQuery plugin?
This can get you selector path of clicked HTML element-
$("*").on("click", function() {
let selectorPath = $(this).parents().map(function () {return this.tagName;}).get().reverse().join("->");
alert(selectorPath);
return false;
});
Are you trying to get the name of the current tag that was clicked?
If so, do this..
$("*").click(function(){
alert($(this)[0].nodeName);
});
You can't really get the "selector", the "selector" in your case is *.
Javascript code for the same, in case any one needs, as i needed it. This just the translation only of the above selected answer.
<script type="text/javascript">
function getAllParents(element){
var a = element;
var els = [];
while (a && a.nodeName != "#document") {
els.unshift(a.nodeName);
a = a.parentNode;
}
return els.join(" ");
}
function getJquerySelector(element){
var selector = getAllParents(element);
/* if(selector){
selector += " " + element.nodeName;
} */
var id = element.getAttribute("id");
if(id){
selector += "#" + id;
}
var classNames = element.getAttribute("class");
if(classNames){
selector += "." + classNames.replace(/^\s+|\s+$/g, '').replace(/\s/gi, ".");
}
console.log(selector);
alert(selector);
return selector;
}
</script>
Thank you p1nox!
My problem was to put focus back on an ajax call that was modifying part of the form.
$.ajax({ url : "ajax_invite_load.php",
async : true,
type : 'POST',
data : ...
dataType : 'html',
success : function(html, statut) {
var focus = $(document.activeElement).getSelector();
$td_left.html(html);
$(focus).focus();
}
});
I just needed to encapsulate your function in a jQuery plugin:
!(function ($, undefined) {
$.fn.getSelector = function () {
if (!this || !this.length) {
return ;
}
function _getChildSelector(index) {
if (typeof index === 'undefined') {
return '';
}
index = index + 1;
return ':nth-child(' + index + ')';
}
function _getIdAndClassNames($el) {
var selector = '';
// attach id if exists
var elId = $el.attr('id');
if(elId){
selector += '#' + elId;
}
// attach class names if exists
var classNames = $el.attr('class');
if(classNames){
selector += '.' + classNames.replace(/^\s+|\s+$/g, '').replace(/\s/gi, '.');
}
return selector;
}
// get all parents siblings index and element's tag name,
// except html and body elements
var selector = this.parents(':not(html,body)')
.map(function() {
var parentIndex = $(this).index();
return this.tagName + _getChildSelector(parentIndex);
})
.get()
.reverse()
.join(' ');
if (selector) {
// get node name from the element itself
selector += ' ' + this[0].nodeName +
// get child selector from element ifself
_getChildSelector(this.index());
}
selector += _getIdAndClassNames(this);
return selector;
}
})(window.jQuery);
This won't show you the DOM path, but it will output a string representation of what you see in eg chrome debugger, when viewing an object.
$('.mybtn').click( function(event){
console.log("%s", this); // output: "button.mybtn"
});
https://developer.chrome.com/devtools/docs/console-api#consolelogobject-object
How about:
var selector = "*"
$(selector).click(function() {
alert(selector);
});
I don't believe jQuery store the selector text that was used. After all, how would that work if you did something like this:
$("div").find("a").click(function() {
// what would expect the 'selector' to be here?
});
The best answer would be
var selector = '#something';
$(selector).anything(function(){
console.log(selector);
});
What is the best way ( fastest / proper ) fashion to do event delegation in vanilla js?
For example if I had this in jQuery:
$('#main').on('click', '.focused', function(){
settingsPanel();
});
How can I translate that to vanilla js? Perhaps with .addEventListener()
The way I can think of doing this is:
document.getElementById('main').addEventListener('click', dothis);
function dothis(){
// now in jQuery
$(this).children().each(function(){
if($(this).is('.focused') settingsPanel();
});
}
But that seems inefficient especially if #main has many children.
Is this the proper way to do it then?
document.getElementById('main').addEventListener('click', doThis);
function doThis(event){
if($(event.target).is('.focused') || $(event.target).parents().is('.focused') settingsPanel();
}
Rather than mutating the built-in prototypes (which leads to fragile code and can often break things), just check if the clicked element has a .closest element which matches the selector you want. If it does, call the function you want to invoke. For example, to translate
$('#main').on('click', '.focused', function(){
settingsPanel();
});
out of jQuery, use:
document.querySelector('#main').addEventListener('click', (e) => {
if (e.target.closest('#main .focused')) {
settingsPanel();
}
});
Unless the inner selector may also exist as a parent element (which is probably pretty unusual), it's sufficient to pass the inner selector alone to .closest (eg, .closest('.focused')).
When using this sort of pattern, to keep things compact, I often put the main part of the code below an early return, eg:
document.querySelector('#main').addEventListener('click', (e) => {
if (!e.target.matches('.focused')) {
return;
}
// code of settingsPanel here, if it isn't too long
});
Live demo:
document.querySelector('#outer').addEventListener('click', (e) => {
if (!e.target.closest('#inner')) {
return;
}
console.log('vanilla');
});
$('#outer').on('click', '#inner', () => {
console.log('jQuery');
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="outer">
<div id="inner">
inner
<div id="nested">
nested
</div>
</div>
</div>
I've come up with a simple solution which seems to work rather well (legacy IE support notwithstanding). Here we extend the EventTarget's prototype to provide a delegateEventListener method which works using the following syntax:
EventTarget.delegateEventListener(string event, string toFind, function fn)
I've created a fairly complex fiddle to demonstrate it in action, where we delegate all events for the green elements. Stopping propagation continues to work and you can access what should be the event.currentTarget through this (as with jQuery).
Here is the solution in full:
(function(document, EventTarget) {
var elementProto = window.Element.prototype,
matchesFn = elementProto.matches;
/* Check various vendor-prefixed versions of Element.matches */
if(!matchesFn) {
['webkit', 'ms', 'moz'].some(function(prefix) {
var prefixedFn = prefix + 'MatchesSelector';
if(elementProto.hasOwnProperty(prefixedFn)) {
matchesFn = elementProto[prefixedFn];
return true;
}
});
}
/* Traverse DOM from event target up to parent, searching for selector */
function passedThrough(event, selector, stopAt) {
var currentNode = event.target;
while(true) {
if(matchesFn.call(currentNode, selector)) {
return currentNode;
}
else if(currentNode != stopAt && currentNode != document.body) {
currentNode = currentNode.parentNode;
}
else {
return false;
}
}
}
/* Extend the EventTarget prototype to add a delegateEventListener() event */
EventTarget.prototype.delegateEventListener = function(eName, toFind, fn) {
this.addEventListener(eName, function(event) {
var found = passedThrough(event, toFind, event.currentTarget);
if(found) {
// Execute the callback with the context set to the found element
// jQuery goes way further, it even has it's own event object
fn.call(found, event);
}
});
};
}(window.document, window.EventTarget || window.Element));
I have a similar solution to achieve event delegation.
It makes use of the Array-functions slice, reverse, filter and forEach.
slice converts the NodeList from the query into an array, which must be done before it is allowed to reverse the list.
reverse inverts the array (making the final traversion start as close to the event-target as possible.
filter checks which elements contain event.target.
forEach calls the provided handler for each element from the filtered result as long as the handler does not return false.
The function returns the created delegate function, which makes it possible to remove the listener later.
Note that the native event.stopPropagation() does not stop the traversing through validElements, because the bubbling phase has already traversed up to the delegating element.
function delegateEventListener(element, eventType, childSelector, handler) {
function delegate(event){
var bubble;
var validElements=[].slice.call(this.querySelectorAll(childSelector)).reverse().filter(function(matchedElement){
return matchedElement.contains(event.target);
});
validElements.forEach(function(validElement){
if(bubble===undefined||bubble!==false)bubble=handler.call(validElement,event);
});
}
element.addEventListener(eventType,delegate);
return delegate;
}
Although it is not recommended to extend native prototypes, this function can be added to the prototype for EventTarget (or Node in IE). When doing so, replace element with this within the function and remove the corresponding parameter ( EventTarget.prototype.delegateEventListener = function(eventType, childSelector, handler){...} ).
Delegated events
Event delegation is used when in need to execute a function when existent or dynamic elements (added to the DOM in the future) receive an Event.
The strategy is to assign to event listener to a known static parent and follow this rules:
use evt.target.closest(".dynamic") to get the desired dynamic child
use evt.currentTarget to get the #staticParent parent delegator
use evt.target to get the exact clicked Element (WARNING! This might also be a descendant element, not necessarily the .dynamic one)
Snippet sample:
document.querySelector("#staticParent").addEventListener("click", (evt) => {
const elChild = evt.target.closest(".dynamic");
if ( !elChild ) return; // do nothing.
console.log("Do something with elChild Element here");
});
Full example with dynamic elements:
// DOM utility functions:
const el = (sel, par) => (par || document).querySelector(sel);
const elNew = (tag, prop) => Object.assign(document.createElement(tag), prop);
// Delegated events
el("#staticParent").addEventListener("click", (evt) => {
const elDelegator = evt.currentTarget;
const elChild = evt.target.closest(".dynamicChild");
const elTarget = evt.target;
console.clear();
console.log(`Clicked:
currentTarget: ${elDelegator.tagName}
target.closest: ${elChild?.tagName}
target: ${elTarget.tagName}`)
if (!elChild) return; // Do nothing.
// Else, .dynamicChild is clicked! Do something:
console.log("Yey! .dynamicChild is clicked!")
});
// Insert child element dynamically
setTimeout(() => {
el("#staticParent").append(elNew("article", {
className: "dynamicChild",
innerHTML: `Click here!!! I'm added dynamically! <span>Some child icon</span>`
}))
}, 1500);
#staticParent {
border: 1px solid #aaa;
padding: 1rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.dynamicChild {
background: #eee;
padding: 1rem;
}
.dynamicChild span {
background: gold;
padding: 0.5rem;
}
<section id="staticParent">Click here or...</section>
Direct events
Alternatively, you could attach a click handler directly on the child - upon creation:
// DOM utility functions:
const el = (sel, par) => (par || document).querySelector(sel);
const elNew = (tag, prop) => Object.assign(document.createElement(tag), prop);
// Create new comment with Direct events:
const newComment = (text) => elNew("article", {
className: "dynamicChild",
title: "Click me!",
textContent: text,
onclick() {
console.log(`Clicked: ${this.textContent}`);
},
});
//
el("#add").addEventListener("click", () => {
el("#staticParent").append(newComment(Date.now()))
});
#staticParent {
border: 1px solid #aaa;
padding: 1rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.dynamicChild {
background: #eee;
padding: 0.5rem;
}
<section id="staticParent"></section>
<button type="button" id="add">Add new</button>
Resources:
Event.target
Element.closest()
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>
</>
}
}
Trying to add a class to a parent of link that contains last part of window location for further styling.
the basic html would look like this
<li>
Some link
<ul>
<li class="subnavli">
Link Text
</li>
</ul>
</li>
and the js/jquery i'm trying to use is
function getLastSegmentOfPath(url) {
var matches = url.match(/\/([^\/]+)\/?$/);
if (matches) {
return matches[1];
}
return null;
}
var endPath = getLastSegmentOfPath(window.location.href);
$(".subnavli a").each(function(){
if ($(this).attr('href').contains(endPath)) {
$(this).parent().parent().parent().addClass('active5');
}
});
As you can probably guess, it's not working.
contains() was problem. Now should work. contains() function is something else: http://api.jquery.com/jquery.contains/ And, also there is contains selector, but that's another story...
function getLastSegmentOfPath(url) {
var matches = url.match(/\/([^\/]+)\/?$/);
if (matches) {
return matches[1];
}
return null;
}
endPath = getLastSegmentOfPath(window.location.href);
console.log(endPath);
$(".subnavli a").each(function(){
if ($(this).attr('href').indexOf(endPath)) {
$(this).parent().parent().prev('a').addClass('active5');
}
});
});
DEMO: http://jsfiddle.net/gvmhtLud/2/
Add multiple items to text-area with duplicate items.
I have one text-area which store data after clicked add data link.
How can i prevent add duplicate items to text-area?
JavaScript call DOM event:
var Dom = {
get: function(el) {
if (typeof el === 'string') {
return document.getElementById(el);
} else {
return el;
}
},
add: function(el, dest) {
var el = this.get(el);
var dest = this.get(dest);
dest.appendChild(el);
},
remove: function(el) {
var el = this.get(el);
el.parentNode.removeChild(el);
}
};
var Event = {
add: function() {
if (window.addEventListener) {
return function(el, type, fn) {
Dom.get(el).addEventListener(type, fn, false);
};
} else if (window.attachEvent) {
return function(el, type, fn) {
var f = function() {
fn.call(Dom.get(el), window.event);
};
Dom.get(el).attachEvent('on' + type, f);
};
}
}()
};
JQuery add data to textarea:
$("#lkaddlanguage").click(function(){
var totalstring;
var checkconstring = $("#contentlng").text();
var strLen = checkconstring.length;
myStr = checkconstring.slice(0,strLen-1);
//alert(myStr);
var checkedItemsArray = myStr.split(";");
var j = 0;
var checkdup=0;
totalstring=escape($("#textval").val()) ;
var i = 0;
var el = document.createElement('b');
el.innerHTML = totalstring +";";
Dom.add(el, 'txtdisplayval');
Event.add(el, 'click', function(e) {
Dom.remove(this);
});
});
HTML Display data
<input type="textbox" id="textval">
<a href="#lnk" id="lkaddlanguage" >Add Data</a>
<textarea readonly id="txtdisplayval" ></textarea>
This seems a very straightforward requirement to me, so I'm not quite clear where you're getting stuck. I have not tried too hard to figure out your existing code given that you are referencing elements not shown in your html ("contentlng"). Also, mixing your own DOM code with jQuery seems a bit pointless. You don't need jQuery at all, but having chosen to include it why then deliberate not use it?
Anyway, the following short function will keep a list of current items (using a JS object) and check each new item against that list. Double-clicking an item will remove it. I've put this in a document ready, but you can manage that as you see fit:
<script>
$(document).ready(function() {
var items = {};
$("#lkaddlanguage").click(function(){
var currentItem = $("#textval").val();
if (currentItem === "") {
alert("Please enter a value.");
} else if (items[currentItem]) {
alert("Value already exists.");
} else {
items[currentItem] = true;
$("#txtdisplayval").append("<span>" + currentItem + "; </span>");
}
// optionally set up for entry of next value:
$("#textval").val("").focus();
return false;
});
$("#txtdisplayval").on("dblclick", "span", function() {
delete items[this.innerHTML.split(";")[0]];
$(this).remove();
});
});
</script>
<input type="textbox" id="textval">
<a href="#lnk" id="lkaddlanguage" >Add Data</a><br>
<div id="txtdisplayval" ></div>
<style>
#txtdisplayval {
margin-top: 5px;
width : 200px;
height : 100px;
overflow-y : auto;
border : 1px solid black;
}
</style>
Note I'm using a div (styled to have a border and allow vertical scrolling) instead of a textarea.
As you can see I've coded it to display an alert for duplicate or empty items, but obviously you could remove that and just ignore duplicates (or substitute your own error handling). Also I thought it might be handy to clear the entry field and set focus back to it ready for entry of the next value, but of course you can remove that too.
Working demo: http://jsfiddle.net/LTsBR/1/
I'm confused.
The only variable that might have duplicates comes from:
var checkedItemsArray = myStr.split(";");
However, checkedItemsArray is not used for anything.
Incidentally, the escape method is deprecated in favour of encodeURIComopnent.
When setting the value of the textarea, do just that: assign to its value property, not to its innerHTML (it can't have markup inside it or any elements, only text nodes).
If you want to check that the members of checkedItemsArray are unique, and you don't mind if they are sorted, you can use a simple function like:
function unique(arr) {
arr.sort();
var i = arr.length;
while (i--) {
if (arr[i] == arr[i - 1]) {
arr.splice(i, 1);
}
}
return arr;
}
Orignal order can be maintained, but it's a bit more code.