changing css custom properties in javascript - javascript

i've a JSX module(card.jsx) which is call in another component to create 5 instances of this module(card.jsx).
import card.css
...
<div className='cistern'>
<div className="shape">
<div className="wave"></div>
</div>
</div>
in my card.css, i have a custom property call filled.
.wave {
position: absolute;
--filled: 20%;
in my card.jsx i try to set different value for the css custom variable "filled".
React.useEffect(() => {
var cisternProperty = document.querySelector( '.wave' )
cisternProperty.style.setProperty('--filled', showFilledRoundString)
console.log('show Tank Level', showFilledRoundString)
}, [])
only the first instance use the new value, all other instance used the default "filled" value set in the css file.
what's happening? what could be the workaround?
thanks

To change the CSS property for every instance of an element in the .wave class you can either change the property in some common ancestor element rather than in the .wave elements themselves or you can go through all the .wave elements change their property.
So instead of using querySelector, which gives you only the first element in that class, use querySelectorAll which gives you a collection of all the elements in that class. You can then loop through them setting the CSS variable individually for each one:
var cisternProperties = document.querySelectorAll( '.wave' );
cisternProperties.forEach( cisternProperty => {
cisternProperty.style.setProperty('--filled', showFilledRoundString);
});

Related

Adding class to an element got by classname

Trying to add a class to an element, I need to get by class name. Any idea why it is not working? (Using Wordpress Code Snippets in case someones wondering)
<?php
add_action( 'wp_head', function () { ?>
<script>
var myButton = document.getElementsByClassName("menu-toggle mobile-menu");
myButton.classList.add(".teststyle");
</script>
<style>
.teststyle {}
</style>
<?php } );
You're querying for multiple elements, as class names are not unique, therefore you need to loop through them. Also, you don't need the selector-specific dot to specify a class when using classList.add.
This snippet should work, if your element has both the classes menu-toggle and mobile-menu:
document.querySelectorAll(".menu-toggle.mobile-menu").forEach((element) => element.classList.add("teststyle"))
If the element with the class mobile-menu is a child of the element with the class menu-toggle, try this:
document.querySelectorAll(".menu-toggle.mobile-menu").forEach((element) => element.classList.add("teststyle"))
Edit:
As you're adding the script into the head, it'll be run before the HTML elements are loaded, so you need to defer the execution until the DOM is loaded.
You can either add the script after the elements in the DOM, or use the DOMContentLoaded event, e.g. like this:
window.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll(".menu-toggle.mobile-menu").forEach((element) => element.classList.add("teststyle"))
});
Normally the response of a document.getElementsByClassName(...) is a NodeList. So basically you have to assign the value to a specific index of that NodeList.
In this case you should be getting an error in console that says something like
TypeError: Cannot read property 'add' of undefined
That happens because your NodeList doesn't have an element called classList and because of that the add function doesn't exist in that context.
Once you get the elements by class you get a NodeList element with all the corresponding elements and what you can do is:
myButton[0].classList.add("teststyle");
Also, here you don't need the point prefix for the class.
Here's an example.
var myButton = document.getElementsByClassName("menu-toggle mobile-menu");
console.log(myButton);
console.log(myButton[0]);
myButton[0].classList.add("test-class");
.test-class {
color: red;
}
<button class="menu-toggle mobile-menu">Button</button>

can we change ngIf to hidden in Angular library explicitely

