Skip to content
This repository has been archived by the owner on Feb 26, 2022. It is now read-only.

JEP Navbar Buttons

ZER0 edited this page Apr 24, 2013 · 15 revisions

This Button component is the foundation of mostly all the new UI components we're going to introduce in jetpack.

Requirements

  • two button modes: 'Toggle' and 'Action'
  • toggle mode has two visual states, on & off
  • when the button is in 'toggle' mode, the api allows developers to react to this change.
  • when the api is in 'action' mode, the api allows developers to assign a callback to handle the event created when a user clicks on the button.
  • an arbitrary number of buttons can be visually grouped together
  • following UI specs, the size of a button can be only 16x16 (default), 32x16 or 64x16.
  • button has an icon and a label. However, in default Firefox settings label for button are hidden. If an icon is not provided, the generic one is used (the puzzle's piece)
  • button can be disabled
  • button can have a badge to display text (just few characters, tipically numbers), see Google Chrome API for example.

Note

I will personally leave out the "grouping" feature for the first iteration, because to me it's a different kind of object – it's not just a "visual thing", but also behavioral, see the proposal below – and should be implemented separately. Plus, we do not have any UX mockup yet for it. Same could be applied for the "badge" feature, we still missing the UX mockup, but maybe in this case it's more strictly related to the button itself, and could be implemented in the first iteration, but maybe makes sense to leave out as well for the moment.

I'd like also to implement Image's Sprite, in order to avoid to use several images; and an 'accessKeyfeature (or 'shortcut') where we pass a combination keys that automatically trigger the button. This it will be possible in any case usingsdk/hotkeys` module and writes some code, it would be just a sugar syntax. But those are definitely future enhancements.

API

The API follows the standard convention we have in jetpack for the high level APIs.

    const { Button } = require("sdk/button"); // or maybe `sdk/ui`?

    // Minimal "action" button, default icon, 16x16
    let actionButton = Button({
      id: "my-button",
      label: "My Button"
    });

    // Minimal "toggle" button, default icon, 16x16
    let toggleButton = Button({
      id: "another-button",
      label: "Another Button",
      // `type` takes "button" (default) or "checkbox"
      type: "checkbox" // it's consistent with web and XUL
    });

    actionButton.on("click", function() {
      // do something when the button is clicked
    });

    toggleButton.on("change", function() {
      if (this.checked) { // consistent with web and XUL
        // do something when is "on"
      }
    }

    toggleButton.on("click", function() {
      // this is fired before "change" event when the state is not changed yet
    })

    actionButton.on("change", function() {
      // never fired, it's an action button
    })

    // disable a button

    actionButton.disabled = true; // web / XUL

    // a more complex button
    let drinkButton = Button({
      id: "drink-button",
      label: "Drink a Beer",
      image: data.url("beer.png"), // only local scheme or 'data:'
      type: "checkbox",
      disabled: true, // `disabled` can be set in the options too, default is `false`
      // `size` can takes "small" (16, default), "medium" (32), or "large" (64)
      // we can also accept both number and string identifier
      size: "medium",
      // badge will display up to around four characters, exceeding characters will
      // be cut out
      badge: {
        text: "+1",
        color: "#5fc24f" // any CSS color syntax is valid
      },
      // like other jetpack API, we can set the listener directly in the object's
      // options
      onChange: function() {
        if (this.checked) {
          // `label` and `image` can be set dynamically
          this.label = "Drink a Coffee";
          this.image = data.url("coffee.png");
        } else {
          this.label = "Drink a Beer";
          this.image = data.url("beer.png");
        }
      },
      onClick: function() {
        // update the badge text each click
        let drinkNumber = +this.badge.text;
        this.badge.text = "+" + (drinkNumber + 1);

        // update the badge background color
        this.badge.color = (drinkNumber > 10) ? "#fb2500" : "#5fc24f";
      }
    });

    // it should be possible trigger the click programmatically too
    drinkButton.click();

    // Once `size` is set, it can't be change, it's read-only. Therefore this code
    // will throw an exception
    drinkButton.size = "small";

    // Group buttons together:
    const { Button, ButtonSet } = require("sdk/button");
    let mySet = ButtonSet({
      // not sure if we need a mandatory id here, it depends by the implementation
      // in XUL.
      id: "my-set",
      // optional. Without it all buttons with type "checkbox" can checked
      // at the same time, otherwise are mutually exclusive:
      exclusive: true // "mutually exclusive" is too long, but I'm not sure this is good enough
    });

    // read-only, it will throw an exception
    myGroup.exclusive = false;

    let actionButton = Button({
      id: "my-button",
      label: "My Button",
      set: mySet
    });

    let toggleButton = Button({
      id: "another-button",
      label: "Another Button",
      type: "checkbox",
      set: mySet
    });

    let drinkButton = Button({
      id: "drink-button",
      label: "Drink a Beer",
      image: data.url("beer.png"),
      type: "checkbox",
      set: mySet
    });

    // iterate all buttons of a set
    for (let button of mySet.buttons)
      console.log(button.id);

    // get the checked buttons of a group, of course in case of mutuallu exclusive
    // it returns an array of one element.
    let checkedButtons = mySet.buttons.filter(function (button) button.checked)

    // ... or maybe we could have this as shortcut:
    for (let button of mySet.checkedButtons)
      console.log(button.id)

    // disable an entire set of buttons
    mySet.disabled = true;

    // add listeners to an entire set of buttons
    // they are emitted after the listener added to each individual button
    mySet.on("change", function(button) {
      // `button` is the button that fired that event
    });

Discussions

Mockups