BT

Your opinion matters! Please fill in the InfoQ Survey!

More Than React, Part 2: How Binding.scala Makes Reusability Easy

| Posted by Yang Bo Follow 0 Followers on Apr 08, 2017. Estimated reading time: 9 minutes |

Key Takeaways

  • Components implemented with Native HTML are barely reusable, and implementation of the component can be complicated.
  • ReactJS has better reusability compared to Native HTML, though the grammar for reusing a component is unnecessarily heavy.
  • The minimal reuse unit in Binding.Scala is an ordinary method.
  • Binding.Scala is advanced in implementing a component with higher reusability.
  • The lighter-weight reuse units in Binding.Scala leads to smoother programming experience.

In the last article of More than React series, More Than React: Why You Shouldn't Use ReactJS for Complex Interactive Front-End Projects, Part I, we listed all of the pain points in front-end development.

In this article, we'll have a detailed discussion about one of the pain points, reusability. For comparison, we'll use native DHTML API, ReactJS and Binding.scala to implement a reusable tag editor component, from which we'll compare the difficulty and convenience of implementation.

Requirements of the tag editor

Let's assume that we are planning to implement a blog system. Like most blog systems, we'd like to allow authors to add tags to their articles.

In this case, we'll provide a tag editor to authors.

As shown in the image, the tag editor requires two UI rows.

The first row shows all the added tags and, next to each tag, there's a 'x' button for deletion. The second row is a text editor with an 'Add' button which creates new tags with the content in the text editor. Each time 'Add' is clicked, the tag editor should avoid duplicates by checking if the tag already exists. After a tag is successfully added, the text editor should be cleared in preparation for the next input.

In addition to UI, the tag editor should also provide an API. The web page that contains the tag editor should be able to fill in initial tags using the API. If the user adds or deletes a tag, there should be a notification mechanism for the rest of the page.

Native DHTML version

First, let's implement it using native DHTML API, without any front-end framework:

<!DOCTYPE html>
<html>
<head>
  <script>
    var tags = [];

    function hasTag(tag) {
      for (var i = 0; i < tags.length; i++) {
        if (tags[i].tag == tag) {
          return true;
        }
      }
      return false;
    }

    function removeTag(tag) {
      for (var i = 0; i < tags.length; i++) {
        if (tags[i].tag == tag) {
          document.getElementById("tags-parent").removeChild(tags[i].element);
          tags.splice(i, 1);
          return;
        }
      }
    }

    function addTag(tag) {
      var element = document.createElement("q");
      element.textContent = tag;
      var removeButton = document.createElement("button");
      removeButton.textContent = "x";
      removeButton.onclick = function (event) {
        removeTag(tag);
      }
      element.appendChild(removeButton);
      document.getElementById("tags-parent").appendChild(element);
      tags.push({
        tag: tag,
        element: element
      });
    }

    function addHandler() {
      var tagInput = document.getElementById("tag-input");
      var tag = tagInput.value;
      if (tag && !hasTag(tag)) {
        addTag(tag);
        tagInput.value = "";
      }
    }
  </script>
</head>
<body>
  <div id="tags-parent"></div>
  <div>
    <input id="tag-input" type="text"/>
    <button onclick="addHandler()">Add</button>
  </div>
  <script>
    addTag("initial-tag-1");
    addTag("initial-tag-2");
  </script>
</body>
</html>

To implement the tag editor's functionality, it takes 45 lines of JavaScript code for UI logic, in addition to several HTML <div> elements and two lines of JavaScript code for filling in initial data.

The HTML file contains several hard coded <div> elements which are used as containers for other dynamically created components.

The code will dynamically update the website content in these <div> elements, hence, when we need two or more tag editors in one page, the id will conflict. In this case, the code above is not reusable.

Even if we replace DHTML API with jQuery, it's still hard to reuse the code. To reuse the UI, jQuery developers usually would have to add extra code, scan the whole page during onload, find those elements that have a specific class attribute, then modify it. For complex webpages, these functions that run during onload would easily have conflicts. For example, if one function modifies an HTML element, this would very likely effect another piece of code which leads to corrupted internal status.

Tag editor component implemented in ReactJS

ReactJS provides reusable component React.Component. If we implement it in ReactJS, the code would look like this:

class TagPicker extends React.Component {

  static defaultProps = {
    changeHandler: tags => {}
  }

  static propTypes = {
    tags: React.PropTypes.arrayOf(React.PropTypes.string).isRequired,
    changeHandler: React.PropTypes.func
  }

  state = {
    tags: this.props.tags
  }

  addHandler = event => {
    const tag = this.refs.input.value;
    if (tag && this.state.tags.indexOf(tag) == -1) {
      this.refs.input.value = "";
      const newTags = this.state.tags.concat(tag);
      this.setState({
        tags: newTags
      });
      this.props.changeHandler(newTags);
    }
  }

  render() {
    return (
      <section>
        <div>{
          this.state.tags.map(tag =>
            <q key={ tag }>
              { tag }
              <button onClick={ event => {
                const newTags = this.state.tags.filter(t => t != tag);
                this.setState({ tags: newTags });
                this.props.changeHandler(newTags);
              }}>x</button>
            </q>
          )
        }</div>
        <div>
          <input type="text" ref="input"/>
          <button onClick={ this.addHandler }>Add</button>
        </div>
      </section>
    );
  }

}

The 51 lines of ECMAScript 2015 code and JSX above implemented a tag editor component TagPicker. Though it takes more lines of code than DHTML, it is much more reusable.

The number of lines of code would be even bigger if you didn't use ECMAScript 2015. Furthermore, you'd have to deal with some pitfalls in JavaScript, such as not being able to use this in callback function.

ReactJS developers can use the ReactDOM.render function anytime to render TagPicker to any empty element. Also, the ReactJS framework triggers the render function whenever state and props changes, which saves the manual work of modifying the existing DOM.

If we put the duplication of the key attribute aside, ReactJS is doing okay with the internal interaction of a single component. But, a complex webpage architecture tends to need multiple layers of nested components. It would be much worse for ReactJS in the case of interaction between parent and child component.

For example, if we need to display all the tags outside of TagPicker, and these tags should be automatically synchronized when user delete or add a tag. To implement this, we'll need to pass changeHandler callback to TagPicker, as shown below:

class Page extends React.Component {

  state = {
    tags: [ "initial-tag-1", "initial-tag-2" ]
  };

  changeHandler = tags => {
    this.setState({ tags });
  };

  render() {
    return (
      <div>
        <TagPicker tags={ this.state.tags } changeHandler={ this.changeHandler }/>
        <h3>All tags:</h3>
        <ol>{ this.state.tags.map(tag => <li>{ tag }</li> ) }</ol>
      </div>
    );
  }

}

In the newly created Page component, there should be a changeHandler callback function, which internally calls Page's setState in order to trigger the Page's re-render.

In the example above, we notice that ReactJS can solve simple problems simply. But on webpages with complex layers and frequent interactions, the implementation becomes complicated. A front-end project using ReactJS would have xxxHandler all over the place just for passing messages. In my experience on an overseas client project, each component would need around five callbacks on average. For a deeper nesting with multiple layers, we'd need to pass the callback through each layer, from the root component to the leaf, when creating a webpage. Vice versa, we need to pass an event message out layer by layer when an event is triggered. At least half of the code in the whole front-end project is trivial boilerplate code like this.

The basic usage of Binding.scala

Before we step into the implementation of tag editor using Binding.scala, I'd like to introduce some Binding.scala basics.

The smallest reusable unit in Binding.scala is data binding expression, i.e. @dom method. Each @dom method is a piece of HTML template, for example:

// Two HTML line breaks
@dom def twoBr = <br/><br/>

// An HTML heading
@dom def myHeading(content: String) = <h1>{content}</h1>

Each template can contain other child template using bind grammar, such as:

@dom def render = {
  <div>
    { myHeading("Feature of Binding.scala").bind }
    <p>
      Shorter code
      { twoBr.bind }
      Less concepts
      { twoBr.bind }
      More functionality
    </p>
  </div>
}

Please check out Appendix: Quick start tutorial for Binding.scala for detailed information about learning Binding.scala.

Also, the fourth article More than React IV: How to statically compile HTML? in the More than React series will list all the features for HTML template that Binding.scala supports.

Implement the tag editor template using Binding.scala

