I have the following function component. Within it, when a user clicks on any of the 4 divs, note_b, note_g, note_p, note_y, I want that class name to then be appended to the div with className note
This is my (incomplete) code
import React from 'react-dom';
import DraggableCore from 'react-draggable';
function Note(props) {
return (
<DraggableCore defaultPosition={{x: 1000, y: 200}}>
<div className={"note " + }>
<div id="note_head">
<div id="note_bin"></div>
<div className="note_b" onClick={}></div>
<div className="note_p" onClick={}></div>
<div className="note_g" onClick={}></div>
<div className="note_y" onClick={}></div>
<div id="note_exit"></div>
</div>
<p>
{props.message}
</p>
</div>
</DraggableCore>
)
}
export default Note;
Thank you #Andy, I took a second look at state hooks and came up with this:
import React, { useState } from 'react';
import DraggableCore from 'react-draggable';
function Note(props) {
const [bg, setBG] = useState('note_bg_b');
return (
<DraggableCore defaultPosition={{x: 1000, y: 200}}>
<div className={"note " + bg}>
<div id="note_head">
<div id="note_bin"></div>
<div className="note_b" onClick={() => setBG('note_b')}></div>
<div className="note_p" onClick={() => setBG('note_b')}></div>
<div className="note_g" onClick={() => setBG('note_b')}></div>
<div className="note_y" onClick={() => setBG('note_b')}></div>
<div id="note_exit"></div>
</div>
<p>
{props.message}
</p>
</div>
</DraggableCore>
)
}
export default Note;
This can probably be done in a cleaner, more efficient fashion. It is however functional.
You can use the onClick event handler for each of the four divs and add the className that was clicked on to the div with the className note.
EDIT for additional question: To prevent the added className from unloading when clicking within the note div, we can use an event listener to check where the click originated from and if it did not come from an element with the note_b, note_p, note_g, or note_y className, then the className should not be removed.
import React, { useState, useRef } from 'react';
import DraggableCore from 'react-draggable';
function Note(props) {
const [className, setClassName] = useState('');
const noteDiv = useRef(null);
const handleClick = e => {
setClassName(e.target.className);
}
//This function prevents the default event action from occurring when the page is unloaded.
//If the target element of the event does not have one of the specified class names, it removes the class from the element with the class "note".
const handleUnload = e => {
e.preventDefault();
const noteDiv = document.querySelector('.note');
if (!['.note', 'note_b', 'note_p', 'note_g', 'note_y'].includes(e.target.className)) {
noteDiv.classList.remove(e.target.className);
}
}
return (
<DraggableCore defaultPosition={{ x: 1000, y: 200 }}>
<div ref={noteDiv} className={`note ${className}`} onClick={handleUnload}>
<div id="note_head">
<div id="note_bin"></div>
<div className="note_b" onClick={handleClick}></div>
<div className="note_p" onClick={handleClick}></div>
<div className="note_g" onClick={handleClick}></div>
<div className="note_y" onClick={handleClick}></div>
<div id="note_exit"></div>
</div>
<p>
{props.message}
</p>
</div>
</DraggableCore>
)
}
export default Note;
If you separate out your classes a little, and add a data attribute for each note, you might get closer to what you need.
Instead of a className that looks like node_b use two classes note b - note can be the general class for all notes, and b can be the one that specifies one particular note. I've used colours here for clarity.
Adding the data attribute makes it more easy to identify each note in the code. In the click handler you can destructure that note id from the dataset of the clicked element, and then use it to set state, and you can use that state in the containing element.
Note: I've only used one click handler on the notes' containing element so that I can use event delegation.
const { useState } = React;
function Note({ message }) {
// Initialise a new state to hold the note id
const [ noteClass, setNoteClass ] = useState('');
// The handler first checks to see if the
// clicked element is a "note" element.
// if it is it destructures the note id from the
// element's dataset, and then uses it to set state
function handleClick(e) {
if (e.target.matches('.note')) {
const { note } = e.target.dataset;
setNoteClass(note);
}
}
// When the state changes the containing element's
// class changes too.
return (
<div className={noteClass}>
<div id="note_head">
<div id="note_bin" onClick={handleClick}>
<div data-note="b" className="note b">B</div>
<div data-note="p" className="note p">P</div>
<div data-note="g" className="note g">G</div>
<div data-note="y" className="note y">Y</div>
</div>
<p>{message}</p>
</div>
</div>
);
}
ReactDOM.render(
<Note message="Message" />,
document.getElementById('react')
);
.note { padding: 0.25em; border: 1px solid #4444; }
.note:not(:last-child) { margin-bottom: 0.25em; }
.note:hover { background-color: #fffff0; cursor: pointer; }
.b { color: red; }
.p { color: blue; }
.g { color: green; }
.y { color: gray; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Related
Here is my function with arguments that i added in index.html in publics folder in a script tag
function displayContent(event, contentNameID) {
let content = document.getElementsByClassName("contentClass");
let totalCount = content.length;
for (let count = 0; count < totalCount; count++) {
content[count].style.display = "none";
}
let links = document.getElementsByClassName("linkClass");
totalLinks = links.length;
for (let count = 0; count < totalLinks; count++) {
links[count].classList.remove("active");
}
document.getElementById(contentNameID).style.display = "block";
event.currentTarget.classList.add("active");
}
Trying to call this function from click of buttons on my react component that looks like below
<button class="linkClass" onclick="displayContent(event, 'project2')">Meet at Campus
</button>
Please guide me with the syntax
Here's the correct syntax
<button className="linkClass" onClick={(event)=>displayContent(event,'project2')}>Meet at Campus</button>
Edit: please note that React components return JSX
It looks like you're trying to make some sort accordion but you shouldn't really be mixing vanilla JS with React as React needs control of the DOM.
So here's a brief example of how you might approach this using 1) state, and 2) a Panel component which comprises a button, and some content.
const { useState } = React;
function Example() {
// Initialise state with an array of false values
const [ state, setState ] = useState([
false, false, false
]);
// When a button in a panel is clicked get
// its id from the dataset, create a new array using `map`
// and then set the new state (at which point the component
// will render again
function handleClick(e) {
const { id } = e.target.dataset;
const updated = state.map((el, i) => {
if (i === id - 1) return true;
return false;
});
setState(updated);
}
// Pass in some props to each Panel component
return (
<div>
<Panel
name="Panel 1"
active={state[0]}
id="1"
handleClick={handleClick}
>
<span className="text1">Content 1</span>
</Panel>
<Panel
name="Panel 2"
active={state[1]}
id="2"
handleClick={handleClick}
>
<span className="text2">Content 2</span>
</Panel>
<Panel
name="Panel 3"
active={state[2]}
id="3"
handleClick={handleClick}
>
<span className="text3">Content 3</span>
</Panel>
</div>
);
}
function Panel(props) {
// Destructure those props
const {
name,
id,
active,
handleClick,
children
} = props;
// Return a div with a button, and
// content found in the children prop
// When the button is clicked the handler is
// called from the parent component, the state
// is updated, a new render is done. If the active prop
// is true show the content otherwise hide it
return (
<div className="panel">
<button data-id={id} onClick={handleClick}>
{name}
</button>
<div className={active && 'show'}>
{children}
</div>
</div>
);
}
ReactDOM.render(
<Example />,
document.getElementById('react')
);
.panel button:hover { cursor: pointer; }
.panel { margin: 1em 0; }
.panel div { display: none; }
.panel div.show { display: block; margin: 1em 0; }
.add { margin-top: 1em; background-color: #44aa77; }
.text1 { color: darkblue; font-weight: 600; }
.text2 { color: darkgreen; font-weight: 700; }
.text3 { color: darkred; font-weight: 300; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Can't you use
document.getElementById("linkClass").onclick = () =>{
displayContent();
}
by giving the element an id with same of the class?
In React, I have a component called DivHelper which generates 2 divs one below the other - instead i want to place them side by side and I cant see the code of Divhelper generates the divs. Is there a way to access dynamically generated divs ?
For example -
///Some random code
< DivHelper/>
///Some more code
This becomes
///Some random code
<div>1 Div</div>
<div>2 Div</div>
///Some more code
and thus, the output is
1 Div
2 Div
Instead I want it to be placed on side by side reversed (like float)
2 Div 1 Div
Is this possible ?
You can dynamically change the className of your components in React.
With your CSS you can then style the component and the layout as you want.
For example:
import { useState } from "react";
import "./styles.css";
export default function App() {
const [display, setDisplay] = useState("row");
const handleClick = () => {
display === "row" ? setDisplay("column") : setDisplay("row");
};
return (
<div className="App">
<h1>Dynamic display</h1>
<button onClick={() => handleClick()}>Change display</button>
<div className={"container " + display}>
<div className="square red">First div</div>
<div className="square green">Second div</div>
</div>
</div>
);
}
And your CSS file:
.container {
display: flex;
}
.row {
flex-direction: row;
}
.column {
flex-direction: column;
}
.square {
background: "red";
height: 100px;
width: 100px;
}
.red {
background: #f00;
}
.green {
background: #0f0;
}
I want to change css width property of my element on some condition
<div className="consoleLayoutRoot-sideMenu">
<ConsoleSideMenu />
</div>
css
.consoleLayoutRoot-sideMenu .ant-menu {
padding-top: 30px;
/* background-color: #191146 !important; */
}
I am doing this way..but nothing is happening
document.getElementsByClassName("conjnjnot-sideMenjnjbhbhu.annjn ").style.width = "77px";
That's not working because you're treating a list as though it were an element. But it's also fundamentally not how you would do this in a React project.
Instead, you'd have the component re-render when the condition becomes true (perhaps by setting a state member). When rendering the div, you optionally include a style or a class name depending on whether you want the width applied:
<div className={`consoleLayoutRoot-sideMenu ${shouldHaveWidthClass ? "width-class" : ""}`}>
<ConsoleSideMenu />
</div>
...where .width-class { width: 50px; } is in your stylesheet.
Or with inline style, but inline styles are best avoided:
<div className="consoleLayoutRoot-sideMenu" style={shouldHaveWidthSetting ? { width: "50px" } : undefined}>
<ConsoleSideMenu />
</div>
Here's an example (using a class);
const {useState} = React;
const ConsoleSideMenu = () => <span>x</span>;
const Example = () => {
const [includeWidth, setIncludeWidth] = useState(false);
const toggle = ({currentTarget: { checked }}) => {
setIncludeWidth(checked);
};
return <React.Fragment>
<div className={`consoleLayoutRoot-sideMenu ${includeWidth ? "width-class" : ""}`}>
<ConsoleSideMenu />
</div>
<label>
<input type="checkbox" onChange={toggle} checked={includeWidth} />
Include width class
</label>
</React.Fragment>;
};
ReactDOM.render(<Example />, document.getElementById("root"));
.width-class {
width: 50px;
}
.consoleLayoutRoot-sideMenu {
border: 1px solid blue;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
I am trying to create a floating label where the label will go up when you click the input. But I am running into a problem where I am unable to call the class(the element) without affecting the other one. For example, I have 2 classes with the same class name, when I try to click on one of the other ones would work at the same time. I am trying to add an array, but as I have just started to learn JavaScript I do not know how to do it properly.
const floatinput = document.getElementsByClassName('entry-form-input');
const floatlabel = document.getElementsByClassName("tryingtoanimatelabel");
function forgetmove() {
floatlabel[0].classList.add("myanimatelabel");
}
function removemove() {
if (floatinput[0].value === "") {
floatlabel[0].classList.remove("myanimatelabel");
} else {
floatlabel[0].classList.add("myanimatelabel");
}
}
<div className="tryingtoanimate-container" onClick={forgetmove} onBlur={removemove}>
<input type="text" className="entry-form-input forgetinput" name="TeamName"/>
<label className="tryingtoanimatelabel">Team Name</label>
</div>
<div className="tryingtoanimate-container" onClick={forgetmove} onBlur={removemove}>
<input type="text" className="entry-form-input forgetinput" name="TeamName" onChange={this.handelChange}/>
<label className="tryingtoanimatelabel">Team Name</label>
</div>
you can try to pass an "event" as argument to the functions above.
In this case your event (click or blur, etc.) will be bind to the element on which event happened, and you can refer to element as event.target
function forgetmove(event){
event.target.classList.add("myanimatelabel");
}
function removemove(event){
if (event.target.previousSibling.value === "") { //take the previous node of the element on which event happened (event.target)
event.target.classList.remove("myanimatelabel");
} else {
event.target.classList.add("myanimatelabel");
}
}
Few edits to answer...
I suggest you to use "onBlur" event on "input,
also you can use event.CurrentTarget, if you want to take exact element on which event happened excluding their child elements.
The final code will looks like this:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleClick = this.handleClick.bind(this);
this.handleBlur = this.handleBlur.bind(this);
}
handleClick(event) { // using "currentTarget" to take element on which event happened, this will ignore cases where we would click on child elements
let _label = event.currentTarget.querySelector('label');
let _input = event.currentTarget.querySelector('input');
_label.classList.add('myanimatelabel');
_input.focus();
}
handleBlur(event) {
let _input = event.target; // using "target" to take element on which event happened
let _label = _input.nextElementSibling; // taking next element to input
if (_input.value === "") {
_label.classList.remove('myanimatelabel');
} else {
_label.classList.add('myanimatelabel');
}
}
render() {
return (
<div className="wrapper">
<div onClick={this.handleClick}>
<input type="text" onBlur={this.handleBlur} />
<label className="tryingtoanimatelabel">
Team Name
</label>
</div>
<div onClick={this.handleClick} >
<input type="text" onBlur={this.handleBlur}/>
<label className="tryingtoanimatelabel">Team Name</label>
</div>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
.wrapper > div {
position: relative;
margin-top: 30px;
margin-left: 30px;
display: inline-block;
}
.wrapper input {
padding-left: 10px;
padding-top:3px;
}
.wrapper label {
position:absolute;
left: 10px;
top: 3px;
transition-duration: 0.6s;
cursor: text;
}
.wrapper .myanimatelabel {
top: -25px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Add a numeric value at the end of the class name so it's unique or give each id="id1" and target that ID specifically that way
Is there a way to wrap each individual element slotted into the shadow dom for a specific slot name?
Assume the Markup looks similar to this
<custom-element>
<div name="item">item 1</div>
<div name="item">item 2</div>
</custom-element>
Currently, the render is similar to:
<custom-element>
<div class="wrap">
<div name="item">item 1</div>
<div name="item">item 2</div>
</div>
</custom-element>
How would I go about wrapping the slotted elements to output similar to:
<custom-element>
<div class="wrap">
<div name="item">item 1</div>
</div>
<div class="wrap">
<div name="item">item 2</div>
</div>
</custom-element>
My current (flawed) approach:
customElements.define('custom-element', class MyCustomElement extends HTMLElement {
constructor(...args) {
super(...args);
let shadow = this.attachShadow({mode: open});
shadow.innerHTML = `
<div class="wrap">
<slot name="item"></slot>
</div>
`;
}
});
I would recommend that you change your approach as to the wrapping of the children. The simplest way would be to just add the missing HTML as a child of the slotted div like the example below. You would still be able to style the slotted element with the ::slotted pseudo selector.
customElements.define('custom-element', class MyCustomElement extends HTMLElement {
constructor(...args) {
super(...args);
let shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>
::slotted([slot="item"]) {
border: 1px solid black;
padding: 15px;
}
</style>
<slot name="item"></slot>
`;
}
});
<custom-element>
<div slot="item">
<div class="wrap">item 1</div>
</div>
<div slot="item">
<div class="wrap">item 2</div>
</div>
</custom-element>
The reason behind this approach is that the wrap ultimately belongs to the child element and should come with each child. The result would be similar to what you request.
Although, if you do want to add wrapping to the element dynamically, then you could do with the slotchange event. The event is fired whenever a slot has been filled and can be listened to from the ShadowRoot element. In the event callback loop over the assignedElements (which are the elements in the slot) and alter their innerHTML value.
customElements.define('custom-element', class MyCustomElement extends HTMLElement {
constructor(...args) {
super(...args);
let shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `<slot name="item"></slot>`;
shadow.addEventListener('slotchange', event => {
const { target } = event;
const assignedElements = target.assignedElements();
for (const element of assignedElements) {
if (element.querySelector('.wrap') === null) {
const wrapper = document.createElement('div');
wrapper.className = 'wrap';
for (const node of element.childNodes) {
wrapper.appendChild(node);
}
element.append(wrapper);
}
}
});
}
});
.wrap {
border: 1px solid black;
padding: 15px;
}
<custom-element>
<div slot="item">item 1</div>
<div slot="item">
<div class="wrap">item 2</div>
</div>
</custom-element>
I have faced the same problem, and this is the solution I came up with: re-targeting the item slots. I have a single "main" slot, which should always be empty - on every slotchange event, I iterate over the new items, create a new wrapper for each of them and create a new with dynamically generated name inside each wrapper. Then I change the "slot" attribute of the original item to move it to that slot.
customElements.define('buttons', class extends HTMLElement {
__allocateId()
{
var i = 0;
while ( this.__buttons.has( "btn" + i ) ) {
++i;
}
return "btn" + i;
}
__onButtonSlotChange(button)
{
if (button.contentSlot.assignedNodes({flatten: true}).length == 0) {
this.__buttons.delete(button.id);
button.remove();
}
}
__onMainSlotChange() {
for (var element of this.mainSlot.assignedElements({flatten: true})) {
var button = document.createElement('arrow-button');
button.id = this.__allocateId();
this.shadowRoot.appendChild(button);
var subSlot = document.createElement('slot');
subSlot.name = button.id;
button.appendChild(subSlot);
button.contentSlot = subSlot;
subSlot.addEventListener('slotchange', this.__onButtonSlotChange.bind(this, button));
element.setAttribute("slot", button.id);
this.__buttons.set(button.id, button);
}
}
constructor() {
super();
this.__buttons = new Map();
}
connectedCallback() {
var shadowRoot = this.attachShadow({mode: 'open'});
this.mainSlot = document.createElement('slot');
shadowRoot.appendChild(this.mainSlot);
var buttonList = document.createElement('div');
shadowRoot.appendChild(buttonList);
this.mainSlot.addEventListener('slotchange', this.__onMainSlotChange.bind(this));
}
});
This has minimal impact on the original objects and the entire component behaves correctly upon any modifications to the "buttoned" code - eg. item removal triggers removal of the corresponding button.