There are multiple tabs like this:
<Menu.Item className="tab-title tab-multiple" key="key">
<p className="tab-title-text">
Tab title
<span className="items-counter">{showText}</span>
</p>
</Menu.Item>
the one that is the active/selected one, beside of its original class (tab-title tab-multiple) it also has active and its class looks like this: active tab-title tab-multiple
I want to show that element only if the class contains "active".
Is there a way to do this in React? Without taking in account onClick.
Tried with a ternary but it seems it does not work:
{element.classList.contains('active') ? (
<span className="items-counter">{showText}</span>
) : (<></>)}
Normally, you don't have to do that in React because you drive the classes on the element from state information in your component, and so you just look at that state information rather than looking at the class list. Your best bet by far is to do that, rather than accessing the DOM class list later.
If the active class is being added by something outside the React realm that's operating directly on the DOM element, you'll have to use a ref so you can access the DOM element.
To create the ref:
const ref = React.createRef();
To connect it to your React element, you add the ref property;
<Menu.Item className="tab-title tab-multiple" key="key" ref={ref}>
Then when you need to know, you check the current property on the ref:
if (ref.current && ref.current.classList.contains("active")) {
// ...
}
Beware that if you do that during a call to render (on a class component) or to your functional component's function, on the first call the ref will be null and on subsequent calls it'll always refer to the element for the previous version of the component. That element will probably get reused, but not necessarily.
React is driven by the data model (props & state). Use whatever data property you use to assign the active class name, to also hide/show the contents.
Another option is to use CSS:
.items-counter {
color: red;
}
.tab-title:not(.active) .items-counter {
display: none;
}
<div class="tab-title tab-multiple" key="key">
<p class="tab-title-text">
Tab title
<span class="items-counter">Not Active</span>
</p>
</div>
<div class="tab-title tab-multiple active" key="key">
<p class="tab-title-text">
Tab title
<span class="items-counter">Active</span>
</p>
</div>
You need to have an indicator, that maintains the active class.
let className ="";
if(isActive)
{
className = className +" active"; // props.isActive in case of child component
}
Now that you have added the className based on the flag.
instead of checking for,
if(element.classList.contains('active'))
you can check for,
if(isActive)
This is applicable for subcomponents also, where you read the isActive flag through props.
Related
I'm reactively loading elements using Svelte's {#each} functionality, like so:
{#each $items as item}
<div>
<Button on:click={someFunction}>{item.text}</Button> (*)
</div>
{/each}
(*) a component which forwards its on:click
What I want to do is when someFunction is called by clicking the created item, reference the actual DOM element. I know you can reference the specific array item by passing the index to the function, but that gives me the array item, not a reference to the unique DOM element. How would one go about doing this?
Things I tried so far:
on:click={() => someFunction(this)}: returns undefined
on:click={(el) => someFunction(el)}: returns undefined
on:click={(e) => someFunction(e)}: followed by using e.target, which does return the button that is clicked, but would need .parentElement to get to the div, which doesn't seem like a very Svelte way.
on:click={someFunction}: combined with bind:this={anItem}, which of course only returns the last created element in the {#each} block.
If you just want the parent div, a quick and dirty solution would be to just use an Array.
A more elegant solution would be to wrap <Button> within a parent component and get the reference from there, through the context API for instance or a slotted prop.
<script>
let itemsElements = [];
const someFunction = (i) => console.log(itemsElements[i]); // <- this is your div
</script>
{#each $items as item, i}
<div bind:this={itemsElements[i]}>
<Button on:click={() => someFunction(i)}>{item.text}</Button>
</div>
{/each}
the button in question is a modal which I want to have an open/close functionality. So, upon clicking itself I want it to toggle the class "open". However, using the class directive this results in all buttons/modals generated by {#each} to simultaneously toggle the class.
That just means you are using the wrong property. If you have multiple objects you need to store the state per item and then use that for the class directive.
E.g.
{#each $items as item}
<div>
<Button on:click={someFunction}>{item.text}</Button>
<div class="modal" class:open={item.show}>...</div>
</div>
{/each}
Alternatively you can store the state in a separate list or a dictionary. You can also extract the content of the {#each} to a component which then can have a local state variable for the open state.
which option among the following is better or used as a standard way to show/hide the html elements
changing element.style.display
adding/removing a separate class called hide {display: none}
any other standard way
PS: this JavaScript hide/show element question uses the first option mentioned( changes the style to block to show which may not be desired). I would like to know whether this method is used in most websites or the adding /removing a separate class or any other way
A third way in the answers below https://stackoverflow.com/a/68983509/14478972
I prefer to toggle a class using DOMTokenList.toggle():
The toggle() method of the DOMTokenList interface removes a given token from the list and returns false. If token doesn't exist it's added and the function returns true.
Well except the first and second, there is the other way.
Which is rendering the element its self.
It has a better security. as the user wont know if there is a hidden element inside the toggle div. Eg when people try to look at the html
Have a look below
I used jQuery as its easier to write. If you are not able to rewrite a JavaScript version will be happy to rewrite for you.
var items = $(".toggle");
var item = {};
// setup the auto toggle
$(".toggle").each(function(el) {
var id = new Date().getUTCMilliseconds() + $(this).index()
item[id] = $(this).find("content")
if (!$(this).hasClass("show")){
$(this).find("content").remove();
}
$(this).attr("id", id)
});
$(".toggle").click(function() {
if ($(this).find("content").length > 0)
$(this).find("content").remove();
else $(this).append(item[$(this).attr("id")])
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="toggle">
<h1>click here to toggle content </h1>
<content>
this is a test
</content>
</div>
<div class="toggle show">
<h1>click here to toggle content(start state is visible) </h1>
<content>
this is a test
</content>
</div>
Option 1 would be standard for only hiding the element, but if you would like to add other styles like transitions and pointer events option 2 is preferred
What is the easiest way to retrieve the (id of the) node that was just clicked on in a <v-treeview>? There is the update:open event that emits an array of all open nodes. I could temporarily store the whole array and compare a new version with the old one to see which element was added or removed. But this seems to be a bit cumbersome, right? I want to use the id of the node to dynamically reload the children of the children of the node from the backend, so that the user has the feeling that the data is already in the frontend. Maybe it is possible to emit in the update:open event not the whole array, but only the current node, somehow like this: #update:open="onExpand(item)"? (This throws the error Property or method "item" is not defined.)
You could use VTreeView's label slot, which passes the item itself in its slot props. In that slot, you could render a span with a click handler that also includes the item:
<template>
<v-treeview :items="items">
<template #label="{ item }">
<span #click="onItemClick(item)">{{item.name}}</span>
</template>
</v-treeview>
</template>
<script>
export default {
methods: {
onItemClick(item) {
console.log(item.id)
}
}
}
</script>
You can use update:active with the return-object prop. It will return the full object instead of the node id.
https://vuetifyjs.com/en/api/v-treeview/#props
Using bootstrap-vue and its list items:
<b-nav pills>
<b-nav-item v-for="item in items">{{ text }}</b-nav-item>
</b-nav>
I want to achieve the following thing:
When i click on a certain item, it would get an active state (there's built-in directive :active, if it's returns true, then it gets an active class), however if i click on another item, the previously clicked item should stay as active either, and new one clicked should be active either. So i've decided to use a component with it's own variable to toggle an active state. But i've faced the problem inside a component, because it must have a root div, so i'm forced to use it this way:
Component
<template>
<div>
<b-nav-item :active="selectedCategory">{{ category.price }} ₸</b-nav-item>
</div>
</template>
<script>
export default {
name: 'Category',
data () {
return {
selectedCategory: false
}
}
}
</script>
Main template after adding the component
<b-nav pills>
<Category v-for="item in items"/>
</b-nav>
As you can see, i've used top level div that breaks out my layout.
Here's the questions:
How can i solve the problem with top level div inside my component? So my list item won't break
is it right decision to use a component in my case?
Should an :active prop be in component or in main template?
Apply a class to b-nav-item (e.g., named "nav-item"), and style its display to be inline-block:
.nav-item {
display: inline-block;
}
demo
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>