【译】在WordPress中使用网络组件比你想象的更容易

316 阅读11分钟

现在我们已经看到,网络组件交互式网络组件都比你想象的要容易,让我们看看如何把它们添加到内容管理系统中,即WordPress。

我们有三种主要的方式来添加它们。第一,通过手动输入到网站将它们直接放入小部件或文本块中,基本上是我们可以放置其他HTML的地方。第二,我们可以把它们作为主题文件中的主题输出来添加。最后,我们可以把它们作为一个自定义块的输出来添加。

加载网络组件文件

现在不管我们最终用哪种方式来添加网络组件,有几件事情我们必须要确保。

  1. 我们的自定义元素的模板在我们需要时是可用的。
  2. 我们需要的任何JavaScript都被正确地排队了,以及
  3. 我们需要的任何未封装的样式都被排队了。

我们将添加我之前关于交互式网络组件的文章中的<zombie-profile> 网络组件。在CodePen上查看代码。

让我们来讨论一下第一点。一旦我们有了模板,把它添加到WordPress主题的footer.php 文件中就很容易了,但与其直接在主题中添加它,还不如挂到 wp_footer这样,该组件就可以独立于footer.php 文件,并且独立于整个主题--假设该主题使用wp_footer ,而大多数都是这样。如果当你尝试时,模板没有出现在你的主题中,请仔细检查wp_footer 是否在你的主题的footer.php 模板文件中被调用。

<?php function diy_ezwebcomp_footer() { ?>
  <!-- print/echo Zombie profile template code. -->
  <!-- It's available at https://codepen.io/undeadinstitute/pen/KKNLGRg -->
<?php } 
add_action( 'wp_footer', 'diy_ezwebcomp_footer');

下一步是排队等候我们组件的JavaScript。我们也可以通过wp_footer 来添加JavaScript,但是enqueueing是链接JavaScript和WordPress的推荐方式。所以,让我们把我们的JavaScript放在一个叫做ezwebcomp.js (这个名字完全是任意的)的文件里,把这个文件放在主题的JavaScript目录里(如果有的话),然后排队(在functions.php 文件里)。

wp_enqueue_script( 'ezwebcomp_js', get_template_directory_uri() . '/js/ezwebcomp.js', '', '1.0', true );

我们要确保最后一个参数被设置为true ,也就是说,它在正文标签结束前加载JavaScript。如果我们把它加载到头部,它将找不到我们的HTML模板,并且会变得非常暴躁(抛出一堆错误。

如果你能完全封装你的网络组件,那么你可以跳过这一步。但如果你(像我一样)无法做到这一点,你就需要对那些未封装的样式进行排队,以便在使用Web组件的任何地方都可以使用它们。(与JavaScript类似,我们可以直接将其添加到页脚,但对样式的排队是推荐的方式)。)所以,我们要排队等待我们的CSS文件

wp_enqueue_style( 'ezwebcomp_style', get_template_directory_uri() . '/ezwebcomp.css', '', '1.0', 'screen' );

这并不难,对吗?如果你不打算让管理员以外的任何用户使用它,你应该可以在你想要的地方添加这些东西。但情况并不总是这样,所以我们要继续向前走

不要过滤掉你的网络组件

WordPress有一些不同的方法,既可以帮助用户创建有效的HTML,又可以防止你的Eddie叔叔把他从Shady Al那里得到的 "搞笑 "图片直接粘贴到编辑器中(其中包括可以打败你的每一个访问者的脚本)。

所以,当直接把网络组件添加到块或部件中时,我们需要小心WordPress的内置代码过滤功能。把它全部禁用会让Eddie叔叔(以及延伸到Shady Al)肆意妄为,但我们可以修改它,让我们的超棒的网络组件通过那道(值得庆幸的)把Eddie叔叔挡在门外的大门。

首先,我们可以使用wp_kses_allowed 过滤器,将我们的网络组件添加到_不_被过滤的元素列表中。这有点像我们把这个组件列入白名单,我们通过把它添加到传递给过滤器函数的允许标签数组来做到这一点。

function add_diy_ezwebcomp_to_kses_allowed( $the_allowed_tags ) {
  $the_allowed_tags['zombie-profile'] = array();
}
add_filter( 'wp_kses_allowed_html', 'add_diy_ezwebcomp_to_kses_allowed');

我们给<zombie-profile> 组件添加一个空数组,因为WordPress除了过滤元素之外,还过滤了属性--这就给我们带来了另一个问题:slot 属性(以及part 和任何其他你可能使用的web-component-ish属性)是默认不允许的。因此,我们必须在你预期使用它们的每个元素上明确地允许它们,而且,推而广之,在你的用户可能决定添加它们的任何元素上。(等等,这些元素列表_是不_一样的,尽管你和每个用户讨论了六次......谁知道呢?)因此,下面我把slot 设置为true<span><img><ul> ,这三个元素我将被放入<zombie-profile> 组件的插槽中。(我还在span元素上把part 设为true ,这样我也可以让那个属性通过。)

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');