I am using Angular Material's Tab. And i am suppose to add and remove class using:
const classotherClustersSelection = document.getElementsByClassName('others');
Array.from(classotherClustersSelection).forEach(item => {
item.classList.remove('others');
})
On change of a particular data i am suppose to remove class from the tabs section, from all tabs. But unfortunately other tab that contains 'others' class is hidden with ng-if, so i am not able to manipulate the DOM directly. Whats are the approaches possible?
The issue is that *ngIf not hide the element, it removes it completle from the dom.
That's why you are not able to access the element.
If you want to hide something but still access it, you should remove the element by style.
You can use class or style binding for that:
style binding:
<div [style.display]="conditionForHide ? 'none' : 'initial'">
class binding:
<div [class.removeClass]="conditionForHide">
For the second option you need to create the class in you style sheet. An example to remove the element without display:
.conditionForHide {
opacity: 0;
pointer-events: none;
cursor: default;
width: 0px;
height: 0px;
position: absolute;
}
Edit
You should not manipulate the material code. And angular offers you a couple of options to manipulate your code next to plane javascript (getElementsByClassName).
The fastest way is when the data change is triggered, you manipulate the class via class binding:
Controller:
public showClassOthers = true;
onDataChange() {
// do some tasks
this.showClassOthers = false;
}
HTML:
<mat-tab-group>
<mat-tab label="First" [class.others]="showClassOthers"> Content 1 </mat-tab>
<mat-tab label="Second" [class.others]="showClassOthers"> Content 2 </mat-tab>
</mat-tab-group>
Add ngClass property to your div like I did below:
<div [ngClass]="{others: boolClass}" *ngIf="data==='requiredValue'"></div>
Then change the value of boolClass variable where the data gets changed:
this.data = 'someOtherValue';
this.boolCass = (this.boolClass)?(this.data==='requiredValue'): false;
Now the class will be removed from the element.
You can refer from my example here https://stackblitz.com/edit/angular-cc6tru
You can use ngClass to Conditionally add/remove the class from the DOM element.
demo
In Demo, I am changing the data on button click and based on the value class is added or removed.

Getting error as : Uncaught TypeError: Cannot read property 'parentNode' of null

I am trying to add new div adjacent to one div with the reference of ComponentID of the first div I want to add the second parallel div
Like:
<div>
<div id="userActivityStatusMenu-1110">Neighbor 1</div>
// want new div here
</div>
Below is the script I am trying:
/* Adds Element BEFORE NeighborElement */
Element.prototype.appendBefore = function (element) {
element.parentNode.insertBefore(this, element);
}, false;
/* Adds Element AFTER NeighborElement */
Element.prototype.appendAfter = function (element) {
element.parentNode.insertBefore(this, element.nextSibling);
}, false;
/* Typical Creation and Setup A New Orphaned Element Object */
var NewElement = document.createElement('div');
NewElement.innerHTML = 'This is new element';
NewElement.id = 'NewElement';
/* Add NewElement BEFORE -OR- AFTER Using the Aforementioned Prototypes */
NewElement.appendAfter(getComponent(Ext.getCmp("userActivityStatusMenu-1110").itemId));
Please let me know where I am doing wrong,or what is the best way to do this
getComponent is not a global function, but a method of every ExtJS Container
someContainer.getComponent will not return a DOMElement, but an Ext.Component or one of its descendants
To get the DOM Node of any ExtJS Component, you can either pass the component itself or the id of the component to Ext.getDom(idOrComponent)
Example:
NewElement.appendAfter(Ext.getDom("userActivityStatusMenu-1110"));
Note: It's not recommended to work with the id field at all. Instead assign a custom, unique itemId and query the Component via Ext.ComponentQuery.query('#itemid') instead.
Note2: To be completely in line with the style of ExtJS, you should not manipulate the internal HTML structure at all. Instead either add new items to the parent element or if you really need custom HTML to be inserted, modify the html property or XTemplate of whatever Component you're trying to modify here.

jQuery set newly created element to a dynamic elements current style

