This is my first Wordpreess Gutenberg block using the <img> tag. I can't seem to get the image to display. The text renders but it seems the image src doesn't work. When I open up the DOM and inspect elements the <img> tag is in the DOM tree but there is no src it's just empty. I've tried putting the <Media Upload> both inline and in the <Panel Body> but either way the <img src> is not taking. I'm certain I'm overlooking something but after much trial and error, I cannot seem to connect the <Media Upload> to the image source properly.
const { registerBlockType } = wp.blocks;
const {
RichText,
InspectorControls,
ColorPalette,
MediaUpload,
MediaUploadCheck,
Button,
RawHTML,
InnerBlocks
} = wp.editor;
const { PanelBody, IconButton } = wp.components;
const ALLOWED_BLOCKS = ['core/button', 'core/html', 'core/image']
registerBlockType('mycustomblock/feature-block', {
//Built-in Attributes
title: 'Feature Block',
description: 'Block Description',
icon: 'align-pull-left',
category: 'design',
//Custom Attributes
attributes: {
title: {
type: 'string',
source: 'html',
selector: 'p'
},
titleColor: {
type: 'string',
default: 'black'
},
bodyColor: {
type: 'string',
default: 'black'
},
image: {
type: 'object',
source:'html',
selector:'feature-icon'
},
body: {
type: 'string',
source: 'html',
selector: 'p'
}
},
//Built-in Functions
edit({attributes, setAttributes}) {
const{
title,
body,
titleColor,
bodyColor,
image,
} = attributes;
//Custom Functions
function onChangeTitle(newTitle) {
setAttributes( { title: newTitle } );
}
function onChangeBody(newBody) {
setAttributes( { body: newBody } );
}
function onTitleColorChange(newColor){
setAttributes( { titleColor: newColor } );
}
function onBodyColorChange(newBodyColor){
setAttributes( { bodyColor: newBodyColor } );
}
function onSelectImage(newImage) {
setAttributes( { image: newImage.sizes.full.url } )
}
return ([
<InspectorControls style={ { marginBottom: '40px' } }>
{/* <PanelBody title={ 'Image Settings' }>
</PanelBody> */}
<PanelBody title={ 'Headline Color' }>
<p><strong>Choose Title Color</strong></p>
<ColorPalette
value={titleColor}
onChange={onTitleColorChange}
/>
</PanelBody>
<PanelBody title={ 'Description Color' }>
<p><strong>Choose Description Color</strong></p>
<ColorPalette
value={bodyColor}
onChange={onBodyColorChange}
/>
</PanelBody>
</InspectorControls>,
<div class="row">
<div class="col-md-4">
<div class="feature-icon-container">
<MediaUpload
onSelect={onSelectImage}
type="image"
value={image}
render={ ( { open } ) =>
<IconButton
onClick={ open }
icon="upload"
className="editor-media-placeholder__button is-button is-default is-default"
>
Select Image
</IconButton>
}
/>
</div>
<div class="feature-description-container">
<RichText
key="editable"
tagName="p"
placeholder="Feature Title"
value= { title }
onChange= { onChangeTitle }
style= { { color: titleColor } }
/>
<RichText
key="editable"
tagName="p"
placeholder="Description"
value= { body }
onChange= { onChangeBody }
/>
</div>
</div>
</div>
]);
},
save({ attributes }) {
const {
title,
body,
titleColor,
bodyColor,
image,
} = attributes;
return(
<div class="row">
<div class="col-md-4">
<div class="feature-image-container">
<img class="feature-icon" src={ { image } } />
</div>
<div class="feature-description-container">
<RichText.Content style={ {color:titleColor } } tagName="p" value={title} />
<RichText.Content style={ {color:bodyColor } } tagName="p" value={body} />
</div>
</div>
</div>
)
}
});
There's a couple of things we need to correct, I'll just point you in the right direction:
Small misconception in how you "source" your image attribute. You are getting the src of the image from the MediaUpload component and set the image attribute (which is a URL string) via your onSelectImage function. In the way you declared it now you try to source it from the innerHTML of an html selector.
Therefore the following syntax is sufficient in your attribute declaration:
image: {
type: 'string',
default: '',
}
Instead of an empty string you can insert the src url for a default/placeholder image – if you want to.
And just to let you know: Sourcing from an html selector only makes sense if the innerHTML of that selector is editable in the content of the block, like an editable rich text component (i. e. paragraph).
You need some logic in edit to actually display the image in the backend after it has been selected. Below is just a simple version that needs to be further optimized (as you cannot select another image this way).
Replace your complete <div class="feature-icon-container"></div> structure in edit with this:
<div class="feature-image-container">
{ image === '' ?
<MediaUpload
onSelect={onSelectImage}
type="image"
value={image}
render={ ( { open } ) =>
<IconButton
onClick={ open }
icon="upload"
className="editor-media-placeholder__button is-button is-default is-default">
Select Image
</IconButton>
}
/>
:
<img class="feature-icon" src={ image } />
</div>
Change the double curly braces to single curly braces in save. Otherwise your URL string will be wrapped in an object.
<img class="feature-icon" src={ image } />
Maybe it will be best to move your MediaUpload to the controls sidebar, then it will be easier to keep the functionality of changing the selected image. Also if you set a default image it will not be possible to base your conditional on checking for an empty image string. But I'll leave this up to you.
Related
I am using the data from below to pass as props in React. Everything works fine but I need to only bold the words "target audience" in the text property. Is there a way to do this?
const SlideData = [
{
index: 1,
title: "Target Audience",
text: [
"The target audience for this course is anyone who is assigned roles as a HR Employee Maintainer...",
],
image: {
src: targetAudience,
width: imageSize,
},
},
index: 2,
title: "Reporting ",
text: [
"Reporting Manager is designated to...",
],
image: {
src: reporting,
width: imageSize,
},
},
]
export default SlideData
Added Render Component
const TextSlide = ({ title, text = [], list, image }) => {
return (
<>
<div className="slide">
<div className="standard-grid">
<span className="slide-title title">{title}</span>
<div className="content">
{text.map((t, i) => (
<p key={i} className="text">
{t}
</p>
))}
</div>
{image ? <img className="picture" src={image.src} style={{ maxWidth: image.width }} alt="image" /> : null}
</div>
</div>
</>
);
};
export default TextSlide;
``
You could split the text by target audience, and map the chunks inbetween to text nodes appending a node to each element:
"The target audience for this course is anyone who is assigned roles as a HR Employee Maintainer...",
.split("target audience")
.map((text, index) => <>{index !== 0 && <b>target audience</b>} {text}</>)
A more sophisticated approach would be to inject html tags into the text, and use dangerouslySetInnerHTML:
const formatted = text.replace(/(target audience)/g, it => `<b>${it}</b>`);
return <div dangerouslySetInnerHTML={formatted} />;
You could change the text to an object with a key of __html and use bold tags to render it by using dangerouslySetInnerHTML:
const SlideData = [
{
index: 1,
title: "Target Audience",
text: [
{
__html:
"The <b>target audience<b> for this course is anyone who is assigned roles as a HR Employee Maintainer...",
},
],
image: {
src: targetAudience,
width: imageSize,
},
},
];
const TextSlide = ({ title, text = [], list, image }) => {
return (
<>
<div className="slide">
<div className="standard-grid">
<span className="slide-title title">{title}</span>
<div className="content">
{text.map((t, i) => (
<p key={i} className="text" dangerouslySetInnerHTML={t} />
))}
</div>
{image ? (
<img
className="picture"
src={image.src}
style={{ maxWidth: image.width }}
alt="image"
/>
) : null}
</div>
</div>
</>
);
};
Here is a small codepen for demonstration.
Try this:
text: ["The ", <strong>target audience</strong>, " for this course is anyone who is assigned roles as a HR Employee Maintainer..."]
React will render this as a string with the HTML tags applied.
I am creating a UI dynamically using JSON data which renders input or textarea elements conditionally.
Sample JSON Data
[
{
type: "input",
val: "input text",
name: "Input Field",
editable: false
},
{
type: "text",
val: "text area text",
name: "Text area field",
editable: true
},
{
type: "text",
val: "text area text",
name: "Text area field",
editable: true
}
];
I have two values for property type one is input other is text, So if type property has value "input" then I am creating an input element otherwise textarea element.
I too have extra properties. One of them is editable, if it is set to true then user can click on edit button which which show Send button later on.
Issues
When I am clicking on edit both input field getting editable and both Edit is changing to Send
I tried using index and matching with index, but that also did not work.
My code
{data.map((li, index) => (
<div className="col-12 col-sm-12 col-md-6 col-lg-6 col-xl-6">
{li.type === "input" && (
<div className="divCont">
{li.editable && disabled && (
<span onClick={editComp}>Edit</span>
)}
<input
type="text"
className="form-control"
disabled={disabled}
defaultValue={li.val}
/>
</div>
)}
{li.type === "text" && (
<div className="divCont">
{li.editable && disabled && (
<span onClick={(e) => editComp(index)}>Edit</span>
)}
{disabled === false && ind === index && (
<span onClick={editComp}>Send</span>
)}
<input
type="text"
className="form-control"
disabled={disabled}
defaultValue={li.val}
/>
</div>
)}
</div>
))}
Code sandbox
Edit / Update
What I am trying to do
When the object have property editable:true it shows the edit button
just above the input or textarea element.
Then when I click on edit I want to make sure that particular input or textarea element is enabled so that user can type. Edit button should be changed to Send button to send data.
You are using the same flag 'disabled' to control the state of all your components. Therefore, when you click on send you change it to false for every field and that renders them all editable. The easiest fix would be to use a different flag for each field, but that may not scale well if you need it to.
Okay, I understand that you probably new to React and maybe even programming. Learning is good. Here some updated version of your code which does what you wanted, at least the way I have understood. Note though, I have no idea what you trying to build here, but hopefully it will give you some new start:
import React, { useState } from "react";
import "./styles.css";
import "bootstrap/dist/css/bootstrap.min.css";
function EditableArea({ val, ...rest }) {
const [disabled, setDisabled] = useState(true);
const [value, setValue] = useState(rest.value);
const handleSend = () => {
// ... send it somewhere maybe?..
console.log(value);
setDisabled(true);
};
return (
<div className="divCont">
{disabled ? (
<span onClick={() => setDisabled(false)}>Edit</span>
) : (
<span onClick={handleSend}>Send</span>
)}
<textarea
type="text"
className="form-control"
disabled={disabled}
placeholder={rest.placeholder}
onChange={(e) => setValue(e.target.value)}
value={value}
></textarea>
</div>
);
}
function Editable({ ...rest }) {
const [disabled, setDisabled] = useState(true);
const [value, setValue] = useState(rest.value || "");
const handleSend = () => {
// ... send it somewhere maybe?..
console.log(value);
setDisabled(true);
};
return (
<div className="divCont">
{disabled ? (
<span onClick={() => setDisabled(false)}>Edit</span>
) : (
<span onClick={handleSend}>Send</span>
)}
<input
type="text"
className="form-control"
disabled={disabled}
placeholder={rest.placeholder}
value={value}
onChange={(e) => setValue(e.target.value)}
/>
</div>
);
}
let data = [
{
type: "input",
val: "input text",
name: "Input Field",
editable: false
},
{
type: "text",
placeholder: "text area text",
name: "Text area field",
editable: true,
value: ""
},
{
type: "text",
placeholder: "text area text",
name: "Text area field",
editable: true,
value: ""
}
];
function redrerInput({ type, editable, ...rest }) {
switch (type) {
case "text":
return <EditableArea {...rest} />;
case "input":
return <Editable {...rest} />;
default:
return null;
}
}
export default function App() {
return (
<div className="App">
<div className="row">
{data.map((item, i) => (
<div key={i} className="col-12 col-sm-12 col-md-6 col-lg-6 col-xl-6">
{redrerInput(item)}
</div>
))}
</div>
</div>
);
}
Here is CodeSandbox fork
But I would strongly recommend to read documentation about React first.
const Dropdown = ({ options, selected, onSelectedChange }) => {
const [ open, setopen ] = useState(false);
const renderedOptions = options.map((option) => {
if (option.value === selected.value) {
return null;
}
return (
<div key={option.value} className="item" onClick={() => onSelectedChange(option)}>
{option.label}
</div>
);
});
return (
<div className="ui form">
<div className="field">
<label className="label">Select a color</label>
<div onClick={() => setopen(!open)} className={`ui selection dropdown ${open ? 'visible active' : ''}`}>
<i className="dropdown icon" />
<div className="text">{selected.label}</div>
<div className={`menu ${open ? 'visible transition' : ''}`}>{renderedOptions}</div>
</div>
</div>
//Here is the selected.value state (value contains string of color name
{<div style={{ color: `${selected.value}` }}>{selected.value}</div>}
</div>
);
};
export default Dropdown;
const options = [
{
label: 'The Color Red',
value: 'red'
},
{
label: 'The Color Green',
value: 'green'
},
{
label: 'The Color Blue',
value: 'blue'
}
];
How can I use the selected.value in an external CSS file?
The data in the selected.value is a string of color name(s).
You can probably use Styled Components if that's what you're looking for, just an example, not really well thought out. The component can be in another file
const HoveredLink = styled.span`
color: ${props => props.selected ? 'black' : 'rgb(150, 153, 156)'};
`
<HoveredLink selected={\\someconditionhere} > Hover me <HoveredLink/>
You can simple use as a variable, this is not exactly CSS. So this one should work:
<div style={{color: selected.value}} >{ selected.label } </div>
You can also set style as a new variable:
const Dropdown = ({options,...}) => {
const styleDiv = {
color: options.value
}
//...
return (
//...
<div style={styleDiv}> {options.label} </div>
)
}
Here is how I solved it by using concept from this URL
import styles from './colorchange.css';
//...
//For setting default value (and closing dropdown when clicked outside)
useEffect(() => {
document.body.addEventListener('click', (event) => {
if (ref.current.contains(event.target)) {
return;
}
setopen(false);
});
document.documentElement.style.setProperty(`--variablename`, selected.value);
}, []);
//...
//inside renderedoption
return (
<div
key={option.value}
className="item"
onClick={() => {
onSelectedChange(option);
document.documentElement.style.setProperty(`--variablename`, option.value);
}}
>
{option.label}
</div>
);
//....
//for printing the color
<div className="colorcolor">This text is: {selected.value}</div>
CSS FILE:
{
--variablename: default;
}
.colorcolor {
color: var(--variablename);
}
Another way using CSS stylesheet.
Usually when I need to pass a value to use it for CSS I'll
go for specific CSS classes as I rather use the stylesheet
for style ( !== style within component ) for scalability reasons &
ease to access. If it can help ...
I. Known values case / no need to convert into word
to expect ( seems like it should be either [ blue,red,green ])
<div className = { selected.value } />
II. Need to convert into word
Let's say you need to change a selected.value that could be an hexadecimal value, you'll need to associate a word className you could use for CSS later on.
This is just an example and you can do a lot more within the expression passed
// convert anticipated value to the word you need
const colors = {'#F00': 'red', '#0F0' : green, '#00F': 'blue'};
<div className = { colors[ selected.value ] } />
📌Applied example
import './style.css' // style.css as same level of your component
const Dropdown = ({options,...}) => {
// if need to make wording *(cf:. case )
// const color = {'#F00': 'red', '#0F0' : green, '#00F': 'blue'};
//...
return (
//...
// if need to make wording *(cf:. case )
// <div className = { colors[ selected.value ] }> { selected.value } </div> */ }
<div className = { selected.value }> { selected.value } </div>
)
}
CSS for the above cases.
/*CSS file: style.css*/
.red { ...code... }
.green { ...code... }
.blue { ...code... }
I'm trying to create a page in my React Js project, with the content on the left side and content's image on the right side. So far, I was able to create a toggle function that changes text and image according to the selected title, so, for example, if a user clicks title 1, text1 & image1 will be displayed, and when the user clicks title2, text2 & image2 will be rendered and etc. The problem is that images don't load until a title was clicked, but I need to display img1 when page loads for the first time(and then img1 should change to img2 or img3, depending on the clicked title).
codesandbox
My code:
import React, { useState } from "react";
import "./styles.css";
const data = [
{
id: "1",
key: "1",
title: "Title1",
text: "Text1.",
img: "1.jpg"
},
{
id: "2",
key: "2",
title: "Title2",
text: "Text2.",
img: "2.jpg"
},
{
id: "3",
key: "3",
title: "Title3",
text: "Text3.",
img: "3.jpg"
},
{
id: "4",
key: "4",
title: "Title4",
text: "Text4",
img: "4.jpg"
}
];
export default function App() {
const [toggled, toggle] = useState("");
return (
<div className="App">
{data.map(({ title, text, key, img }) => {
return (
<>
<div className="main">
<div className="text">
<h1 onClick={() => toggle(key)}>{title} </h1>
{toggled === key ? (
<>
<p>{text}</p>
</>
) : null}
</div>
<div className="img">
{toggled === key ? (
<>
<img src={img} key={key} className="photo" />
</>
) : null}
</div>
</div>
</>
);
})}
</div>
);
}
This is how the page is displaying on load now
This is how I want the page to be on load(with img1 being displayed)
What I would love to do is when the page is loaded, it should display img1, but when the user clicks title2, img1 should change to img2, any suggestions will be greatly appreciated,
Thank you.
I did a bit of refactoring for you, try this: https://codesandbox.io/s/toggle-kc3q2
I set the initial state to be "1" and using toggle and setToggle as the state and state setter
I'm posting my solution here, in case anyone else stumbles upon a similar problem. I added the following lines of code to my project:
***const [visible, setVisible] = useState(true);***
<h1 onClick={() => {
setToggle(key);
***setVisible(false);***
}}>
<div className="img">
***{visible && key === "1" ? (
<img src={img}
key={key}
/>)
: null}***
</div>
codesandbox
In this case, when the page is loaded, it displays img1 and only titles(without their texts), and only when a user clicks any title, a text, and its image is displayed.
I'm developing a WP Gutenberg block based on https://github.com/JimSchofield/Guty-Blocks-2 and I'm running into an issue where the saved content doesn't match the editor when loaded therefore I'm seeing an error 'This block contains unexpected or invalid content'.
I have tried looking in the browser console but I can't figure out where the discrepancy is, both the edit and save functions reference the images but they're not being stored by the save function.
It's worth noting that once the block is loaded for the first time, used and the post is saved it works correctly on the front-end. It's when you go back to the editor it doesn't work anymore.
import './__block__.view.scss';
import './__block__.editor.scss';
const {
registerBlockType,
getBlockDefaultClassName
} = wp.blocks;
const {
InspectorControls,
MediaUpload
} = wp.editor;
const {
Button
} = wp.components;
registerBlockType('__namespace__/__block__', {
title: '__prettyname__(noCase)',
icon: '__icon__',
category: '__category__',
attributes: {
imgUrl: {
type: 'array',
source: 'children',
selector: 'img',
},
},
edit({ attributes, className, setAttributes }) {
//Destructuring the images array attribute
const {images = []} = attributes;
// This removes an image from the gallery
const removeImage = (removeImage) => {
//filter the images
const newImages = images.filter( (image) => {
//If the current image is equal to removeImage the image will be returnd
if(image.id != removeImage.id) {
return image;
}
});
//Saves the new state
setAttributes({
images:newImages,
})
}
//Displays the images
const displayImages = (images) => {
return (
//Loops throug the images
images.map( (image) => {
return (
<div className="gallery-item-container">
<img className='gallery-item' src={image.url} key={ images.id } />
<div className='remove-item' onClick={() => removeImage(image)}><span class="dashicons dashicons-trash"></span></div>
<div className='caption-text'>{image.caption[0]}</div>
</div>
)
})
)
}
//JSX to return
return (
<div>
<MediaUpload
onSelect={(media) => {setAttributes({images: [...images, ...media]});}}
type="image"
multiple={true}
value={images}
render={({open}) => (
<Button className="select-images-button is-button is-default is-large" onClick={open}>
Add images
</Button>
)}
/>
<br />
<div class="modal__img">
<div class="flexslider">
<ul class="slides" data-total-slides={images.length}>{ displayImages(images) }</ul>
</div>
</div>
</div>
);
},
save({attributes}) {
// Destructuring the images array attribute
const { images = [] } = attributes;
// Displays the images
const displayImages = (images) => {
return (
images.map( (image,index) => {
return (
<li><img
className='lazy'
key={images.id}
data-src={image.url}
data-slide-no={index}
data-caption={image.caption[0]}
alt={image.alt}
/></li>
)
})
)
}
//JSX to return
return (
<div class="modal__img">
<div class="flexslider">
<ul class="slides" data-total-slides={images.length}>{ displayImages(images) }</ul>
</div>
</div>
);
},
});
I expected the block to output the original HTML when back in the editor, but this behaviour does not work.
In both the save and edit function your are referencing images from the attributes prop. Yet, when you register your block and set up the attributes, you only have imageUrl as an attribute. This means images are never getting stored in the DB, and do not exist when you come back to edit.
Adding images as a attribute should fix this.
What you have
attributes: {
imgUrl: {
type: 'array',
source: 'children',
selector: 'img',
},
},
What it should be
attributes: {
images: {
type: 'array',
default: []
},
},
Try passing props instead of attributes in edit and save functions, and then simply use
var attributes = props.attributes;
For more reference read the code in these examples.