I have been looking everywhere to see why my code will not work. I am trying to create a WordPress Gutenberg Block using the following code. I have tested 3 versions of code from different websites and have not been able to figure out why it fails on <div className={className}>
PHP - functions.php
function register_block_editor_assets() {
$dependencies = array(
'wp-blocks', // Provides useful functions and components for extending the editor
'wp-i18n', // Provides localization functions
'wp-element', // Provides React.Component
'wp-components' // Provides many prebuilt components and controls
);
wp_register_script( 'my-block-editor', get_stylesheet_directory_uri().'/js/testing2.js', $dependencies );
}
add_action( 'admin_init', 'register_block_editor_assets' );
function register_block_assets() {
wp_register_script( 'my-block', get_stylesheet_directory_uri().'/js/testing2.js', array( 'jquery' ) );
}
add_action( 'init', 'register_block_assets' );
JS - testing2.js
const { registerBlockType } = wp.blocks;
const { Fragment } = wp.element;
const {
RichText,
BlockControls,
AlignmentToolbar,
} = wp.editor;
registerBlockType( 'example/example-block', {
title = __('Example Block', 'example'),
icon = 'screenoptions',
category = 'common',
attributes = {
content: { // Parsed from HTML selector
source: 'children',
selector: 'p',
type: 'array'
},
textColor: { // Serialized by default
type: 'string'
},
isPinned: { // Pulled from REST API
type: 'boolean',
source: 'meta',
meta: 'example_is_pinned'
}
},
edit = ({ attributes, setAttributes, className, isSelected }) => {
const {
content
} = attributes
return (
<div className={className}>
<RichText
className="example-content"
value={content}
onChange={(content) =>setAttributes({ content })} />
</div>
)
}),
save = ({ attributes }) {
const {
content
} = attributes
return (
<div>
<p>{content}</p>
</div>
)
})
};
You have a syntax error in your js file.
Try this code
const { registerBlockType } = wp.blocks;
const { Fragment } = wp.element;
const {
RichText,
BlockControls,
AlignmentToolbar,
} = wp.editor;
registerBlockType( 'example/example-block', {
title :__('Example Block', 'example'),
icon : 'screenoptions',
category : 'common',
attributes : {
content: { // Parsed from HTML selector
source: 'children',
selector: 'p',
type: 'array'
},
textColor: { // Serialized by default
type: 'string'
},
isPinned: { // Pulled from REST API
type: 'boolean',
source: 'meta',
meta: 'example_is_pinned'
}
},
edit(props){
const {attributes, setAttributes, className} = props;
const {
content
} = attributes
return (
<div className={className}>
<RichText
className="example-content"
value={content}
onChange={(content) =>setAttributes({ content })} />
</div>
)
},
save(props){
const {attributes} = props;
const {
content
} = attributes
return (
<div>
<p>{content}</p>
</div>
)
} });
You need to transpile jsx and react components into regular javascript syntax, that's why you get that error. You can use a transpiler like babel.
JSX is a language extension that requires transpilation. You can read more about it from its spec: https://github.com/facebook/jsx
If you are not familiar at all, please check these links:
https://reactjs.org/docs/introducing-jsx.html
https://reactjs.org/docs/jsx-in-depth.html
Related
I'm trying to dynamically create and change values of components with mapping an array. Everything works fine initially (when I map through default values) until I get to setPadding function which is supposed to set an attribiute. Then my array is changed into object making the second mapping to throw an error - react-dom.min.js?ver=16.9.0:103 TypeError: attributes.padding.map is not a function . Here is the code:
The attribute:
padding: {
type: 'array',
default: [
{ name: 'top',
image: `${ url }images/padding-top.svg`,
v: 10,
},
{
name: 'bottom',
image: `${ url }images/padding-bottom.svg`,
v: 10,
},
],
},
The component render (note attribiutes.padding being mapped - this works fine with default values):
<PanelBody
title="Test"
initialOpen={ true }
>
{ attributes.padding.map( ( attr ) => (
<div className="component-row-wrapper margin-padding">
<PanelRow>
<RangeControl
label={
<img src={ attr.image } alt="" />
}
value={ attr.v }
onChange={ ( value ) => setPadding( attr.name, value ) }
min={ 0 }
max={ 300 }
/>
</PanelRow>
</div>
), ) }
</PanelBody>
And finally setPadding function (with console.log before the function itself - this is placed somewhere between edit( props ) and component render):
console.log( attributes.padding ); // initially it works fine, after setPadding it logs object instead of array.
const setPadding = ( name, value ) => {
const paddingAttr = attributes.padding;
{paddingAttr.map( r => {
if ( name === r.name ) {
r.v = value;
}
} );}
console.log( paddingAttr ); // works fine, being displayed as array every time!
props.setAttributes( {
padding: { paddingAttr }, // saves 'padding' attribute as object 'paddingAttr: Array(2)...' instead of array ??
} );
};
What am I doing wrong here?
Thank you very much!
Where you call props.setAttributes, the reason the attribute is an object is because you are creating an object! You need the following:
props.setAttributes( {
padding: paddingAttr
} );
Read more about property shorthand notation on MDN.
Additionally, as I said, you'll need to fix that call to Array#map. Change it to the following:
paddingAttr = paddingAttr.map( r => {
if ( name === r.name ) {
r.v = value;
}
return r;
} );
{paddingAttr.map( r => {
if ( name === r.name ) {
r.v = value;
}
} );}
Here, (reason for TypeError: attributes.padding.map is not a function), paddingAttr is not array, change it to
{paddingAttr.default.map( r => {
if ( name === r.name ) {
r.v = value;
}
} );}
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.
So i've been trying to troubleshoot this error and I've been coming up blank. I tried replacing title in .filter() with subreddit, key, link, type, and body, they all work fine without throwing an error, only title gives me the error. During troubleshooting I also placed that console.log to see if something was actually stored, and it printed the title to console like it should have, yet saved.title is still undefined in .filter. I also tried just saved.title.includes without toLowerCase() to no avail.
The code in question
renderPostTitles = () => {
const importantValues = this.props.totalUserSaves.map((saved) => {
return {
subreddit: saved.data.subreddit,
title: saved.data.title,
key: saved.data.id,
link: `https://www.reddit.com${saved.data.permalink}`,
type: saved.kind,
body: saved.data.body
}
})
console.log(importantValues[0].title)
const threadsArray_searched = importantValues.filter(saved => saved.title.toLowerCase().includes(this.props.searchQuery.toLowerCase()) )
More of the component:
import React from 'react';
import { connect } from 'react-redux';
class DisplaySaves extends React.Component {
renderPostTitles = () => {
const importantValues = this.props.totalUserSaves.map((saved) => {
return {
subreddit: saved.data.subreddit,
title: saved.data.title,
key: saved.data.id,
link: `https://www.reddit.com${saved.data.permalink}`,
type: saved.kind,
body: saved.data.body
}
})
const threadsArray_searched = importantValues.filter(saved => saved.title.toLowerCase().includes(this.props.searchQuery.toLowerCase()) )
return threadsArray_searched.map((saved, i) => {
return (
<div key={saved.key}>
<div> {i+1}. </div>
<div> r/{saved.subreddit}: </div>
<p> {saved.title} </p>
</div>
)
})
}
render () {
return (
<div>
<div>{this.renderPostTitles()}</div>
</div>
)
}
}
const mapStateToProps = state => {
console.log(state)
return {
totalUserSaves: state.userData.totalUserSaves,
searchQuery: state.userData.searchQuery
}
}
export default connect(mapStateToProps)(DisplaySaves);
Make sure title is a string in all cases (an undefined is possibly sneaking through in one of the elements), you could use a default or string constructor:
return {
title: saved.data.title || '',
or
return {
title: String(saved.data.title),
I am trying to use JsonSchema-Form component but i ran into a problem while trying to create a form that, after choosing one of the options in the first dropdown a secondary dropdown should appear and give him the user a different set o options to choose depending on what he chose in the first dropdown trough an API call.
The thing is, after reading the documentation and some examples found here and here respectively i still don't know exactly how reference whatever i chose in the first option to affect the second dropdown. Here is an example of what i have right now:
Jsons information that are supposed to be shown in the first and second dropdowns trough api calls:
Groups: [
{id: 1,
name: Group1}
{id: 2,
name: Group2}
]
User: [User1.1,User1.2,User2.1,User2.2,User3.1,User3.2, ....]
If the user selects group one then i must use the following api call to get the user types, which gets me the the USER json.
Component That calls JSonChemaForm
render(){
return(
<JsonSchemaForm
schema={someSchema(GroupOptions)}
formData={this.state.formData}
onChange={{}}
uiSchema={someUiSchema()}
onError={() => {}}
showErrorList={false}
noHtml5Validate
liveValidate
>
)
}
SchemaFile content:
export const someSchema = GroupOptions => ({
type: 'object',
required: [
'groups', 'users',
],
properties: {
groups: {
title: 'Group',
enum: GroupOptions.map(i=> i.id),
enumNames: GroupOptions.map(n => n.name),
},
users: {
title: 'Type',
enum: [],
enumNames: [],
},
},
});
export const someUISchema = () => ({
groups: {
'ui:autofocus': true,
'ui:options': {
size: {
lg: 15,
},
},
},
types: {
'ui:options': {
size: {
lg: 15,
},
},
},
});
I am not really sure how to proceed with this and hwo to use the Onchange method to do what i want.
I find a solution for your problem.There is a similar demo that can solve it in react-jsonschema-form-layout.
1. define the LayoutField,this is part of the demo in react-jsonschema-form-layout.To make it easier for you,I post the code here.
Create the layoutField.js.:
import React from 'react'
import ObjectField from 'react-jsonschema-form/lib/components/fields/ObjectField'
import { retrieveSchema } from 'react-jsonschema-form/lib/utils'
import { Col } from 'react-bootstrap'
export default class GridField extends ObjectField {
state = { firstName: 'hasldf' }
render() {
const {
uiSchema,
errorSchema,
idSchema,
required,
disabled,
readonly,
onBlur,
formData
} = this.props
const { definitions, fields, formContext } = this.props.registry
const { SchemaField, TitleField, DescriptionField } = fields
const schema = retrieveSchema(this.props.schema, definitions)
const title = (schema.title === undefined) ? '' : schema.title
const layout = uiSchema['ui:layout']
return (
<fieldset>
{title ? <TitleField
id={`${idSchema.$id}__title`}
title={title}
required={required}
formContext={formContext}/> : null}
{schema.description ?
<DescriptionField
id={`${idSchema.$id}__description`}
description={schema.description}
formContext={formContext}/> : null}
{
layout.map((row, index) => {
return (
<div className="row" key={index}>
{
Object.keys(row).map((name, index) => {
const { doShow, ...rowProps } = row[name]
let style = {}
if (doShow && !doShow({ formData })) {
style = { display: 'none' }
}
if (schema.properties[name]) {
return (
<Col {...rowProps} key={index} style={style}>
<SchemaField
name={name}
required={this.isRequired(name)}
schema={schema.properties[name]}
uiSchema={uiSchema[name]}
errorSchema={errorSchema[name]}
idSchema={idSchema[name]}
formData={formData[name]}
onChange={this.onPropertyChange(name)}
onBlur={onBlur}
registry={this.props.registry}
disabled={disabled}
readonly={readonly}/>
</Col>
)
} else {
const { render, ...rowProps } = row[name]
let UIComponent = () => null
if (render) {
UIComponent = render
}
return (
<Col {...rowProps} key={index} style={style}>
<UIComponent
name={name}
formData={formData}
errorSchema={errorSchema}
uiSchema={uiSchema}
schema={schema}
registry={this.props.registry}
/>
</Col>
)
}
})
}
</div>
)
})
}</fieldset>
)
}
}
in the file, you can define doShow property to define whether to show another component.
Next.Define the isFilled function in JsonChemaForm
const isFilled = (fieldName) => ({ formData }) => (formData[fieldName] && formData[fieldName].length) ? true : false
Third,after you choose the first dropdown ,the second dropdown will show up
import LayoutField from './layoutField.js'
const fields={
layout: LayoutField
}
const uiSchema={
"ui:field": 'layout',
'ui:layout': [
{
groups: {
'ui:autofocus': true,
'ui:options': {
size: {
lg: 15,
},
},
}
},
{
users: {
'ui:options': {
size: {
lg: 15,
},
},
doShow: isFilled('groups')
}
}
]
}
...
render() {
return (
<div>
<Form
schema={schema}
uiSchema={uiSchema}
fields={fields}
/>
</div>
)
}
I have a function that generates fields for a form like so:
export const makeFields: Function = (itemData: Object) => {
return [
{
// PROJECT DETAIL SECTION
name: 'chooseAccount',
label: 'Choose Account',
fields: [{
name: 'account',
label: 'Choose Trading Account',
rules: 'required',
...(itemData ? { value: itemData.trading_account ? itemData.trading_account.name : null } : null)
}]
},
{
name: 'projectDetails',
label: 'Project detail',
fields: [
{
name: 'projectCode',
label: 'Project code',
rules: 'required',
...(itemData ? { value: itemData.code } : null)
},
...
and the component that uses this function for the form fields:
...
export default class ProjectForm extends React.Component<*> {
props: TypeProps;
getMode = () => this.props.mode
componentDidMount() {
const projectDetailsStore: Object = this.props.projectDetailsStore;
this.getMode() === 'edit'
?
projectDetailsStore.loadProjectDetails(this.props.projectId)
:
projectDetailsStore.resetStore();
}
#computed get form(): Object {
const itemData: Object = (typeof this.props.itemData === 'undefined') ? {} : this.props.itemData;
const fields: Array<*> = makeFields(this.props.projectDetailsStore.details);
return this.getMode() === 'edit'
? projectEdit(fields, itemData)
: projectCreate(fields);
}
render(): React.Element<*> {
const t: Function = this.props.t;
const TAmodel: AutoCompleteData = new AutoCompleteData(autoCompleteTradingAccounts);
const Pmodel: AutoCompleteData = new AutoCompleteData(autoCompleteProject);
const projectDetailsStore: Object = this.props.projectDetailsStore;
this.form.add(
{
name: 'test'
}
)
console.log(this.form)
return (
<PageWrapper>
{projectDetailsStore.loadingProjectDetails
?
<Loader />
:
<FormWrapper form={this.form}>
<form>
<FormSection form={this.form} section="chooseAccount">
<InputLabel htmlFor="account">
{t('projectForm: Choose trading account')}
</InputLabel>
<ElectroTextField field={this.form.$('chooseAccount.account')} />
{/* <ElectroAutoComplete
field={this.form.$('chooseAccount.account')}
form={this.form}
props={{
model: TAmodel
}}
/> */}
</FormSection>
<FormSection form={this.form} section="projectDetails">
<ElectroTextField field={this.form.$('projectDetails.projectCode')} />
<ElectroTextField field={this.form.$('projectDetails.projectName')} />
...
I would like to add some fields to the form based on a condition. I have tried the following:
this.form.add(
{
name: 'test'
}
)
it doesn't throw an error but nothing happens.
The add method takes an object as per the docs (https://foxhound87.github.io/mobx-react-form/docs/api-reference/fields-methods.html). Ideally I would like a click event to fire the add method and add the newly created field.
this.form is neither a React state nor a MobX observable, so that nothing happens when you change its value.
To get it to work, you should create an observable form field that is initialized by makeFields, and use an action function to change its value, and then use observer to re-render.
If you are not quite familiar with mentioned above, read React & MobX official tutorials first.