Using Web Components in WordPress is Easier Than You Think

Now that we’ve seen that web components and interactive web components are both easier than you think, let’s take a look at adding them to a content management system, namely WordPress.
There are three major ways we can add them. First, through manual input into the site—putting them directly into widgets or text blocks, basically anywhere we can place other HTML. Second, we can add them as the output of a theme in a theme file. And, finally, we can add them as the output of a custom block.

Loading the web component files
Now whichever way we end up adding web components, there’s a few things we have to ensure:
our custom element’s template is available when we need it,any JavaScript we need is properly enqueued, andany un-encapsulated styles we need are enqueued.We’ll be adding the web component from my previous article on interactive web components. Check out the code over at CodePen.
Let’s hit that first point. Once we have the template it’s easy enough to add that to the WordPress theme’s footer.php file, but rather than adding it directly in the theme, it’d be better to hook into wp_footer so that the component is loaded independent of the footer.php file and independent of the overall theme— assuming that the theme uses wp_footer, which most do. If the template doesn’t appear in your theme when you try it, double check that wp_footer is called in your theme’s footer.php template file.



component because WordPress filters out attributes in addition to elements—which brings us to another problem: the slot attribute (as well as part and any other web-component-ish attribute you might use) are not allowed by default. So, we have to explitcly allow them on every element on which you anticipate using them, and, by extension, any element your user might decide to add them to. (Wait, those element lists aren’t the same even though you went over it six times with each user… who knew?) Thus, below I have set slot to true on , and

    , the three elements I’m putting into slots in the component. (I also set part to true on span elements so that I could let that attribute through too.)
    function add_diy_ezwebcomp_to_kses_allowed( $the_allowed_tags ) {
    $the_allowed_tags[‘zombie-profile’] = array();
    $the_allowed_tags[‘span’][‘slot’] = true;
    $the_allowed_tags[‘span’][‘part’] = true;
    $the_allowed_tags[‘ul’][‘slot’] = true;
    $the_allowed_tags[‘img’][‘slot’] = true;
    return $the_allowed_tags;
    }
    add_filter( ‘wp_kses_allowed_html’, ‘add_diy_ezwebcomp_to_kses_allowed’);

    We could also enable the slot (and part) attribute in all allowed elements with something like this:
    function add_diy_ezwebcomp_to_kses_allowed($the_allowed_tags) {
    $the_allowed_tags[‘zombie-profile’] = array();
    foreach ($the_allowed_tags as &$tag) {
    $tag[‘slot’] = true;
    $tag[‘part’] = true;
    }
    return $the_allowed_tags;
    }
    add_filter(‘wp_kses_allowed_html’, ‘add_diy_ezwebcomp_to_kses_allowed’);

    Sadly, there is one more possible wrinkle with this. You may not run into this if all the elements you’re putting in your slots are inline/phrase elements, but if you have a block level element to put into your web component, you’ll probably get into a fistfight with the block parser in the Code Editor. You may be a better fist fighter than I am, but I always lost.
    The code editor is an option that allows you to inspect and edit the markup for a block.For reasons I can’t fully explain, the client-side parser assumes that the web component should only have inline elements within it, and if you put a

      or

      ,

      or some other block-level element in there, it’ll move the closing web component tag to just after the last inline/phrase element. Worse yet, according to a note in the WordPress Developer Handbook, it’s currently “not possible to replace the client-side parser.”
      While this is frustrating and something you’ll have to train your web editors on, there is a workaround. If we put the web component in a Custom HTML block directly in the Block Editor, the client-side parser won’t leave us weeping on the sidewalk, rocking back and forth, and questioning our ability to code… Not that that’s ever happened to anyone… particularly not people who write articles…
      Component up the theme
      Outputting our fancy web component in our theme file is straightforward as long as it isn’t updated outside the HTML block. We add it the way we would add it in any other context, and, assuming we have the template, scripts and styles in place, things will just work.
      But let’s say we want to output the contents of a WordPress post or custom post type in a web component. You know, write a post and that post is the content for the component. This allows us to use the WordPress editor to pump out an archive of elements. This is great because the WordPress editor already has most of the UI we need to enter the content for one of the components:
      The post title can be the zombie’s name.A regular paragraph block in the post content can be used for the zombie’s statement.The featured image can be used for the zombie’s profile picture.That’s most of it! But we’ll still need fields for the zombie’s age, infection date, and interests. We’ll create these with WordPress’s built in Custom Fields feature.
      We’ll use the template part that handles printing each post, e.g. content.php, to output the web component. First, we’ll print out the opening tag followed by the post thumbnail (if it exists).




      In my code, I have tested whether these fields exist before printing them for two reasons:
      It’s just good programming practice (in most cases) to hide the labels and elements around empty fields.If we end up outputting an empty for the name (e.g. ), then the field will show as empty in the final profile rather than use our web component’s built-in default text, image, etc. (If you want, for instance, the text fields to be empty if they have no content, you can either put in a space in the custom field or skip the if statement in the code).Next, we will grab the custom fields and place them into the slots they belong to. Again, this goes into the theme template that outputs the post content.





        One of the downsides of using the WordPress custom fields is that you can’t do any special formatting, A non-technical web editor who’s filling this out would need to write out the HTML for the list items (

      • ) for each and every interest in the list. (You can probably get around this interface limitation by using a more robust custom field plugin, like Advanced Custom Fields, Pods, or similar.)
        Lastly. we add the zombie’s statement and the closing tag.




        Because we’re using the body of the post for our statement, we’ll get a little extra code in the bargain, like paragraph tags around the content. Putting the profile statement in a custom field will mitigate this, but depending on your purposes, it may also be intended/desired behavior.
        You can then add as many posts/zombie profiles as you need simply by publishing each one as a post!
        Block party: web components in a custom block
        Creating a custom block is a great way to add a web component. Your users will be able to fill out the required fields and get that web component magic without needing any code or technical knowledge. Plus, blocks are completely independent of themes, so really, we could use this block on one site and then install it on other WordPress sites—sort of like how we’d expect a web component to work!
        There are the two main parts of a custom block: PHP and JavaScript. We’ll also add a little CSS to improve the editing experience.
        First, the PHP:
        function ez_webcomp_register_block() {
        // Enqueues the JavaScript needed to build the custom block
        wp_register_script(
        ‘ez-webcomp’,
        plugins_url(‘block.js’, __FILE__),
        array(‘wp-blocks’, ‘wp-element’, ‘wp-editor’),
        filemtime(plugin_dir_path(__FILE__) . ‘block.js’)
        );

        // Enqueues the component’s CSS file
        wp_register_style(
        ‘ez-webcomp’,
        plugins_url(‘ezwebcomp-style.css’, __FILE__),
        array(),
        filemtime(plugin_dir_path(__FILE__) . ‘ezwebcomp-style.css’)
        );

        // Registers the custom block within the ez-webcomp namespace
        register_block_type(‘ez-webcomp/zombie-profile’, array(
        // We already have the external styles; these are only for when we are in the WordPress editor
        ‘editor_style’ => ‘ez-webcomp’,
        ‘editor_script’ => ‘ez-webcomp’,
        ));
        }
        add_action(‘init’, ‘ez_webcomp_register_block’);

        The CSS isn’t necessary, it does help prevent the zombie’s profile image from overlapping the content in the WordPress editor.
        /* Sets the width and height of the image.
        * Your mileage will likely vary, so adjust as needed.
        * “pic” is a class we’ll add to the editor in block.js
        */
        #editor .pic img {
        width: 300px;
        height: 300px;
        }
        /* This CSS ensures that the correct space is allocated for the image,
        * while also preventing the button from resizing before an image is selected.
        */
        #editor .pic button.components-button {
        overflow: visible;
        height: auto;
        }

        The JavaScript we need is a bit more involved. I’ve endeavored to simplify it as much as possible and make it as accessible as possible to everyone, so I’ve written it in ES5 to remove the need to compile anything.

        Show code (function (blocks, editor, element, components) {
        // The function that creates elements
        var el = element.createElement;
        // Handles text input for block fields
        var RichText = editor.RichText;
        // Handles uploading images/media
        var MediaUpload = editor.MediaUpload;

        // Harkens back to register_block_type in the PHP
        blocks.registerBlockType(‘ez-webcomp/zombie-profile’, {
        title: ‘Zombie Profile’, //User friendly name shown in the block selector
        icon: ‘id-alt’, //the icon to usein the block selector
        category: ‘layout’,
        // The attributes are all the different fields we’ll use.
        // We’re defining what they are and how the block editor grabs data from them.
        attributes: {
        name: {
        // The content type
        type: ‘string’,
        // Where the info is available to grab
        source: ‘text’,
        // Selectors are how the block editor selects and grabs the content.
        // These should be unique within an instance of a block.
        // If you only have one img or one

          etc, you can use element selectors.
          selector: ‘.zname’,
          },
          mediaID: {
          type: ‘number’,
          },
          mediaURL: {
          type: ‘string’,
          source: ‘attribute’,
          selector: ‘img’,
          attribute: ‘src’,
          },
          age: {
          type: ‘string’,
          source: ‘text’,
          selector: ‘.age’,
          },
          infectdate: {
          type: ‘date’,
          source: ‘text’,
          selector: ‘.infection-date’
          },
          interests: {
          type: ‘array’,
          source: ‘children’,
          selector: ‘ul’,
          },
          statement: {
          type: ‘array’,
          source: ‘children’,
          selector: ‘.statement’,
          },
          },
          // The edit function handles how things are displayed in the block editor.
          edit: function (props) {
          var attributes = props.attributes;
          var onSelectImage = function (media) {
          return props.setAttributes({
          mediaURL: media.url,
          mediaID: media.id,
          });
          };
          // The return statement is what will be shown in the editor.
          // el() creates an element and sets the different attributes of it.
          return el(
          // Using a div here instead of the zombie-profile web component for simplicity.
          ‘div’, {
          className: props.className
          },
          // The zombie’s name
          el(RichText, {
          tagName: ‘h2’,
          inline: true,
          className: ‘zname’,
          placeholder: ‘Zombie Name…’,
          value: attributes.name,
          onChange: function (value) {
          props.setAttributes({
          name: value
          });
          },
          }),
          el(
          // Zombie profile picture
          ‘div’, {
          className: ‘pic’
          },
          el(MediaUpload, {
          onSelect: onSelectImage,
          allowedTypes: ‘image’,
          value: attributes.mediaID,
          render: function (obj) {
          return el(
          components.Button, {
          className: attributes.mediaID ?
          ‘image-button’ : ‘button button-large’,
          onClick: obj.open,
          },
          !attributes.mediaID ?
          ‘Upload Image’ :
          el(‘img’, {
          src: attributes.mediaURL
          })
          );
          },
          })
          ),
          // We’ll include a heading for the zombie’s age in the block editor
          el(‘h3’, {}, ‘Age’),
          // The age field
          el(RichText, {
          tagName: ‘div’,
          className: ‘age’,
          placeholder: ‘Zombie’s Age…’,
          value: attributes.age,
          onChange: function (value) {
          props.setAttributes({
          age: value
          });
          },
          }),
          // Infection date heading
          el(‘h3’, {}, ‘Infection Date’),
          // Infection date field
          el(RichText, {
          tagName: ‘div’,
          className: ‘infection-date’,
          placeholder: ‘Zombie’s Infection Date…’,
          value: attributes.infectdate,
          onChange: function (value) {
          props.setAttributes({
          infectdate: value
          });
          },
          }),
          // Interests heading
          el(‘h3’, {}, ‘Interests’),
          // Interests field
          el(RichText, {
          tagName: ‘ul’,
          // Creates a new

        • every time `Enter` is pressed
          multiline: ‘li’,
          placeholder: ‘Write a list of interests…’,
          value: attributes.interests,
          onChange: function (value) {
          props.setAttributes({
          interests: value
          });
          },
          className: ‘interests’,
          }),
          // Zombie statement heading
          el(‘h3’, {}, ‘Statement’),
          // Zombie statement field
          el(RichText, {
          tagName: ‘div’,
          className: “statement”,
          placeholder: ‘Write statement…’,
          value: attributes.statement,
          onChange: function (value) {
          props.setAttributes({
          statement: value
          });
          },
          })
          );
          },

          // Stores content in the database and what is shown on the front end.
          // This is where we have to make sure the web component is used.
          save: function (props) {
          var attributes = props.attributes;
          return el(
          // The Typed at

      • Share your love

    One comment

    Leave a Reply