I have different attributes which contains identical text strings. Like:
<div id="outer">
<div id="carousel-example-generic"></div>
<div data-target="#carousel-example-generic"></div>
<a href="#carousel-example-generic" ></a>
</div>
How do I find and rename "carousel-example-generic" on those elements using jquery?
You could do something like:
$(document.documentElement).html(function(i,val){
return val.replace(/carousel-example-generic/g,'my-new-string');
});
...but be careful, this will replace the string everywhere in the document: tags, text, values, everywhere. Also, as Salman says below, event handlers are also destroyed, including those of parent elements.
Given your example above, the code shown here yields the following html:
<div id="outer">
<div id="my-new-string">asdfasdf</div>
<div data-target="#my-new-string">asdfadsfad</div>
asdfasdfa
</div>
Here's a fiddle. Examine the elements to see the changes.
The following is one approach:
// iterates over each of the descendant elements of '#outer':
$('#outer *').each(function() {
// iterates over the array-like 'this.attributes' (list of the element's attributes)
// property, using Array.prototype.forEach, 'a' is the array-element itself:
Array.prototype.forEach.call(this.attributes, function(a) {
// setting the value of the attribute:
// if the string 'carousel-example-generic' is found within the attribute-value,
// we replace that string with 'new-string', otherwise
// return the current attribute-value:
a.value = a.value.indexOf('carousel-example-generic') > -1 ? a.value.replace('carousel-example-generic', 'new-string') : a.value;
});
});
$('#outer *').each(function() {
Array.prototype.forEach.call(this.attributes, function(a) {
a.value = a.value.indexOf('carousel-example-generic') > -1 ? a.value.replace('carousel-example-generic', 'new-string') : a.value;
});
});
$('#result').text($('#outer').html());
#result {
white-space: pre-wrap;
font-family: monospace;
border: 1px solid #f90;
margin-top: 1em;
position: relative;
padding: 0.5em;
}
#result::before {
content: 'HTML converted to:';
border: 1px solid #f90;
background-color: #fff;
position: absolute;
top: -1em;
left: 0.5em;
height: 2em;
line-height: 2em;
padding: 0 0.2em;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="outer">
<div id="carousel-example-generic"></div>
<div data-target="#carousel-example-generic"></div>
</div>
<div id="result"></div>
References:
JavaScript:
Array.prototype.forEach().
Element.attributes.
Function.prototype.call().
String.indexOf().
jQuery:
each().
Related
I have a <div> wrapped around a set of nested <div> elements; I'd like to present this as a drop-down menu. The element is part of a plugin I'm using on a WordPress website but this should still be adjustable using custom script.
I would like to replace the encasing div with a <ul>, and turn all of the inner <div>s into <li> elements, so I can show this as a drop-down. Either that or a <select>, with nested <option> tags.
Is there a way I can change the HTML tag type , or replace the div, using pure JavaScript, or jQuery?
From:
<div class="slots">
<div class="availableslot"></div>
<div class="availableslot"></div>
</div>
to:
<ul class="slots">
<li class="availableslot"></li>
<li class="availableslot"></li>
</ul>
or:
<select class="slots">
<option class="availableslot"></option>
<option class="availableslot"></option>
</select>
The isolated code: https://codepen.io/bolti95/pen/rNpMdJx
//div into list
const slots_list = document.createElement("ul");
var slots = document.getElementsByClassName("slots")
console.log(slots)
slots.insertBefore(slots_list, slots.children[0])
.slots {
width: 100% !important;
height: max-content;
border: none !important;
box-shadow: none !important;
margin: auto;
}
.slots div a {
color: #77635A;
background: none !important;
}
.slots div a:hover {
background-color: #F7F7F7 !important;
transition: 0.6s;
}
.availableslot {
width: 120px;
height: 55px;
border-radius: 30px;
margin-bottom: 25px;
margin: 10px;
border: none !important;
border-width: 0px;
text-align: center;
font-size: 20px !important;
background-color: #EFE8DA;
color: #77635A;
}
.availableslot a {
margin: 10px;
}
.availableslot:hover {
outline: 1px solid #5FDEAB;
background-color: #F7F7F7;
transition: 0.6;
}
.availableslot a:hover {
background-color: #F7F7F7;
color: #5FDEAB;
transition: 0.6s;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="<?php bloginfo('charset'); ?>">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div class="slots">
<span>03/22/2022</span>
<br>
<div class="availableslot"><a>11:00</a></div>
<div class="availableslot"><a>12:00</a></div>
<div class="availableslot"><a>13:00</a></div>
<div class="availableslot"><a>14:00</a></div>
<div class="availableslot"><a>15:00</a></div>
</div>
</body>
</html>
To convert the .availableslot div elements to a ul/li list you can use a combination of wrapAll() and replaceWith(), like this:
$('.availableslot').wrapAll('<ul class="slots" />').replaceWith(function() {
return `<li class="availableslot">${this.innerHTML}</li>`
});
$('.availableslot').wrapAll('<ul class="slots" />').replaceWith(function() {
return `<li class="availableslot">${this.innerHTML}</li>`
});
.slots {
width: 100% !important;
height: max-content;
border: none !important;
box-shadow: none !important;
margin: auto;
}
.slots div a {
color: #77635A;
background: none !important;
}
.slots div a:hover {
background-color: #F7F7F7 !important;
transition: 0.6s;
}
.availableslot {
width: 120px;
height: 55px;
border-radius: 30px;
margin-bottom: 25px;
margin: 10px;
border: none !important;
border-width: 0px;
text-align: center;
font-size: 20px !important;
background-color: #EFE8DA;
color: #77635A;
}
.availableslot a {
margin: 10px;
}
.availableslot:hover {
outline: 1px solid #5FDEAB;
background-color: #F7F7F7;
transition: 0.6;
}
.availableslot a:hover {
background-color: #F7F7F7;
color: #5FDEAB;
transition: 0.6s;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="slots">
<span>03/22/2022</span>
<br>
<div class="availableslot"><a>11:00</a></div>
<div class="availableslot"><a>12:00</a></div>
<div class="availableslot"><a>13:00</a></div>
<div class="availableslot"><a>14:00</a></div>
<div class="availableslot"><a>15:00</a></div>
</div>
The same technique can be used to convert them to a select/option form control:
$('.availableslot').wrapAll('<select />').replaceWith(function() {
let time = this.querySelector('a').innerText;
return `<option value="${time}">${time}</option>`;
});
$('.availableslot').wrapAll('<select />').replaceWith(function() {
let time = this.querySelector('a').innerText;
return `<option value="${time}">${time}</option>`;
});
.slots {
width: 100% !important;
height: max-content;
border: none !important;
box-shadow: none !important;
margin: auto;
}
.slots div a {
color: #77635A;
background: none !important;
}
.slots div a:hover {
background-color: #F7F7F7 !important;
transition: 0.6s;
}
.availableslot {
width: 120px;
height: 55px;
border-radius: 30px;
margin-bottom: 25px;
margin: 10px;
border: none !important;
border-width: 0px;
text-align: center;
font-size: 20px !important;
background-color: #EFE8DA;
color: #77635A;
}
.availableslot a {
margin: 10px;
}
.availableslot:hover {
outline: 1px solid #5FDEAB;
background-color: #F7F7F7;
transition: 0.6;
}
.availableslot a:hover {
background-color: #F7F7F7;
color: #5FDEAB;
transition: 0.6s;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="slots">
<span>03/22/2022</span>
<br>
<div class="availableslot"><a>11:00</a></div>
<div class="availableslot"><a>12:00</a></div>
<div class="availableslot"><a>13:00</a></div>
<div class="availableslot"><a>14:00</a></div>
<div class="availableslot"><a>15:00</a></div>
</div>
One approach, using plain JavaScript, is below; explanatory comments are in the code:
// creating a named function, using Arrow syntax, that allows the user to pass in an
// Object of updated preferences:
const replaceWith = (opts) => {
let settings = {
// String, sets the element-type to replace the outer wrapper:
outerTo: 'select',
// String, sets the element-type to replace the inner wrapper:
innerTo: 'option',
// String, CSS selector to select the relevant elements:
source: '.slots',
// Boolean, true: replaces the element when its replacement is inserted into the document,
// false: hides the element when its replacement is inserted into the document:
replace: true,
// Boolean, true: preserves the child-nodes of the inner-elements being replaced,
// false: preserves the text-content of the inner-element, but does not
// keep the child-nodes that may have contributed text:
preserveHTMLChildren: false,
};
// while I'm sure there's an easy way to use destructruring to avoid this step, I chose to
// use this approach, wherein we take the keys of the opts Object passed to the function
// or - if no Object is passed - we look at the empty Object-literal (to avoid errors),
// we then iterate over the Array of Object-keys using Array.prototype.forEach(), again
// using Arrow syntax:
Object.keys(opts || {}).forEach(
// the 'key' passes in a reference to the current key of the Array of Object-keys
// over which we're iterating, and in the function we set the Object-key of the
// settings Object to the value of the opts Object key-value:
(key) => settings[key] = opts[key]
);
// to reduce unnecessary typing, we alias the document to the D variable:
let D = document,
// we create an element according to the desired elements:
outer = D.createElement(settings.outerTo),
inner = D.createElement(settings.innerTo),
// we use document.querySelectorAll(), using the supplied, or default,
// CSS selector, to retrieve all matching elements:
sources = [...D.querySelectorAll(settings.source)];
// we use NodeList.prototype.forEach() to iterate over the NodeList of matching
// elements returned to the sources variable:
sources.forEach(
// using Arrow syntax we pass in a reference to the current Node ('source') of
// the NodeList:
(source) => {
// we clone the created-element for the outer-element's replacement:
let outerClone = outer.cloneNode();
// we copy the classList:
outerClone.classList = source.classList;
// we use an Array-literal with the spread syntax to create an Array of
// the child-nodes of the current 'source', and then iterate over that
// Array of Nodes using Array.prototype.forEach():
[...source.children].forEach(
// we pass in a reference to the current child-element of the parent
// 'source', in the variable named 'child':
(child) => {
// we clone the created-element for the inner element:
let innerClone = inner.cloneNode();
// if settings.preserveHTMLChildren is exactly equal to Boolean true:
if (settings.preserveHTMLChildren === true) {
// while there is a firstChild node (whether comment, text, HTMLElement...):
while (child.firstChild) {
// we copy that firstChild Node accross to the innerClone element:
innerClone.append(child.firstChild);
}
} else {
// we update the text-content of the innerClone to be equal to the
// original text-content:
innerClone.textContent = child.textContent;
}
// copying the classList of the existing child to the innerClone:
innerClone.classList = child.classList;
// we append the current innerClone to the outerClone:
outerClone.append(innerClone);
});
// we then access the current 'source' element's parentNode, and
// use ParentNode.insertBefore() to insert the newly-created
// outerClone before the current 'source' element's next-sibling
// (effectively inserting it after the current 'source' element):
source.parentNode.insertBefore(outerClone, source.nextSibling);
// if the user wants to replace the element, so settings.replace is
// exactly-equal to (Boolean) true:
if (settings.replace === true) {
// we remove the current 'source' element, using Node.remove():
source.remove();
} else {
// otherwise we set source.hidden to 'true' to hide the element
// using the 'hidden' HTML attribute (though in the demo I chose
// to modify the opacity to show that the element does, in fact,
// remain in the document); this should probably be modified when
// in use:
source.style.opacity = 0.4;
// source.hidden = true;
}
})
};
// calling the function:
replaceWith({
// using a non-default CSS Selector:
source: '#demo1'
});
// calling the functiona again:
replaceWith({
// modifying the CSS selector again:
source: '#demo2',
// using a <ul> to 'replace' the outer element:
outerTo: 'ul',
// using an <li> to wrap the inner contents:
innerTo: 'li',
// setting Boolean false, to not replace the original
// element(s), so inserting the new element(s) and hiding
// the original(s):
replace: false,
});
replaceWith({
source: '#demo3',
outerTo: 'ul',
innerTo: 'li',
// setting preserveHTMLChildren to true means the <span> elements
// contained within the inner-<div> elements will be preserved when
// the HTML is modified:
preserveHTMLChildren: true,
});
replaceWith({
source: '#demo4',
// again, setting preserveHTMLChildren to true means the <span> elements
// contained within the inner-<div> elements will be preserved when
// the HTML is modified; but while those <span> elements may be present
// in the DOM (in FF 98/Ubuntu 21.10), they are not styled by the CSS
// (and an <option> element has no valid child-elements), so this isn't
// necessarily going to be respected by the browser:
preserveHTMLChildren: true,
});
*,
::before,
::after {
box-sizing: border-box;
font: normal 400 1rem / 1.5 sans-serif;
list-style-type: none;
margin: 0;
padding: 0;
}
.slots {
border: 1px solid palegreen;
margin: 1em auto;
padding: 0.5em;
width: 20rem;
}
.slots:not([id]) {
border: 1px solid rebeccapurple;
}
.slots span {
color: #f90;
}
<div id="demo1" class="slots">
<div class="availableslot">available 1</div>
<div class="availableslot">available 2</div>
</div>
<div id="demo2" class="slots">
<div class="availableslot">available 3</div>
<div class="availableslot">available 4</div>
</div>
<div id="demo3" class="slots">
<div class="availableslot">available <span>5</span></div>
<div class="availableslot">available <span>6</span></div>
</div>
<div id="demo4" class="slots">
<div class="availableslot">available <span>7</span></div>
<div class="availableslot">available <span>8</span></div>
</div>
JS Fiddle demo.
I have to admit that I'm curious as to why the <select> elements aren't being positioned correctly (in terms of margin-inline: auto), but that's a problem for another time.
If there are any questions then please don't hesitate to leave a comment below.
References:
Array.prototype.forEach().
Array literals [ /*...*/ ].
Arrow functions.
document.createDocumentFragment().
document.createElement().
document.querySelectorAll().
Element.children().
Element.classList.
Element.remove().
Node.cloneNode().
Node.firstChild.
Node.insertBefore().
Node.parentNode.
NodeList.prototype.forEach().
Object.keys().
Spread syntax ....
while (...) {...} statement.
This is the markup that I'm working with
<div class="checkout-row__form-column__billing">
<div class="checkout-row__form-column__billing__title billing-collapse collapse-btn">
<div class="checkout-row__form-column__billing__title__arrow">
<i class="fa fa-chevron-right" aria-hidden="true"></i>
</div>
<h2>1. Billing Details</h2>
</div>
<div class="checkout-row__form-column__shipping__content shipping-collapse-content collapse-target"></div>
<div class="checkout-row__form-column__shipping">
<div class="checkout-row__form-column__shipping__title shipping-collapse collapse-btn">
<div class="checkout-row__form-column__shipping__title__arrow">
<i class="fa fa-chevron-right" aria-hidden="true"></i>
</div>
<h2>2. Shipping Details</h2>
</div>
<div class="checkout-row__form-column__shipping__content shipping-collapse-content collapse-target"></div>
All this markup is wrapped in div with a class of checkout-row__form-column.
So, I'm grabbing the parent wrapper, on click I match target with .collapse-btn. and then I do class toggle on the sibling which is .collapse-target. Works great except on issue, that I can't get past. If I click on the inner element, the event doesn't happen.
Here is my js
parent = document.querySelector('.checkout-row__form-column');
parent.addEventListener('click', (e)=>{
if (e.target.matches('.collapse-btn')) {
e.stopPropagation();
e.target.nextElementSibling.classList.toggle('hide');
}
});
With these code I am able to toggle classes if I don't click on i element or h2. How can I write my JavaScript that event will be fired if I click anywhere inside the e.target.
Any help is much appreciated fellow commarades.
Your code only checks if the event.target is the .collapse-btn element or not. You also need to check if the clicked element is a descendant element of the .collapse-btn element.
To achieve the desired result, you can use the two conditions to check if the event.target is the .collapse-btn or any of its descendant. To check for descendant elements, use the universal selector *.
parent.addEventListener('click', (e) => {
if (
e.target.matches('.collapse-btn') ||
e.target.matches('.collapse-btn *')
) {
// code
}
});
Demo
Following snippet shows an example:
const parent = document.querySelector('.container');
parent.addEventListener('click', e => {
if (
e.target.matches('.collapse-btn') ||
e.target.matches('.collapse-btn *')
) {
parent.classList.toggle('active');
}
});
.container {
background: #eee;
width: 120px;
height: 120px;
}
.container.active {
border: 2px solid;
}
.collapse-btn {
background: #999;
padding: 10px;
}
.collapse-btn span {
background: #fc3;
font-size: 1.3rem;
cursor: pointer;
}
<div class="container">
<div class="collapse-btn">
<span>Click</span>
</div>
</div>
You could also put the selectors in an array and then use .some() method to check if event.target matches ay of the selector in the array.
const selectors = ['.collapse-btn', '.collapse-btn *'];
parent.addEventListener('click', e => {
if (selectors.some(s => e.target.matches(s))) {
// code
}
});
Demo
Following snippet shows an example:
const parent = document.querySelector('.container');
parent.addEventListener('click', e => {
const selectors = ['.collapse-btn', '.collapse-btn *'];
if (selectors.some(s => e.target.matches(s))) {
parent.classList.toggle('active');
}
});
.container {
background: #eee;
width: 120px;
height: 120px;
}
.container.active {
border: 2px solid;
}
.collapse-btn {
background: #999;
padding: 10px;
}
.collapse-btn span {
background: #fc3;
font-size: 1.3rem;
cursor: pointer;
}
<div class="container">
<div class="collapse-btn">
<span>Click</span>
</div>
</div>
Alternative Solution
Instead of using the .matches() method, use the combination of Element.closest() and Node.contains() methods to check if the event.target is the .collapse-btn element or is a child element of the .collapse-btn element.
For this to work, you need to do two steps:
You first need to get the .collapse-btn that is the closest ancestor of the event.target.
After that, you need to check if the event.target is a descendant of the button selected in step 1.
Code:
parent.addEventListener('click', (e) => {
const targetCollapseBtn = e.target.closest('.collapse-btn');
if (targetCollapseBtn && targetCollapseBtn.contains(e.target)) {
...
}
});
If event.target is the .collapse-btn itself, then
e.target.closest('.collpase-btn')
will return the e.target, i.e. .collapse-btn and
targetCollapseBtn.contains(e.target)
will also return true because .contains() method returns true even if the node passed to it as an argument is the same node as the one on which .contains() method was called.
So using .closest() and .contains() methods in this way covers both the use cases, i.e. when .collapse-btn is clicked or when any of its descendant element is clicked.
Demo
Following snippet shows a simple example:
const parent = document.querySelector('.container');
parent.addEventListener('click', e => {
const targetCollapseBtn = e.target.closest('.collapse-btn');
if (targetCollapseBtn && targetCollapseBtn.contains(e.target)) {
parent.classList.toggle('active');
}
});
.container {
background: #eee;
width: 120px;
height: 120px;
}
.container.active {
border: 2px solid;
}
.collapse-btn {
background: #999;
padding: 10px;
}
.collapse-btn span {
background: #fc3;
font-size: 1.3rem;
cursor: pointer;
}
<div class="container">
<div class="collapse-btn">
<span>Click</span>
</div>
</div>
I'm wondering if it's possible to on each appendTo make the new div unique but still use the same jquery.
As you can see in the mark-up below, each new div shares the same jquery so doesn't work independently.
Within my Javascript i'm selecting the ID to fire each function.
I've tried just adding + 1 etc to the end of each ID, but with that it changes the name of the ID making the new created DIV not function.
I've thought of using DataAttribues, but i'd still have the same issue having to create multiple functions all doing the same job.
Any ideas?
Thanks
$(function() {
var test = $('#p_test');
var i = $('#p_test .upl_drop').length + 1;
$('#addtest').on('click', function() {
$('<div class="file-input"><div class="input-file-container upl_drop"><label for="p_test" class="input-file-trigger">Select a file...<input type="file" id="p_test" name="p_test_' + i + '" value=""class="input-file"></label></div><span class="remtest">Remove</span><p class="file-return"></p></div>').appendTo(test);
i++;
});
$('body').on('click', '.remtest', function(e) {
if (i > 2) {
$(this).closest('.file-input').remove();
i--;
}
});
});
var input = document.getElementById( 'file-upload' );
var infoArea = document.getElementById( 'file-upload-filename' );
input.addEventListener( 'change', showFileName );
function showFileName( event ) {
// the change event gives us the input it occurred in
var input = event.srcElement;
// the input has an array of files in the `files` property, each one has a name that you can use. We're just using the name here.
var fileName = input.files[0].name;
// use fileName however fits your app best, i.e. add it into a div
textContent = 'File name: ' + fileName;
$("#input-file-trigger").text(function () {
return $(this).text().replace("Select a file...", textContent);
});
}
/*
#### Drag & Drop Box ####
*/
.p_test{
display: inline-block;
}
.upl_drop{
border: 2px dashed #000;
margin: 0px 0px 15px 0px;
}
.btn--add p{
cursor: pointer;
}
.input-file-container {
position: relative;
width: auto;
}
.input-file-trigger {
display: block;
padding: 14px 45px;
background: #ffffff;
color: #1899cd;
font-size: 1em;
cursor: pointer;
}
.input-file {
position: absolute;
top: 0; left: 0;
width: 225px;
opacity: 0;
padding: 14px 0;
cursor: pointer;
}
.input-file:hover + .input-file-trigger,
.input-file:focus + .input-file-trigger,
.input-file-trigger:hover,
.input-file-trigger:focus {
background: #1899cd;
color: #ffffff;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<div class="p_test" id="p_test">
<div class="file-input">
<div class="input-file-container upl_drop">
<input class="input-file" id="file-upload" type="file">
<label tabindex="0" for="file-upload" id="input-file-trigger" class="input-file-trigger">Select a file...</label>
</div>
<div id="file-upload-filename"></div>
</div>
<button class="btn--add" id="addtest">
Add
</button>
</div>
I'd advise against using incremental id attributes. They become a pain to maintain and also make the logic much more complicated than it needs to be.
The better alternative is to use common classes along with DOM traversal to relate the elements to each other, based on the one which raised any given event.
In your case, you can use closest() to get the parent .file-input container, then find() any element within that by its class. Something like this:
$(function() {
var $test = $('#p_test');
$('#addtest').on('click', function() {
var $lastGroup = $test.find('.file-input:last');
var $clone = $lastGroup.clone();
$clone.find('.input-file-trigger').text('Select a file...');
$clone.insertAfter($lastGroup);
});
$test.on('click', '.remtest', function(e) {
if ($('.file-input').length > 1)
$(this).closest('.file-input').remove();
}).on('change', '.input-file', function(e) {
if (!this.files)
return;
var $container = $(this).closest('.file-input');
$container.find(".input-file-trigger").text('File name: ' + this.files[0].name);
});
});
.p_test {
display: inline-block;
}
.upl_drop {
border: 2px dashed #000;
margin: 0px 0px 15px 0px;
}
.btn--add p {
cursor: pointer;
}
.input-file-container {
position: relative;
width: auto;
}
.input-file-trigger {
display: block;
padding: 14px 45px;
background: #ffffff;
color: #1899cd;
font-size: 1em;
cursor: pointer;
}
.input-file {
position: absolute;
top: 0;
left: 0;
width: 225px;
opacity: 0;
padding: 14px 0;
cursor: pointer;
}
.input-file:hover+.input-file-trigger,
.input-file:focus+.input-file-trigger,
.input-file-trigger:hover,
.input-file-trigger:focus {
background: #1899cd;
color: #ffffff;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<div class="p_test" id="p_test">
<div class="file-input">
<div class="input-file-container upl_drop">
<input class="input-file" type="file">
<label tabindex="0" for="file-upload" class="input-file-trigger">Select a file...</label>
</div>
<div class="file-upload-filename"></div>
</div>
<button class="btn--add" id="addtest">Add</button>
</div>
Note that I've made a couple of other optimisations to the code. Firstly it now makes a clone() of the last available .file-input container when the Add button is clicked. This is preferred over writing the HTML in the JS file as it keeps the two completely separate. For example, if you need to update the UI, you don't need to worry about updating the JS now, as long as the classes remain the same.
Also note that you were originally mixing plain JS and jQuery event handlers. It's best to use one or the other. As you've already included jQuery in the page, I used that as it makes the code easier to write and more succinct.
Finally, note that you didn't need to provide a function to text() as you're completely over-writing the existing value. Just providing the new string is fine.
I want to change the background color of the button based upon the class. Why it is not going back after second click?
var $begin1 = $(".begin1").click(function(e) {
e.preventDefault();
var buttonState = $(this).attr("class");
if (buttonState != 'pressed') {
$begin1.removeClass('pressed');
$(this).addClass('pressed');
} else {
$(this).removeClass('pressed');
$(this).addClass('unpressed');
}
});
li {
list-style-type: none;
}
.begin1.unpressed,
.begin2.unpressed {
background-color: white;
color: black;
border: 2px solid #4CAF50;
margin: 0 0 10px 0;
}
li.begin1.pressed,
li.begin2.pressed {
background: #4CAF50;
color: white;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<li class="begin1 unpressed">
<h2>Button</h2>
</li>
https://jsfiddle.net/nrebaL00/
You can simplify your code greatly. Apply the default styling from the beginning and you don't need an .unpressed class.
The issue with using .attr( 'class' ) is that it will retrieve all the classes applied to the element as a string. Performing a check like if ( 'a' === $el.attr( 'class' ) ) won't work where $el is <li class="a b c"> as $el.attr( 'class' ) would return 'a b c' and not 'a'. Which is why your check was failing after the first click. This kind of check would be good for .hasClass().
e.prevendDefault() is not required for an <li>, so remove that.
Note: the selector I used for jQuery is pretty generic. You may need to increase it's specificity if there are other <li> on the page that don't require the functionality. Something along the lines of adding a class to the <ul> and using that as the part of the jQuery selector. i.e. <ul class="clicky-mcclickens"> and $( '.clicky-mcclickens li' ).
$('li').on('click', function(e) {
$(this).toggleClass('pressed');
});
li {
list-style-type: none;
background-color: white;
color: black;
border: 2px solid #4CAF50;
margin: 0 0 10px 0;
}
.pressed {
background: #4CAF50;
color: white;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul>
<li>
<h2>Button 1</h2>
</li>
<li>
<h2>Button 2</h2>
</li>
</ul>
Sometimes you need more control than simply adding/removing a class when an element is clicked. In those instances you can use .hasClass() to check if the element has the class in question and apply the appropriate action.
Your code is much more complex than it needs to be; you can just call toggleClass() like this:
var $begin1 = $(".begin1").click(function() {
$(this).toggleClass('pressed unpressed');
});
Updated fiddle
Note that e.preventDefault() is redundant for an li element as it has no default behaviour to prevent.
I would use toggleClass instead of adding and removing manually. This seems to work:
var $begin1 = $(".begin1").click( function(e) {
$begin1.toggleClass('pressed');
});
Instead of check the complete string of the class of the element you can check if the element has specific class using hasClass:
var $begin1 = $(".begin1").click( function(e) {
e.preventDefault();
if(!$(this).hasClass('pressed')){
$begin1.removeClass('unpressed');
$(this).addClass('pressed');
} else{
$(this).removeClass('pressed');
$(this).addClass('unpressed');
}
});
li{
list-style-type: none;
}
.begin1.unpressed,
.begin2.unpressed {
background-color: white;
color: black;
border: 2px solid #4CAF50;
margin: 0 0 10px 0;
}
li.begin1.pressed,
li.begin2.pressed{
background: #4CAF50;
color: white;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<li class="begin1 unpressed"><h2>Button</h2></li>
The problem with using the attr('class') is that you can't know what exactly will be the final string.
Just replace your js with:
$(document).ready(function() {
$(".begin1").click(function(){
$(this).toggleClass("pressed");
});
});
I have a div element that I want to be printed on the page when I click a Create Button.
Thus, when I click create I call a function that has: document.getElementById("createdDiv").textContent = document.querySelector("[data-feed]");
This finds my div element and prints to the page [object HTMLDivElement]
However, when I print the element to the console, I get my div element:
<div data-feed class="feed-element" ... ></div>
I know the console has a toString function that converts the div element into a string but I am not sure how to do this in javascript so I can print the same string to the page. Any suggestions?
You could use outerHTML:
document.getElementById("createdDiv").textContent = document.querySelector("[data-feed]").outerHTML;
document.getElementById("createdDiv").textContent = document.querySelector("[data-feed]").outerHTML;
[data-feed]::before {
content: 'The source element: ';
color: #f00;
}
#createdDiv {
white-space: pre-wrap;
border: 1px solid #000;
padding: 0.5em;
border-radius: 1em;
}
<div data-feed="something"><span>Text in here</span> with <em>various</em> <strong>elements</strong></div>
<div id="createdDiv"></div>
In order to remove HTML from any childNodes, then you could use a function to clone the node, remove the children, and then return only the outerHTML of that specific node:
function tagHTMLOnly(elem) {
var temp = elem.cloneNode();
while (temp.firstChild) {
temp.removeChild(temp.firstChild);
}
return temp.outerHTML;
}
document.getElementById("createdDiv").textContent = tagHTMLOnly(document.querySelector("[data-feed]"));
function tagHTMLOnly(elem) {
var temp = elem.cloneNode();
while (temp.firstChild) {
temp.removeChild(temp.firstChild);
}
return temp.outerHTML;
}
document.getElementById("createdDiv").textContent = tagHTMLOnly(document.querySelector("[data-feed]"));
[data-feed]::before {
content: 'The source element: ';
color: #f00;
}
#createdDiv {
white-space: pre-wrap;
border: 1px solid #000;
padding: 0.5em;
border-radius: 1em;
}
<div data-feed="something"><span>Text in here</span> with <em>various</em> <strong>elements</strong>
</div>
<div id="createdDiv"></div>
References:
Element.outerHTML.