{"id":133,"date":"2019-10-07T10:38:14","date_gmt":"2019-10-07T14:38:14","guid":{"rendered":"https:\/\/www.bates.edu\/webtech\/?p=133"},"modified":"2021-01-21T12:53:33","modified_gmt":"2021-01-21T17:53:33","slug":"switching-from-shortcodes-to-blocks-part2","status":"publish","type":"post","link":"https:\/\/www.bates.edu\/webtech\/2019\/10\/07\/switching-from-shortcodes-to-blocks-part2\/","title":{"rendered":"Switching from Shortcodes to Blocks, part 2"},"content":{"rendered":"\n<p>In the first post of this series, we discussed <a href=\"https:\/\/www.bates.edu\/webtech\/2019\/10\/07\/switching-from-shortcodes-to-blocks-part-1\/#no-inner-content\" data-type=\"post\">transforming a simple shortcode to a block<\/a>, and <a href=\"https:\/\/www.bates.edu\/webtech\/2019\/10\/07\/switching-from-shortcodes-to-blocks-part-1\/#content-as-attribute\" data-type=\"post\">using shortcode content for a block attribute value<\/a>. In this post, we&#8217;ll get into a more difficult case: using the shortcode inner content (the text in between the opening and closings tags), as inner blocks for the new block. Our block is going to act as a wrapper for these nested blocks, also known as <code>innerBlocks<\/code>.<\/p>\n\n\n\n<p>Here&#8217;s an example shortcode that will be our transform starting point:<\/p>\n\n\n\n<pre class=\"wp-block-code language-html\"><code>&#91;slidetoggle initialstate=\"closed\"]Learn more about Lorem Ipsum...&#91;\/slidetoggle]<\/code><\/pre>\n\n\n\n<p>And here&#8217;s our basic block, before we add the transform:<\/p>\n\n\n\n<pre class=\"wp-block-code language-js\"><code>registerBlockType('example\/slidetoggle', {\n    ...\n    attributes: {\n        initiallyClosed: {\n            type: 'boolean',\n            default: true\n        },\n    },\n    edit: function(props) {\n       return (\n          &lt;Fragment&gt;\n             &lt;button&gt;Click to toggle&lt;\/button&gt;\n             &lt;div className=\"slide-toggle\"&gt;\n                &lt;InnerBlocks \/&gt;\n             &lt;\/div&gt;\n          &lt;\/Fragment&gt;\n       );\n   },\n   save: function({attributes}) {\n      const initialState = (attributes.initiallyClosed) ? 'start-open' : '';\n      return (\n         &lt;Fragment&gt;\n            &lt;button&gt;Click to toggle&lt;\/button&gt;\n            &lt;div className={ `slide-toggle ${initialState}`}&gt;\n              &lt;InnerBlocks.Content \/&gt;\n            &lt;\/div&gt;\n         &lt;\/Fragment&gt;\n      );\n   },\n   ...\n}<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">The Problem<\/h3>\n\n\n\n<p>In the previous post we looked at just one kind of transform, the &#8220;shortcode&#8221; type. There are <a rel=\"noreferrer noopener\" aria-label=\"other kinds of transforms (opens in a new tab)\" href=\"https:\/\/developer.wordpress.org\/block-editor\/developers\/block-api\/block-registration\/#transforms-optional\" target=\"_blank\">other kinds of block transforms<\/a> available as well, including &#8220;raw&#8221; and &#8220;block&#8221; types. When you use those types of transforms, you can also use a &#8220;transform&#8221; function which gives you more granular control over how the transform is executed. However, when using the &#8220;shortcode&#8221; transform type, you don&#8217;t get access to that function, and therefore don&#8217;t have granular control over how the transform is managed. The &#8220;shortcode&#8221; transform type only allows us to configure, not to explicitly define it&#8217;s output. <\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The Solution<\/h3>\n\n\n\n<p>At this time, the best way to solve this issue is a two step transform:<\/p>\n\n\n\n<p>The first transform is from the raw html (or classic block) to the  built-in shortcode block. That will change this:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"900\" height=\"188\" src=\"https:\/\/www.bates.edu\/webtech\/files\/2019\/10\/Screen-Shot-2019-10-07-at-7.45.45-AM-900x188.png\" alt=\"\" class=\"wp-image-138\" srcset=\"https:\/\/www.bates.edu\/webtech\/files\/2019\/10\/Screen-Shot-2019-10-07-at-7.45.45-AM-900x188.png 900w, https:\/\/www.bates.edu\/webtech\/files\/2019\/10\/Screen-Shot-2019-10-07-at-7.45.45-AM-400x84.png 400w, https:\/\/www.bates.edu\/webtech\/files\/2019\/10\/Screen-Shot-2019-10-07-at-7.45.45-AM-768x161.png 768w, https:\/\/www.bates.edu\/webtech\/files\/2019\/10\/Screen-Shot-2019-10-07-at-7.45.45-AM.png 1252w\" sizes=\"(max-width: 900px) 100vw, 900px\" \/><\/figure>\n\n\n\n<p>into this:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"900\" height=\"98\" src=\"https:\/\/www.bates.edu\/webtech\/files\/2019\/10\/Screen-Shot-2019-10-07-at-7.47.36-AM-1-900x98.png\" alt=\"\" class=\"wp-image-178\" srcset=\"https:\/\/www.bates.edu\/webtech\/files\/2019\/10\/Screen-Shot-2019-10-07-at-7.47.36-AM-1-900x98.png 900w, https:\/\/www.bates.edu\/webtech\/files\/2019\/10\/Screen-Shot-2019-10-07-at-7.47.36-AM-1-400x43.png 400w, https:\/\/www.bates.edu\/webtech\/files\/2019\/10\/Screen-Shot-2019-10-07-at-7.47.36-AM-1-768x83.png 768w, https:\/\/www.bates.edu\/webtech\/files\/2019\/10\/Screen-Shot-2019-10-07-at-7.47.36-AM-1-200x22.png 200w, https:\/\/www.bates.edu\/webtech\/files\/2019\/10\/Screen-Shot-2019-10-07-at-7.47.36-AM-1.png 1438w\" sizes=\"(max-width: 900px) 100vw, 900px\" \/><\/figure>\n\n\n\n<p>That is core functionality, and no special transform code is needed. <\/p>\n\n\n\n<p>To effect the second transform, we&#8217;ll have to write a transformer configuration of <code>type: \"block\"<\/code> which can read from that shortcode block and transform it into our custom block. Here&#8217;s what that might look like:<\/p>\n\n\n\n<pre class=\"wp-block-code language-js\"><code>transforms: {\n   from: &#91;\n      {\n         type: 'block',\n         blocks: &#91;'core\/shortcode'],\n         isMatch: function( {text} ) {\n            return \/^\\&#91;slidetoggle \/.test(text);\n         },\n         transform: ({text}) =&gt; {\n\n            const initialStateVal = getAttributeValue('slidetoggle', 'initialstate', text);\n            const initiallyClosed = ( initialStateVal == 'closed' );\n\n            const content = getInnerContent('slidetoggle', text);\n\n            const innerBlocks = wp.blocks.rawHandler({\n              HTML: content,\n            });\n\n            return wp.blocks.createBlock('example\/slidetoggle', {\n              initiallyClosed: initiallyClosed,\n            }, innerBlocks);\n         },\n      },<\/code><\/pre>\n\n\n\n<p>Let&#8217;s break that down a little bit. <\/p>\n\n\n\n<p>The <code>blocks<\/code> key is an array of block types that can be transformed <em>from<\/em>. <\/p>\n\n\n\n<p>The <code>isMatch<\/code> key is a function which allows us to look at the text in the shortcode block and decide if our transform should be available (by returning a boolean). In our example, we use a regular expression to check if the value in the shortcode block begins with <strong>[slidetoggle<\/strong> . <\/p>\n\n\n\n<p>The <code>transform<\/code> key is a function which manually handles the transform and returns a block as its output. Let&#8217;s look at what&#8217;s in that function in a little more detail.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The transform function<\/h2>\n\n\n\n<p>The transform of <code>type: \"shortcode\"<\/code> parses the textual shortcode for us, but when we write our own manual handler, we have to parse all that on our own. <\/p>\n\n\n\n<p>There are a couple of helper functions I&#8217;ve defined elsewhere in my code (for easier reuse), namely  <code>getAttributeValue<\/code> and <code>getInnerContent<\/code>. <\/p>\n\n\n\n<h6 class=\"js-foldaway-sections foldaway-section-header\" id=\"\"><a href=\"#\"><span>+<\/span>see the getAttributeValue() and getInnerContent() helper functions<\/a><\/h6><div class=\"foldaway-section \">\n<p>The <code>getAttributeValue<\/code> function accepts the shortcode tag, the attribute name, and the block of text to parse:<\/p>\n\n\n\n<pre class=\"wp-block-code language-js\"><code>\/**\n * Get the value for a shortcode attribute, whether it's enclosed in double quotes, single\n * quotes, or no quotes.\n * \n * @param  {string} tag     The shortcode name\n * @param  {string} att     The attribute name\n * @param  {string} content The text which includes the shortcode\n *                          \n * @return {string}         The attribute value or an empty string.\n *\/\nexport const getAttributeValue = function(tag, att, content){\n\t\/\/ In string literals, slashes need to be double escaped\n\t\/\/ \n\t\/\/    Match  attribute=\"value\"\n\t\/\/    \\&#91;tag&#91;^\\]]*      matches opening of shortcode tag \n\t\/\/    att=\"(&#91;^\"]*)\"    captures value inside \" and \"\n\tvar re = new RegExp(`\\\\&#91;${tag}&#91;^\\\\]]* ${att}=\"(&#91;^\"]*)\"`, 'im');\n\tvar result = content.match(re);\n\tif( result != null &amp;&amp; result.length &gt; 0 )\n\t\treturn result&#91;1];\n\n\t\/\/    Match  attribute='value'\n\t\/\/    \\&#91;tag&#91;^\\]]*      matches opening of shortcode tag \n\t\/\/    att=\"(&#91;^\"]*)\"    captures value inside ' and ''\n\tre = new RegExp(`\\\\&#91;${tag}&#91;^\\\\]]* ${att}='(&#91;^']*)'`, 'im');\n\tresult = content.match(re);\n\tif( result != null &amp;&amp; result.length &gt; 0 )\n\t\treturn result&#91;1];\n\n\t\/\/    Match  attribute=value\n\t\/\/    \\&#91;tag&#91;^\\]]*      matches opening of shortcode tag \n\t\/\/    att=\"(&#91;^\"]*)\"    captures a shortcode value provided without \n        \/\/                     quotes, as in &#91;me color=green]\n\tre = new RegExp(`\\\\&#91;${tag}&#91;^\\\\]]* ${att}=(&#91;^\\\\s]*)\\\\s`, 'im');\n\tresult = content.match(re);\n\tif( result != null &amp;&amp; result.length &gt; 0 )\n\t   return result&#91;1];\n\treturn false;\n};<\/code><\/pre>\n\n\n\n<p>The <code>getInnerContent<\/code> function accepts the shortcode tag and the block of text to parse:<\/p>\n\n\n\n<pre class=\"wp-block-code language-js\"><code>\/**\n * Get the inner content of a shortcode, if any.\n * \n * @param  {string} tag         The shortcode tag\n * @param  {string} content      The text which includes the shortcode. \n * @param  {bool}   shouldAutoP  Whether or not to filter return value with autop\n * \n * @return {string}      An empty string if no inner content, or if the\n *                       shortcode is self-closing (no end tag). Otherwise\n *                       returns the inner content.\n *\/\nexport const getInnerContent = function(tag, content, shouldAutoP=true) {\n   \/\/   \\&#91;tag&#91;^\\]]*?]    matches opening shortcode tag with or \n                              without attributes, (not greedy)\n   \/\/   (&#91;\\S\\s]*?)       matches anything in between shortcodes tags, \n                              including line breaks and other shortcodes\n   \/\/   \\&#91;\\\/tag]         matches end shortcode tag\n   \/\/ remember, double escaping for string literals inside RegExp\n   const re = new RegExp(`\\\\&#91;${tag}&#91;^\\\\]]*?](&#91;\\\\S\\\\s]*?)\\\\&#91;\\\\\/${tag}]`, 'i');\n   var result = content.match(re);\n   if( result == null || result.length &lt; 1 )\n      return '';\n\n   if( shouldAutoP == true)\n      result&#91;1] = wp.autop(result&#91;1]);\n\n   return result&#91;1];\n};<\/code><\/pre>\n\n\n\n<p class=\"has-text-align-right\"><em>Note that this getShortcodeContent function requires the <\/em><code><em>wp-autop<\/em><\/code><em> script as a dependency. <\/em><\/p>\n<\/div>\n\n\n\n<div style=\"height:46px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<p>So, we use <code>getAttributeValue<\/code> to parse out our attributes, and <code>getInnerContent<\/code> to get the inner content. <\/p>\n\n\n\n<pre class=\"wp-block-code language-js\"><code>transform: ({text}) =&gt; {\n\n   const initialStateVal = getAttributeValue('slidetoggle', 'initialstate', text);\n   const initiallyClosed = ( initialStateVal == 'closed' );\n\n   const content = getInnerContent('slidetoggle', text);\n   ...<\/code><\/pre>\n\n\n\n<p>The <code>content<\/code> variable now holds a string of the inner html content.  We&#8217;ll need to run that through wp&#8217;s block parser to change it into a block or blocks. Here we use <code>rawHandler<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code language-js\"><code>transform: ({text}) =&gt; {\n   ...\n\n   const innerBlocks = wp.blocks.rawHandler({\n      HTML: content\n   });\n   ...<\/code><\/pre>\n\n\n\n<p>Lastly we use <code>createBlock<\/code>to make the final output. That function takes three parameters: the block slug (in our case <strong>example\/slidetoggle<\/strong>), the block attributes object, and the inner blocks: <\/p>\n\n\n\n<pre class=\"wp-block-code language-js\"><code>transform: ({text}) =&gt; {\n   ...\n\n   return wp.blocks.createBlock( 'example\/slidetoggle', {\n         initiallyClosed: initiallyClosed,\n      }, innerBlocks);\n },<\/code><\/pre>\n\n\n\n<p>Return the result of that function, and the function is complete. Now you should have the ability to click the transform button on a shortcode block to change it into your own block while transferring the shortcode&#8217;s inner content to the block&#8217;s innerBlocks. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>Hopefully this work-around won&#8217;t always be necessary, but until then if this helped you or if you have any questions, let me know in the comments section. Thanks for reading!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In the first post of this series, we discussed transforming a simple&hellip;<\/p>\n","protected":false},"author":428,"featured_media":157,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_hide_ai_chatbot":false,"_ai_chatbot_style":"","associated_faculty":[],"_Page_Specific_Css":"","_bates_restrict_mod":false,"_table_of_contents_display":false,"_table_of_contents_location":"","_table_of_contents_disableSticky":false,"_is_featured":false,"footnotes":"","_bates_seo_meta_description":"","_bates_seo_block_robots":false,"_bates_seo_sharing_image_id":0,"_bates_seo_sharing_image_twitter_id":0,"_bates_seo_share_title":"","_bates_seo_canonical_overwrite":"","_bates_seo_twitter_template":""},"categories":[1],"tags":[10,7,11],"class_list":["post-133","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-articles","tag-block-editor","tag-javascript","tag-wordpress"],"_links":{"self":[{"href":"https:\/\/www.bates.edu\/webtech\/wp-json\/wp\/v2\/posts\/133","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.bates.edu\/webtech\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.bates.edu\/webtech\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.bates.edu\/webtech\/wp-json\/wp\/v2\/users\/428"}],"replies":[{"embeddable":true,"href":"https:\/\/www.bates.edu\/webtech\/wp-json\/wp\/v2\/comments?post=133"}],"version-history":[{"count":4,"href":"https:\/\/www.bates.edu\/webtech\/wp-json\/wp\/v2\/posts\/133\/revisions"}],"predecessor-version":[{"id":251,"href":"https:\/\/www.bates.edu\/webtech\/wp-json\/wp\/v2\/posts\/133\/revisions\/251"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.bates.edu\/webtech\/wp-json\/wp\/v2\/media\/157"}],"wp:attachment":[{"href":"https:\/\/www.bates.edu\/webtech\/wp-json\/wp\/v2\/media?parent=133"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.bates.edu\/webtech\/wp-json\/wp\/v2\/categories?post=133"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.bates.edu\/webtech\/wp-json\/wp\/v2\/tags?post=133"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}