I am trying to create a custom block for Wordpress Gutenberg.
I have the following attributes:
"icon": {
"type": "object",
"source": "html",
"selector": ".jig_defbox-icon"
},
"color": {
"type": "string",
"default": "#919191"
}
In my EditJS I try to store values from a color-picker and a radiogroup into these attributes.
const [ toggled, setToggled ] = useState( );
return(
<InspectorControls>
<ColorPalette
colors={[
{name: 'lightgray', color: '#d8d8d8'},
{name: 'darkgray', color: '#919191'},
{name: 'red', color: '#dc1a22'}
]}
disableCustomColors={true}
onChange={(value) => {props.setAttributes({color: value});}}
value={props.attributes.color}
clearable={false}
/>
<RadioGroup
onChange={ (value) => {setToggled(value);props.setAttributes({icon: value});} }
checked={props.attributes.icon}
>
{
str_array.map(
item => ( <Radio value={item}><Icon icon={icon_getter(item);}/></Radio>)
)
}
</RadioGroup>
</InspectorControls>
)
In my SaveJS I try to render my markup according to these attributes:
const {attributes} = props
<div style={{"fill": attributes.color}}>
<Icon icon={icon_getter(attributes.icon)}/>
</div>
The goal is to render an svg-icon according to the selection on my radiogroup.
Issue 1: Every new edit-session in backend, the selection of my radiogroup is gone, even with useState (tried without useState first)
Issue 2: Every new edit-session, a console error is logged, that says that the post-body markup does not match the markup returned by the save function, because the save-markup does not contain the icon attribute content
As far as I was able to enclose the problem, the icon attribute is not correctly saved. I tried to save the "key" for the icon as a string and object. If I log the value in the save function, it is empty, while the selected color works as expected, both in frontend and backend.
I was able to fix it via rethinking my concept of fetching the icon.
Apparently, Gutenberg tried to store the code of the icon into the database, but not the key. When loading the backend editor, the icon_getter function received a null value, therefore, the difference between post body and save function code.
I changed my editJS:
<RadioGroup
onChange={ (value) => {props.setAttributes({icon: value});} }
checked={props.attributes.icon}
>
{
my_icons_as_list.map(
item => ( <Radio value={item}><Icon icon={my_icons[props.attributes.icon];}/></Radio>)
)
}
</RadioGroup>
and my saveJS:
<Icon icon={ my_icons[ attributes.icon ] } />
Related
I have this kind of objects
{
asignaturaId: 1,
content: {
[question_var]: {
elements: {
text: 'text1',
image: 'img1.jpg'
}
}
[question_var]: {
text: 'text2',
image: 'image2.jpg'
}
}
}
the thing that varies is the [question_var] part and how many objects does content key includes.
I want to be able to dynamically write out all the data on inputs and being able to replace the needed data.
so far I got this:
[...]
<FormControl>
<InputLabel htmlFor="modulo">Asignatura ID:</InputLabel>
<Input id="asignaturaId" name="asignaturaId" value={arrayData[0]} onChange={(e) => setAsignaturaId(e.target.value)} />
</FormControl>
{arrayData.map(pregunta => {
console.log(pregunta)
return (
<>
<FormControl>
<InputLabel htmlFor="texto">Texto:</InputLabel>
<Input id="texto" name="texto" onLoad={() => setTexto(pregunta[1].elements.text)} value={pregunta[1].elements} onChange={(e) => setText(e.target.value)} />
</FormControl>
[...]
what I omited was, I took the object and did Object.entries(myObject) and assigned it to the arrayData array. asignaturaId will be just one key on every object I receive, so I can easily pass that value and change it on an input. The problem is the content, as it is dynamic and change too much, I find it hard to use useEffect() hook to populate my input with the data I receive and manipulating it in the same input without changing the other values with the same keys or straight foward, not being able to edit them at all.
I have 4 days in this problem. please help!
Edit: I am sorry about my english, if I didn't make myself clear, I want to overwrite the object, keeping the data the user doesn't update in the front end.
Declear your json array first in your React hook Component and make sure each object should have unique key
const [yourJson,setYourJson] = useState({
asignaturaId: 1,
content: {
[question_var1]: {
elements: {
text: 'text1',
image: 'img1.jpg'
}
},
[question_var2]: {
text: 'text2',
image: 'image2.jpg'
}
}
});
Add Setter to add new object in your json content
const addNewData = (text, imgSrc) =>{
let newIndex = Object.keys(yourJson.content).length + 1;
let newJson = yourJson;
newJson.content[`question_var${newIndex}`] = {
'text': text,
'image': imgSrc
}
setYourJson({...newJson})
}
And in render callback use this mapping
<FormControl>
{Object.keys(yourJson.content).map(key => (
<InputLabel htmlFor="texto">{yourJson.content[key].text}</InputLabel>
// <Input id="texto" name="texto" ....
))}
</FormControl>
When I am using autocomplete for getting the selected dates, The selected options is not getting filtered. Hence i am able to select multiple instance of the same data. Although when i remove OnChange prop its giving the result but now i am not able to update the state.
<Autocomplete
multiple
name="ClassSchedule"
onChange={(event, value) => setDays(value)}
ChipProps={{
style: {
backgroundColor: "#2EC5B6",
borderRadius: "5px",
color: "#fff",
fontFamily: "Source Sans Pro",
},
}}
id="tags-standard"
options={[
{ title: "sunday" },
{ title: "monday" },
{ title: "tuesday" },
{ title: "wednesday" },
{ title: "thursday" },
{ title: "friday" },
{ title: "saturday" },
]}
getOptionLabel={(option) => option.title}
renderInput={(params) => (
<CssTextField
{...params}
style={{
borderRadius: "10px",
backgroundColor: "#F5FCFB",
fontFamily: "Source Sans Pro",
}}
variant="outlined"
id="custom-css-outlined-input"
/>
)}
/>
According to Autocomplete doc:
value - any - The value of the autocomplete. The value must have reference equality with the option in order to be selected. You can customize the equality behavior with the getOptionSelected prop.
getOptionSelected - func - Used to determine if an option is selected, considering the current value. Uses strict equality by default.
So to know whether to exclude the selected value from the value in the list, you must implement getOptionSelected to check for the equality for exclusion, here is .title
<Autocomplete
...
getOptionSelected={(option, value) => option.title === value.title}
...
/>
Below is the forked codesandbox
Your onChange function needs to be enhanced. You have to set the value array same as given in options array. Both array needs to match. You were getting details from options--> Titles. then You must set it by creating a similar array on onChange. like set options--> Titles
Try My working Sandbox example here
To give you more knowledge on what is happening: the main reason behind why putting your onChange handler is causing the options to be populated again with all the days, is because your component is rerendered everytime you set the state for days versus when you weren't setting the state for days. I've found that Material UI Autocomplete default checking works with an array stored in a state versus the hardcoded options you have
options={availableDays} //state based
CodeSandBox: https://codesandbox.io/s/so-react-material-ui-autocomplete-h2unq?file=/src/App.js
I am working with ReactJS and using SemanticUI for ReactJS to style the front end,
Is it possible to specify a header or divider from within the options array of objects for a dropdown component?
I get the impression from the documentation that this is not supported yet.
I solved this by changing to object in the options array to have more properties (which allow you to customise the content):
{
text: "YouGov Filters",
value: "yougov-header",
content: <Header content="YouGov Filters" color="teal" size="small" />,
disabled: true
},
It's probably not the ideal way to achieve what I want because I have to set disabled to true (I don't want it to be a selectable option) which means it adopts the greyed out 'disabled' style. I tried to counter this by specifying a color for the header which resulted in the disabled style being applied over the teal colour, not perfect but it will do for now.
Another workaround is to do it by map array:
const options = [
{
text: "note",
icon: 'sticky note outline',
description: 'test',
},
{
divider: true
},
{
text: "task",
icon: 'calendar check outline',
description: 'test',
},
];
return (
<Dropdown className='multicontent__button' text='add new' button>
<Dropdown.Menu>
<Dropdown.Header icon='tags' content='Tag Label' />
{options.map((option, i) => {
if (option.divider === true) return (<Dropdown.Divider key={i}/>);
return (
<Dropdown.Item
key={i}
text={option.text}
icon={option.icon}
description={option.description}
action={option.action}
onClick={this.handleOption}
/>
);
})}
</Dropdown.Menu>
</Dropdown>
);
Mr B's solution is genius. And it can be cleaner with a little modification of his:
function FragmentWithoutWarning({key, children}) {
// to get rid of the warning:
// "React.Fragment can only have `key` and `children` props."
return <React.Fragment key={key}>{children}</React.Fragment>;
}
// then just:
{
as: FragmentWithoutWarning,
content: <Header content="YouGov Filters" color="teal" size="small" />
}
Since <React.Fragment /> is not able to capture any event, you even don't have to disable the item.
I am writing a react application that is a thin client UI for a financial trading application. A core requirement is that the application be completely dynamic and configurable, including forms. Specifically, I have trade entry forms that need to be defined on the server side and stored in the database to be rendered dynamically on the client but the layout is important and needs to be able to support multiple different formats. I have seen a few libraries that take JSON form schemas and create static forms from them but none of them seem to support the kind of layout flexibility I need. For example, I need to support tabs, columns, and rows of components. My question is - can anyone suggest a ReactJs library that can do what I'm looking for? If not, how might I go about implementing it myself?
Here's a more concrete example; Suppose I have a schema fetched from the server via a REST call that looks something like this:
{
title: "Securities Finance Trade Entry",
children: [
{
containerType: "tabs",
children: [
{
title: "Common",
children: [
{
containerType: "row",
children: [
{
input: "ComboBox",
label: "Trade Type",
options: ["Repo", "Buy/Sell", "FeeBased"]
},
{
input: "ComboBox",
label: "Direction",
options: ["Loan", "Borrow"]
}
]
},
{
containerType: "row",
children: [
{
containerType: "column",
children: [
{
containerType: "row",
children: [
{
input: "text",
label: "Book"
},
{
input: "text",
label: "Counterparty"
}
]
},
{
containerType: "row",
children: [
{
input: "date",
label: "StartDate"
},
{
input: "date",
label: "EndDate"
}
]
},
{
containerType: "row",
children: [
{
input: "text",
label: "Security"
},
{
input: "numeric",
label: "Quantity"
}
]
}
]
}
]
}
]
}
]
}
]
}
Which I expect to render something like:
Basically in that schema only one tab would be shown, but there could be multiple tabs, each containing multiple children in rows and columns and potentially nested containers of tabs too. If I were to render this myself in react I would think about using .map to iterate through the json and a number of if statements to insert tags where appropriate. However, items need to be nested so I don't know how to render it such that the tag chosen is dynamic and it can have children... For example I could write:
{ if (container.containerType === "column") { () } but then I'd somehow need to embed the rest of the controls inside that tag, I don't think I could just emit a () at the end...
Another option I've considered is translating the above json to JSX on the server side and sending that. It would be fairly easy to write a parser on the Java server side that converts the above json to a JSX document and return that to the client, but then how would I render it? Is there any way I could do something like:
onComponentMount() {
fetch(webserviceUrl + 'getJsxForForm/' + props.formName)
.then(result => {this.setState({form : result});
}
render() {
return ({this.state.form});
}
But again, I don't think this will work. If I fetch the document from the server it would render it just as plain text and not actually convert it to valid html, right?
So, what are my options? I'm looking for suggestions of an existing library that would do this, or suggestions on either of the other two approaches that I'm mentioned (would they work?, how could I do it?), or alternative ideas.
Thanks,
Troy
I love the concept of dynamically rendering pages from some sort of JSON configuration.
The key will be defining Components to match containerTypes and inputs and then traversing your JSON configuration through a recursive function. In your JSON configuration, I suggest using component naming conventions wherever you want a component to be rendered. Hence, capitalizing Tabs, Row, Column, etc
Here is one example of that function. Note, in each of the containerType Components there is a call to this function with children being passed in.
See this pen: https://codepen.io/wesleylhandy/pen/oQaExK/
Example Component:
const Container = props => {
return (
<div className="container">
<h1>{props.title}</h1>
{renderChildren(props.children)}
</div>
)
}
Example Recursive-ish rendering of children
const renderChildren = children => {
return children ? children.map((child, ind) => {
const newChildren = child.children ? [...child.children] : [];
const {containerType, title, input, label, options} = child
let key;
if (newChildren.length) {
key = `${containerType}-${ind}`;
switch (containerType) {
case "Tabs":
return <Tabs
key={key}
title={title}
children={newChildren}
/>
case "Column":
return <Column
key={key}
title={title}
children={newChildren}
/>
case "Row":
return <Row
key={key}
title={title}
children={newChildren}
/>
default:
return <Common
key={key}
title={title}
children={newChildren}
/>
}
} else {
key=`${input}-${ind}`
switch (input) {
case "ComboBox":
return <SelectGroup
key={key}
label={label}
options={options}
/>
case "Text":
case "Date":
case "Numeric":
return <InputGroup
key={key}
label={label}
type={input}
/>
}
}
}) : null
}
Given the following data, how can I get the birds name and push it (Using the add button) to a new array to be displayed in another div (Using react es6)? So basically I want a user to click a bird from the semantic dropdown and display it in a different div for example shown below. This is probably simple but I can't seem to find a way to it when I'm using Semantic elements. Do I need to use onChange?
I need to to do this in a class I am exporting (react) (just havent shown the class/constructor/state definitions)
<div>
<p>How can i display 'Bird_Name' here?<p>
</div>
addClick = () => {
}
const {
Button,
Container,
Divider,
Dropdown,
Header,
Message,
Segment,
} = semanticUIReact
const birds = [
{
"value": "001",
"Bird_Name": "Eurasian Collared-Dove"
},
{
"value": "002",
"Bird_Name": "Bald Eagle"
},
{
"value": "003",
"Bird_Name": "Cooper's Hawk"
},
];
const options = birds.map(({ ID, Bird_Name }) => ({ value: ID, text: Bird_Name }))
const App = () => (
<Container>
<Divider hidden />
<Header as='h1'>Semantic-UI-React</Header>
<Dropdown
placeholder='Select...'
selection
search
options={options}
renderLabel={({ Bird_Name }) => 1}
/>
<button className="ui primary button add" onClick={this.addClick}>Add</button>
</Container>
)
// ----------------------------------------
// Render to DOM
// ----------------------------------------
const mountNode = document.createElement('div')
document.body.appendChild(mountNode)
ReactDOM.render(<App />, mountNode)
So, what you basically want is the onChange function which will display.
<Dropdown
placeholder='Select...'
selection
search
options={options}
renderLabel={({ Bird_Name }) => 1}
onChange={this.getBird}
/>
and make a getBird function
getBird = (event, {value}) => {
console.log(value);
let bird_name = event.target.textContent;
console.log(bird_name);
}
The value and text variable in the getBird function are basically the value and bird_name of the selected bird from the dropdown.