Display div value that matches with attribute from other div - javascript

I have two divs created from PHP loop:
<div class="sku">
<span class="sku-value" data-id="150">1000</span>
<span class="sku-value" data-id="151">2000</span>
<span class="sku-value" data-id="152">3000</span>
</div>
<div class="size-values">
<span class="size-value" data-id="150">M</span>
<span class="size-value" data-id="151">L</span>
<span class="size-value" data-id="152">XL</span>
</div>
These divs has parent div called attributes.
sku-value and size-value have something in common: data-id attribute.
With CSS I'm manipulating the sku-value spans:
.sku-value {
display:none;
}
.sku-value.active {
display:inline-block;
}
With jQuery I'm displaying only the first size-value:
$('.sku-value:first').addClass('active');
So in this case when the page is loaded only this sku-value will be visible:
<span class="sku-value" data-id="150">1000</span>
With my code bellow I can successfully change the active class of each clicked size-value but how can I change the displayed sku-value that matches the data-id value?
For example: if a user click on one of size-value that has data-id 152, how can I display the sku-value that has data-id 152 and hide the current visible sku-value?
$(document).ready(function(){
$('.sku-value:first').addClass('active');
$('.attributes').find('.size-value').on('click', function(){
if ($this.hasClass('active')) {
$this.removeClass('active');
} else {
$this.closest('.size-values').find('.size-value').removeClass('active');
$this.addClass('active');
}
});
});

