结合 twig 模板,探索 Drupal 前端组件化开发

前后端开发配合

Drupal 项目开发前后端配合一般常见两种:

  • 后端输出模板,前端根据 UI 设计稿修改模板结构,写样式和 JavaScript;
  • 前端根据 UI 设计好组件,后端到静态组件中套前端组件结构;

缺点

  • 第一种缺点是前端必须等后端输出数据,不能够很好的复用,难以维护;
  • 第二种前后端都可以先行,不必互相等待,缺点是当前端组件 HTML 结构发生改变时,需要同时维护静态组件和后端输出的模板。

问题

  • 有没有办法只维护一个前端组件,后端模板和前端静态组件同时生效?
  • 在不同的页面中,组件是否不通过复制的方式使用同一个组件?

实践
这是一个经典的 showcase 组件,每个 showcase 组件还有几组子组件,子组件包含有图片,标题,内容和更多链接,正常的静态化开发流程:

  • 设计好子组件,命名编号;
  • 设计好父组件,命名编号,复制子组件,修改静态内容;

效果如图:

一、如何让静态模板在 Drupal 主题中可预览?
暂且把前端主题命名为:easy_ui,可以将以下文件命名为 showcase-v1.html.twig 放入主题的的 UI 目录下面,通过后端代码(省略)可以实现twig文件的静态预览。

