Toggle class on buttons in Svelte - javascript

Consider this Svelte code
{#each filters as filters, i}
<div class='filter-container'>
<div class='button filter' on:click={ () => setFilter(filters.name, filterContext)}>{filters.name}</div>
</div>
{/each}
The above filter buttons are made depending on how many filters there are in some js object. How can I create a class that is toggled for all filter buttons when the one button is clicked.
eg. when one is active all the others are not?

Class are just strings - in svelte you can bind them to an expression as any other attribute.
For example:
<div class={'button filter ' + (activefilter === filters.name ? 'isactive' : '')}/>
when activefilter === filters.name is true, then the button class will became 'button filter isactive'
A special short syntax to toggle classes is provided too. Find more here

We can easily bind class names to expressions, so that the class will be added if the expression becomes truthy:
<script>
let isActive = false
</script>
<style>
.active {
color: red;
}
</style>
<h1 class:active={isActive}>Hello!</h1>
<button on:click={() => isActive = !isActive}>Click me</button>
Here, clicking the button toggles the isActive boolean. The class active is bound to that variable on the h1 element and you can see that the color now changes on every button click.
That is the standard way on how to set single classes dynamically.
REPL: https://svelte.dev/repl/988df145876a42c49bb8de51d2cae0f2?version=3.23.0

Related

How to access dynamically created buttons in angular?

So, I am creating a quiz application where I have a scrollbar for questions and in that scrollbar I have buttons depending on the length of a question set. So I've created those buttons using *ngFor directive. Now what I want to do is whenever a user selects any option (mcq), the question buttons in the scrollbar should get highlighted in following way:
If user selects any option, then change the question button color to Green
If user skips the question, then change the question button color to Yellow
HTML Code for Question Scrollbar:
<div id="answer-buttons" class="ques-grid ">
<button #navBarButton *ngFor="let item of items">{{item}}</button>
</div>
I'm have tried doing it by first accessing the buttons using ViewChild in ts file and then apply some logic, but it's not working, it is only changing the color of first button
#ViewChild('navBarButton',{ static: false }) navBarButton:ElementRef
//and in some function I've tried this logic
if(this.attemptedQuestionCount[this.currentQuestionIndex]==1){
this.navBarButton.nativeElement.style.backgroundColor = "#228B22"
}
else{
this.navBarButton.nativeElement.style.backgroundColor = "#FCF55F"
}
How can I achieve my objective?
You can check for attemptedQuestionCount and change background color like this
<div id="answer-buttons" class="ques-grid ">
<button *ngFor="let question of questions; let i=index"
[style.background-color]="attemptedQuestionCount[i] === 1 ? '#228B22' : '#FCF55F'">{{question}}</button>
</div>
Add button tag as follows:
<button *ngFor="let question of questions; let i=index"
[style.background-color]="attemptedQuestionCount[i] === 1 ? '#228B22' : '#FCF55F'">{{question}}</button>
You can add the click handler directly to the button using
<button *ngFor="let item of items; let indexOfelement=index"
(click)="heyYouClicked(indexOfelement)">{{item}}</button>
And then in the component you place the handler
export class AppComponent {
items = ["hello", "world"]
heyYouClicked(index){
console.log("you clicked " + index)
}
}
You can try ngClass for simplicity.
<button #navBarButton *ngFor="let item of items" class="defualt_state" [ngClass]="{'new_state': (condition_here)}">{{item}}</button>
And in the stylesheet you can have the above class configured
.new_state { background-color: #228B22 !important }
And set the default color of the button this way
.default_state { background-color : #FCF55F}
So when the condition matches it will take the color specified in the new_state class or else will take the default color from default_state class.

Conditional styling on class in Svelte

I'm trying to use Svelte to do some conditional styling and highlighting to equations. While I've been successful at applying a global static style to a class, I cannot figure out how to do this when an event occurs (like one instance of the class is hovered over).
Do I need to create a stored value (i.e. some boolean that gets set to true when a class is hovered over) to use conditional styling? Or can I write a function as in the example below that will target all instances of the class? I'm a bit unclear why targeting a class in styling requires the :global(classname) format.
App.svelte
<script>
// import Component
import Katex from "./Katex.svelte"
// math equations
const math1 = "a\\htmlClass{test}{x}^2+bx+c=0";
const math2 = "x=-\\frac{-b\\pm\\sqrt{b^2-4ac}}{2a}";
const math3 = "V=\\frac{1}{3}\\pi r^2 h";
// set up array and index for reactivity and initialize
const mathArray = [math1, math2, math3];
let index = 0;
$: math = mathArray[index];
// changeMath function for button click
function changeMath() {
// increase index
index = (index+1)%3;
}
function hoverByClass(classname,colorover,colorout="transparent")
{
var elms=document.getElementsByClassName(classname);
console.log(elms);
for(var i=0;i<elms.length;i++)
{
elms[i].onmouseover = function()
{
for(var k=0;k<elms.length;k++)
{
elms[k].style.backgroundColor=colorover;
}
};
elms[i].onmouseout = function()
{
for(var k=0;k<elms.length;k++)
{
elms[k].style.backgroundColor=colorout;
}
};
}
}
hoverByClass("test","pink");
</script>
<h1>KaTeX svelte component demo</h1>
<h2>Inline math</h2>
Our math equation: <Katex {math}/> and it is inline.
<h2>Displayed math</h2>
Our math equation: <Katex {math} displayMode/> and it is displayed.
<h2>Reactivity</h2>
<button on:click={changeMath}>
Displaying equation {index}
</button>
<h2>Static math expression within HTML</h2>
<Katex math={"V=\\pi\\textrm{ m}^3"}/>
<style>
:global(.test) {
color: red
}
</style>
Katex.svelte
<script>
import katex from "katex";
export let math;
export let displayMode = false;
const options = {
displayMode: displayMode,
throwOnError: false,
trust: true
}
$: katexString = katex.renderToString(math, options);
</script>
<svelte:head>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex#0.12.0/dist/katex.min.css" integrity="sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X" crossorigin="anonymous">
</svelte:head>
{#html katexString}
If I understand it correctly you have a DOM structure with arbitrary nested elements and you would want to highlight parts of the structure that share the same class.
So you would have a structure like this:
<div>
<p>This is some text <span class="a">highlight</span></p>
<span class="a">Another highlight</span>
<ul>
<li>Some listitem</li>
<li class="a">Some listitem</li>
<li class="b">Some listitem</li>
<li class="b">Some listitem</li>
</ul>
</div>
And if you select an element with class="a" all elements should be highlighted regardles where they are in the document. This arbitrary placement makes using the sibling selector in css not possible.
There is no easy solution to this, but I will give you my attempt:
This is the full code with some explanation
<script>
import { onMount } from 'svelte'
let hash = {}
let wrapper
onMount(() => {
[...wrapper.querySelectorAll('[class]')].forEach(el => {
if (hash[el.className]) return
else hash[el.className] = [...wrapper.querySelectorAll(`[class="${el.className}"]`)]
})
Object.values(hash).forEach(nodes => {
nodes.forEach(node => {
node.addEventListener('mouseover', () => nodes.forEach(n => n.classList.add('hovered')))
node.addEventListener('mouseout', () => nodes.forEach(n => n.classList.remove('hovered')))
})
})
})
</script>
<div bind:this={wrapper}>
<p>
Blablabla <span class="a">AAA</span>
</p>
<span class="a">BBBB</span>
<ul>
<li>BBB</li>
<li class="a b">BBB</li>
<li class="b">BBB</li>
<li class="b">BBB</li>
</ul>
</div>
<style>
div :global(.hovered) {
background-color: red;
}
</style>
The first thing I did was use bind:this to get the wrapping element (in your case you would put this around the {#html katexString}, this will make that the highlight is only applied to this specific subtree.
Doing a querySelector is a complex operation, so we will gather all the related nodes in a sort of hashtable during onMount (this kind of assumes the content will never change, but since it's rendered with #html I believe it's safe to do so).
As you can see in onMount, I am using the wrapper element to restrict the selector to this section of the page, which is a lot faster than checking the entire document and is probably what you want anyway.
I wasn't entirely sure what you want to do, but for simplicity I am just grabbing every descendant that has a class and make a hash section for each class. If you only want certain classes you could write out a bunch of selectors here instead:
hash['selector-1'] = wrapper.querySelectorAll('.selector-1');
hash['selector-2'] = wrapper.querySelectorAll('.selector-2')];
hash['selector-3'] = wrapper.querySelectorAll('.selector-3');
Once this hashtable is created, we can loop over each selector, and attach two event listeners to all of the elements for that selector. One mouseover event that will then again apply a new class to each of it's mates. And a mouseout that removes this class again.
This still means you have to add hovered class. Since the class is not used in the markup it will be removed by Svelte unless you use :global() as you found out yourself. It is indeed not that good to have global classes because you might have unintended effect elsewhere in your code, but you can however scope it as I did in the code above.
The line
div > :global(.hovered) { background-color: red; }
will be processed into
div.svelte-12345 .hovered { background-color: red; }
So the red background will only be applied to .hovered elements that are inside this specific div, without leaking all over the codebase.
Demo on REPL
Here is the same adapted to use your code and to use a document-wide querySelector instead (you could probably still restrict if wanted by having the bind one level higher and pass this node into the component)
Other demo on REPL

Getting index of an element from his parent

I found this code as the answer of a question:
function getNodeIndex(elm){
return [...elm.parentNode.children].indexOf(elm)
}
I made something so when you click on the document, it logs the target of the click;
If the target is myClass, I want it logs the index of it.
The myClass elements are buttons, that are added when the user clicks on a button.
document.addEventListener("click", function(e) {
if(e.target.classList.value == "myClass") {
console.log(getNodeIndex(e.target))
}
})
But, that's weird:
Even if we click on 1st button, 4th button or 35th button, it will always log 2.
What's the problem there?
Thanks.
Edit:
The full code is here: http://pasted.co/6e55109a
And it is executable on http://zombs.io/
It's due to the structure of your DOM which probably looks something like
<div>
<div>Some Text: <button>Button 1</button></div>
<div>Some Text: <button>Button 2</button></div>
<div>Some Text: <button>Button 3</button></div>
</div>
Each of those buttons is the second child of its parent, i.e. one of the inner divs
Here's how to modify getNodeIndex to get it to work with DOM in this shape. If this still doesn't work, post your DOM.
function getNodeIndex(elm) {
return [...elm.parentNode.parentNode.children].indexOf(elm.parentNode)
}
$('button').on('click', e => {
console.log(getNodeIndex(e.target))
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div>
<div>Some Text: <button>Button 1</button></div>
<div>Some Text: <button>Button 2</button></div>
<div>Some Text: <button>Button 3</button></div>
</div>
I don't know if your actual code is exactly the same, but the code you posted has 2 errors, you're missing 2 brackets:
/*
Yours:
document.addEventListener("click", function(e) {
if(e.target.classList.value == "myClass") {
console.log(getNodeIndex(e.target)
}
}
*/
// With the brackets
document.addEventListener("click", function(e) {
if(e.target.classList.value == "myClass") {
console.log(getNodeIndex(e.target))
}
})
I've run your code in jsbin and I had no problem, check the brackets and if you´re still having a problem please post your html
You are adding your eventListener to the document. Then you are checking for the classList. The classList may have another class and break your code. You should use classList.contains('some-class') instead.
I would add the click events directly to the 'some-class'-items you want them to be trigger by. This should work as long as you don't add more items to the DOM later. If you do, make sure to add the eventListener too.
// wait for all the html is loaded
window.addEventListener('load', () => {
// get all buttons with the .some-class
const someClassElements = document.querySelectorAll('.some-class');
// iterate over the 'array like' elements
someClassElements.forEach( element => {
// add a click event to the someClassElements
element.addEventListener('click', () => {
// log the nodeIndex
const nodeIndex = getNodeIndex(element);
console.log( nodeIndex );
});
});
}); // end on load
function getNodeIndex(elm){
return [...elm.parentNode.children].indexOf(elm)
}
div{
margin-top: 50px;
}
<div>
<button class='some-class'>some class (0)</button>
<button class='some-class'>some class (1)</button>
<button class='some-class'>some class (2)</button>
<button>no class (3)</button>
<button class='some-class'>some class (4)</button>
<button class='some-class'>some class (5)</button>
<button>no class (6)</button>
<button class='some-class'>some class (7)</button>
</div>
<div>
<button>no class (0)</button>
<button class='some-class'>some class (1)</button>
<button>no class (2)</button>
<button class='some-class'>some class (3)</button>
<button class='some-class'>some class (4)</button>
<button>no class (5)</button>
<button class='some-class'>some class (6)</button>
</div>
The actual answer in your situation:
It seems like you want to know the index of the parent div, not the actual element.
use
console.log(getNodeIndex(e.target.parentNode));
instead of
console.log(getNodeIndex(e.target));
PS: You are always getting 2 as result without your html, you may actually always be clicking the third child of the parent element. I verified that this is the case from your code.
PSII: an extra.. In the code you linked you removed a parent node of an element. Later you try to use that element to make some unsuccessful console.log's, which won't work because you just removed the element.

Getting HTML element by Id and switch its CSS through React

I have some files that load into my react components, which have HTML code.
As it is now, the pure HTML code renders just fine, however there is some 'hidden' code that appears whenever you click certain buttons in other parts of the application or on the text above (think of it like panels that expand when you click on it).
The HTML is hidden just using the good old <div id="someId" style="display:none">.
Anyway I am trying to get the correct panel to expand upon clicking their respective buttons.
So in theory, what I need to do is find the element by id, and switch it's display to block whenever needed, and then switch it back when the parent is clicked again.
Unfortunately I have no idea how to do this and so far have gotten nowhere. As it is now, I have access to the component's ids. What I want to know is how in the world can I access that and get to change whatever is rendering?
Create your function:
function element_do(my_element, what_to_do) {
document.getElementById(my_element).style.display = what_to_do;
}
and latter in code you can append wherever you want through javascript onclick or not depends what do you need:
element_do("someId", "none"); // to hide
element_do("someId", "block"); // to show
or create yourself toggle:
function toggle_element(element_id) {
var element = document.getElementById(element_id);
element.style.display = (element.style.display != 'none' ? 'none' : 'block' );
}
// and you can just call it
<button onClick="toggle_element('some_id')">toggle some element</button>
The react way to do it would be with states. Assuming that you know how to use states I'd do something like this:
class ShowHide extends React.Component {
constructor() {
super();
this.state = {myState: true};
this.onClick = this.onClick.bind(this)
}
onClick() {
this.setState({myState: !this.state.myState}) //set the opposite of true/false
}
render() {
const style = {myState ? "display: none" : "display:block"} //if myState is true/false it will set the style
return (<div>
<button onClick={this.onClick}>Click me to hide/show me </button>
<div id="myDiv" style={style}> Here you will hide/show div on click </div>
</div>)
}
}

how to add css classes to a component in react?

So basically what I am doing is iterating through an array of data and making some kind of list. What I want to achieve here is on clicking on a particular list item a css class should get attached.
Iteration to make a list
var sports = allSports.sportList.map((sport) => {
return (
<SportItem icon= {sport.colorIcon} text = {sport.name} onClick={this.handleClick()} key= {sport.id}/>
)
})
A single list item
<div className="display-type icon-pad ">
<div className="icons link">
<img className="sport-icon" src={icon}/>
</div>
<p className="text-center">{text}</p>
</div>
I am not able to figure out what to do with handleClick so that If I click on a particular list it gets highlighted.
If you want to highlight the particular list item it's way better to call the handleClick function on the list item itself, and you can add CSS classes more accurately with this approach,
here is my sample code to implement the single list component
var SingleListItem = React.createClass({
getInitialState: function() {
return {
isClicked: false
};
},
handleClick: function() {
this.setState({
isClicked: true
})
},
render: function() {
var isClicked = this.state.isClicked;
var style = {
'background-color': ''
};
if (isClicked) {
style = {
'background-color': '#D3D3D3'
};
}
return (
<li onClick={this.handleClick} style={style}>{this.props.text}</li>
);
}
});
Keep a separate state variable for every item that can be selected and use classnames library to conditionally manipulate classes as facebook recommends.
Edit: ok, you've mentioned that only 1 element can be selected at a time,it means that we only need to store which one of them was selected (I'm going to use the selected item's id). And also I've noticed a typo in your code, you need to link the function when you declare a component, not call it
<SportItem onClick={this.handleClick} ...
(notice how handleClick no longer contains ()).
And now we're going to pass the element's id along with the event to the handleClick handler using partial application - bind method:
<SportItem onClick={this.handleClick.bind(this,sport.id} ...
And as I said we want to store the selected item's id in the state, so the handleClick could look like:
handleClick(id,event){
this.setState({selectedItemId: id})
...
}
Now we need to pass the selectedItemId to SportItem instances so they're aware of the current selection: <SportItem selectedItemId={selectedItemId} ....Also, don't forget to attach the onClick={this.handleClick} callback to where it needs to be, invoking which is going to trigger the change of the state in the parent:
<div onClick={this.props.onClick} className={classNames('foo', { myClass: this.props.selectedItemId == this.props.key}); // => the div will always have 'foo' class but 'myClass' will be added only if this is the element that's currently selected}>
</div>

Categories

Resources