To achieve what you require you can retrieve the data-id of the clicked element, then use filter() to retrieve all elements by that data attribute before setting the active class on them.
Here's an example showing how to do that, and also some tweaks to the logic to make it more succinct:
$(document).ready(function() {
let $skuAttributes = $('.sku-value');
let $sizeAttributes = $('.size-value');
let $allAttributes = $skuAttributes.add($sizeAttributes);
// set default state on page load
$skuAttributes.first().addClass('active');
$sizeAttributes.first().addClass('active');
// on click of a size attribute, set active class on all relevant elements
$sizeAttributes.on('click', function() {
$sizeAttributes.removeClass('active');
let dataId = $(this).data('id');
$allAttributes.removeClass('active').filter((i, el) => el.dataset.id == dataId).addClass('active');
});
});
div { font-size: 1.3em; }
.sku-value { display: none; }
.sku-value.active { display: inline-block; }
.active { color: #C00; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="attributes">
<div class="sku">
<span class="sku-value" data-id="150">1000</span>
<span class="sku-value" data-id="151">2000</span>
<span class="sku-value" data-id="152">3000</span>
</div>
<div class="size-values">
<span class="size-value" data-id="150">M</span>
<span class="size-value" data-id="151">L</span>
<span class="size-value" data-id="152">XL</span>
</div>
</div>

Here's a functional-style, vanilla-flavored solution I came up with. Several helper functions share the heavy lifting, so the main listener function stays simple and clean.
Instead of the active class, this uses a hidden class that can apply to sku spans as well as size spans.
(I'm late to the party because I wrote this script while I was offline today.)
const
// Collects DOM elements as arrays
sizes = [...document.getElementsByClassName("size-value")],
skus = [...document.getElementsByClassName("sku-value")],
spans = [...sizes, ...skus],
// Defines helper functions
isHidden = (_this) => _this.classList.contains("hidden"),
isActive = (_this) => sizes.every((size) => isHidden(size) || size === _this),
showAll = (_these) => _these.forEach((_this) => _this.classList.remove("hidden")),
hideAll = (_these) => _these.forEach((_this) => _this.classList.add("hidden")),
filterByDataId = (_these, id) => _these.filter(_this => _this.dataset.id === id);
// Invokes listener when user clicks a size
sizes.forEach(size => size.addEventListener("click", hideAndShow));
// Defines listener
function hideAndShow({target}){ // Destructures click event
if(isActive(target)){
showAll(spans);
}
else{
hideAll(spans);
showAll(filterByDataId(spans, target.dataset.id));
}
}
div { margin: 1em 0; }
span { padding: 0.1em 0.3em; border: 1px solid grey; }
.hidden{ display: none; }
<div class="sku">
<span class="sku-value" data-id="150">1000</span>
<span class="sku-value hidden" data-id="151">2000</span>
<span class="sku-value hidden" data-id="152">3000</span>
</div>
<div class="size-values">
<span class="size-value" data-id="150">Medium</span>
<span class="size-value" data-id="151">Large</span>
<span class="size-value" data-id="152">Xtra Large</span>
</div>

Related

Get content based on CSS pseudo class

I have a CSS declaration as follows:
span.boshbashbosh:nth-child(1):active:after {
content: 'FC';
}
I am trying to access the content (FC) it by using:
var content = window.getComputedStyle(document.getElementsByClassName("boshbashbosh:nth")[0], '::active').getPropertyValue('content');
alert(content);
However, all the alert does is show normal or none
Any advice on how to do this in plain JS? If I had 1000 of these, I wouldn't want to click/hover each one, is there a way I could dump some code into the developer console to do this?
There are a few issues here, the main one being that the CSS selector will only return an active element during a click interaction by the user, seeing that a click interaction causes the target element to become :active.
With that in mind, you could wrap your login in a mousedown element as shown below to extract the expected content value while the corresponding span element is :active as shown:
document.addEventListener("mousedown", () => {
/* When mouse down occours, look for the element that we want to read
pseudo content from */
var element = document.querySelector(".boshbashbosh:nth-child(1):active");
if (element) {
/* If the target element is active, read the content of the ::after
pseudo element */
var content = window.getComputedStyle(element, ":after")
.getPropertyValue("content");
alert(content);
}
})
span.boshbashbosh:nth-child(1):active:after {
content: 'FC';
}
/* Added for clarity/usability of snippet */
span {
background: pink;
margin: 1rem 0;
padding: 1rem;
display: block;
height: 1rem;
}
span.boshbashbosh:active {
background: yellow;
}
<p>Clicking first box alerts the ::after content</p>
<div>
<span class="boshbashbosh"></span>
<span class="boshbashbosh"></span>
<span class="boshbashbosh"></span>
</div>
I've also replaced the getElementsByClassName() call with querySelector() to simplify the code. Hope that helps!
Update
To access the content of multiple pseduo elements, you could adapt the snippet above as follows:
document.querySelectorAll(".boshbashbosh").forEach((element) => {
var content = window.getComputedStyle(element, ":after")
.getPropertyValue("content");
console.log(content);
});
span.boshbashbosh:nth-child(1):after {
content: 'FC';
}
span.boshbashbosh:nth-child(2):after {
content: 'EB';
}
span.boshbashbosh:nth-child(3):after {
content: 'DA';
}
<div>
<span class="boshbashbosh"></span>
<span class="boshbashbosh"></span>
<span class="boshbashbosh"></span>
</div>

Javascript set CSS Class for Multiple Elements (one element set, remaining elements unset)

I am developing a website which has a few filter buttons which are grouped into several groups. I am trying to find a way to set the class of one of these buttons to "filter-set" while all other buttons in the group are set to "not-set".
Each button is a DIV with a unique ID.
i have some bloated code where each button has its own function and sets the associated buttons to "not-set" but this seems inefficient and im sure there's a better way!
Bloated code example:
function setClassR(){
document.getElementById('filter_rare').className= 'filter-set';
document.getElementById('filter_common').className= 'not-set';
document.getElementById("filter_occasional").className = 'not-set';
}
function setClassC(){
document.getElementById('filter_rare').className= 'not-set';
document.getElementById('filter_common').className= 'filter-set';
document.getElementById("filter_occasional").className = 'not-set';
}
function setClassO(){
document.getElementById('filter_rare').className= 'not-set';
document.getElementById('filter_common').className= 'not-set';
document.getElementById("filter_occasional").className = 'filter-set';
}
I would like to be able to have a function for each group of filters which when run using an onClick=function() sets the clicked button to "filter-set" and all others to "not-set"
I have tried the following code but it doesnt appear to run:
function setClassSeas(rareClass, commonClass, occClass) {
setClass("filter_rare", rareClass);
setClass("filter_common", commonClass);
setClass("filter_occ", occClass);
}
function setClass(IDName, displayValue) {
var items = document.getElementById(IDName);
for (var i=0; i < items.length; i++) {
items[i].className = (displayValue? "filter-set" : "not-set");
}
}
UPDATE///
HTML Code for the Divs acting as buttons:
<div id="filter_rare" title="Rare"
class="not-set"
onclick="chosenFrequency('frequency=Rare'); setClassR();"></div>
<div id="filter_common" title="Common"
class="not-set"
onclick="chosenFrequency('frequency=Common'); setClassC();"></div>
<div id="filter_occasional" title="Occasional"
class="not-set"
onclick="chosenFrequency('frequency=Occasional'); setClassO();"></div>
If every button has a class, say filter-button, then you can address all buttons at once.
In modern development you should attach an event handler instead of using inline onclick handlers.
With all buttons having a common class you can find them all at once. I'm changing your buttons to look like this, adding the "filter-button" class and removing the onclick handler:
<div id="filter_rare" title="Rare"
class="filter-button not-set">Rare</div>
(I've put text in the div just to simplify this demonstration)
Now collect all the filter buttons:
let filters = document.querySelectorAll('div.filter-button');
This gets you a NodeList of elements (kind of like an Array but not one) You'll want to attach an onclick event handler to each of the buttons. To do this you can use the NodeList.forEach() call.
filters.forEach(node => node.addEventListener('click', someFunction));
In the function that gets called when you click a button, you want to clear any filter-set class that's currently set, put back the original not-set class, then set the filter-set class only on the button that was clicked. This will look something like this:
function someFunction(event) {
// again, use forEach to do the same thing to each filter button
filters.forEach( function(node) {
node.classList.remove('filter-set');
node.classList.add('not-set');
} );
// now add the 'filter-set' class on the button that was clicked
event.target.classList.add('filter-set');
}
The good thing about using classList instead of just doing className="something" is that classList can add/remove classes while leaving other classes alone; doing className="something" wipes out all the classes that are present and replaces them with "something".
Putting that all together, and using an anonymous function instead of named function gives this snippet:
let filters = document.querySelectorAll('div.filter-button');
filters.forEach(node => node.addEventListener('click',
function(event) {
console.log(event.target);
filters.forEach(function(node) {
node.classList.remove('filter-set');
node.classList.add('not-set');
});
event.target.classList.add('filter-set');
}));
/* Make these look like buttons; put a green border on them */
.filter-button {
min-height: 2ex;
max-width: 12em;
padding: .25em;
margin: .7em .3em;
background-color: lightgreen;
border: 2px solid green;
border-radius: 4px;
}
/* use a Red border on any button that has "filter-set" */
.filter-button.filter-set {
border: 2px solid red;
}
/* limit the height of the stack-snippet console */
div.as-console-wrapper {
max-height: 2.5em;
}
<div id="filter_rare" title="Rare"
class="filter-button not-set">Rare</div>
<div id="filter_common" title="Common"
class="filter-button not-set">Common</div>
<div id="filter_occasional" title="Occasional"
class="filter-button not-set">Occasional</div>
Using the class not-set is really redundant — you could just have no extra class on buttons by default and it would simplify things a little. Buttons would have the class(es) filter-button or filter-button filter-set.
Change your setClass function according to this. Hope it will work. document.getElementById() function will always return a single element (not a list of elements). Even if you have multiple elements having the same ID this function will always return the first element having the given ID. Do not forget to call your setClassSeas() function from html.
function setClassSeas(rareClass, commonClass, occClass) {
setClass("filter_rare", rareClass);
setClass("filter_common", commonClass);
setClass("filter_occ", occClass);
}
function setClass(IDName, displayValue) {
var item = document.getElementById(IDName);
item.className = displayValue ? "filter-set" : "not-set";
}
<div id="filter_rare" title="Rare" class="not-set"
onclick="chosenFrequency('frequency=Rare'); setClassSeas(true, false, false);"></div>
<div id="filter_common" title="Common" class="not-set"
onclick="chosenFrequency('frequency=Common'); setClassSeas(false, true, false);"></div>
<div id="filter_occasional" title="Occasional" class="not-set"
onclick="chosenFrequency('frequency=Occasional'); setClassSeas(false, false, true);"></div>
Here is another (as in "alternative") way to do it (but with jQuery, oh no!)
$('body').click(function(e) {
let $clicked = $(e.target);
console.log("Clicked " + $clicked.attr('id'))
if ($clicked.hasClass('filter')) {
let $filters = $clicked.closest('.filter-group').find('.filter');
let unset = $clicked.hasClass('set');
$filters.toggleClass('not-set', true);
$filters.toggleClass('set', false);
if (!unset) {
$clicked.toggleClass('not-set', false);
$clicked.toggleClass('set', true);
}
}
})
button.filter.not-set {
background: white;
}
button.filter.set {
color: white;
background: green;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="filter-group">
<button id="filter_rare" class="filter not-set">
filter_rare
</button>
<button id="filter_common" class="filter not-set">
filter_common
</button>
<button id="filter_occasional" class="filter not-set">
filter_occasional
</button>
</div>
<div class="filter-group">
<button id="filter_one" class="filter not-set">
filter_one
</button>
<button id="filter_two" class="filter not-set">
filter_two
</button>
<button id="filter_three" class="filter not-set">
filter_three
</button>
</div>

How do I hide the closest element to the element that was clicked without jQuery?

I have a function that changes the src attribute of an icon when this one is clicked.
I also want it to hide the closest icon of the class fave_icon. I tried the following but it's not working:
function trash(event, trashcan){
event.stopPropagation();
if (trashcan.getAttribute('src') == "Iconos/tacho.png")
{
trashcan.src = "Iconos/warning.png"; //this works ok
var heart = trashcan.closest(".fave_icon");
heart.style.visibility = "hidden"
}
}
Basically I want to hide the closest element with class fave_icon to trashcan.
On the HTML I have this several times:
<button class="accordion">
<div>
<img src="Iconos/heart.png" onclick="fav(event,this);" alt="Fave" class="fave_icon">
</div>
<div>
<img src="Iconos/tacho.png" onclick="trash(event,this);" alt="Delete" class="delete_icon">
</div>
</button>
If fave_icon is a class then you have to place dot (.) before the class name as part of the selector.
Change var heart = trashcan.closest("fave_icon");
To
var heart = trashcan.closest(".fave_icon");
Based on the code and HTML you have provided you can do something like the following:
function trash(event, trashcan){
event.stopPropagation();
if (trashcan.getAttribute('src') == "Iconos/tacho.png"){
trashcan.src = "Iconos/warning.png"; //this works ok
var heart = trashcan.closest('button').querySelector('.fave_icon');
heart.style.visibility = "hidden";
}
}
<button class="accordion">
<div>
<img src="Iconos/heart.png" onclick="fav(event,this);" alt="Fave" class="fave_icon">
</div>
<div>
<img src="Iconos/tacho.png" onclick="trash(event,this);" alt="Delete" class="delete_icon">
</div>
</button>
From the trash icon, you go up a level to the div, select the previousElementSibling to get the heart's div, and then go down a level to the heart image itself.
Because the element is already included in the event target, you don't need to pass this. Or, even better, if you select the trash image first, you can avoid this entirely and use explicit variable names, which are easier to understand and debug.
But inline event handlers are essentially eval inside HTML markup - they're bad practice and result in poorly factored, hard-to-manage code. Seriously consider attaching your events with JavaScript, instead, eg: https://developer.mozilla.org/en/DOM/element.addEventListener
Another problem is that buttons should not have closing tags. Use a container element instead, like a div.
So, try something like this:
document.querySelectorAll('img[src="Iconos/tacho.png"]').forEach(img => {
img.onclick = () => {
const heartImg = img.parentElement.previousElementSibling.children[0];
heartImg.style.visibility = 'hidden';
};
});
<div class="accordion">
<div>
<img src="Iconos/heart.png" alt="Fave" class="fave_icon">
</div>
<div>
<img src="Iconos/tacho.png" alt="Delete" class="delete_icon">
</div>
</div>
you can add a class to the clicked element and use the general sibling combinator if the two items are adjacent.
document.getElementById("hide")
.addEventListener("click", (event) => {
event.target.classList.add('active');
}, false);
#hide.active~.element {
visibility: hidden;
}
#hide {
cursor: pointer;
}
.accordion {
padding: 15px;
background: lightgrey;
border-bottom: 1px solid grey;
}
.accordion div {
color: black;
margin-right: 20px;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/icono/1.3.0/icono.min.css" rel="stylesheet" />
<div class="accordion">
<div class="icono-trash" id="hide"></div>
<div class="element icono-heart"></div>
</div>

how to dynamically remove css in Angular?

.items {
color: black;
background-color: white;
-webkit-transition:all cubic-bezier(0.250,0.460,0.450,0.940) 0.5s;
transition:all cubic-bezier(0.250,0.460,0.450,0.940) 0.5s;
}
.items.currentlySelected {
color: orange;
background-color: yellow;
font-size: 3em;
}
<div ng-repeat="returnedUser in returnedUsers" value="set" ng-click="myVar='currentlySelected'">
<span class="items" ng-class="myVar">
{{returnedUser.login}}
</span>
</div>
$scope.getRepoData = function(singleUser) {
$scope.selectedUser = singleUser;
$http.get(singleUser.repos_url).
success(function(data,status) {
if(status===200) {
$scope.returnedRepos = data;
}
}).
error(function(data,status){
alert("something happened in single user");
});
}
When I click the link, it works beautifully. However when I want to select another link, I want to restore the link back to items (from items.currentlySelected) before applying my css on the newly selected link
How do I do this using Angular (no jquery please)
First remove the ng-click from the div that has ng-repeat. Next in your getRepoData function, do something where you set the currently clicked item to some $scope-bound variable. Let's say that variable is $scope.selectedRepo.
Now, to set the class dynamically for your span, you'd have:
<span ng-class="{active: selectedRepo.id === returnedUser.id}">
You are using a global scope variable for all items (myVar). Instead, conditionally apply the class if the id is equal to the selected:
<div ng-repeat="returnedUser in returnedUsers" value="set"
ng-click="selected=returnedUser"> <!-- Assign current to selected variable -->
<span class="items"
ng-class="{ 'currentlySelected': returnedUser.id == selected.id }"> <!-- Apply class 'currentlySelected' if selected.id is equal to returnedUser.id -->
<a href="#"
ng-click="getRepoData(returnedUser);">
{{returnedUser.login}}
</a>
</span>
</div>

Removing an element added by ::before pseudo selector

I have the following case: (styling is done in SASS and unnecessary stylings are omitted.)
.header {
...
&::before {
...
position: absolute;
height: 0.5rem;
...
}
}
This creates a bar on top of the application's menu bar. In certain cases this bar has to be removed. I have read questions like these, but with no success. What would be the best way to remove this bar added by the ::before selector?
Only CSS can remove pseudo element, so you need to have an other class that display:none; the before. First declare that class in the CSS :
.header {
...
&::before {
...
position: absolute;
height: 0.5rem;
...
}
&.no-before::before{
display:none;
}
}
Then, when you want to remove it :
$('.header').addClass('no-before'); //Remove before
$('.header').removeClass('no-before'); //Re-add before
The usual way is to create a more specific rule that applies to the element(s) in question (or a later rule with the same specificity), and specify display: none to hide the pseudo in that case.
For example: Here, I want to have an X in front of <span class="foo">, but not if they're in .header:
span.foo::before {
content: 'X ';
}
.header span.foo::before {
display: none;
}
<div>
These have the X:
<span class="foo">span.foo 1</span>
<span class="foo">span.foo 2</span>
<span class="foo">span.foo 3</span>
</div>
<div class="header">
These don't:
<span class="foo">span.foo 4</span>
<span class="foo">span.foo 5</span>
<span class="foo">span.foo 6</span>
</div>
If you are manipulating the DOM by using JavaScript, you can add a class name - for instance .remove-bar - to the element having .header in order to remove the pseudo-element (generated content):
.remove-bar {
&::before { content: none; }
}
Also make sure that it is placed after the previous styles, or use a more specific selector if needed.
For remove special element use this method.
<button onclick="myFunction()">Remove</button>
<div id="myList">
<div> Coffee </div>
<div id="child2" > Tea </div>
<div> Milk </div>
</div>
your JavaScript :
<script>
function myFunction() {
const list = document.getElementById("myList");
if (list.hasChildNodes()) {
list.removeChild(list.children[0]);
}
}
</script>
you can combine above function with this code:
const parent = document.getElementById('myList');
const children = parent.children;
let index = -1;
for (let i = 0; i < children.length; i++) {
if (children[i].id === 'child3') {
index = i;
break;
}
}
alert(index); // 👉️ 2

Categories