我们也可以在所有允许的元素中启用slot (和part )属性,就像这样。

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');

不幸的是,这还有一个可能的问题。如果你在插槽中放入的所有元素都是内联/短语元素,你可能不会遇到这个问题,但如果你有一个块级元素要放入你的网络组件中,你可能会与代码编辑器中的块分析器发生争执。你可能是一个比我更好的拳头战士,但我总是输。

代码编辑器是一个选项,允许你检查和编辑一个块的标记。

由于我不能完全解释的原因,客户端解析器假定网络组件内只能有内联元素,如果你在里面放一个<ul><div>,<h1> 或其他块级元素,它就会把网络组件的关闭标签移到最后一个内联/短语元素之后。更糟糕的是,根据WordPress开发者手册的说明,目前 "不可能取代客户端解析器"。

虽然这很令人沮丧,而且你必须训练你的网络编辑,但有一个解决方法。如果我们把网页组件直接放在Block Editor的Custom HTML块中,客户端解析器就不会让我们在人行道上哭泣,来回摇晃,并质疑我们的编码能力......并不是说这曾经发生在任何人身上......特别是写文章的人......

组件的主题

在我们的主题文件中输出我们花哨的网络组件是很简单的,只要它不在HTML块之外更新。我们以在其他情况下添加它的方式添加它,而且,假设我们有模板、脚本和样式,事情就会顺利进行。

但是,假设我们想在一个网页组件中输出WordPress帖子或自定义帖子类型的内容。你知道,写一个帖子,这个帖子就是这个组件的内容。这允许我们使用WordPress编辑器来抽出一个<zombie-profile> 元素的档案。这很好,因为WordPress编辑器已经有了我们需要的大部分用户界面,可以为其中的一个<zombie-profile> 组件输入内容。

  • 帖子的标题可以是僵尸的名字。
  • 帖子内容中的一个普通段落块可以用于僵尸的声明。
  • 特色图片可以用于僵尸的个人资料图片。

这就是它的大部分!但我们仍然需要僵尸的年龄、感染日期和兴趣等字段。我们将用WordPress的内置自定义字段功能创建这些字段。

我们将使用处理打印每个帖子的模板部分,例如:content.php ,来输出网络组件。首先,我们将打印出开头的<zombie-profile> 标签,然后是帖子的缩略图(如果它存在的话)。

<zombie-profile>
  <?php 
    // If the post featured image exists...
    if (has_post_thumbnail()) {
      $src = wp_get_attachment_image_url(get_post_thumbnail_id()); ?>
      <img src="<?php echo $src; ?>" slot="profile-image">
    <?php
    }
  ?>

接下来,我们将打印名称的标题

<?php
  // If the post title field exits...
  if (get_the_title()) { ?>
  <span slot="zombie-name"><?php echo get_the_title(); ?></span>
  <?php
  }
?>

在我的代码中,我在打印这些字段之前已经测试了它们是否存在,原因有二。

  1. 这只是良好的编程实践(在大多数情况下),隐藏空字段周围的标签和元素。
  2. 如果我们最终为名字输出一个空的<span> (例如:<span slot="zombie-name"></span> ),那么这个字段将在最终的简介中显示为空,而不是使用我们的Web组件内置的默认文本、图像等(如果你想,例如,文本字段在没有内容时为空,你可以在自定义字段中放入一个空格,或者跳过代码中的if 声明)。

接下来,我们将抓取自定义字段,并将其放入它们所属的槽中。同样,这将进入输出帖子内容的主题模板。

<?php
  // Zombie age
  $temp = get_post_meta(the_ID(), 'Age', true);
  if ($temp) { ?>
    <span slot="z-age"><?php echo $temp; ?></span>
    <?php
  }
  // Zombie infection date
  $temp = get_post_meta(the_ID(), 'Infection Date', true);
  if ($temp) { ?>
    <span slot="idate"><?php echo $temp; ?></span>
    <?php
  }
  // Zombie interests
  $temp = get_post_meta(the_ID(), 'Interests', true);
  if ($temp) { ?>
    <ul slot="z-interests"><?php echo $temp; ?></ul>
    <?php
  }
?>

使用WordPress自定义字段的一个缺点是,你不能做任何特殊的格式化,一个非技术性的网络编辑者在填写这个时,需要为列表中的每一个兴趣写出HTML(<li> )。(你也许可以通过使用一个更强大的自定义字段插件,如高级自定义字段Pods或类似的插件来绕过这个界面限制)。)

最后,我们添加僵尸的声明和关闭的<zombie-profile> 标签。

<?php
  $temp = get_the_content();
  if ($temp) { ?>
    <span slot="statement"><?php echo $temp; ?></span>
  <?php
  }
