Get information and attributes from other gutenberg blocks - wordpress

Is it possible to get information, such as the block attributes, from other blocks from within a registerBlockType call?
E.g. if I have a block with InnerBlocks as the content, is it possible to get attributes from the blocks inside that InnerBlocks, or vice versa?

Yes it is possible if you manages your own store inside Gutenberg which is actually a Redux store. #wordpress/data
It is recommended to keep logic of your multiple blocks separately in their own attributes (that's why they are blocks). For re-usability purpose you can make React components which can be used inside different blocks, in this way attributes of your block can be passed to the props of React Component.

You could also use Block context to achieve this.
In your parent block, map the attribute you want to use in your child block using providesContext. So if you would like to map the recordId property, your parent block configuration would look like this:
registerBlockType('my-plugin/parent-block', {
// ...
attributes: {
recordId: {
type: 'number',
},
},
providesContext: {
'my-plugin/recordId': 'recordId',
},
// ...
}
The child block can then "consume" the context by adding the following useContext line to your child block configuration:
registerBlockType('my-plugin/child-block', {
// ...
usesContext: ['my-plugin/recordId'],
// ...
}
In your child block's edit and save methods, you can then access the context like this:
registerBlockType('my-plugin/child-block', {
// ...
edit(props) {
const { context } = props;
const { "my-plugin/recordId": recordId } = context;
return (
<p>{ recordId }</p>
);
},
save(props) {
const { context } = props;
const { "my-plugin/recordId": recordId } = context;
return (
<p>{ recordId }</p>
);
}
// ...
}

Related

How to change the Gutenberg Group Block's "Inner blocks use content width" Toggle to Off/False as Default

When you wrap a new set of blocks in the core/group block the "Inner blocks use content width" toggle switch defaults to true. There is an object attribute showing called layout for that block. I'm assuming that I can update the settings on the layout attribute like I can with the align attribute.
Here is how I'm updating the align attribute:
const { addFilter } = wp.hooks;
const { assign, merge } = lodash;
function filterCoverBlockAlignments(settings, name) {
if (name === 'core/group') {
return assign({}, settings, {
attributes: assign( {}, settings.attributes, { align: {
type: 'string', default: 'wide'
} } ),
});
// console.log({ settings, name });
}
return settings;
}
addFilter(
'blocks.registerBlockType',
'intro-to-filters/cover-block/alignment-settings',
filterCoverBlockAlignments,
);
The above works so I assume updating the layout's default would be similar, but either I don't have the syntax for an object type correct, or possibly you can't update the layout object like you can update the align string. This is what I tried for the function:
function filterCoverBlockAlignments(settings, name) {
if (name === 'core/group') {
return assign({}, settings, {
attributes: assign( {}, settings.attributes, { layout: {
type: 'object', [{
type: 'default'
}]
} } ),
});
// console.log({ settings, name });
}
return settings;
}
In short I'm trying to get the blocks layer attribute (which is an object and not a string) have it's attribute of type to default to "default" instead of "constrain".
I mean yes, you could solve it by filtering. What I would suggest however, is making a block-variation. Then setting that block-variation as the default. That block variation then can have any of your settings you like to set. Quite simple in theory.
By default, all variations will show up in the Inserter in addition to the regular block type item. However, setting the isDefault flag for any of the variations listed will override the regular block type in the Inserter.
source: https://developer.wordpress.org/block-editor/reference-guides/block-api/block-variations/
wp.blocks.registerBlockVariation( 'core/group', {
name: 'custom-group',
// this one is important, so you don't end up with an extra block
isDefault: true,
// and here you add all your attributes
attributes: { providerNameSlug: 'custom' },
} );
Here is a slightly cleaner variant (using destructuring), if you have more code:
// extract the function from wp.blocks into a variable
const { registerBlockVariation } = wp.blocks;
// register like above
registerBlockVariation();

stenciljs: Issue with slot

I have created a simple web (stencil) component AuthGuard, not to be be confused with Angular's AuthGuard.
The purpose of this component is to check if the user is logged in.
If yes, render the slot html.
If not, render the Signup button.
The component code is as follows:
import { Component, Host, h } from '#stencil/core';
import { Build, State } from '#stencil/core';
import { AuthService } from 'auth/auth.service';
import { ConfigService } from 'common/config.service';
#Component({
tag : 'auth-guard',
styleUrl : 'auth-guard.css',
shadow : true,
})
export class AuthGuard {
#State() canRender : boolean = false;
componentWillLoad() {
if (Build.isBrowser) {
const timerId = setInterval(() => {
if (AuthService.isInitialized) {
AuthService.vol$.subscribe(_u => {
this.canRender= true;
});
clearInterval(timerId);
}
}, ConfigService.loadTime);
}
}
render() {
console.log('auth guard :: render', this.canRender, AuthService.me);
return (
<Host>
{
this.canRender ? (
AuthService.me && AuthService.me.id.length > 0 ? (
<slot></slot>
) : (
<ion-button
href="/signup"
routerDirection="forward"
color="danger">
Signup
</ion-button>
)
): null
}
</Host>
);
}
}
Now in the other file, I use the following code:
<auth-guard slot='end'>
<volunteer-mini volunteer={AuthService.me}></volunteer-mini>
</auth-guard>
With this what I am expecting is
Nothing to be rendered, till this.canRender becomes true.
Once this.canRender becomes true, If AuthService.me is valid, render the slot HTML,
If AuthService.me is null, render signup button.
But seems when this.canRender is false, it tried to render volunteer-mini the slot HTML, which is a problem. Since volunteer-mini internally depends on AuthService.me, which is not yet initialized.
But once this.canRender becomes true, other 2 scenarios are working fine.
It's in general a bad idea to write an auth-guard using stencil. The core problem is that your slot exists before your component has initialized.
Therefor, using your current code, you'd have to manually remove the slot after you decided that you don't have the rights.
Additionally, if you do not define a slot position, but still provide a slot-content in your parent, it will still be appended to your inner children.
To resolve this problem, you can refactor your component to an function, like <Host>, but this has other pits to consider.

How to update text element after property change in Polymer 3?

So I'm using a data table which has an active element. When that active elment changes I store the name of the active element in a property of my polymer element. Then I display this String property in a div.
Now I know for certain that the property change works, because I console.log it after a change, the div displaying the property doesn't update and continually displays the default value I have set.
export class ProjectsOverview extends PolymerElement {
static get template() {
return html`
...
<div>{{currentProject}}</div>
...
`
}
static get properties() {
return {
currentProject: {
type: String,
value: "placeholder",
notify: true,
reflectToAttribute: true
}
};
}
connectedCallback() {
super.connectedCallback();
const grid = this.shadowRoot.querySelector('vaadin-grid');
grid.addEventListener('active-item-changed', function(event) {
const item = event.detail.value;
grid.selectedItems = [item];
if (item) {
this.set('currentProject', item.name);
} else {
this.set('currentProject', '');
}
console.log(this.currentProject);
});
}
}
My expected result would be that every time the currentProject property is updated, the div displaying the property updates as well.
The active-item-changed callback does not have its context bound to the Polymer instance (i.e., this is the grid and not the Polymer component). Instead of the function expression, use an arrow function to automatically bind this to the correct context.
// grid.addEventListener('active-item-changed', function(event) { // DON'T DO THIS
grid.addEventListener('active-item-changed', (event) => {
/* this is the Polymer instance here */
this.set('currentProject', ...);
})
Your scope is wrong. You're using an anonymous function so when you try to set currentProject, you do that when your this is your anonymous function. Use .bind(this) to fix your problem.
grid.addEventListener('active-item-changed', function(event) {
const item = event.detail.value;
grid.selectedItems = [item];
if (item) {
this.set('currentProject', item.name);
} else {
this.set('currentProject', '');
}
console.log(this.currentProject);
}.bind(this));

ReactJS not setting class based on expression

I'm actually trying to toggle a class on an element when user clicked. But unfortunately, my code only set class for single element. It looks like the view is not refreshing for subsequent clicks, even the set class is not removing. But my store is properly updating.
Here is my code.
class MyInterests extends Component {
constructor(props) {
super(props);
this.state = {selected: []};
}
toggleChip(id, index, event){
const { interestChanged } = this.props;
let index = this.state.selected.indexOf(id);
if(index === -1){
this.state.selected.push(id);
}else{
this.state.selected.splice(index, 1);
}
interestChanged(this.state.selected);
}
render() {
const {classes, myinterests: { categories, userinterest } } = this.props;
const getClassNames = (id) => {
return classNames(classes.chip, {[classes.selected]: (userinterest.indexOf(id) !== -1)});
}
return (
/*..... Few element to support styles....*/
{categories.map((data, index) => {
return (
<Chip
key={data._id.$oid}
label={data.name}
onClick={this.toggleChip.bind(this, data._id.$oid, index)}
className={getClassNames(data._id.$oid)}
/>
);
})}
);
}
}
Can anyone tell me what is wrong in this or how can I achieve this?.
Since state is immutable, you cannot use .push on it.
By using this.state.selected.push(id) you're mutating the state thus not signaling react on the change making the change vulnerable to future state updates (remember that setState is asynchronous and changes are batched for single action).
Take a look at this for how to solve it.
In your case, a better way to update the state would be something like this:
// create a copy of the current array
var selected = this.state.selected.slice();
// push the new element to the copy
selected.push(id);
// replace the current array with the modified copy
this.setState({ selected: selected });

Selecting a Row in React Griddle, and changing tr background color

I'm just wondering if someone has already been able to change the color of a row, in React Griddle, by clicking on it (just once).
I'm experimenting things with JQuery, and even with Griddle Metadata, but it may be done in a cleaner way ?
Edit : I'm using React 15, Griddle inside MantraJS/Meteor, getting the data in my react Component using a Mantra container.
I can get the data by using onClick event, but not able to switch the background color in the onClick event, or playing with Metadatas.
Thanks !
EDIT : I use another view to display the content of the table, so for now I don't need to change the background of my tables cells, but if I found a solution I'll complete this post
You can use react-griddle props rowMetadata and onRowClick to do this:
class ComponentWithGriddle extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedRowId: 0,
};
}
onRowClick(row) {
this.setState({ selectedRowId: row.props.data.id });
}
render() {
const rowMetadata = {
bodyCssClassName: rowData => (rowData.id === this.state.selectedRowId ? 'selected' : ''),
};
return (
<Griddle
...
rowMetadata={rowMetadata}
onRowClick={this.onRowClick.bind(this)}
/>
);
}
}
Now this adds a selected class to the selected <tr> elements, so you can use custom styles to add colors or whatever styles you want to apply to the selected row.
Note that a more convenient API for selecting rows has been called for in the Griddle Github issues.
For whatever reason, I couldn't get Waiski's answer to work for me at all. I'm assuming that something must have changed in Griddle over the past two years. It looks like the current prevailing advice on the Web is to "implement row selection as a plugin", but I couldn't find any examples of that either. After a long hard look at the code for the Position plugin’s TableEnhancer on GitHub and a bunch of trial and error I eventually managed to cobble together the following row selection plugin for Griddle in TypeScript:
import * as React from "react";
import * as Redux from "redux";
import Griddle, { connect, GriddlePlugin, components } from "griddle-react";
export type RowId = string | number;
export type RowClickHandler = (event: React.MouseEvent<Element>, rowId: RowId) => void;
export type RowIdGetter<TData> = (rowData: TData) => RowId;
export interface IRowEnhancerProps {
rowClickHandler: RowClickHandler;
rowId: RowId;
isSelected: boolean;
}
export class RowSelector<TData> {
private _rowClickHandler: RowClickHandler = null;
private _rowIdGetter: RowIdGetter<TData>;
constructor(rowClickHandler: RowClickHandler, rowIdGetter: (rowData: TData) => RowId) {
this._rowClickHandler = rowClickHandler;
this._rowIdGetter = rowIdGetter;
}
public rowIdToSelect: RowId;
public plugin: GriddlePlugin = {
components: {
RowEnhancer: (OriginalComponent: React.ComponentClass<components.RowProps>) =>
this.rowSelectionEnhancer(OriginalComponent)
}
}
private rowSelectionEnhancer(
OriginalComponent: React.ComponentClass<components.RowProps>
): React.ComponentClass<components.RowProps> {
const rowDataSelector = (state, { griddleKey }) => {
return state
.get('data')
.find(rowMap => rowMap.get('griddleKey') === griddleKey)
.toJSON();
};
return Redux.compose(
connect((state, props) => {
const rowData: TData = rowDataSelector(state, props as { griddleKey });
const rowId: RowId = this._rowIdGetter(rowData);
return {
...props,
rowClickHandler: this._rowClickHandler,
rowId: rowId,
isSelected: rowId.toString() === this.rowIdToSelect.toString()
};
})
)(class extends React.Component<IRowEnhancerProps, any>{
public render() {
return (
<OriginalComponent
{...this.props}
onClick={(event) => this.props.rowClickHandler(event, this.props.rowId)}
className={this.props.isSelected ? "selected" : ""}
/>
);
}
});
}
}
Here's a rough outline of how it's used by a component. (Note that I had to selectively extract this example from a much larger and more complicated component, so there might be some errors/inconsistencies; sorry about that. It should still give a good overall idea of the approach.)
import * as React from "react";
import Griddle, { RowDefinition, plugins, GriddlePlugin} from "griddle-react";
import * as MyGriddlePlugins from "../GriddlePlugins";
export interface IPartInfo {
serialNumber: number,
name: string,
location: string
}
export interface IPartListProps{
parts: IPartInfo[],
selectedSerialNumber: number
}
export class PartList extends React.Component<IPartListProps, void > {
private rowSelector: MyGriddlePlugins.RowSelector<IPartInfo>;
private rowIdGetter: MyGriddlePlugins.RowIdGetter<IPartInfo>;
constructor(props?: IPartListProps, context?: any) {
super(props, context);
this._rowClickHandler = this._rowClickHandler.bind(this);
this.rowSelector = new MyGriddlePlugins.RowSelector(
this._rowClickHandler,
this._rowIdGetter);
}
private _rowClickHandler: MyGriddlePlugins.RowClickHandler =
(event: React.MouseEvent<Element>, selectedSerialNumber: MyGriddlePlugins.RowId) => {
if (selectedSerialNumber !== this.props.selectedSerialNumber) {
/*
Set state, dispatch an action, do whatever. The main point is that you
now have the actual event from the click on the row and the id value from
your data in a function on your component. If you can trigger another
render pass from here and set a fresh value for this.rowSelector.rowIdToSelect
then the "selected" CSS class will be applied to whatever row this click
event just came form so you can style it however you like.
*/
}
}
private _rowIdGetter: (rowData: IPartInfo) => MyGriddlePlugins.RowId =
(rowData: IPartInfo) => rowData.serialNumber;
public render(): JSX.Element {
this.rowSelector.rowIdToSelect = this.props.selectedSerialNumber;
return (
<div>
<Griddle
data={this.props.parts}
plugins={[plugins.LocalPlugin, this.rowSelector.plugin]}
>
<RowDefinition>
<ColumnDefinition id="name" title="Part Name" />
<ColumnDefinition id="location" title="Installed Location" />
<ColumnDefinition id="serailNumber" title="Serial Number" />
</RowDefinition>
</Griddle>
</div>
);
}
}
So, what's actually going on here? The component creates an instance of the plugin class at instantiation time, passing in an event handler to capture the click on the row and an accessor function to retrieve your ID value (not an inscrutable internal ID) from a row of your data. Just before the component returns its rendering, a value is set on the component's instance of the plugin, that way, when Griddle renders the plugin has the data to figure out when it's on a selected row and then adjust the CSS accordingly. The handler function from your component is then assigned to the row's onClick handler so your component can get the data from the click and do whatever it needs to do.
This passes the "It works for me" test (on React 15.6) which in my case is a straightforward master/detail view driven by a traditional table implemented through Griddle. I have no idea how well it would work with some of Griddle's more advanced features.

Resources