Create a TagList control with jide.js

Today, we’re going to look at yet another example of a custom control written with jide.js.

I had the idea for this control while taking a look at Ghost, a blog engine written in Javascript (Node.js). The way you edit tags for a blog entry in Ghost looks quite nice and I wanted to see how easy it was to implement something like that using jide.js.

Our TagList control

Our TagList control

For now, I wanted to keep it simple. You can add tags and you can remove them. I haven’t added auto completion to it, yet. We’ll explore how to do that tomorrow.

Before we start: Due to a bug in jidejs-1.0.0-beta2, the list of tags, which are displayed in the TagList control we’re about to create, must be objects. I’ll have that bug fixed soon and it’ll be fixed in the upcoming 1.0.0-beta3 release, which is scheduled for the end of the month.

By now, you already know the procedure of how to create a custom control in jide.js, so I’ll keep this short.

Our TagList needs an ObservableList of tags, where (due to issue #3) a tag is an object with a name property.

Logically, a TagList contains of an HTML element which renders each tag and a jidejs/ui/control/TextField which accepts new tags.

define([
  'jidejs-extra/Class',
  'jidejs/base/ObservableList',
  'jidejs/ui/Control',
  'jidejs/ui/Skin',

  'text!./TagList.html'
], function(
  Class, ObservableList,
  Control, Skin,
  TagListTemplate
) {
  var TagList = Class({
    $extends: Control,
    $init: function(config) {
      config || (config = {});
      this.tags = ObservableList(config.tags || []);
      delete config.tags;

      Control.call(this, config);
    }
  });

  TagList.Skin = Skin.create(Skin, {
    template: TagListTemplate,
    install: function() {
      Skin.prototype.install.call(this);
      this.component.classList.add('jide-extra-taglist');
    },

    addTag: function(event) {
      var tagList = this.component,
        textField = event.source,
        tag = textField.text;
      // in jide.js-1.0.0-beta3 this will simply be tagList.tags.contains(tag)
      // since that version fixes a bug that prevents primitives from being used
      // in a foreach binding
      if(!tagList.tags.findFirst(tagPredicate(tag))) {
        tagList.tags.add({name:tag});
        textField.text = '';
      }
    },

    removeTag: function(item) {
      this.$parent.component.tags.remove(item);
    }
  });

  function tagPredicate(tag) {
    return function(tagObj) {
      return tagObj.name === tag;
    };
  }

  return TagList;
});

Now, something here is definitely different. Why does removeTag access a member variable named $parent? Well, I’m glad you asked! In all previous examples, the event handler functions within templates used an expression similar to

click: handler.bind($item)

to ensure that the event handler was called with the skin as its context (this within the function). This time, however, we’ll not do that and instead rely on the native way that the on binding handles things, which is to execute the handler function in the context of the element – the thing the element is bound to. And in that context, $parent refer’s to the Skin.

Before we continue with the HTML Template for this control, why is the #featured tag using a different set of colors? Well, I thought it might be fun to add a syntax for “special” tags that are important to the blog engine but not so much to the reader of the entry. Other uses of such tags might be a #page tag that instructs the blog engine to render a blog entry as a page and exclude it from the blog entry list. The rule for this is simple: If a tag starts with a #, it is “special”.

Okay, now let’s take a look at the template:

<template>
  <ul pseudo="x-tags" bind="
    foreach: component.tags
  ">

    <template>
      <li bind="
        css: {
          'inverse': $item.name[0] === '#'
        }
      ">

        <span bind="text: $item.name"></span>
        <svg width="14" height="14" class="icon-close" bind="
          on: {
            click: $parent.removeTag
          }
        ">

        <line x1="4" y1="4" x2="12" y2="14" />
        <line x1="4" y1="14" x2="12" y2="4" />
        </svg>
      </li>
    </template>
  </ul>
  <input type="text" pseudo="x-edit" bind="
    is: 'jidejs/ui/control/TextField',
    on: {
      action: addTag.bind($item)
    }
  ">

</template>

One thing you’ll probably have noticed is that the element with the foreach binding has a child element. In jide.js, elements with a foreach binding can have at most one child element that must be a template element. The content of this template element is then used to render each element in the provided list. If you look carefully, you’ll notice the css binding that is responsible for rendering “special” tags differently. Otherwise, this is mostly old news. We’re using an inline SVG icon again (you might want to use a real icon instead).

The most interesting part here is the is binding on the input element. An element with an is binding is upgraded to a jide.js control once the control has been loaded. In order to allow an optimizer (such as r.js) to work properly, it is preferable to add such controls as a dependency to the control that uses them. Either way, this is a special kind of binding as the interpretation of any other binding for that element is delayed until the element can be properly upgraded. Also, our standard bindings won’t work anymore since the other parts of the bind expression are passed directly to the constructor of the inner control.

In other words, the on “binding” here is the same as passing an on object to a control. You can also use other bindings, i.e. you could set the promptText property of the TextField or anything else you’d like to do.

For this example, we never had to actually access the TextField control in our Skin (except for when listening to its action event, where it was available as the source property of the event). If we had to access it for some reason, jide.js offers a way to do so, using Skin#queryComponent – since we can’t be sure if the element has actually been upgraded by the time we need to access it.

clearEditField: function() {
  this.queryComponent('x-edit').then(function(edit) {
    edit.text = '';
  });
}

As you can see, the Skin#queryComponent method returns a Promise object that is fulfilled when the element has been upgraded successfully.

Don’t know what a Promise is supposed to be? No problem, there are great examples of what a promise is and why it is useful all around the web. If you don’t care, here’s a short version: A Promise is an object with a then method that accepts either one or two callbacks. The first callback is invoked if whatever the Promise is trying to do is successful (“when the promise is fulfilled”) and the second one is invoked if the action failed (“the promise is rejected”). A Promise only ever runs once (great for caching), but the callbacks to the then method are always executed after every other piece of code has finished (on the next “tick”), to make sure that your code stays predictable. If it didn’t, you’d never know if in which order your code is being executed.

Okay, now that you’ve read about promises, upgrading elements and other interesting bindings, let’s take a look at the LESS styling for this control:

.jide-extra-taglist {
  #padding > .medium();

  & > .x-tags {
    display: inline-block;
    margin: 0;
    padding: 0;
    list-style-type: none;

    & > li {
      display: inline-table;
      margin: 0 @padding-small;
      #padding > .small();
      #background > .default();
      .border-radius(@border-radius-base);

      & > span {
        display: table-cell;
        padding-right: @unit;
      }

      & > .icon-close {
        display: table-cell;
        & line {
          stroke: @base;
          stroke-width: 3;
        }

        &:hover {
          & line {
            stroke: @danger;
          }
        }
      }

      &.inverse {
        #background > .inverse();

        & > .icon-close {
          & line {
            stroke: @inverse;
          }

          &:hover line {
            stroke: @danger;
          }
        }
      }
    }
  }

  & > .x-edit {
    border: 0 none;
    outline: 0 none;
    background: transparent;
    padding: @padding-medium 0;
  }
}

In case you haven’t noticed already, we’re using quite a few macros and variables provided to us by the standard theme for jide.js. You definitely should use LESS to make your live easier when creating a jide.js control or app.

And, finally, let’s put it all together in a small demo app:

require([
  'jidejs-extra/control/TagList'
], function(TagList, Button) {
  var tagList = new TagList({
    // tags needs to be an array of objects due to a bug in jidejs-1.0.0-beta2 that prevents
    // primitive types from being used in a foreach binding
    tags: [{name: 'jidejs'}]
  });
  document.body.appendChild(tagList.element);
});

The control is not yet available as part of the jidejs-extra repository. I’ll push it tomorrow after we’ve added auto complete features to it!

Leave a Reply

Your email address will not be published. Required fields are marked *