Unlayer Examples
Documentation

Accordion Custom Tool

In this example, we will create a custom tool for Accordion. This tool will help us create accordion in web pages.

This example is built using vanilla JavaScript and lodash. Lodash is available in Unlayer's environment by default. Check our documentation for more details.


Register Tool

We will create a accordionTool.js file for the custom tool, which will be passed to unlayer.init.

accordionTool.js

Our tool needs some templates for different HTML views so we will first define those using lodash template function.

var emptyTemplate = _.template(`
<div style="padding: 15px; border: 2px dashed #CCC; background-color: #EEE; color: #999; text-align: center;">
  Empty  Accordion
</div>
`);

var accordionTemplate = _.template(`
<div class="menu">
  <% _.forEach(items, function(item,index) { %>
    <div>
        <div class="toggle-btn accordion title-font" onclick="handleClick(this)" style="display:flex;justify-content:space-between;padding:10px 20px; align-items:center">
            <span><%= item.title %></span>
            <span style="font-size:20px;">+</span>
        </div>
        <div class="panel description-font" style="padding:10px 20px;display:none">
            <p><%= item.description %></p>
        </div>
    </div>
  <% }); %>
</div>
`);

Now let's register our tool. Notice that this tool is using a custom property editor called accordion_editor which we will build below. Custom CSS and Custom JS are also added at the end of the register tool function in css and js sections.

unlayer.registerTool({
  name: 'accordion_tool',
  label: 'Accordion',
  icon: 'fa-bars',
  supportedDisplayModes: ['web'],
  options: {
    default: {
      title: null,
    },
    menu: {
      title: 'Accordion Items',
      position: 1,
      options: {
        accordionRow: {
          label: 'Accordion Row',
          defaultValue: {
            items: [],
          },
          widget: 'accordion_editor', // Custom Property Editor
        },
      },
    },
    colors: {
      title: 'Colors',
      position: 1,
      options: {
        titleTextColor: {
          label: 'Title Text Color',
          defaultValue: '#212121',
          widget: 'color_picker',
        },
        descriptionTextColor: {
          label: 'Description Text Color',
          defaultValue: '#212121',
          widget: 'color_picker',
        },
        titleBackgroundColor: {
          label: 'Title Background Color',
          defaultValue: '#FAFAFA',
          widget: 'color_picker',
        },
        descriptionBackgroundColor: {
          label: 'Description Background Color',
          defaultValue: '#fff',
          widget: 'color_picker',
        },
        titleHoverBackground: {
          label: 'Title Hover Background Color',
          defaultValue: '#ECEDEF',
          widget: 'color_picker',
        },
      },
    },
    fontFamily: {
      title: 'Fonts',
      position: 1,
      options: {
        fontFamily: {
          label: 'Title Font',
          defaultValue: {
            label: 'Arial',
            value: 'arial,helvetica,sans-serif',
          },
          widget: 'font_family',
        },
      },
    },
  },
  values: {},
  renderer: {
    Viewer: unlayer.createViewer({
      render(values) {
        // If the user has added no items yet, show empty placeholder template
        if (values.accordionRow.items.length == 0) return emptyTemplate();

        return accordionTemplate({ items: values.accordionRow.items });
      },
    }),
    exporters: {
      web: function (values) {
        return accordionTemplate({ items: values.accordionRow.items });
      },
    },
    head: {
      css: function (values) {
        return `
          .accordion {
            background-color: ${values.titleBackgroundColor};
            color: ${values.titleTextColor};
            cursor: pointer;
            padding: 18px;
            width: 100%;
            border: none;
            text-align: left;
            outline: none;
            font-size: 15px;
            transition: 0.4s;
            margin-top: 3px;
          }

          .accordion:hover {
            background-color: ${values.titleHoverBackground}; 
          }

          .panel {
            padding: 0 18px;
            display: none;
            background-color: ${values.descriptionBackgroundColor};
            color: ${values.descriptionTextColor};
            overflow: hidden;
            border-top: 1px solid #F2F2F2;
          }

          .title-font{
            font-family: ${values.fontFamily.value}!important;
          }

          .description-font{
            font-family: ${values.fontFamily.value}!important;
          }

          `;
      },
      js: function (values) {
        return `async function handleClick(item){ 

          let panel = item.nextElementSibling
          if (panel.style.display === "block") {
            panel.style.display = "none";
          } else {
            panel.style.display = "block";
          }

        }`;
      },
    },
  },
});

