I created a Gutenberg custom block. My Gutenberg custom block
In this block, I need to change the background colour (green) and text colours. Is there any way for me to enable the inbuilt Gutenberg colour pallet which appears when I click the default Gutenberg paragraph and other blocks.
Here is my code for my Gutenberg block.
( function( blocks, components, i18n, element ) {
var el = element.createElement;
var registerBlockType = wp.blocks.registerBlockType;
var RichText = wp.blocks.RichText;
var BlockControls = wp.blocks.BlockControls;
var AlignmentToolbar = wp.blocks.AlignmentToolbar;
var MediaUpload = wp.blocks.MediaUpload;
var InspectorControls = wp.blocks.InspectorControls;
var TextControl = wp.components.TextControl;
registerBlockType( 'futurelab/color-block', { // The name of our block. Must be a string with prefix. Example: my-plugin/my-custom-block.
title: i18n.__( 'Color Block' ), // The title of our block.
description: i18n.__( 'A custom block for displaying color blocks.' ), // The description of our block.
icon: 'media-document', // Dashicon icon for our block. Custom icons can be added using inline SVGs.
category: 'common', // The category of the block.
attributes: {
title: {
type: 'array',
source: 'children',
selector: 'h3',
},
content: {
type: 'array',
source: 'children',
selector: 'p',
},
buttonText: {
type: 'array',
source: 'children',
selector: 'span',
},
buttonURL: {
type: 'url',
selector:'div'
},
},
// The "edit" property must be a valid function.
edit: function( props ) {
var title = props.attributes.title; // Content in our block.
var content = props.attributes.content; // Content in our block.
var buttonText = props.attributes.buttonText; // Content in our block.
var buttonURL = props.attributes.buttonURL;
/**
* Update title on change.
*/
function onChangeTitle( newTitle ) {
props.setAttributes( { title: newTitle } );
}
function onChangeContent( newContent ) {
props.setAttributes( { content: newContent } );
}
function onChangeButtonText( newButtonText ) {
props.setAttributes( { buttonText: newButtonText } );
}
// The editable title.
return [
el( InspectorControls, { key: 'inspector' }, // Display the block options in the inspector panel.
el( components.PanelBody, {
title: i18n.__( 'Color Block Settings' ),
className: 'block-social-links',
initialOpen: true,
},
el( 'p', {}, i18n.__( 'Enter the button url here to navigate button when click.' ) ),
el( TextControl, {
type: 'url',
label: i18n.__( 'Button URL' ),
value: buttonURL,
onChange: function( newButton ) {
props.setAttributes( { buttonURL: newButton } );
},
} ),
),
),
el(
'div',
{className: props.className + ' color-content-block'},
el(RichText, // Editable React component.
{
tagName: 'h3', // <p></p>.
className: props.className, // The class="wp-editor-gb-03-block-editable".
value: title, // Content in our block. i.e. props.attributes.title;
placeholder: 'Block Title...',
keepPlaceholderOnFocus: true,
focus: focus, // Focus — should be truthy. i.e. props.focus;
onFocus: props.setFocus,
onChange: onChangeTitle
}
),
el(RichText, // Editable React component.
{
tagName: 'p', // <p></p>.
className: props.className + ' block-content', // The class="wp-editor-gb-03-block-editable".
onChange: onChangeContent, // Run the onChangeContent() function onChange of title.
placeholder: 'Block Content...',
value: content, // Content in our block. i.e. props.attributes.title;
focus: focus, // Focus — should be truthy. i.e. props.focus;
onFocus: props.setFocus
}
),
el (
'span',
{ className: props.className + ' btn' },
el(RichText, // Editable React component.
{
tagName: 'span', // <p></p>.
className: props.className, // The class="wp-editor-gb-03-block-editable".
placeholder: 'Button Title...',
onChange: onChangeButtonText, // Run the onChangeContent() function onChange of title.
value: buttonText, // Content in our block. i.e. props.attributes.title;
focus: focus, // Focus — should be truthy. i.e. props.focus;
onFocus: props.setFocus,
}
),
),
el (
'div',
{ className: props.className + ' display-none' },
buttonURL,
),
)
]
},
// The "save" property must be specified and must be a valid function.
save: function( props ) {
var title = props.attributes.title; // Content in our block.
var content = props.attributes.content; // Content in our block.
var buttonText = props.attributes.buttonText; // Content in our block.
var buttonURL = props.attributes.buttonURL;
// The frontend title.
return el(
'div',
{className: 'color-title-block'},
el( 'h3', { // <p></p>.
className:'', // The class="wp-block-gb-block-editable-03".
},
title,
),
el( 'p', { // <p></p>.
className:'block-content', // The class="wp-block-gb-block-editable-03".
},
content,
),
el('span', {
className: 'btn'
},
buttonText,
),
el( 'div', {
className: ''
},
buttonURL
),
);
},
} );
} )(
window.wp.blocks,
window.wp.components,
window.wp.i18n,
window.wp.element,
);
Please help me with that.
You need to add an attribute to store the color and then wrap your edit function in the Higher Order Component called withColors and include PanelColorSettings in InspectorControls, both of which are in wp.editor.
var PanelColorSettings = wp.editor.ColorPanelSettings;
var withColors = wp.editor.withColors
var compose = wp.compose.compose
...
attributes: {
myColorAttributeName: {
type: 'string'
}
}
...
edit: compose([withColors('myColorAttributeName')])(myRegularEditFunction)
this exposes a new prop function that gets passed automatically called setMyColorAttributeName ( like setAttributes() ) that you can use in the PanelColorSettings elements onChange function.
*Update: March 2019*
updating response with a more complete example
// get these from the wp object.
const { withColors, PanelColorSettings, getColorClassName } = wp.editor;
const { compose } = wp.compose;
const { Fragment } = wp.element;
...
/** extract your edit component into a function like this.
* this will give you a settings panel on the sidebar with
* the color swatches and handle the onChange function
*/
const EditComponent = ({colorScheme, setColorScheme} /* and your other props */) => (
<Fragment>
<InspectorControls>
<PanelColorSettings
title={ 'Color Options' }
colorSettings={ [
{
value: colorScheme.color,
label: 'Block Color Scheme',
onChange: setColorScheme,
},
] }
/>
</InspectorControls>
<YOUR-ACTUAL-BLOCK-MARKUP />
<Fragment>
);
...
registerBlockType( 'namespace/blockslug', {
...
edit: compose( [withColors('colorScheme')] )(EditComponent)
});
Once you add this markup, you have access to getColorClassName( 'background-color', attributes.colorScheme) in the render function. in this instance it returns something like has-purple-background-color. It's a lot of code to write for something that should be much easier, but it works.
the WP and Gutenberg team may have changed this since the official release of WP 5, but it's still working for me on WP 5.1.1 so, I'm submitting it.
Like "8-Bit Echo" said, only use the slug attribute to get, in this case, the "has-color-name-background-color" result IF you are using the color object directly, e.g.:
getColorClassName( 'background-color', colorScheme.slug)
You can also inspect the colorScheme object and see it has the following attributes: class (has-color-name-color-sheme), color (rgba(4,3,2,1)), name (Color Name), slug (color-name).
Related
I have made a custom gutenberg block:
(function(blocks, components, element) {
var el = element.createElement;
var registerBlockType = blocks.registerBlockType;
var TextControl = components.TextControl;
registerBlockType("blocks/test-block", {
title: "Test Block",
description: "A custom block.",
icon: "businessman",
category: "common",
attributes: {
headline: {
type: "string"
}
},
edit: function(props) {
var headline = props.attributes.headline;
var onHeadlineChange = function(val) {
props.setAttributes({
headline: val
});
};
return el(TextControl, {
value: headline,
label: "Headline",
onChange: onHeadlineChange
});
},
save: function(props) {
alert('');
return el(
"h3",
{
className: "headline"
},
props.attributes.headline
);
}
});
})(window.wp.blocks, window.wp.components, window.wp.element);
I want to run a function when the block is loaded on front end.
I tried in save function but it only work when the block is saved on wp-dashboard.
Is there any way by which I can run a function when block is loaded?
You can use render callback parameter in register block type inside your PHP file.
like
register_block_type(
'blocks/test-block', array(
'editor_script' => 'index-js',
'render_callback' => 'recent_posts_block'
)
);
function recent_posts_block($attributes){
//your code here
}
I want to add a custom style to the wordpress tiny mce. There are tons of tutorials for just adding a simple option like "highlight" which will add a span with a "highlight" class. Like: https://torquemag.io/2016/09/add-custom-styles-wordpress-editor-manually-via-plugin/
But what I need is an option to add additional data, like if you add a link. You mark the words, hit the link button, an input for the url shows up.
What I want to achieve? A custom style "abbriation" (https://get.foundation/sites/docs/typography-base.html). The solution I'm thinking of is, the user marks the word, chooses the abbriation style, an input for the descriptions shows up. fin.
Hope you can help me out!
So I have something similar in most of my WordPress projects. I have a TinyMCE toolbar button that has a couple of fields that output a bootstrap button.
What you need to do is create your own TinyMCE "plugin" and to achieve this you need two parts:
A javascript file (your plugin)
A snippet of PHP to load your javascript (plugin) into the TinyMCE editor.
First we create the plugin:
/js/my-tinymce-plugin.js
( function() {
'use strict';
// Register our plugin with a relevant name
tinymce.PluginManager.add( 'my_custom_plugin', function( editor, url ) {
editor.addButton( 'my_custom_button', {
tooltip: 'I am the helper text',
icon: 'code', // #link https://www.tiny.cloud/docs/advanced/editor-icon-identifiers/
onclick: function() {
// Get the current selected tag (if has one)
var selectedNode = editor.selection.getNode();
// If we have a selected node, get the inner content else just get the full selection
var selectedText = selectedNode ? selectedNode.innerHTML : editor.selection.getContent();
// Open a popup
editor.windowManager.open( {
title: 'My popup title',
body: [
// Create a simple text field
{
type: 'textbox',
name: 'field_name_textbox',
label: 'Field label',
value: selectedText || 'I am a default value' // Use the selected value or set a default
},
// Create a select field
{
type: 'listbox',
name: 'field_name_listbox',
label: 'Field list',
value: '',
values: {
'value': 'Option 1',
'value-2': 'Option 2'
}
},
// Create a boolean checkbox
{
type: 'checkbox',
name: 'field_name_checkbox',
label: 'Will you tick me?',
checked: true
}
],
onsubmit: function( e ) {
// Get the value of our text field
var textboxValue = e.data.field_name_textbox;
// Get the value of our select field
var listboxValue = e.data.field_name_listbox;
// Get the value of our checkbox
var checkboxValue = e.data.field_name_checkbox;
// If the user has a tag selected
if ( selectedNode ) {
// Do something with selected node
// For example we can add a class
selectedNode.classList.add( 'im-a-custom-class' );
} else {
// Insert insert content
// For example we will create a span with the text field value
editor.insertContent( '<span>' + ( textboxValue || 'We have no value!' ) + '</span>' );
}
}
} );
}
} );
} );
} )();
Now we add and modify the below snippet to your themes functions.php file.
/functions.php
<?php
add_action( 'admin_head', function() {
global $typenow;
// Check user permissions
if ( !current_user_can( 'edit_posts' ) && !current_user_can( 'edit_pages' ) ) {
return;
}
// Check if WYSIWYG is enabled
if ( user_can_richedit() ) {
// Push my button to the second row of TinyMCE actions
add_filter( 'mce_buttons', function( $buttons ) {
$buttons[] = 'my_custom_button'; // Relates to the value added in the `editor.addButton` function
return $buttons;
} );
// Load our custom js into the TinyMCE iframe
add_filter( 'mce_external_plugins', function( $plugin_array ) {
// Push the path to our custom js to the loaded scripts array
$plugin_array[ 'my_custom_plugin' ] = get_template_directory_uri() . '/js/my-tinymce-plugin.js';
return $plugin_array;
} );
}
} );
Make sure to update the file name and path if you it's different to this example!
WordPress uses TinyMCE 4 and the documentation for this is lacking so finding exactly what you need can be painful.
This is merely a starting point and has not been tested.
Hope this helps!
EDIT
The below code should help you with the insertion of an "abbreviations" tag and title attribute.
( function() {
'use strict';
tinymce.PluginManager.add( 'my_custom_plugin', function( editor, url ) {
editor.addButton( 'my_custom_button', {
tooltip: 'Insert an abbreviation',
icon: 'plus',
onclick: function() {
var selectedNode = editor.selection.getNode();
var selectedText = selectedNode ? selectedNode.innerHTML : editor.selection.getContent();
editor.windowManager.open( {
title: 'Insert an abbreviation',
body: [
{
type: 'textbox',
name: 'abbreviation',
label: 'The abbreviated term',
value: selectedText
},
{
type: 'textbox',
name: 'title',
label: 'The full term',
value: ''
}
],
onsubmit: function( e ) {
var abbreviation = e.data.abbreviation;
var title = e.data.title.replace( '"', '\"' );
if ( selectedNode && selectedNode.tagName === 'ABBR' ) {
selectedNode.innerText = abbreviation;
selectedNode.setAttribute( 'title', title );
} else {
editor.insertContent( '<abbr title="' + title + '">' + abbreviation + '</abbr>' );
}
}
} );
}
} );
} );
} )();
I want to have anchor support for my dynamic wordpress block. I did
//in registerBlockType
supports: {
anchor: true,
},
This adds the HTML Anchor control under the sidebar panel.
My block is a dynamic block that has
save: ( props ) => {
return <InnerBlocks.Content />;
}
I tried everything to get the anchor attribute to to frontend. According to this github issue I should add
anchor: {
type: 'string',
source: 'attribute',
attribute: 'id',
selector: '*',
},
to the blocks attributes. This will make the anchor available in the save function via props.anchor, however it never appears in my render_callback $attributes.
This is basically a port of the github issue to SO. Hope anyone can help here.
if anyone is still interested this worked for me:
so this is my custom block registering, this statement will enable standard wordpress HTML anchor field (with valuable validation for spaces etc.) under Advanced tab of selected gutenberg block:
supports: {
anchor: true
}
then in the same place we define:
attributes: {
anchor: {
type: 'string'
}
}
then in save function (I have it exactly for the same purpose of InnerBlocks):
save: function(props) {
const { anchor } = props.attributes;
return (
el( anchor, {}),
el( InnerBlocks.Content, {})
);
}
if you are using jsx, the save function could look like this:
save: function(props) {
const { anchor } = props.attributes;
return (
<div id={anchor}>
<InnerBlocks.Content />
</div>
);
}
then in your render callback function (in php) it's going to be available via first arg's (which is array) element
function your_callback( $block, $content ) {
// display your anchor value
echo $block['anchor'];
}
You could use this filter (targeting whatever blocks you want)
const withAnchor = props => {
if (props.attributes) { // Some blocks don't have attributes
props.attributes = {
...props.attributes,
anchor: {
type: 'string'
}
}
}
return props
}
wp.hooks.addFilter(
'blocks.registerBlockType',
'namespace/with-anchor',
withAnchor
)
And then you can access the 'anchor' attribute in the render callback
'render_callback' => function($attributes) {
echo $attributes['anchor'];
}
Have you tried manually adding a field that will take care of the ID attribute?
Something like this:
<InspectorControls>
<PanelBody title={ __( 'Element Settings' ) }>
<TextControl
label={ __( 'Element ID', 'fleximpleblocks' ) }
value={ elementID}
placeholder={ __( 'Type in the element ID…' ) }
onChange={ ( value ) => setAttributes( { elementID: value } ) }
/>
</PanelBody>
</InspectorControls>
And then:
save: ( props ) => {
return <InnerBlocks.Content id={ props.attributes.elementID } />;
}
I'm not sure if it'll work, I'm just taking a wild guess here. Let me know how it goes :)
Based on answers above.
You just need create an attribute which collects all other attributes/variables/whatever into string.
Step 1
Create an attribute with string type(in block.json)
"phpRender": {
"type": "string"
}
Step 2
In the "edit" function of the block, create a function to save whatever you need to the attribute above. Put this function in "useEffect" hook.
const saveAllToString = () => {
const blockProps = {
id: attributes.anchor,
}
setAttributes({phpRender: JSON.stringify(blockProps)});
}
useEffect(() => {
saveAllToString();
});
Step 3
Now you can decode this string and use variables easily.
$blockProps = !empty($attributes['phpRender']) ? json_decode($attributes['phpRender']) : false;
echo $blockProps->id;
I am attempting to extend an existing block which is registered in the Tasty Recipes plugin. I've added a control to the sidebar to handle selection of a given diet type, however when an option is selected, the following error is displayed:
Error loading block: Invalid parameter(s): attributes
Currently running on WordPress 5.2.1 and I am attempting to extend the Tasty Recipes Plugin
extendTastyRecipes.js
/**
* Add custom attribute to store diet type
*/
var addCustomAttributes = function( settings, name ) {
if ( name !== 'wp-tasty/tasty-recipe' ) {
return settings;
}
settings.attributes = Object.assign( settings.attributes, {
dietType: {
type: 'string',
// ? Setting default here causes break
},
} );
return settings;
}
wp.hooks.addFilter( 'blocks.registerBlockType', 'wp-tasty/tasty-recipe', addCustomAttributes );
/**
* Create HOC to add diet type control to inspector controls of block.
*/
var el = wp.element.createElement;
var withInspectorControls = wp.compose.createHigherOrderComponent( function( BlockEdit ) {
return function ( props ) {
function onChangeDietType (newDietType) {
props.setAttributes( { dietType: newDietType } );
}
return el(
wp.element.Fragment,
{},
el(
BlockEdit,
props
),
el(
wp.editor.InspectorControls,
{},
el(
wp.components.PanelBody,
{},
el (
wp.components.SelectControl,
{
label: 'Diet Type',
onChange: onChangeDietType,
options: [
{ label: 'Paleo', value: 'paleo' },
{ label: 'Vegan', value: 'vegan' },
{ label: 'Vegetarian', value: 'vegetarian' },
],
},
)
)
)
);
};
}, 'withInspectorControls' );
wp.hooks.addFilter( 'editor.BlockEdit', 'wp-tasty/tasty-recipe', withInspectorControls );
/**
* Not sure this is even necessary as the plugin I'm extending handles rendering on server
* -- extra --
* #param {Object} extraProps
* #param {Object} blockType
* #param {Object} attributes
*/
function addSaveProps( element ) {
return element; // This returns null
}
wp.hooks.addFilter( 'blocks.getSaveElement', 'wp-tasty/tasty-recipe', addSaveProps );
functions.php
function extendTastyRecipes() {
$blockPath = get_stylesheet_directory_uri() . '/assets/scripts/modules/extendTasyRecipes.js';
wp_enqueue_script(
'extend-tasty-recipes-js',
$blockPath,
[ 'wp-i18n', 'wp-edit-post', 'wp-element', 'wp-editor', 'wp-components', 'wp-data', 'wp-plugins', 'wp-edit-post', 'wp-api' ]
);
}
add_action('init', 'extendTastyRecipes');
Hoping I'm missing something simple... Not entirely familiar with Gutenberg so I could definitely just be misunderstanding the docs.
I tried several ways to set an icon, in the displayfield, when an item of the combo is selected with not luck, this is the fiddle for anyone to want try to help with this. very much appreciated any light.
fiddle example
The only solution is to transform the input type combo in a div with this:
fieldSubTpl: [
'<div class="{hiddenDataCls}" role="presentation"></div>',
'<div id="{id}" type="{type}" style="background-color:white; font-size:1.1em; line-height: 2.1em;" ',
'<tpl if="size">size="{size}" </tpl>',
'<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
'class="{fieldCls} {typeCls}" autocomplete="off"></div>',
'<div id="{cmpId}-triggerWrap" class="{triggerWrapCls}" role="presentation">',
'{triggerEl}',
'<div class="{clearCls}" role="presentation"></div>',
'</div>', {
compiled: true,
disableFormats: true
}
],
Override the setRawValue method of the combo like this:
setRawValue: function (value) {
var me = this;
me.rawValue = value;
// Some Field subclasses may not render an inputEl
if (me.inputEl) {
// me.inputEl.dom.value = value;
// use innerHTML
me.inputEl.dom.innerHTML = value;
}
return value;
},
and style your fake combo div like you want.
Thats because an input on HTML can't have HTML like value inside it.
Keep attenction, the get Value method will return you the HTML inside the div, and maybe you should also override it, but thats the only one method.
You will be able to get the selected value with this method:
Ext.fly(combo.getId()+'-inputEl').dom.innerHTML.replace(/<(.|\n)*?>/gm, '');
If I were you I would like to do something like this:
combo.getMyValue();
So add this property to your combo:
getMyValue:function(){
var combo=this;
if(Ext.fly(combo.id+'-inputEl'))
return Ext.fly(combo.id+'-inputEl').dom.innerHTML.replace(/<(.|\n)*?>/gm, '');
},
Here is a working fiddle
Perhaps my solution is similar to a hack, but it works in 6.7.0 and is a bit simpler.
Tested in Chrome. Theme - Material. For another theme will require minor improvements.
Sencha Fiddle live example
Ext.application({
name: 'Fiddle',
launch: function () {
var store = new Ext.data.Store({
fields: [{
name: 'class',
convert: function (value, model) {
if (value && model) {
var name = value
.replace(/(-o-)|(-o$)/g, '-outlined-')
.replace(/-/g, ' ')
.slice(3)
.trim();
model.data.name = name.charAt(0).toUpperCase() + name.slice(1);
return value;
}
}
}, {
name: 'name'
}],
data: [{
class: 'fa-address-book'
}, {
class: 'fa-address-book-o'
}, {
class: 'fa-address-card'
}]
});
var form = Ext.create('Ext.form.Panel', {
fullscreen: true,
referenceHolder: true,
items: [{
xtype: 'combobox',
id: 'iconcombo',
queryMode: 'local',
editable: false,
width: 300,
valueField: 'class',
displayField: 'name',
store: store,
itemTpl: '<div><i class="fa {class}"></i> {name}</div>',
afterRender: () => {
var component = Ext.getCmp('iconcombo');
var element = document.createElement('div');
element.className = 'x-input-el';
element.addEventListener('click', () => component.expand());
component.inputElement.parent().dom.prepend(element);
component.inputElement.hide();
component.addListener(
'change', (me, newValue, oldValue) => {
component.updateInputValue.call(me, newValue, oldValue);
},
component
);
var method = component.updateInputValue;
component.updateInputValue = (value, oldValue) => {
method.call(component, value, oldValue);
var selection = component.getSelection();
if (selection) {
element.innerHTML =
'<div><i class="fa ' + selection.get('class') + '"></i> ' + selection.get('name') + '</div>';
}
};
}
}, {
xtype: 'button',
text: 'getValue',
margin: '30 0 0 0',
handler: function (component) {
var combo = Ext.getCmp('iconcombo');
alert(combo.getValue());
}
}]
});
form.show();
}
});