I am trying to make custom Gutenberg Blocks through a plugin. Everything is going smooth the only issue is when I select my block from the blocks menu, it just pastes the JSON on the front. What I rather want is to render this JSON to make blocks.
I am fetching blocks' content from an API. I am attaching my code as well.
function makeBlock(block, category){
var jsonBlock = {
"__file": "wp_export",
"version": 2,
"content": ""}
;
$.ajax({
type: "POST",
url: document.location.origin+"/blocknets/wp-admin/admin-ajax.php",
data: {
'action': 'makeBlocks',
'id': block.id
},
dataType: "json",
encode: true,
}).done(function (resp) {
// console.log(resp);
jsonBlock.content = resp.data.content;
});
( function ( blocks, element, data, blockEditor ) {
var el = element.createElement,
registerBlockType = blocks.registerBlockType,
useSelect = data.useSelect,
useBlockProps = blockEditor.useBlockProps;
// debugger;
registerBlockType( 'custom-blocks/'+category+'-'+block.id, {
apiVersion: 2,
title: block.name,
icon: 'megaphone',
category: category,
edit: ()=>{return jsonBlock.content},
save: () => null
} );
} )(
window.wp.blocks,
window.wp.element,
window.wp.data,
window.wp.blockEditor
);
}
Purple Highlighted is my plugin, and Yellow is what it prints out.
What I rather want is to render this JSON. If I just paste this JSON into code editor it would look like this.
Can anyone help me out?
The jsonBlock.content displayed in the Editor view is serialized block content. The first step is to use parse() to transform the content into valid blocks. Next, to render the blocks I found RawHTML can be used to render innerHTML from the block content. The <RawHTML/> component uses dangerouslySetInnerHTML as seen commonly in React to render inner HTML content. Eg:
Edit()
const { parse } = wp.blockSerializationDefaultParser;
const { RawHTML } = wp.element;
export default function Edit({ attributes, setAttributes }) {
// Example of serialized block content to mimic resp.data.content data
var content = "<!-- wp:paragraph --><p>paragraph one</p><!-- /wp:paragraph --><!-- wp:paragraph --><p>then two</p><!-- /wp:paragraph -->";
// Parse the serialized content into valid blocks using parse from #wordpress/block-serialization-default-parser
var blocks = parse(content);
// Iterate over each block to render innerHTML within RawHTML that sets up dangerouslySetInnerHTML for you..
return blocks.map((block, index) => <RawHTML key={index}>{block.innerHTML}</RawHTML>);
}
Nb. The example covers parsing and displaying block content in the Editor, it does not cover saving the content, as your existing save() function is set to null.
I was able to render all the blocks by using the following edit function:
edit: ()=>{
window.wp.data.dispatch( 'core/block-editor' ).insertBlocks( window.wp.blocks.parse( jsonBlock.content));
return null;
}
Related
If a Gutenberg block has an attachment ID attribute stored, is there are way to dynamically get the url of a specific thumbnail size using that ID?
The attribute will be stored in the block like this:
imageID: {
type: 'integer',
},
And the idea is to dynamically show that image in the Gutenberg editor view.
I ran into this problem a few weeks ago. It had me baffled for a while but you can do it using withSelect()() and getMedia(). In a nut shell, we are going to have to get the media object from the ID that we have. Look inside that object for the thumbnail object. Then we will get the source_url property. Your file should look something like:
// Block image preview
const blockEdit = createElement("div", null,
// If image defined, get the source_url
const imageThumbURL = props.imageObj ? props.imageObj.media_details.sizes.thumbnail.source_url : null
createElement("img", {
src: imageThumbURL
})
)
// Use withSelect(x)(y) to load image url on page load
const fieldData = withSelect( (select, props) => {
// Get the image ID
const imageId = props.attributes.imageID
// Create "props.imageObj"
return {
// If image defined, get the image "media" object
imageObj: imageId ? select("core").getMedia(imageId) : null
}
})(blockEdit)
wp.blocks.registerBlockType('plugin-namespace/block-name', {
attributes: {
imageID: {
type: 'Number'
}
},
edit: fieldData
}
The above is untested but I used that solution to allow my media item to load when the page is loaded by using it's ID. Hopefully this helps.
I am working on a Gutenberg sidebar plugin which does some text analysis and based on that, it needs to annotate text in the paragraph blocks. This is the easier part and is achieved using the Annotations API by iterating over each block like this:
wp.data.dispatch( 'core/annotations' ).__experimentalAddAnnotation( {
source: "my-annotations-plugin",
blockClientId: wp.data.select( 'core/editor' ).getBlockOrder()[0],
richTextIdentifier: "content",
range: {
start: 50,
end: 100,
},
} );
Now, the challenge that I am facing is persisting these annotations (as that's the requirement of the plugin). I figured out that Annotations API internally uses applyFormat method of #wordpress/rich-text package but I am not able to figure out how to use applyFormat directly. The documentation is in-adequate and lacks code examples.
If you have worked with this it would help to have sample working (ES5 or ES6) code to use applyFormat in the right way.
I finally figured it out. Just posting it here if anyone needs to do this as the Gutenberg documentation lacks code examples.
With reference to the below code, blockIndex is the block you are dealing with; and startIndex and endIndex are ranges to annotate in context of the block:
// Get latest modified content of the block
let html = wp.data.select( "core/editor" ).getBlocks()[blockIndex].attributes.content;
// Get uuid of the block
let blockUid = wp.data.select( "core/editor" ).getBlocks()[blockIndex].clientId;
// Create a RichText value from HTML string of block content
let value = wp.richText.create({
html
});
// Apply a format object to a Rich Text value from the given startIndex to the given endIndex
value = wp.richText.applyFormat(value, { type: 'strong' }, startIndex, endIndex);
// Update the block with new content
wp.data.dispatch( 'core/editor' ).updateBlock( blockUid, {
attributes: {
content: wp.richText.toHTMLString({
value
})
}
} );
I am building a custom Gutenberg block that makes a request to the WordPress REST API to get some Posts. I'm using axios to make the request to the REST endpoint.
When the result comes back, there is an array of Post objects, and I can see the titles of the Posts, but they are all contained in the JSON object as title.rendered and contain HTML entities eg.
title: {
rendered: "This has a hyphen – oh dear"
}`
I'm trying to populate a <SelectControl> with the resulting data, so there's no way to use the React dangerouslySetInnerHTML method which would solve the entities problem. So how can I get rid of these entities when populating the options?
Here is the code I'm using to populate the options from the REST response:
const options = response.data.map((post) => {
return {
label: post.title.rendered,
value: post.id,
};
});
It's not immediately obvious, but there is in fact a method made available in the Blocks API to do this.
At the top of your block code, type:
const { decodeEntities } = wp.htmlEntities;
Then you can use it like this:
const options = response.data.map((post) => {
return {
label: decodeEntities(post.title.rendered),
value: post.id,
};
});
Bazoozaa! HTML entities are gone.
And why not using rest_prepare_<post_type> filter ?
$post_type = "post";
add_filter( "rest_prepare_{$post_type}", 'prefix_title_entity_decode' );
function prefix_title_entity_decode( $response ) {
$data = $response->get_data();
$data['title']['rendered'] = html_entity_decode( $data['title']['rendered'] );
$response->set_data( $data );
return $response;
}
I'm facing an issue when trying to perform server side pagination using an enhanced datagrid (dojo v1.10).
The first page is correctly displayed, but the widget (store ? grid ? plugin ?) seems to ignore the 'Content-Range' header value in response and does not allow to get next page.
For example with response header containing 'Content-Range: items 0-9/17', pagination displays '1 to 10 of 10 items', and next page is not available.
After some debug I see that range value is correctly read from JsonRest store (query function)
results.total = results.then(function(){
var range = results.ioArgs.xhr.getResponseHeader("Content-Range");
return range && (range = range.match(/\/(.*)/)) && +range[1];
});
...
But in fetch method from ObjectStore, totalCount value is undefined, results.length is then used:
var results = this.objectStore.query(query, args);
Deferred.when(results.total, function(totalCount){
Deferred.when(results, function(results){
if(args.onBegin){
args.onBegin.call(scope, totalCount || results.length, args);
...
Any idea ?
Thanks,
My code:
// get grid store
var restStore = new JsonRest(
{
target: "ks2/api/workflow/...",
});
var memoryStore = new Memory();
var store = Cache(restStore, memoryStore);
/*set up layout*/
var layout = [{
name: "id",
field: 'id',
width: '5%',
datatype:"string"
},
....
];
/*create a new grid*/
this.workflowGridWidget = new EnhancedGrid({
id: 'workflowGridWidget',
store: new ObjectStore({objectStore: store}),
structure: layout,
rowSelector: '20px',
plugins: {
pagination: {
pageSizes: ["10", "25", "50"],
defaultPageSize: 10,
description: true,
sizeSwitch: true,
pageStepper: true,
gotoButton: true,
maxPageStep: 4,//page step to be displayed
position: "bottom" //position of the pagination bar
}
}
});
/*append the new grid to the div*/
this.workflowGridWidget.placeAt("workflowDataGrid");
/*Call startup() to render the grid*/
this.workflowGridWidget.startup();
I found the issue: I was using a non dojo restful compliant API, and I needed to add JSON response post-processing using
aspect.after(store, "query", this.processResponse);
...
processResponse: function ks2ProcessMonitor_datagrid_WorkflowDataGrid_processResponse(deferred) {
return deferred.then(function(response) {
//process response content
return processedResponse;
});
},
This was working properly but for some reason, it has an impact on pagination. Removing this post-processing (using another API which is dojo compliant) fix the pagination issue.
Maybe I should try response post-processing using an Observable as suggested by Layke.
I have the following situation with an autocomplete plugin on an .aspx page. It is working fine. The result from the autocomplete search yields a product id and a product description is concatenated with it (i.e. 2099 -- A Product). I know that I need to use split() with this but where do I put it? I'm still rather new to jQuery and javascript.
$(document).ready(function() {
$('.divAutoComplete').autocomplete("LookupCodes.aspx?type=FC", {
mustMatch: true
});
});
If it's the same autocomplete I've used (by Tomas Kirda) you should be able to add an onSelected event like so:
$(document).ready(function() {
$('.divAutoComplete').autocomplete("LookupCodes.aspx?type=FC", {
mustMatch: true,
onSelect: function(value, data) { autoCompleteSelected(value, data); }
});
});
function autoCompleteSelected(value, data) {
var parts = data.split("--");
... do something with parts
}
Obviously, if it's not the then it will have different events
In JavaScript, any string can be split using the split function like so:
"Pandas enjoy tasty bamboo".split(' ')
The above splits the string on spaces returning the following array:
["Pandas", "enjoy", "tasty", "bamboo"]
Any string can be fed to the split function and it'll cope with multi-character strings just fine.
Now as for your question with the jQuery autocomplete plugin, you'll need to have your .aspx page return a JS array of options in order for it to work. Alternatively, you can load the data some other way and then pass an array to autocomplete. If the server returns an array like the following then you can pass it directly:
["1234 -- Chicken", "4321 -- Noodle", "1432 -- Irrational Monkeys"]
The point is that autocomplete uses an array for matching.
The docs for the autocomplete plugin seem decent enough.
do this code for splitting
<script type="text/javascript">
$(function() {
var availableTags = ["c++", "java", "php", "coldfusion", "javascript", "asp", "ruby", "python", "c", "scala", "groovy", "haskell", "perl"];
function split(val) {
return val.split(/,\s*/);
}
function extractLast(term) {
return split(term).pop();
}
$("#tags").autocomplete({
minLength: 0,
source: function(request, response) {
// delegate back to autocomplete, but extract the last term
response($.ui.autocomplete.filter(availableTags, extractLast(request.term)));
},
focus: function() {
// prevent value inserted on focus
return false;
},
select: function(event, ui) {
var terms = split( this.value );
// remove the current input
terms.pop();
// add the selected item
terms.push( ui.item.value );
// add placeholder to get the comma-and-space at the end
terms.push("");
this.value = terms.join(", ");
return false;
}
});
});
</script>