I'm trying to build a custom set of column blocks using InnerBlocks and am having trouble passing attributes to block templates. To start, I scaffolded a new plugin using Create-Guten-Block. I then created two blocks, one to serve as a row container, and one for individual columns.
Here is a simplified version of how I'm creating the main container (with two columns hard-coded for testing):
registerBlockType( 'wabe/multi-column-block', {
title: __( 'Multi-Column' ),
icon: 'columns',
category: 'common',
keywords: [
__( 'columns' ),
__( 'grid' ),
],
attributes: {
layout: {
type: 'string',
},
},
edit: () => {
return (
<div>
{ /* inspector controls for choosing a layout will go here */ }
<InnerBlocks
template={ [
[ 'wabe/multi-column-column', { columnwidth: '6' }, [
[ 'core/paragraph', { content: 'Insert column content here.' } ],
],
],
[ 'wabe/multi-column-column', { columnwidth: '6' }, [
[ 'core/paragraph', { content: 'Insert column content here.' } ],
],
],
] }
/>
</div>
);
},
save: () => {
// This is a dynamic block
return (
<InnerBlocks.Content />
);
},
} );
... and how I'm creating the individual column block:
registerBlockType( 'wabe/multi-column-column', {
title: __( 'Multi-Column Column' ),
icon: 'columns',
category: 'common',
keywords: [
__( 'columns' ),
__( 'grid' ),
],
attributes: {
columnwidth: {
type: 'string',
default: '',
},
},
edit: ( props ) => {
return (
<div>
<p>Width: { props.attributes.columnwidth }</p>
<InnerBlocks />
</div>
);
},
save: () => {
return (
<InnerBlocks.Content />
);
},
} );
I'll have a custom select for users to choose a column layout, and using that, will determine how many columns to include, and pass the "columnwidth" attribute to each column block. This will tell each column what CSS class to use.
The problem is no matter what I try, that "columnwidth" attribute just will not be passed to the column block. In fact, I can't get it to pass anything, even "className".
I've looked at the code for core columns/column blocks, and can't see what I'm doing wrong. Everything has gone great up until this giant roadblock. Any tips are appreciated.
The block code example you have provided shows you are setting up the InnerBlocks fine. The issue may be a simple oversight that you have missed importing the required dependancy of <InnerBlocks>.
I was able to build your block code successfully once I added the required imports:
import { registerBlockType } from '#wordpress/blocks';
import { __ } from '#wordpress/i18n';
import { InnerBlocks} from '#wordpress/block-editor';
Here is the result:
The code editor shows that your attributes are being passed and being saved.
If you are still encountering an issue, double check that you are clearing your browser cache before retesting and also check the browser console to see if any errors are present.
As you mentioned you have already reviewed the Gutenberg code for Columns/Column, a tip would be to look at how they do variations for creating the options in your Inspector Control. Hope this gets you back on track with creating your block.
Related
I am trying to use react-table to visualise some data structured as below:
{
companyName: "Barclays Bank Plc",
tenancies: ["G.01 # Newbuilding"],
status: "Active",
accountNumber: "EX209302",
logo: "companylogo1",
},
{
companyName: "Boeing AerospaceLtd",
tenancies: ["G.01 # Newbuilding", "G.02 # Newbuilding"],
status: "Active",
accountNumber: "EX209302",
logo: "companylogo1",
},
My column definition (which doesn't work) looks something like this:
{
Header: "Tenancies",
accessor: "tenancies",
Cell: (tableProps) => (
<div>{tableProps.cell.value.forEach((item) => item)}</div>
),
},
What I am trying to do is display each array item within it's own HTML tags (such as a div) so I can visually style and seperate them within the table cell.
Can anyone point me in the right direction as to the best way to achieve this?
According to the API document, the Cell receives the table Instance and returns a valid JSX.
{
...
Cell: (tableInstance) => JSX
}
Since the tenancies array are available from tableInstance.row.original.tenancies, so you can change your tenancies column definition as follow:
{
Header: "Tenancies",
Cell: ({ row }) => {
return (
<div>
{row.original.tenancies.map((tenancy, i) => (
<div key={i}>{tenancy}</div>
))}
</div>
);
}
}
Here is the example:
For aesthetic reasons I'm trying to add a HeaderGroup to the "select" (checkboxes) column solution provided in react-teable examples.
hooks => {
hooks.visibleColumns.push(columns => {
return [
{
id: 'selection',
// Make this column a groupByBoundary. This ensures that groupBy columns
// are placed after it
groupByBoundary: true,
// The header can use the table's getToggleAllRowsSelectedProps method
// to render a checkbox
Header: ({ getToggleAllRowsSelectedProps }) => (
<div>
<IndeterminateCheckbox {...getToggleAllRowsSelectedProps()} />
</div>
),
// The cell can use the individual row's getToggleRowSelectedProps method
// to the render a checkbox
Cell: ({ row }) => (
<div>
<IndeterminateCheckbox {...row.getToggleRowSelectedProps()} />
</div>
),
},
...columns,
]
})
Source:
https://codesandbox.io/s/github/tannerlinsley/react-table/tree/master/examples/kitchen-sink
I've tried many ways using hooks object but none of them worked (for example nested calls to hooks.headerGroups.push and then hooks.visibleColumns.push).
Here is my last attempt which makes sense but doesn't work either (TypeError: Cannot read property 'forEach' of undefined) :
hooks => {
hooks.headerGroups.push(headerGroups => {
return [
{
Header: 'X',
columns: [
{
id: 'selection',
// Make this column a groupByBoundary. This ensures that groupBy columns
// are placed after it
groupByBoundary: true,
// The header can use the table's getToggleAllRowsSelectedProps method
// to render a checkbox
Header: ({ getToggleAllRowsSelectedProps }) => (
<div>
<IndeterminateCheckbox {...getToggleAllRowsSelectedProps()} />
</div>
),
// The cell can use the individual row's getToggleRowSelectedProps method
// to the render a checkbox
Cell: ({ row }) => (
<div>
<IndeterminateCheckbox {...row.getToggleRowSelectedProps()} />
</div>
),
}
]
},
...headerGroups
]
})
})
Thank you for your help ^^
I created custom Gutenberg block for social links but I would need to add input fields where user can paste the url to that social profile. Here is where I would like to put the field (same as paragraph block has alignment settings there for example):
This is my code for the block:
const { registerBlockType } = window.wp.blocks;
const { __ } = window.wp.i18n;
const { BlockControls, AlignmentToolbar} = window.wp.editor;
registerBlockType('social-block/social', {
title: __('Social'),
icon: 'smiley',
category: 'common',
attributes: {
content: {type: 'string'},
color: {type: 'string'}
},
edit: function (props) {
return React.createElement(
"div",
{style: {
display: 'flex',
justifyContent: 'center'
}},
// facebook
React.createElement(
'a',
{
'href': '',
'rel': 'noopener noreferrer',
'target': '_blank'
},
React.createElement(
'svg',
{
'xmlns': "http://www.w3.org/2000/svg",
'xmlns:xlink': "http://www.w3.org/1999/xlink",
'viewBox': "0 0 24 24",
'fill': "currentColor",
'width': "48px",
'height':"48px"
},
React.createElement(
'path',
{
'fill-rule': "evenodd",
'd': "M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm3-11.762h-1.703V9.2c0-.39.278-.48.474-.48h1.202V7.005L13.318 7c-1.838 0-2.255 1.278-2.255 2.096v1.142H10v1.765h1.063V17h2.234v-4.997h1.508L15 10.238z"
}
)
),
),
}
});
I tried implementing https://developer.wordpress.org/block-editor/tutorials/block-tutorial/block-controls-toolbars-and-inspector/ but its not the behavior that I need, anyone has a suggestion where to look or what to do?
First of all I will recommend you to use the ES6 syntax as it will make your code a lot easier. For ES6 whenever you see any code in WordPress docs then you can choose ESNEXT tab from top of snippet that will then shows you ES6 code.
Now your answer. Gutenberg provides us two kind of controls BlockControl and InspectorControl both of these provides you the way to manipulate your block but the difference is that BlockControl is a toolbar that appears on top of block (its the same link that you shared) while InspectorControls serves as a sidebar setting option the thing that you wanted to do. Here is the actual documentation of Inspector Controls and here is one working example from Image block of Gutenberg core.
I want to develop a custom block that will let the user pick some information from autocomplete. I manage to create the autocomplete component on edit function.
A user can select an item but i don't know how to handle the attribute save.
I'm trying to save the selected item as attribute package_name. I created the onChange function on Autocomplete component but event.target.value is undefined.
Here is my code from block.js
const { __ } = wp.i18n; // Import __() from wp.i18n
const { AlignmentToolbar,
BlockControls,
registerBlockType } = wp.blocks;
const { RichText } = wp.editor;
const { Autocomplete, } =wp.components;
const MyAutocomplete = () => {
const autocompleters = [
{
name: 'fruit',
triggerPrefix: '#',
options: [
{ name: 'Apple', id: 1 },
{ name: 'Orange', id: 2 },
{ name: 'Grapes', id: 3 },
{ name: 'test', id: 4 },
],
getOptionLabel: option => (
<span>
{ option.name }
</span>
),
getOptionKeywords: option => [ option.name ],
isOptionDisabled: option => option.name === 'Grapes',
getOptionCompletion: option => (
<abbr title={ option.name }>{ option.name }</abbr>
),
}
];
function onChangeAuto(newContent){
console.log('autocompletexx '+newContent);
}
function onSelectAuto(event){
console.log(event.target);
console.log( event.target.value);
}
return (
<div>
<Autocomplete completers={ autocompleters }>
{ ( { isExpanded, listBoxId, activeId } ) => (
<div
contentEditable
suppressContentEditableWarning
aria-autocomplete="list"
aria-expanded={ isExpanded }
aria-owns={ listBoxId }
aria-activedescendant={ activeId }
onChange={onChangeAuto }
onSelect={onSelectAuto}
>
</div>
) }
</Autocomplete>
<p class="autocomplete_p">Type # for triggering the autocomplete.</p>
</div>
);
};
registerBlockType( 'residence-gutenberg-block/membership-package-settings', {
title: __( 'Residence - Membership Package Settings' ), // Block title.
icon: 'shield',
category: 'common',
keywords: [
__( 'membership-package-settings' ),
],
attributes:{
package_id:{
type:'string',
select:'p'
},
package_name:{
type:'string',
},
},
edit: function( props ) {
const { attributes: {package_name}, className,setAttributes,isSelected } = props;
return (
<div className={ props.className }>
<form>
<label className="wpresidence_editor_label">Current Package: {package_name}</label>
<MyAutocomplete></MyAutocomplete>
</form>
</div>
);
},
save: function( props ) {
// Rendering in PHP
return null;
},
} );
Passing down onChange, onSelect to the div element won't work, because these attributes are only applicable to the form field elements (as input, select, etc.).
I checked the documentation and the source code and didn't find any details or official approaches for dealing with the case.
However, I'm seeing two possible approaches for getting the selected value:
1. Using Autocomplete onReplace prop
Looking into the Autocomplete's source code, I noticed that onSelect callback invokes onReplace prop with the selected option as array. It may not fit all the cases, but you can give it a try! It may be enough for your case! You can try to add your handler to the onReplace as follows:
<Autocomplete
onReplace={ option => { console.log(option) } }
completers={ autocompleters }>
{ ( { isExpanded, listBoxId, activeId } ) => (
<div
contentEditable
suppressContentEditableWarning
aria-autocomplete="list"
aria-expanded={ isExpanded }
aria-owns={ listBoxId }
aria-activedescendant={ activeId }
/>
) }
</Autocomplete>
2. Listen for <div /> changes manually
You can add onInput, onBlur listeners to the <div />, having an uncontrolled react div component and when the div's value is changed then we can keep the changed value in your parent component's state.
Here's a great discussion, which describes these technique: React.js: onChange event for contentEditable
The good think is that there's already a plugin (based on this discussion) that can do it for you: react-contenteditable.
Firstly you have to convert your <MyAutocomplete /> component to be a statefull (not functional) and then:
import ContentEditable from 'react-contenteditable'
// Boilerplate ...
<Autocomplete completers={ autocompleters }>
{ ( { isExpanded, listBoxId, activeId } ) => (
<ContentEditable
html={this.state.html}
onChange={this.handleChange}
contentEditable
suppressContentEditableWarning
aria-autocomplete="list"
aria-expanded={ isExpanded }
aria-owns={ listBoxId }
aria-activedescendant={ activeId }
/>
) }
</Autocomplete>
Conclusion
I'm surprised that in the Autocomplete's documentation there aren't any details for this case. I guess it's because of the following statement (27.08.2018):
Gutenberg is being developed on GitHub, and you can try an early beta
version today from the plugin repository. Though keep in mind it’s not
fully functional, feature complete, or production ready.
However, one of both mentioned approaches above will help you, until they provide a complete API to work with their components.
I would suggest you to keep wrapping the Wordpress's <Autocomplete /> with your own component - in order to easily refactor your code later, when they release the complete API.
If you have questions, feel free to comment below.
When you use the Ext.dataview.List in Sencha, it automatically inserts a nice loading spinner while the data is loading. That's all fine and dandy, but I'd like to know how to use a custom loader .gif.
I've looked around and think I need to change the loadmask but not positive cause I'm new to this. See this link.
Below is the code for one of my Lists. I'm hoping to be able to add the code suggested to one place so that all lists are affected and have the new spinner.
{
xtype: 'list',
height: '',
id: 'categoryList',
itemId: '',
width: '100%',
scrollable: false,
emptyText: '<div class="pdtsListHtml" style="margin-top:30%">Product List Empty</div>',
itemTpl: [
'<div ><div class="pdtsListHtml" style="display:inline;">{navigationElementItemName} ({navigationElementItemRecordCounts})</div><div style="display:inline;float:right;margin-right:5px;"><img src="resources/image/arrow.png" width="11" height="11"></div></div>'
],
store: 'productListStore',
allowDeselect: true,
onItemDisclosure: false
}
Here's a fiddle that shows you an example list with a loading mask that uses a gif.
Basically you should define your own Ext.LoadMask subclass and use it on the list:
Ext.define("Test.MyLoadMask", {
extend: "Ext.LoadMask",
alias: "widget.myloadmask",
getTemplate: function() {
var prefix = Ext.baseCSSPrefix;
return [
{
reference: 'innerElement',
cls: prefix + 'mask-inner',
children: [
{
reference: 'indicatorElement',
cls: prefix + 'loading-spinner-outer',
children: [
{ tag: 'img', src: "http://example.com/my-spinner.gif" }
]
},
{
reference: 'messageElement'
}
]
}
];
}
});
Then you should define an applyMasked on your lists that modifies the xtype and sets it to myloadmask instead of loadmask.