One of our large customers recently contacted us and said they were ready to migrate from Wijmo 3 to the current version of Wijmo. But they needed a Ribbon control, which is not included.

Initially, we thought of creating a new control and adding it to Wijmo’s nav module, along with the TreeView and TabPanel controls. But after a little research, we realized that implementing a ribbon using the TabPanel is quite easy, so there is no need for any new controls.

Wijmo 3’s wijribbon Control

This is what the ribbon control included with Wijmo 3 (wijribbon) looks like:

Wijmo 3 Ribbon Control

You can see a live demo here of the Wijmo 3 Ribbon.

Like most ribbon controls, it provides a tabbed UI where each tab contains groups of commands. The commands are usually buttons or drop-downs, but any UI control can be used.

Wijmo's JavaScript Ribbon Control

To implement something similar in Wijmo, we used a TabPanel control and some CSS.

This is what the Wijmo ribbon control looks like:

Wijmo JavaScript Ribbon Control

You can see a live demo of the Wijmo Ribbon here.

The default appearance is like modern UIs and Material Design, but of course it can be easily and extensively customized using CSS. Because this new ribbon uses Wijmo, it has no dependencies and works on mobile and desktop devices.

Control Architecture

As we mentioned, the ribbon is really a TabPanel. The tabs contain group elements. Each group element has a content area (wj-content) that hosts command controls, and a header (wj-header) that displays the group name.

Each group content (wj-content) element contains elements with columns (wj-column) and/or rows (wj-rows). This allows the designer to define the layout of the group content with flexibility and control. The row and column elements host the command controls, which may be regular HTML elements or other Wijmo controls.

This is the markup used to define the first group in the ribbon demo. Notice the hierarchical structure where each tab contains two elements (tab header and content), and the tab content elements in turn contain lists of group elements which also have two children (group header and content). The group content elements in turn contain collections of row and column elements used to lay out the individual controls:

<div id="ribbon">

  <!-- Format tab -->
  <div>
    <div>Format</div>
    <div>
      <div class="wj-group">

        <!-- Group content (Action) -->
        <div class="wj-content">
          <div class="wj-column">
            <button id="save" class="wj-btn wj-btn-large">
              <i class="material-icons">save</i>
              <br/>
              Save
            </button>
          </div>
          <div class="wj-column">
            <div class="wj-row">
              <button id="undo" class="wj-btn" aria-label="Undo">
                <i class="material-icons">undo</i>
              </button>
              … more buttons in the row …
            </div>
            <div class="wj-row">
              <button id="cut" class="wj-btn" aria-label="Cut">
                <i class="material-icons">&#x2702;</i>
              </button>
              … more buttons in the row …
            </div>
          </div>
        </div>

        <!-- Group header -->
        <div class="wj-header">
          Actions
        </div>
      </div>

We chose to specify the ribbon content using HTML markup because it provides a flexible and familiar paradigm. We could also populate the ribbon using code and data provided in a JSON file or some other data structure.

Additional Ribbon Behaviors

In addition to the standard TabPanel functionality, we added some code to toggle the ribbon content when users double-click the tab header area (like MS ribbons do) and to show the tab area when users click a tab:

// create ribbon
var ribbon = new wijmo.nav.TabPanel('#ribbon');

// toggle ribbon content visibility on double-clicks, restore on clicks
var headers = document.querySelector('.wj-tabheaders');
headers.addEventListener('dblclick', function (e) {
  if (!wijmo.hasClass(e.target, 'wj-tabheader')) {
    wijmo.toggleClass(ribbon.hostElement, 'hide-content');
  }
});
headers.addEventListener('click', function (e) {
  if (wijmo.hasClass(e.target, 'wj-tabheader')) {
    wijmo.toggleClass(ribbon.hostElement, 'hide-content', false);
  }
});

We also defined an addTooltips function that scans all the elements in a ribbon (or any other Wijmo control) and adds tooltips based on the aria-label attribute of the contained controls. This enhances accessibility and eliminates the need to keep redundant label information (like aria-label and title attributes):

// add tooltips based on the element's aria-label attribute
function addTooltips(ctl) {
  var tt = new wijmo.Tooltip(),
      els = ctl.hostElement.querySelectorAll('[aria-label]');
  for (var i = 0; i < els.length; i++) {
    tt.setTooltip(els[i], els[i].getAttribute('aria-label'));
  }
}

Command Controls

The controls within each group are created and used normally:

// populate combos
var fontFace = new wijmo.input.ComboBox('#font-face', {
  itemsSource: 'Arial,Courier New,…,WingDings'.split(','),
  textChanged: function(s, e) {
    execCommand('fontName', s.text);
  }
});
var fontSize = new wijmo.input.ComboBox('#font-size', {
  itemsSource: 'Very Small,Smaller,…,Very Large'.split(','),
  selectedIndex: 3,
  textChanged: function(s, e) {
    execCommand('fontSize', s.selectedIndex + 1);
  }
});

And so are the regular HTML buttons and input elements:

// execute commands
ribbon.hostElement.addEventListener('click', function(e) {
  switch (e.target.id) {

    // format group
    case 'save':
      localStorage.editorContent = document.getElementById('editor').innerHTML;
      alert('Document Saved to Local Storage.');
      break;
    case 'undo':
    case 'redo':
    case 'preview':
    case 'removeFormat':
    case 'cut':
    case 'copy':
    case 'paste':
    case 'selectAll':
      execCommand(e.target.id);
      break;

    // font group
    case 'bold':
    case 'italic':
    case 'underline':
    case 'strikethrough':
    case 'subscript':
    case 'superscript':
      execCommand(e.target.id);
      break;

    case 'click-me':
      alert('Wijmo Ribbon Sample');
  }
});

Conclusion

Our story has a happy ending: we sent the sample to the customer and they were able to migrate their app from Wijmo 3 to Wijmo. In the process, they reduced the dependencies of their app and made it compatible with mobile/touch devices. The TabPanel control provided enough functionality and flexibility to make this possible and we didn’t have to make any changes or add any code to Wijmo.

We hope other customers that need ribbon UIs in their JavaScript apps will be able to benefit from this sample as well. Try the Ribbon fiddle now.

If you have any questions or suggestions, please contact us at wijmoexperts@grapecity.com.

Try Wijmo's ribbon sample

Download the latest version of Wijmo

Download Now!