?>
</zombie-profile>

因为我们使用帖子的正文作为我们的声明,我们会在讨价还价中得到一点额外的代码,比如内容周围的段落标签。将个人资料声明放在一个自定义字段中会减轻这种情况,但根据你的目的,这也可能是预期/期望的行为。

然后,你可以根据你的需要添加任意多的帖子/僵尸资料,只需将每个帖子发布为一个帖子即可。

区块聚会:自定义区块中的网络组件

创建一个自定义块是添加网络组件的好方法。你的用户将能够填写所需的字段并获得那个网络组件的魔力,而不需要任何代码或技术知识。另外,块是完全独立于主题的,所以真的,我们可以在一个网站上使用这个块,然后在其他WordPress网站上安装它--有点像我们期望一个网络组件的工作方式

自定义块有两个主要部分。PHP和JavaScript。我们还将添加一点CSS来改善编辑体验。

首先是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' =&gt; 'ez-webcomp',
    'editor_script' =&gt; 'ez-webcomp',
  ));
}
add_action('init', 'ez_webcomp_register_block');

CSS不是必须的,它确实有助于防止僵尸的个人资料图片与WordPress编辑器中的内容重叠。

/* 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;
}

我们需要的JavaScript有点复杂。我尽量简化它,并使它尽可能为每个人所接受,所以我用ES5编写它,以消除对编译的需要。

显示代码

(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 <ul> 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 <li> 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 <zombie-profile web component
      'zombie-profile',
      // This is empty because the web component does not need any HTML attributes
      {},
      // Ensure a URL exists before it prints
      attributes.mediaURL &&
      // Print the image
      el('img', {
        src: attributes.mediaURL,
        slot: 'profile-image'
      }),
      attributes.name &&
      // Print the name
      el(RichText.Content, {
        tagName: 'span',
        slot: 'zombie-name',
        className: 'zname',
        value: attributes.name,
      }),
      attributes.age &&
      // Print the zombie's age
      el(RichText.Content, {
        tagName: 'span',
        slot: 'z-age',
        className: 'age',
        value: attributes.age,
    }),
      attributes.infectdate &&
      // Print the infection date
      el(RichText.Content, {
        tagName: 'span',
        slot: 'idate',
        className: 'infection-date',
        value: attributes.infectdate,
    }),
      // Need to verify something is in the first element since the interests's type is array
      attributes.interests[0] &&
      // Pint the interests
      el(RichText.Content, {
        tagName: 'ul',
        slot: 'z-interests',
        value: attributes.interests,
      }),
      attributes.statement[0] &&
      // Print the statement
      el(RichText.Content, {
        tagName: 'span',
        slot: 'statement',
        className: 'statement',
        value: attributes.statement,
    })
    );
    },
  });
})(
  //import the dependencies
  window.wp.blocks,
  window.wp.blockEditor,
  window.wp.element,
  window.wp.components
);

插入到网络组件中

现在,如果有一些好心的、写文章的、完全了不起的人创造了一个模板,你可以直接把你的网络组件插入并在你的网站上使用,那不是很好吗?那个人没空(他去帮助慈善机构或什么的),所以我做了。它已经在github上了。

Do It Yourself - Easy Web Components for WordPress

这个插件是一个编码模板,它注册了你的自定义网络组件,排队等待组件所需的脚本和样式,提供了你可能需要的自定义块字段的例子,甚至确保东西在编辑器中被很好地样式化。把它放在/wp-content/plugins ,就像你手动安装任何其他WordPress插件一样,确保用你的特定网络组件更新它,然后在WordPress的 "已安装插件 "屏幕上激活它。

不是那么糟糕,对吗?

尽管它看起来像很多代码,但我们实际上是在做一些非常标准的WordPress的事情来注册和渲染一个自定义的网络组件。而且,由于我们把它打包成一个插件,我们可以把它放到任何WordPress网站上,并开始发布我们心目中的僵尸资料。

我想说的是,平衡的行为是试图让这个组件在WordPress的块状编辑器中工作得像在前端一样好。如果不考虑这一点,我们就可以用更少的代码来完成这个任务。

不过,我们还是设法把我们在以前的文章中制作的完全相同的组件放到了CMS中,这使我们可以在网站上放置尽可能多的僵尸资料。我们把我们的网络组件的知识与WordPress块结合起来,为我们的可重复使用的网络组件开发了一个可重复使用的块。

你将为你的WordPress网站建立什么样的组件?我想象这里有很多的可能性,我很想看看你最终会做什么。

文章系列

  1. 网络组件比你想象的要简单
  2. 交互式网络组件比你想象的要简单
  3. 在WordPress中使用网络组件比你想象的要简单

The postUsing Web Components in WordPress is Easier Than You Thinkappeared first onCSS-Tricks.你可以通过成为MVP支持者来支持CSS-Tricks。