I have an element, for communication purposes we'll call $elA which during runtime is having some CSS properties changed via .animate()
for example:
$elA.stop().animate({
top: `${Math.floor(Math.random()*99)}%`,
left: `${Math.floor(Math.random()*99)}%`,
width: '15px',
height: '15px',
opacity: '1.0'
//etc etc etc
});
When a certain event triggers later in the code, I am in need of creating a clone of $elA. For communication purposes lets call this $elB.
How can I do something akin to $elB.css = $elA.css during this event? It doesn't need to be a jQuery method, or it can be there is no problem, I just am not sure if there is an elegant way of handling this case where there is no current class associated with it because the properties for the DOM element I wish to clone doesn't exist in a sheet anywhere.
Thank you.
if you only need the styles to be cloned you can use a function for it like that: (Fiddle hier: https://jsfiddle.net/taxostd0/2/)
function copyStyles(from, to) {
var fromStyles = getComputedStyle(from);
for(prop in fromStyles) {
to.style[prop] = fromStyles[prop];
}
}
and then call it like this:
copyStyles($elA[0], $elB[0]);
Use .clone() method of jQuery. It performs a deep copy of the set of matched elements, meaning that it copies the matched elements as well as all of their descendant elements and text nodes.
If using javascript, the following would help to copy just the styles to another element.
document.getElementById("copy2").style.cssText= document.getElementById("copy1").style.cssText;
<div id="copy1" class ="copycss" style="width: 100px; background-color: blue; color: red; font-size: 15;">
10
</div>
<div id="copy2" class ="copycss">
10
</div>

Aurelia conditionally wrapping slots in components

I'm creating Aurelia components which wrap material-components-web, cards specifically right now and am wondering what's the correct way of implementing multiple content sections (actions, etc.).
Slots seem to be the right choice but I cannot just put the actions div on the template at all times, but only if any actions are actually present.
Simply put I need to check if a slot has been defined inside the component template.
<template>
<div class="card">
<div class="content">
<slot></slot>
</div>
<!-- somehow check here if the slot has been defined -->
<div class="actions">
<slot name="actions"></slot>
</div>
</div>
</template>
Out-of-the-box, there is no direct way to do this like a $slots property, however you should be able to access slots via the template controller instance itself: au.controller.view.slots - the specific slot inside of this array has more information about the slot itself and its children.
Here is an example of an Aurelia application with a modal component (custom modal element). The modal itself has a slot where HTML can be projected inside of it. We have a header, body and footer.
Each predefined slot inside of our custom element should show up inside of a children object, where the property name is the name of our slot. If you do not provide a name for a slot (the default slot) the name of it internally is: __au-default-slot-key__.
We first check if the slot exists and then we check the length of its children, the children array exists inside each slot. If a slot has no HTML projected into it, it will have a children length of zero. This is reliable, because default content defined inside of the slot does not get put into the children array, only projected HTML does.
You'll see the work is being done mostly inside of modal.html, but pay close attention to modal.js where we inject the element reference of the custom element and then access the Aurelia instance using au to get to the controller containing our slots itself.
There is one caveat with this approach: you cannot use if.bind to conditionally remove HTML inside of your custom element. If you use if.bind on a DIV containing a slot, it actually removes its slot reference so it can't be checked. To work around this, just use show.bind (as I do in my provided running example).
Use CSS :empty Selector
CSS is the right tool for this job, not Aurelia. The :empty selector will allow you to display: none the div.actions when the slot isn't populated.
.card .actions:empty {
display: none;
}
According to the :empty selector spec as explained by CSS-Tricks, white space will cause empty to fail to match, so we just need to remove the white space around the slot.
<div class="actions"><slot name="actions"></slot></div>
Working example here: https://gist.run/?id=040775f06aba5e955afd362ee60863aa
Here's a method I've put together to detect if any slots have children (excluding HTML comments)
TypeScript
import { autoinject } from 'aurelia-framework';
#autoinject
export class MyClass {
private constructor(readonly element: Element) {
}
private attached() {
}
get hasSlotChildren(): boolean {
if (!this.element ||
!(this.element as any).au) {
return false;
}
let childrenCount = 0;
const slots = (this.element as any).au.controller.view.slots;
for (let slotName of Object.keys(slots)) {
const slot = slots[slotName];
if (slot.children &&
slot.children.length > 0) {
for (let child of slot.children) {
if (child instanceof Comment) {
// Ignore HTML comments
continue;
}
childrenCount++;
}
}
}
return childrenCount > 0
}
}
HTML
<template
class="my-class"
show.bind="hasSlotChildren"
>
<slot></slot>
</template>

Categories