<div class="showcase-1">
  <div class="container">
    <div class="row">
      <div class="col-sm-4 col-md-4">
        {#子组件#}
        <div class="box-1">
          <div class="big-icon bg"><i class="fa fa-picture-o"></i></div>
          <h4 class="title">响应式设计</h4>
          <div class="text-small">...<div class="clearfix"></div><br data-tomark-pass>            <a class="btn btn-default" href="/contact" target="_blank">更多</a>
          </div>
        </div>
      </div>
      ...    </div>
  </div></div>

二、如何解决只维护一个子组件?
使用 twig include 可以引入子组件,在 UI 目录中可以新建 box 文件夹存放子组件,当你的组件越来越多的时候可以很好的整理分类,并抽出子组件命名为:box-v1.html.twig

{# box/box-v1.html.twig #}<div class="box-1">
  <div class="big-icon bg"><i class="fa fa-picture-o"></i></div>
  <h4 class="title">响应式设计</h4>
  <div class="text-small">...<div class="clearfix"></div><br data-tomark-pass>    <a class="btn btn-default" href="/contact" target="_blank">更多</a>
  </div></div></div>

父组件 include 引入子组件:

{# showcae-v1.html.twig #}<div class="container">
  <div class="row">
    <div class="col-sm-4 col-md-4">
      {% include '@easy_ui/ui/box/box-v1.html.twig' %}
    </div>
    <div class="col-sm-4 col-md-4">
      {% include '@easy_ui/ui/box/box-v1.html.twig' %}
    </div>
    <div class="col-sm-4 col-md-4">
      {% include '@easy_ui/ui/box/box-v1.html.twig' %}
    </div>
  </div></div>

三、如何解决同一个子组件使用不同的数据?
当父组件使用 include 引用 box-v1.html.twig 的时候,三个子组件会显示同样的静态内容,让我们一步一步解决,先把静态内容改成变量:

twig 可在模板中声明局部变量

{%  set item = {
    'icon': 'fa-picture-o',
    'title': '响应式设计',
    'body': 'At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. ',
    'moreText': '更多',
    'moreLink': '/content'
  }
%}<div class="box-1">
  <div class="big-icon bg"><i class="fa {{item.icon}}"></i></div>
  <h4 class="title">{{item.title}}</h4>
  <div class="text-small">{{item.body}}<div class="clearfix"></div><br data-tomark-pass>    <a class="btn btn-default" href="{{item.moreLink}}" target="_blank">{{item.moreText}}</a>
  </div></div>

下一步,数据从父组件传递到子组件,twig 的 with 可以做这个事情:),继续改造:

{# 父组件 showcase-v1.html.twig #}<div class="container">
  <div class="row">
    <div class="col-sm-4 col-md-4">
      {% include '@easy_ui/ui/box/box-v1.html.twig' with {
            'icon': 'fa-picture-o',
            'title': '响应式设计',
            'body': 'At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. ',
            'moreText': '更多',
            'moreLink': '/content'
          } %}
    </div>
    <div class="col-sm-4 col-md-4">
      {% include '@easy_ui/ui/box/box-v1.html.twig' with {
            'icon': 'fa-wrench',
            'title': '组件化开发',
            'body': 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat ',
            'moreText': '更多',
            'moreLink': '/content'
          } %}
    </div>
    <div class="col-sm-4 col-md-4">
      {% include '@easy_ui/ui/box/box-v1.html.twig' with {
            'icon': 'fa-bug',
            'title': '市场应用',
            'body': 'Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. ',
            'moreText': '更多',
            'moreLink': '/content'
          } %}
    </div>
  </div></div>{# 子组件 box-v1.html.twig #}<div class="box-1">
  <div class="big-icon bg"><i class="fa {{icon}}"></i></div>
  <h4 class="title">{{title}}</h4>
  <div class="text-small">
    {{body}}
    <div class="clearfix"></div><br data-tomark-pass>    <a class="btn btn-default" href="{{moreLink}}" target="_blank">{{moreText}}</a>
  </div></div>

是不是很酷!

四、使用 for 循环继续提取改造,维护数据更加清晰

{# 父组件 showcase-v1.html.twig #}{%  set items = [
    {
            'icon': 'fa-picture-o',
            'title': '响应式设计',
            'body': 'At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. ',
            'moreText': '更多',
            'moreLink': '/content'
          },
          {
            'icon': 'fa-wrench',
            'title': '组件化开发',
            'body': 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat ',
            'moreText': '更多',
            'moreLink': '/content'
          },{
            'icon': 'fa-bug',
            'title': '市场应用',
            'body': 'Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. ',
            'moreText': '更多',
            'moreLink': '/content'
          }
  ]
%}<div class="container">
  <div class="row">
    {% for item in items %}
    <div class="col-sm-4 col-md-4">
      {% include '@easy_ui/ui/box/box-v1.html.twig' with item %}
    </div>
    {% endfor %}
  </div></div>

五、数据从父组件传递到子组件

重点来了,后端数据如何使用同一个前端组件?又不影响前端组件的静态化预览?可以使用twig的默认值来优化:
我们模拟一下后端模板和数据,通过include引用前端showcase.html.twig组件,使用with传递数据:

{# modules template #}
 {%  set data = [
    {
            'icon': 'fa-picture-o',
            'title': 'new',
            'body': 'Lorem ipsum dolor sit amet consectetur, adipisicing elit. Neque, ipsa. Totam autem repellendus voluptatum atque accusantium!',
            'moreText': '更多',
            'moreLink': '/content'
          },
          {
            'icon': 'fa-wrench',
            'title': 'new',
            'body': 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Nisi amet numquam molestiae temporibus, aspernatur illum quas.',
            'moreText': '更多',
            'moreLink': '/content'
          },{
            'icon': 'fa-bug',
            'title': 'new',
            'body': 'Lorem ipsum, dolor sit amet consectetur adipisicing elit. Error, non laborum! Consequatur velit facere odit dolorem?',
            'moreText': '更多',
            'moreLink': '/content'
          }
  ]
%}

 {% include '@easy_ui/ui/showcase-v1.html.twig' with data %}

前端组件定义好静态预览数据,使用 default 函数设置默认值,当父组件的 data 没有传数据时使用静态数据,意味着:

可以静态化预览前端组件;

后端可以传递数据给前端组件;

{# 父组件 showcase-v1.html.twig #}{%  set items = [
    {
            'icon': 'fa-picture-o',
            'title': '响应式设计',
            'body': 'At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. ',
            'moreText': '更多',
            'moreLink': '/content'
          },
          {
            'icon': 'fa-wrench',
            'title': '组件化开发',
            'body': 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat ',
            'moreText': '更多',
            'moreLink': '/content'
          },{
            'icon': 'fa-bug',
            'title': '市场应用',
            'body': 'Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. ',
            'moreText': '更多',
            'moreLink': '/content'
          }
  ]
%}<div class="container">
  <div class="row">
    {% for item in data|default(items) %}
    <div class="col-sm-4 col-md-4">
      {% include '@easy_ui/ui/box/box-v1.html.twig' with item %}
    </div>
    {% endfor %}
  </div></div>

这样,带来怎样的好处:
前后端都可以使用include引入相同的showcase-v1.html.twig组件,谁给什么数据就渲染什么数据,没有数据使用静态数据;

父组件的多个子组件,只需要维护一个子组件

已移除图像。

前端同学是不是和熟悉,这不就是类似Angular组件化开发吗:)

参考资料
HTTPS://TWIG.SYMFONY.COM/DOC/3.X/TAGS/INCLUDE.HTML
HTTPS://TWIG.SYMFONY.COM/DOC/3.X/TAGS/FOR.HTML
HTTPS://TWIG.SYMFONY.COM/DOC/3.X/FILTERS/DEFAULT.HTML
HTTPS://TWIG.SYMFONY.COM/DOC/3.X/TEMPLATES.HTM