Then, we'll pass the accordionTool.js URL to the editor in init option customJS.

  • The URL must be absolute
  • You can load multiple URLs by passing more in the array
  • If you don't have the option to pass a URL, you can directly pass your JavaScript code as a string
unlayer.init({
  id: 'editor-container',
  displayMode: 'web',
  customJS: [
    'https://examples.unlayer.com/examples/accordion-custom-tool/accordionTool.js',
  ],
});

Accordion Editor

Now we need to register our accordion editor which the user will use to add, update or delete accordion items.

Our editor needs a HTML template to render the accordion items and input fields. We will define add those using a lodash template.

var editorTemplate = _.template(`
<% _.forEach(items, function(item, index) { %>
  <div style="padding: 10px; margin: 10px 0px; background-color: #FFF; border: 1px solid #CCC;">
    <div class="blockbuilder-widget-label">
      <label>Title</label>
    </div>
    <input class="accordion-title form-control" data-index="<%= index %>" type="text" value="<%= item.title %>" />

    <div class="blockbuilder-widget-label pt-2">
      <label>Description</label>
    </div>
    <input class="accordion-description form-control" data-index="<%= index %>" type="text" value="<%= item.description %>" />

    <a class="delete-btn" data-index="<%= index %>" style="display: inline-block; cursor: pointer; color: red; margin-top: 10px; font-size: 12px;">
      Delete Item
    </a>
  </div>
<% }); %>

<div>
  <a class="add-btn" style="display: block; text-align: center; padding: 10px; background-color: #EEE; border: 1px solid #CCC; color: #999; cursor: pointer;">
    Add New Row
  </a>
</div>
`);

Now we'll register our accordion_editor property editor that will render the template above and attach events to the buttons and input fields.

In the render function, we will render the HTML template. And in the mount function, we will attach events to input fields and delete button. Learn More

unlayer.registerPropertyEditor({
  name: 'accordion_editor',
  layout: 'bottom',
  Widget: unlayer.createWidget({
    render(value, updateValue, data) {
      return editorTemplate({ items: value.items });
    },
    mount(node, value, updateValue, data) {
      var addButton = node.querySelector('.add-btn');
      addButton.onclick = function () {
        var newItems = value.items.slice(0);
        newItems.push({
          title: 'Title',
          description:
            'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.',
        });
        updateValue({ items: newItems });
      };

      // Text Change
      // Look for inputs with class accordion-title and attach onchange event
      node.querySelectorAll('.accordion-title').forEach(function (item) {
        item.onchange = function (e) {
          // Get index of item being updated
          var itemIndex = e.target.dataset.index;

          // Get the item and update its value
          var updatedItems = value.items.map(function (item, i) {
            if (i == itemIndex) {
              return {
                title: e.target.value,
                description: item.description,
              };
            }

            return {
              title: item.title,
              description: item.description,
            };
          });

          updateValue({ items: updatedItems });
        };
      });

      // URL Change
      // Look for inputs with class accordion-description and attach onchange event
      node.querySelectorAll('.accordion-description').forEach(function (item) {
        item.onchange = function (e) {
          // Get index of item being updated
          var itemIndex = e.target.dataset.index;

          // Get the item and update its value
          var updatedItems = value.items.map(function (item, i) {
            if (i == itemIndex) {
              return {
                title: item.title,
                description: e.target.value,
              };
            }

            return {
              title: item.title,
              description: item.description,
            };
          });

          updateValue({ items: updatedItems });
        };
      });

      // Delete
      node.querySelectorAll('.delete-btn').forEach(function (item) {
        item.onclick = function (e) {
          // Get index of item being deleted
          var itemIndex = e.target.dataset.index;
          var updatedItems = value.items
            .map(function (item, i) {
              if (i == itemIndex) {
                return false;
              }

              return {
                title: item.title,
                description: item.description,
              };
            })
            .filter(function (item) {
              return item;
            });

          updateValue({ items: updatedItems });
        };
      });
    },
  }),
});

More Styling Options

Once your accordion tool is working, you will need to add more styling options to update fonts, colors, padding etc. You can use our built-in property editors to easily add more options for the user to customize the accordion.


Live Preview

Here's a live demo preview of our Accordion custom tool. Drag and drop the custom tool My Accordion and add accordion items.