Now, we'll show you how to create a tag editor using Binding.scala.

This tag editor is more complicated than the Binding.scala HTML template we learnt earlier; it contains interactions instead of just being a static template.

@dom def tagPicker(tags: Vars[String]) = {
  val input: Input = <input type="text"/>
  val addHandler = { event: Event =>
    if (input.value != "" && !tags.get.contains(input.value)) {
      tags.get += input.value
      input.value = ""
    }
  }
  <section>
    <div>{
      for (tag <- tags) yield <q>
        { tag }
        <button onclick={ event: Event => tags.get -= tag }>x</button>
      </q>
    }</div>
    <div>{ input } <button onclick={ addHandler }>Add</button></div>
  </section>
}

This tag editor HTML template is done in 18 lines.

Since the tag editor needs to display all of the tags, we use tabs:Vars[String] to save the tag data, then use a for/yield loop to render each tag in tags into UI component.

Vars is a list container that supports data binding; UI will change automatically when the data in list container changes. Hence, when the onclick event on x button deletes the data in tags, the tag displayed would disappear correspondingly; vice versa for additions.

Binding.scala not only simplifies the implementation of tag editor, but also simplifies the usage.

  val tags = Vars("initial-tag-1", "initial-tag-2")
  <div>
    { tagPicker(tags).bind }
    <h3>All tags:</h3>
    <ol>{ for (tag <- tags) yield <li>{ tag }</li> }</ol>
  </div>
}

All you need to do is to write another HTML template in 9 lines and call the existing tagPicker in it.

For a complete version of the Demo code, please refer to ScalaFiddle

Binding.scala does not require the same kind of callback like changeHandler in ReactJS. When the tags Var is changing, the page will reflect the change automatically.

After comparing the code between ReactJS and Binding.scala, we would find the following difference:

  • Developers using Binding.scala can use @dom functions such as tagPicker to provide an HTML template instead of using component.
  • Developers using Binding.scala can pass parameters like tags between functions, without needing the concept of props.
  • Developers using Binding.scala can use local variables inside of a function, rather than using state.

In general, Binding.scala is much more concise than ReactJS.

If you happen to have experience with server side webpage templating language such as ASP, PHP, JSP, you'll see that Binding.scala and HTML template are very similar to them.

Using Binding.scala doesn't require any knowledge about functional programming. All that's required is to copy the HTML prototype generated by design tools into code, then replace the dynamic part with curly brackets, replace the repeating part with for/yield, That's all.

Conclusion

This article compares the difficulty of implementing and using a reusable tag editor with different tech stacks.

 

Native HTML

ReactJS

Binding.scala

Lines of code required to implement the tag editor

45 LOC

51 LOC

18 LOC

Difficulty encountered during implementation

The implementation of dynamically updating HTML page is too complicated.

The grammar used for implementing component is too heavy

None

Lines of code required to reuse tag editor and display the tag list

Hard to reuse

21 LOC

8 LOC

Blockers for reuse

Hard to modularize the static HTML element

It's too complicated to transfer callback function through multiple layers.

None

Binding.scala does not try to reinvent concepts like 'components'. Instead, we encourage you create the ordinary 'method' as minimal reuse unit, because the lighter-weight reuse unit will lead to smoother programming experience, and increase the reusability.

In the next article of the More than React series, we will compare the virtual DOM mechanism of ReactJS with the precise data binding mechanism of Binding.scala, unveil the different algorithms beneath the veneer of similar usages.

Reference

About the Author

Yang Bo is a ten-year working experience developer and works at ThoughtWorks China as a Lead Consultant now. He's an active open-source contributor in Scala, ActionScript, and Haxe community. 

Rate this Article

Adoption Stage
Style

Hello stranger!

You need to Register an InfoQ account or or login to post comments. But there's so much more behind being registered.

Get the most out of the InfoQ experience.

Tell us what you think

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread
Community comments

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

Discuss

Login to InfoQ to interact with what matters most to you.


Recover your password...

Follow

Follow your favorite topics and editors

Quick overview of most important highlights in the industry and on the site.

Like

More signal, less noise

Build your own feed by choosing topics you want to read about and editors you want to hear from.

Notifications

Stay up-to-date

Set up your notifications and don't miss out on content that matters to you

BT