Related
I'm working on building a Gutenberg block but it seems I keep getting an infinite loop. How can I invoke the rest API only once?
I am trying to get a list of officers - custom post type - but as the initial call doesn't provide the featured image I thought I should loop through and add the images "manually". If there is a better approach please do let me know.
registerBlockType('test/officers-block', {
title: __('Officers Block', 'test'),
description: __('Block to generate the officers block', 'test'),
icon: '',
category: 'test',
attributes: {
title: {
type: 'string',
},
posts: {
type: 'array',
}
},
edit: ({ attributes, setAttributes }) => {
const {
title,
posts,
} = attributes
// Request data
const data = useSelect((select) => {
return select('core').getEntityRecords('postType', 'officers', { per_page: -1 });
});
const isLoading = useSelect((select) => {
return select('core/data').isResolving('core', 'getEntityRecords', [
'postType', 'officers',
]);
});
const setOfficers = ( data ) => {
let officerData = [];
data.map(( officer ) => {
wp.apiFetch( { path: '/wp/v2/media/' + officer?.featured_media } ).then( function( image ){
officerDataEach.title = officer?.title?.rendered
officerDataEach.content = officer?.content?.rendered
officerDataEach.job = officer?.meta?._job_title
officerDataEach.phone = officer?.meta?._phone
officerDataEach.email = officer?.meta?._email
officerDataEach.image = image?.media_details?.sizes?.full?.source_url
officerData.push( officerDataEach )
})
})
console.log(officerData);
setAttributes( { posts: officerData } )
}
if( !isLoading && data ){
setOfficers( data );
}
if ( isLoading && !data ) {
return <h3>Loading...</h3>;
}
...
Thanks in advance
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).
Hello I have a problem
Working:
Meteor
Angular Meteor
ui-grid
I follow the plunker example in documentation ui-grid link
The problem is that the data don't show when filters are activated.
I have no errors in console.
I put my code:
html file
<button id='toggleFiltering' ng-click="inventario.toggleFiltering()" class="btn btn-success">Toggle Filtering</button>
<div id="grid1" ui-grid="inventario.gridOptions" class="grid"></div>
js file
class Inventario {
constructor($scope, $reactive, $uibModal, $http, uiGridConstants) {
'ngInject';
$reactive(this).attach($scope);
this.$uibModal = $uibModal;
var today = new Date();
var nextWeek = new Date();
this.highlightFilteredHeader = (row, rowRenderIndex, col, colRenderIndex) => {
if( col.filters[0].term ){
return 'header-filtered';
} else {
return '';
}
};
this.gridOptions = {
enableFiltering: true,
onRegisterApi: (gridApi) => {
this.gridApi = gridApi;
},
columnDefs: [
// default
{ field: 'name', headerCellClass: this.highlightFilteredHeader },
// pre-populated search field
{ field: 'gender', filter: {
term: '1',
type: uiGridConstants.filter.SELECT,
selectOptions: [ { value: '1', label: 'male' }, { value: '2', label: 'female' }, { value: '3', label: 'unknown'}, { value: '4', label: 'not stated' }, { value: '5', label: 'a really long value that extends things' } ]
},
cellFilter: 'mapGender', headerCellClass: this.highlightFilteredHeader },
// no filter input
{ field: 'company', enableFiltering: false, filter: {
noTerm: true,
condition: (searchTerm, cellValue) => {
return cellValue.match(/a/);
}
}},
// specifies one of the built-in conditions
// and a placeholder for the input
{
field: 'email',
filter: {
condition: uiGridConstants.filter.ENDS_WITH,
placeholder: 'ends with'
}, headerCellClass: this.highlightFilteredHeader
},
// custom condition function
{
field: 'phone',
filter: {
condition: (searchTerm, cellValue) => {
var strippedValue = (cellValue + '').replace(/[^\d]/g, '');
return strippedValue.indexOf(searchTerm) >= 0;
}
}, headerCellClass: this.highlightFilteredHeader
},
// multiple filters
{ field: 'age', filters: [
{
condition: uiGridConstants.filter.GREATER_THAN,
placeholder: 'greater than'
},
{
condition: uiGridConstants.filter.LESS_THAN,
placeholder: 'less than'
}
], headerCellClass: this.highlightFilteredHeader},
// date filter
{ field: 'mixedDate', cellFilter: 'date', width: '15%', filter: {
condition: uiGridConstants.filter.LESS_THAN,
placeholder: 'less than',
term: nextWeek
}, headerCellClass: this.highlightFilteredHeader
},
{ field: 'mixedDate', displayName: "Long Date", cellFilter: 'date:"longDate"', filterCellFiltered:true, width: '15%',
}
]
};
$http.get('https://cdn.rawgit.com/angular-ui/ui-grid.info/gh-pages/data/500_complex.json')
.success((data) => {
this.gridOptions.data = data;
this.gridOptions.data[0].age = -5;
data.forEach( function addDates( row, index ){
row.mixedDate = new Date();
row.mixedDate.setDate(today.getDate() + ( index % 14 ) );
row.gender = row.gender==='male' ? '1' : '2';
});
});
this.toggleFiltering = () => {
this.gridOptions.enableFiltering = !this.gridOptions.enableFiltering;
this.gridApi.core.notifyDataChange( uiGridConstants.dataChange.COLUMN );
};
}
}
const name = 'inventario';
// Módulo
export default angular
.module(name, [
uiRouter,
EditarArticulo
])
.component(name, {
templateUrl: `imports/ui/components/${name}/${name}.html`,
controllerAs: name,
controller: Inventario
})
.config(config)
.filter('mapGender', function() {
var genderHash = {
1: 'male',
2: 'female'
};
return function(input) {
if (!input){
return '';
} else {
return genderHash[input];
}
};
});
Given that everything seems to work when filtering is disabled, you must have a problem with the (multiple) filters you have declared.
It is most likely a combination of the filters that is excluding all of your data. Start by commenting out all of the filters (you should see all the data), and then re-introduce the filters one by one until you see the problem again.
This will narrow down the problem, and allow you to see which filter is wrong.
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();
}
});
I'm using reactive table package from aslagle in my app and I want to create in-line editing, I searched and I found that there's x-editable package for Meteor, so how can I use aslagle:reactive-table package with workman:x-editable-reactive-template package?
I tried this:
Reactive-Table settings:
tableSettings: function () {
return {
collection: fLogCollection,
rowsPerPage: 10,
showFilter: true,
fields: [
{ key: 'name', label: 'Name'},
{ key: 'amount',
label: 'Amount',
tmpl: Template.xEditableAmount
},
{ key: 'cashFrom', label: 'Cash From'},
{ key: 'dateIs', label: 'Date', sortOrder: 0, sortDirection: 'descending'},
{ key: 'controls', label: 'Controls', fn: function () {
return new Spacebars.SafeString(
"<button class='editFlog'><span class='glyphicon glyphicon-pencil'></span> </button>"+
"<button class='delete'><span class='glyphicon glyphicon-remove'></span> </button>"
); } },
{ key: 'createdAt', label: 'createdAt', hidden: true },
],
};
},
xEditableAmount template:
<template name="xEditableAmount">
{{amount}}
</template>
This code to get the x-editable rendered:
Template.fLog.onRendered(function() {
this.$('.editable').editable({
success: function (response, newValue) {
if(response.status == 'error') return response.msg; //msg will be shown in editable form
else Meteor.call('flog.edit2', this._id, newValue);
},
});
});
I succeeded in making x-editable render but
I failed at getting the field updated with the new value in collection...
You can inject templates into fields which makes it convenient to add almost anything you want.
Template helper:
tableSettings: function() {
return {
collection: foo,
fields: [
{
key: 'foo_1',
label: 'Foo 1',
tmpl: Template.foo1,
},
{
key: 'foo_2',
label: 'Foo 2',
tmpl: Template.foo2,
},
{
key: 'foo_2',
label: 'Foo 2',
tmpl: Template.foo2,
}
]
};
}
In foo2 helper (copied directly from workman/x-editable-reactive-template atmosphere page):
Template.foo2.helpers({
onSuccess: function () {
var id = this._id;
return function (res, val) {
MyColl.update({ _id: id }, { $set: { prop: val } });
}
}
});
In your Templates:
<template name='table>
{{> reactiveTable settings=tableSettings}}`
</template>
<template name='foo1'>
<!-- Any html (below pasted from docs (link at bottom of post)-->
superuser
</template>
<template name='foo2'>
{{> xEditable type="text" success=onSuccess placement="right" }} <!-- put your workman:x-editable-reactive-template here -->
</template>
This should get you pointed in the right direction.
https://vitalets.github.io/x-editable/docs.html