addon-sdk/source/lib/sdk/ui/toolbar/model.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/addon-sdk/source/lib/sdk/ui/toolbar/model.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,151 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 +* License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 +* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +"use strict";
     1.8 +
     1.9 +module.metadata = {
    1.10 +  "stability": "experimental",
    1.11 +  "engines": {
    1.12 +    "Firefox": "> 28"
    1.13 +  }
    1.14 +};
    1.15 +
    1.16 +const { Class } = require("../../core/heritage");
    1.17 +const { EventTarget } = require("../../event/target");
    1.18 +const { off, setListeners, emit } = require("../../event/core");
    1.19 +const { Reactor, foldp, merges, send } = require("../../event/utils");
    1.20 +const { Disposable } = require("../../core/disposable");
    1.21 +const { InputPort } = require("../../input/system");
    1.22 +const { OutputPort } = require("../../output/system");
    1.23 +const { identify } = require("../id");
    1.24 +const { pairs, object, map, each } = require("../../util/sequence");
    1.25 +const { patch, diff } = require("diffpatcher/index");
    1.26 +const { contract } = require("../../util/contract");
    1.27 +const { id: addonID } = require("../../self");
    1.28 +
    1.29 +// Input state is accumulated from the input received form the toolbar
    1.30 +// view code & local output. Merging local output reflects local state
    1.31 +// changes without complete roundloop.
    1.32 +const input = foldp(patch, {}, new InputPort({ id: "toolbar-changed" }));
    1.33 +const output = new OutputPort({ id: "toolbar-change" });
    1.34 +
    1.35 +// Takes toolbar title and normalizes is to an
    1.36 +// identifier, also prefixes with add-on id.
    1.37 +const titleToId = title =>
    1.38 +  ("toolbar-" + addonID + "-" + title).
    1.39 +    toLowerCase().
    1.40 +    replace(/\s/g, "-").
    1.41 +    replace(/[^A-Za-z0-9_\-]/g, "");
    1.42 +
    1.43 +const validate = contract({
    1.44 +  title: {
    1.45 +    is: ["string"],
    1.46 +    ok: x => x.length > 0,
    1.47 +    msg: "The `option.title` string must be provided"
    1.48 +  },
    1.49 +  items: {
    1.50 +    is:["undefined", "object", "array"],
    1.51 +    msg: "The `options.items` must be iterable sequence of items"
    1.52 +  },
    1.53 +  hidden: {
    1.54 +    is: ["boolean", "undefined"],
    1.55 +    msg: "The `options.hidden` must be boolean"
    1.56 +  }
    1.57 +});
    1.58 +
    1.59 +// Toolbars is a mapping between `toolbar.id` & `toolbar` instances,
    1.60 +// which is used to find intstance for dispatching events.
    1.61 +let toolbars = new Map();
    1.62 +
    1.63 +const Toolbar = Class({
    1.64 +  extends: EventTarget,
    1.65 +  implements: [Disposable],
    1.66 +  initialize: function(params={}) {
    1.67 +    const options = validate(params);
    1.68 +    const id = titleToId(options.title);
    1.69 +
    1.70 +    if (toolbars.has(id))
    1.71 +      throw Error("Toolbar with this id already exists: " + id);
    1.72 +
    1.73 +    // Set of the items in the toolbar isn't mutable, as a matter of fact
    1.74 +    // it just defines desired set of items, actual set is under users
    1.75 +    // control. Conver test to an array and freeze to make sure users won't
    1.76 +    // try mess with it.
    1.77 +    const items = Object.freeze(options.items ? [...options.items] : []);
    1.78 +
    1.79 +    const initial = {
    1.80 +      id: id,
    1.81 +      title: options.title,
    1.82 +      // By default toolbars are visible when add-on is installed, unless
    1.83 +      // add-on authors decides it should be hidden. From that point on
    1.84 +      // user is in control.
    1.85 +      collapsed: !!options.hidden,
    1.86 +      // In terms of state only identifiers of items matter.
    1.87 +      items: items.map(identify)
    1.88 +    };
    1.89 +
    1.90 +    this.id = id;
    1.91 +    this.items = items;
    1.92 +
    1.93 +    toolbars.set(id, this);
    1.94 +    setListeners(this, params);
    1.95 +
    1.96 +    // Send initial state to the host so it can reflect it
    1.97 +    // into a user interface.
    1.98 +    send(output, object([id, initial]));
    1.99 +  },
   1.100 +
   1.101 +  get title() {
   1.102 +    const state = reactor.value[this.id];
   1.103 +    return state && state.title;
   1.104 +  },
   1.105 +  get hidden() {
   1.106 +    const state = reactor.value[this.id];
   1.107 +    return state && state.collapsed;
   1.108 +  },
   1.109 +
   1.110 +  destroy: function() {
   1.111 +    send(output, object([this.id, null]));
   1.112 +  },
   1.113 +  // `JSON.stringify` serializes objects based of the return
   1.114 +  // value of this method. For convinienc we provide this method
   1.115 +  // to serialize actual state data. Note: items will also be
   1.116 +  // serialized so they should probably implement `toJSON`.
   1.117 +  toJSON: function() {
   1.118 +    return {
   1.119 +      id: this.id,
   1.120 +      title: this.title,
   1.121 +      hidden: this.hidden,
   1.122 +      items: this.items
   1.123 +    };
   1.124 +  }
   1.125 +});
   1.126 +exports.Toolbar = Toolbar;
   1.127 +identify.define(Toolbar, toolbar => toolbar.id);
   1.128 +
   1.129 +const dispose = toolbar => {
   1.130 +  toolbars.delete(toolbar.id);
   1.131 +  emit(toolbar, "detach");
   1.132 +  off(toolbar);
   1.133 +};
   1.134 +
   1.135 +const reactor = new Reactor({
   1.136 +  onStep: (present, past) => {
   1.137 +    const delta = diff(past, present);
   1.138 +
   1.139 +    each(([id, update]) => {
   1.140 +      const toolbar = toolbars.get(id);
   1.141 +
   1.142 +      // Remove
   1.143 +      if (!update)
   1.144 +        dispose(toolbar);
   1.145 +      // Add
   1.146 +      else if (!past[id])
   1.147 +        emit(toolbar, "attach");
   1.148 +      // Update
   1.149 +      else
   1.150 +        emit(toolbar, update.collapsed ? "hide" : "show", toolbar);
   1.151 +    }, pairs(delta));
   1.152 +  }
   1.153 +});
   1.154 +reactor.run(input);

mercurial