Svelte click handler toggle boolean in array from div - javascript

The toggle works only if clicking on a button, ignores the div, seems button triggers some sort of state change, how do I get this to work when clicking anywhere ?
ToolBar.ts
export default class ToolBar {
options:Array<ToolBarOptions>;
constructor() {
this.options = [
new ToolBarOptions(ToolButton.sideMenu,SideMenuIcon,false,true, []),
new ToolBarOptions(ToolButton.mainMenu,MainMenuIcon,false,true, [ new ToolBarOptions(ToolButton.export,exportIcon,true,true,[])]),
new ToolBarOptions(ToolButton.entities,EntityIcon,false,true,[]),
new ToolBarOptions(ToolButton.setting,settingsIcon,false,true,[]),
];
}
}
class ToolBarOptions {
disabled:boolean;
name:ToolButton;
icon:string;
show:boolean;
options:Array<ToolBarOptions>;
constructor(name: ToolButton,icon:string,disabled:boolean,show:boolean, options:Array<ToolBarOptions>) {
this.name = name;
this.disabled = disabled;
this.icon = icon;
this.show=show;
this.options=options;
}
}
export const enum ToolButton{
mainMenu="mainMenu",
export="export",
entities="entities",
sideMenu="sideMenu",
setting="setting",
}
App.svelte
let toolbarOptions = new ToolBar();
function handleClickOutSide() {
console.log(toolbarOptions.options)
toolbarOptions.options.forEach((o) => {
o.show=!o.show;
});
console.log(toolbarOptions.options)
<div on:click={handleClickOutSide } class="toolbar">
<ul class="">
{#each toolbarOptions.options as {name, icon,options, show }, i}
<li>
<button on:click={()=>show=!show} name={name} class="flex items-center justify-center relative {toolbarOptions.options.length-1 === i ? "h-10":""}">
{#if toolbarOptions.options.length-1 ===i}
<div>100%</div>
{/if}
<icon> {#html icon}</icon>
<span>
<svg fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
</svg>
</span>
{#if options.length >0 }
<div class="absolute top-10 w-32 bg-black h-10 cursor-pointer {show ? "hidden":""}">
<ul class="w-full flex">
{#each options as {name, icon,show }}
<li class="min-w-full flex items-center h-10 px-2">
<span class=""> {#html icon} </span>
<span class="left-4 w-1/2"> {name}</span>
</li>
{/each}
</ul>
</div>
{/if}
</button>
</li>
{/each}
</ul>
</div>

When interacting with each item in a list there are several options that take reactivity into account:
Use item index access
Map all items
Use a dummy assignment
Examples for each:
let items = [
{ name: 'Item 1', checked: false },
{ name: 'Item 2', checked: false },
{ name: 'Item 3', checked: false },
];
const toggleViaIndex = () =>
items.forEach((e, i) => items[i].checked = !items[i].checked);
const toggleViaMap = () =>
items = items.map(item => ({ ...item, checked: !item.checked }));
const toggleViaDummyAssignment = () => {
items.forEach(item => item.checked = !item.checked);
items = items;
}
REPL
Personally, I am not a fan of the dummy assignment, because it looks like a useless statement. You can of course add comments to make it clearer why the statement exists.
I would not recommend using classes unless necessary, by the way. It breaks things like the map approach, if the class defines any functions or the prototype matters, because those aspects get lost in the spread.
Also: click events on things that are not button elements should always have a keyboard equivalent for accessibility; in this case probably an escape press.
You also probably should just set show to false rather than invert it on click outside.
By the way, ToolBarOptions could be shortened significantly via TypeScript:
class ToolBarOptions {
constructor(
public name: ToolButton,
public icon: string,
public disabled: boolean,
public show: boolean,
public options: Array<ToolBarOptions>,
) { }
}

Related

React How to Individually Target li State

I am somewhat new to react and I am building a navigation menu where I target the ul and li components using the map() function with index to avoid duplicating code. Whenever I click to open one component all components open instead of an individual one. I know the issue is probably in me not targeting the components correctly so any help will be appreciated.
Here is the code:
...react icon and state imports
const Navbar = () => {
const [subMenuOpen, setSubmenuOpen] = useState(false);
const menu = [
{
title: "Management",
submenu: true,
icon: <FaUserTie/>,
submenuItems: [
{ title: "MGMT Cockpit" },
{ title: "P&L by Month" },
{ title: "B/O Report" },
{ title: "User List" },
],
},
{
title: "Tools",
submenu: true,
icon: <BsTools/>,
submenuItems: [
{ title: "Inventory" },
{ title: "Damages" },
{ title: "MGMGT Tools" },
{ title: "Plan Tools" },
{ title: "Sales Tools" },
{ title: "Planning Tools" },
]
},
];
return (
<div className="flex">
<div className="bg-primary h-screen w-64 p-3 relative overflow-y-scroll">
{menu.map((item, index) => <sidebarItem key={index} sidebarItem = {index}/>)}
<ul className="pt-2">
{menu.map((menu, index) => (
<>
<li className="
text-gray-300 text-lg flex item-center gap-x-4 cursor-pointer p-2 my-4
hover:bg-slate-50 hover:text-primary hover:rounded-lg duration-500"
onClick={() => setSubmenuOpen(!subMenuOpen)}> //Fix this???
<span className="text-2xl block float-left">
{menu.icon ? menu.icon : <RiDashboardFill/>}
</span>
<span className="text-base font-medium flex-1">{menu.title}</span>
{menu.submenu && (
<BsChevronDown className={`${subMenuOpen && "rotate-180"} duration-300`}/>
)}
</li>
{menu.submenu && subMenuOpen && (
<ul>
{menu.submenuItems.map((submenuItem, index) => (
<li key={index} className="text-gray-300 text-md gap-x-4 px-5 my-3">
{submenuItem.title}
</li>
))}
</ul>
)}
</>
))}
</ul>
</div>
</div>
)
}
export default Navbar
App.js just imports this Navbar so I didn't include the code for that.
You have different possibilities of open submenus (since you have multiple menu types), so you should have state that reflects that. A plain true/false state won't be able to store enough information. One approach would be an array of submenu indices that are open.
const [submenuIndicesOpen, setSubmenuIndicesOpen] = useState([]);
Then examine the indices when iterating to determine what should be shown and how to call setSubmenuItemsOpen when toggling.
<ul className="pt-2">
{menu.map((menuItem, index) => ( /* don't shadow the menu variable here */
<>
<li className="
text-gray-300 text-lg flex item-center gap-x-4 cursor-pointer p-2 my-4
hover:bg-slate-50 hover:text-primary hover:rounded-lg duration-500"
onClick={() => setSubmenuOpen(
submenuIndicesOpen.includes(index)
? submenuIndicesOpen.filter(i => i !== index)
: [...submenuIndicesOpen, index]
)}>
{menuItem.submenu && submenuIndicesOpen.includes(index) && (
If only one submenu can be open at a time, you could instead have a state that's just a single number, the index of the open submenu.
your subMenuOpen state should be an array with index and in onClick handler you should pass index of li that you have in map , to control which menu should be open
or
you can set your menu as a state with a property of open on each submenu to control open of it

Need help making a switch selector compnent in reactJS similar to that on https://www.themoviedb.org/

So I'm trying to make a dynamic reusable switch selector component exactly like that on https://www.themoviedb.org/ to select between a number of options such as ["a", "b", "c"].
I've got most of the logic down, it might be a bit messy now, but my problem really is that I can't seem to figure out at which width or distance to move the coloured div to accurately place it right on top of the label/option title.
This is what I've got so far, the text colour changes correctly when selected, and the transaction is also smooth, but the position is wrong.
type SwitchProps = {
optionTitles: string[];
};
type Selector = {
isToggled: boolean;
optionTitle: string;
width: number | undefined;
};
const Switch: FC<SwitchProps> = (props) => {
const [selectors, setSelectors] = useState<Selector[]>([]);
const [currentToggled, setCurrentToggled] = useState<Selector & { index: number }>({
index: 0,
isToggled: true,
optionTitle: props.optionTitles[0],
width: 110,
});
const elementsRef = useRef<RefObject<HTMLDivElement>[]>(props.optionTitles.map(() => createRef()));
useLayoutEffect(() => {
if (selectors.length >= props.optionTitles.length) {
return;
}
props.optionTitles.map((optionName, index) => {
setSelectors((prevState) => [
...prevState,
{
isToggled: index === 0 ? true : false,
optionTitle: optionName,
width: 110,
},
]);
});
}, []);
const handlerToggleClick = (sectorIndex: number, toggleState: boolean) => {
let data = selectors;
data.forEach((selector, index) => {
selector.isToggled = false;
selector.width = elementsRef.current[sectorIndex].current?.offsetWidth;
});
data[sectorIndex].isToggled = true;
setCurrentToggled({ index: sectorIndex, ...data[sectorIndex] });
setSelectors(data);
};
return (
<div className="relative z-[1] h-8 border border-solid border-tmdbDarkBlue rounded-[30px] font-medium flex items-center">
{selectors.map((sector, index) => (
<div
key={index}
ref={elementsRef.current[index]}
className={`py-1 px-5 h-8 text-sm font-semibold flex items-center ${
sector.isToggled && "switch-active-text"
}`}
>
<span
className="cursor-pointer flex items-center"
onClick={() => handlerToggleClick(index, !sector.isToggled)}
>
{sector.optionTitle}
</span>
</div>
))}
<div
className={`absolute z-[-1] h-8 w-20 bg-tmdbDarkBlue rounded-[30px] transition-all duration-150 ease-in`}
style={{
width: currentToggled.width,
left: currentToggled.index === 0 ? 0 : (currentToggled.width as number) * 1.8,
}}
></div>
</div>
);
};
export default Switch;
If there are other ways to improve my code, please do let me know. I'm trying to get better at things, which is why I'm working on this clone sorta project.

Am having issue implementing the javascript code with vue

Am very new to vuejs.am trying to replicate the javascript with vue. where a user can toggle button. I have a list of buttons and I would like to toggle the active class but remove the active class from all other buttons.. Is there a better way of writtting the function without the querySelector? Am really stuck..
<template>
<div #click="selectItem" class="menu-tabs">
<button type="btn" class="menu-tab-item active" data-target="#remis-transfer"> Transfer
</button>
<button type="btn" class="menu-tab-item text-muted" data-target="#bank-transfer">
Transfer Money
</button>
<button type="btn" class="menu-tab-item text-muted" data-target="#fueling">
Fueling
</button>
</div>
</template>
<script>
export default {
methods: {
selectItem(e) {
if (e.target.classList.contains("menu-tab-item") && !e.target.classList.contains("active")) {
const target = e.target.getAttribute("data-target")
menuTabs.querySelector(".active").classList.remove("active");
e.target.classList.add("active");
const menuSection = document.querySelector(".menu-section");
menuSection.querySelector(".menu-tab-content.active").classList.remove("active");
menuSection.querySelector(target).classList.add('active');
}
}
}
}
</script>
Look at the code
var Main = {
data() {
return {
active: 0,
buttonList: [
{
text: "Transfer",
target: "#remis-transfer",
},
{
text: "Transfer Money",
target: "#bank-transfer",
},
{
text: "Fueling",
target: "#fueling",
},
],
};
},
methods: {
selectItem(i) {
this.active = i;
},
},
};
var Ctor = Vue.extend(Main)
new Ctor().$mount('#app')
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.9/vue.js"></script>
<div id="app">
<div class="menu-tabs">
<button
type="btn"
class="menu-tab-item"
v-for="(item, index) in buttonList"
:class="[{ active: active == index }, { 'text-muted': active != index }]"
:data-target="item.target"
:key="index"
#click="selectItem(index)"
>
{{ item.text }}
</button>
</div>
</div>
You can simply define some states like:
data() {
return {
myActiveClass: 'active',
myMutedClass: 'text-muted'
}
},
and pass this myActiveClass and myMutedClass states to your elements like this:
class=menu-tab-item ${myMutedClass} ${myActiveClass}
with this approach, you can quickly achieve what you want. So when you want an element to not be active, in your function you can make myActiveClass = '', or if you want the text to lose muted class you can say myMutedClass = '' .
Just make sure to play with states in the way you want.

Javascript loop with dynamic react component handler binding

I am new to Javascript/ReactJs world. I am building a side nav. Below are some part of my code -
import React, {Component} from "react";
import Link from "./Link/Link";
class Sidebar extends Component {
state = {
dashboard: {
name: "Dashboard",
href: "#",
active: true,
svgPath: "M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6",
children: []
},
team: {
name: "Team",
href: "#",
active: false,
svgPath: "M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z",
children: [
{
name: "Overflow",
href: "#",
},
{
name: "Members",
href: "#",
},
{
name: "Calender",
href: "#",
},
{
name: "Settings",
href: "#",
}
]
}
}
navClickHandler = (e, key) => {
console.log(e, key)
}
render() {
let navLinks = [];
for (const item in this.state) {
navLinks.push(<Link key={item} navClicked={(e, item) => this.navClickHandler(e, item)} config={this.state[item]} />)
}
return (
<div className="hidden bg-indigo-700 md:flex md:flex-shrink-0">
<div className="flex flex-col w-64 border-r border-gray-200 pt-5 pb-4 bg-white overflow-y-auto">
<div className="flex items-center flex-shrink-0 px-4">
<img className="h-8 w-auto"
src="https://tailwindui.com/img/logos/workflow-logo-indigo-600-mark-gray-800-text.svg"
alt="Workflow" />
</div>
<div className="mt-5 flex-grow flex flex-col">
<nav className="flex-1 px-2 space-y-1 bg-white" aria-label="Sidebar">
{navLinks}
</nav>
</div>
</div>
</div>
);
}
}
export default Sidebar;
How can I get the item value inside the navClickedHandler function -
let navLinks = [];
for (const item in this.state) {
navLinks.push(<Link key={item} navClicked={(e, item) => this.navClickHandler(e, item)} config={this.state[item]} />)
}
Actually, I want to use that key(item) to check which of the side-nav is active. It will change the state object based on the click.
console.log in nacClickHandler is giving undefined.
You are not passing item to Link, so you cannot get it as an argument back from the click event.
So change this:
navClicked={(e, item) => this.navClickHandler(e, item)}
to this:
navClicked={e => this.navClickHandler(e, item)}
Or, you can change your <Link/> component to something like this:
const Link = ({navClicked, item}) => (
<div onClick={e => navClicked(e, item)}></div>
);
And pass item as a prop:
<Link key={item} item={item} navClicked={this.navClickHandler} config={this.state[item]} />
The second option is preferable since it allows you to pass the same function reference always, thus avoiding redundant renders (provided that Link is a PureComponent, or a functional component wrapped in memo)
Hello other problems I see is that key={item} will be translated to key=[object Object] therefore your behaviour will fail due to the check key1 !== key2 because the actual value will be [object Object] !=== [object Object] which fails.
An alternative I would suggest to either use some unique data item.id for instance or generate some using a basic package like uuid
Back to your issue, in order to check which item is active I would suggest to you to do this:
<Link key={item.id} .... active={state.selected.id === item.id}/>

Custom Gutenberg richtext block outputting the HTML formatting as text on the websites front end

I am editing the (Testimonial Slider Block plugin)[https://github.com/laccadive-io/testimonials-slider-block] to allow for the custom block to have a richtext field as well as the existing plaintext fields in WP.
After making some changes to the original code, I was able to implement the formatting options in the Gutenberg editor for the custom block. However, the formatting options/HTML are being displayed as plain text on the website frontend.
For example, if I input "Hello World" into the richtext field and make it bold, the front end will literally display (without actually formatting the text);
<strong>Hello World</strong>
Here is the edited slider.js file that handles the front end and back end parts to the custom block.
/**
* BLOCK: my-block
*
* Registering a basic block with Gutenberg.
* Simple block, renders and saves the same content without any interactivity.
*/
// Import CSS.
import "./style.scss";
import "./editor.scss";
const __ = wp.i18n.__; // The __() for internationalization.
const registerBlockType = wp.blocks.registerBlockType; // The registerBlockType() to register blocks.
const { RichText, PlainText } = wp.editor;
/**
* Register: a Gutenberg Block.
*
* Registers a new block provided a unique name and an object defining its
* behavior. Once registered, the block is made editor as an option to any
* editor interface where blocks are implemented.
*
* #link https://wordpress.org/gutenberg/handbook/block-api/
* #param {string} name Block name.
* #param {Object} settings Block settings.
* #return {?WPBlock} The block, if it has been successfully
* registered; otherwise `undefined`.
*/
registerBlockType("gts/testimonials-slider-block", {
// Block name. Block names must be string that contains a namespace prefix. Example: my-plugin/my-custom-block.
title: __("Text Carousel"), // Block title.
icon: "format-quote", // Block icon from Dashicons → https://developer.wordpress.org/resource/dashicons/.
category: "common", // Block category — Group blocks together based on common traits E.g. common, formatting, layout widgets, embed.
keywords: [__("Text Carousel"), __("gts")],
attributes: {
id: {
source: "attribute",
selector: ".carousel.slide",
attribute: "id"
},
testimonials: {
source: "query",
default: [],
selector: "blockquote.testimonial",
query: {
index: {
source: "text",
selector: "span.testimonial-index"
},
content: {
type: "string",
source: "text",
selector: "span.testimonial-text"
},
author: {
source: "text",
selector: "span.testimonial-author span"
}
}
}
},
/**
* The edit function describes the structure of your block in the context of the editor.
* This represents what the editor will render when the block is used.
*
* The "edit" property must be a valid function.
*
* #link https://wordpress.org/gutenberg/handbook/block-api/block-edit-save/
*/
// The "edit" property must be a valid function.
edit: props => {
const { testimonials } = props.attributes;
if (!props.attributes.id) {
const id = `testimonial${Math.floor(Math.random() * 100)}`;
props.setAttributes({
id
});
}
const testimonialsList = testimonials
.sort((a, b) => a.index - b.index)
.map(testimonial => {
return (
<div className="gts-testimonial-block">
<p>
<span>
Insert Text Block {Number(testimonial.index) + 1} Here:
</span>
<span
className="remove-testimonial"
onClick={() => {
const newTestimonials = testimonials
.filter(item => item.index != testimonial.index)
.map(t => {
if (t.index > testimonial.index) {
t.index -= 1;
}
return t;
});
props.setAttributes({
testimonials: newTestimonials
});
}}
>
<i className="fa fa-times" />
</span>
</p>
<div className="row">
<div className="col-9 mt-3">
<PlainText
className="author-plain-text"
placeholder="Header"
value={testimonial.author}
onChange={author => {
const newObject = Object.assign({}, testimonial, {
author: author
});
props.setAttributes({
testimonials: [
...testimonials.filter(
item => item.index != testimonial.index
),
newObject
]
});
}}
/>
</div>
</div>
<blockquote className="wp-block-quote">
{/* <label>Content:</label> */}
<RichText
className="content-plain-text"
placeholder="Main Text"
value={testimonial.content}
autoFocus
onChange={content => {
const newObject = Object.assign({}, testimonial, {
content: content
});
props.setAttributes({
testimonials: [
...testimonials.filter(
item => item.index != testimonial.index
),
newObject
]
});
}}
/>
</blockquote>
</div>
);
});
return (
<div className={props.className}>
{testimonialsList}
<button
className="add-more-testimonial"
onClick={content =>
props.setAttributes({
testimonials: [
...props.attributes.testimonials,
{
index: props.attributes.testimonials.length,
content: "",
author: "",
link: ""
}
]
})
}
>
+
</button>
</div>
);
},
/**
* The save function defines the way in which the different attributes should be combined
* into the final markup, which is then serialized by Gutenberg into post_content.
*
* The "save" property must be specified and must be a valid function.
*
* #link https://wordpress.org/gutenberg/handbook/block-api/block-edit-save/
*/
save: props => {
const { id, testimonials } = props.attributes;
const carouselIndicators = testimonials.map(function(testimonial, index) {
return (
<li
data-target={"#" + id}
data-slide-to={index}
className={testimonial.index == 0 ? "active" : ""}
/>
);
});
const testimonialsList = testimonials.map(function(testimonial) {
const carouselClass =
testimonial.index == 0 ? "carousel-item active" : "carousel-item";
return (
<div className={carouselClass} key={testimonial.index}>
<blockquote className="testimonial">
<span className="testimonial-index" style={{ display: "none" }}>
{testimonial.index}
</span>
<div className="row">
<div className="testimonial-author-container">
{testimonial.author && (
<p className="testimonial-author-name">
{testimonial.author}
</p>
)}
</div>
</div>
{testimonial.content && (
<p className="testimonial-text-container">
<span className="testimonial-text"><p>{testimonial.content}</p></span>
</p>
)}
</blockquote>
</div>
);
});
if (testimonials.length > 0) {
return (
<div className="testimonial-slider">
<div className="carousel slide" data-ride="carousel" id={id}>
<ol className="carousel-indicators">{carouselIndicators}</ol>
<div className="carousel-inner w-75 mx-auto">
{testimonialsList}
</div>
<a
class="carousel-control-prev"
href={"#" + id}
role="button"
data-slide="prev"
>
<span class="carousel-control-prev-icon" aria-hidden="true">
<i className="fa fa-chevron-left" />
</span>
<span class="sr-only">Previous</span>
</a>
<a
class="carousel-control-next"
href={"#" + id}
role="button"
data-slide="next"
>
<span class="carousel-control-next-icon" aria-hidden="true">
<i className="fa fa-chevron-right" />
</span>
<span class="sr-only">Next</span>
</a>
</div>
</div>
);
} else return null;
}
});
You should use <RichText.Content /> to display the value from RichText correctly.
Try this instead:
{testimonial.content && (
<p className="testimonial-text-container">
<span className="testimonial-text"><RichText.Content tag={'p'} value={testimonial.content} /></span>
</p>
)}
<RichText.Content tag={'p'} value={title} />

Categories