{"id":5187,"date":"2010-07-03T08:24:00","date_gmt":"2010-07-03T15:24:00","guid":{"rendered":"http:\/\/4.115"},"modified":"2010-07-03T08:24:00","modified_gmt":"2010-07-03T15:24:00","slug":"tutorial-adding-to-bespins-gui","status":"publish","type":"post","link":"https:\/\/blog.mozilla.org\/labs\/2010\/07\/tutorial-adding-to-bespins-gui\/","title":{"rendered":"Tutorial: Adding to Bespin&#8217;s GUI"},"content":{"rendered":"<p><em>In this tutorial, we&#8217;ll learn how to add on to Bespin&#8217;s graphical user interface with a new, custom component.<\/em><\/p>\n<p><!--more--><\/p>\n<h1>Introduction<\/h1>\n<p>Since Bespin is a webapp, any Bespin command could conceivably pop up some bit of user interface to interact with the user. It\u2019s just a matter of creating some DOM nodes and placing them on the screen.<\/p>\n<p>But, if you want something a bit more persistent and integrated, you\u2019ll need to learn a bit about how Bespin\u2019s GUI is put together. That\u2019s what this tutorial is about.<\/p>\n<p>At the same time, we\u2019re going to talk about how Bespin plugins can themselves be pluggable.<\/p>\n<p>In this tutorial, we assume that you\u2019re already familiar with using dryice and the basics of making plugins. We\u2019re also going to assume a directory structure like the one from the <a href=\"commands.html\">commands tutorial<\/a>.<\/p>\n<p>By the end of this tutorial, we\u2019ll have something that looks toolbar-like. We\u2019re not trying to create a fully-functional toolbar here, because the focus is on hooking in to Bespin\u2019s GUI.<\/p>\n<p>This tutorial should work with Bespin 0.9a1 or later. However, there is a <strong>bug in Firefox 3.6.x that will cause the toolbar to not display properly<\/strong>. Bespin 0.9a2 has a workaround for this, and the bug itself is fixed in Firefox versions after 3.6.x.<\/p>\n<h2 id=\"creating_our_plugin\">Creating Our Plugin<\/h2>\n<p>As with the commands tutorial, we\u2019re assuming that you\u2019re working within a directory called <code>bespintutorial<\/code> that has Bespin itself in a subdirectory called <code>bespin<\/code>.<\/p>\n<p>In plugins that have user interface elements, you will almost always need to create a \u201cmulti file plugin\u201d. That just means we\u2019ll be creating a directory rather than a single .js file.<\/p>\n<p>We\u2019ll create a directory called <code>tutorialtoolbar<\/code>. To turn it into a plugin, we need to create a file called <code>package.json<\/code> in the <code>tutorialtoolbar<\/code> directory.<\/p>\n<p>We also need a dryice manifest so that we can see our plugin in action. Create a file called <code>toolbar.json<\/code> in the <code>bespintutorial<\/code> directory. Here\u2019s what we\u2019ll put in it to start with:<\/p>\n<pre><code>{\n    \"output_dir\": \"..\/build\",\n    \"plugins\": [\"embedded\", \"tutorialtoolbar\"],\n    \"search_path\": [\"..\"]\n}\n<\/code>\n<\/pre>\n<p>Switch to the <code>bespin<\/code> directory and run:<\/p>\n<pre><code>python dryice.py -s 8080 ..\/toolbar.json\n<\/code>\n<\/pre>\n<p>You shouldn\u2019t get any errors from dryice. That means that it was able to find our tutorialtoolbar plugin. If you open your browser to http:\/\/localhost:8080\/, you\u2019ll see a normal looking Bespin. Now we just need to put something in our plugin.<\/p>\n<h2 id=\"creating_a_simple_view\">Creating A Simple View<\/h2>\n<p>For a first step, we\u2019ll just toss a string up on the screen. In the <code>tutorialtoolbar<\/code> directory, create a new file called <code>index.js<\/code>. Here\u2019s how our toolbar component will start out:<\/p>\n<pre><code>exports.ToolbarView = function() {\n    var elem = document.createElement(\"div\");\n    elem.innerHTML = \"&lt;b&gt;Toolbar!&lt;\/b&gt;\";\n    this.element = elem;\n};\n<\/code>\n<\/pre>\n<p>We\u2019re creating a <code>Toolbar<\/code> class. When a new <code>Toolbar<\/code> is created, it gets a new element which just has our string in it. We set that to <code>element<\/code> on the instance. This is the key to GUI components that are integrated into Bespin\u2019s UI: they just offer up an <code>element<\/code> that Bespin will put in place.<\/p>\n<p>The next step is telling the Bespin system about our new component. We do that in the <code>package.json<\/code> file, which will now look like this:<\/p>\n<pre><code>{\n    \"provides\": [\n        {\n            \"ep\": \"factory\",\n            \"name\": \"tutorialtoolbar\",\n            \"action\": \"new\",\n            \"pointer\": \"#ToolbarView\"\n        }\n    ]\n}\n<\/code>\n<\/pre>\n<p>The factory extension point is used for extensions that provide components for Bespin to automatically instantiate. That may sound kind of vague, but you\u2019ll see in a moment that it\u2019s actually easy to use. The <code>action<\/code> tells Bespin that it\u2019s going to be creating a new instance and the <code>pointer<\/code> is telling Bespin to look in the tutorialtoolbar plugin\u2019s <code>index<\/code> module for something called <code>ToolbarView<\/code>.<\/p>\n<p>We\u2019ve written code that will generate our initial toolbar and we\u2019ve told Bespin that this is available. The next step is to tell Bespin to put one into the UI. This can be done either in the dryice manifest file or even at runtime when <code>useBespin<\/code> is called. We\u2019ll do it in the manifest file (<code>toolbar.json<\/code>). Here\u2019s the new manifest:<\/p>\n<pre><code>{\n    \"output_dir\": \"..\/build\",\n    \"plugins\": [\"embedded\", \"tutorialtoolbar\"],\n    \"search_path\": [\"..\"],\n    \"config\": {\n        \"objects\": {\n            \"toolbar\": {\n                \"factory\": \"tutorialtoolbar\"\n            }\n        },\n        \"gui\": {\n            \"north\": {\n                \"component\": \"toolbar\"\n            }\n        }\n    }\n}\n<\/code>\n<\/pre>\n<p>The new section is the <code>config<\/code> section. This provides the application configuration (about which you can read more in the <a href=\"..\/embedding\/appconfig.html\">Embedder\u2019s Guide<\/a>).<\/p>\n<p>The <code>objects<\/code> part of the config is telling Bespin to create a \u201cglobal\u201d (global within Bespin) object called <code>toolbar<\/code>. Bespin will look for a factory called <code>tutorialtoolbar<\/code>, which happens to be what we defined in the <code>package.json<\/code> file.<\/p>\n<p>The <code>gui<\/code> part of the config is telling Bespin to toss the component (object) that we created with the name <code>toolbar<\/code> into the \u201cnorth\u201d part of the interface. Bespin uses a simple \u201cborder layout\u201d with north, south, east, west and center locations. So, our toolbar should appear at the top of the page.<\/p>\n<p>Refresh your browser, and you should see our Toolbar! appear at the top. <em>If it appears at the bottom, you\u2019ve witnessed the bug mentioned in the introduction.<\/em><\/p>\n<h2 id=\"css3_flexible_box_model_an_aside\">CSS3 Flexible Box Model (an aside)<\/h2>\n<p>CSS3 includes a module called the Flexible Box Model (sometimes called flexbox), which Paul Rouget did a great job of introducing <a href=\"http:\/\/hacks.mozilla.org\/2010\/04\/the-css-3-flexible-box-model\/\">in this Mozilla Hacks article<\/a>.<\/p>\n<p>Traditionally, HTML+CSS have been quite nice for laying out documents but not so great for laying out user interfaces. Flexbox makes laying out UI a far easier process.<\/p>\n<p>Bespin uses flexbox to create the border layout. When you declare that a GUI component belongs in the \u201cnorth\u201d, all Bespin has to do is add the \u201cnorth\u201d class to the element, and it pops into the right place.<\/p>\n<p>Flexbox would also be a great way to create a toolbar, but we\u2019re not going to go that far in this tutorial.<\/p>\n<p>By using flexbox, we let the browser do all of the layout which is generally faster and smoother than JavaScript-based layouts.<\/p>\n<h2 id=\"making_our_toolbar_pluggable\">Making Our Toolbar Pluggable<\/h2>\n<p>One of Bespin\u2019s main features is that it is customizable and extendable. While it\u2019s certainly possible to make a static toolbar with a certain collection of functions, it\u2019s a lot more interesting to create a toolbar that is itself pluggable. Plus, that\u2019s part of the point of our tutorial.<\/p>\n<p>We\u2019ll start by defining an extension point of our own in <code>package.json<\/code>. Add this as another item in the <code>provides<\/code> list in <code>package.json<\/code>:<\/p>\n<pre><code>{\n    \"ep\": \"extensionpoint\",\n    \"name\": \"tutorialtoolbaritem\",\n    \"description\": \"Toolbar item views\",\n    \"params\": [\n        {\n            \"name\": \"name\",\n            \"description\": \"name of this toolbar item\",\n            \"type\": \"string\"\n        },\n        {\n            \"name\": \"pointer\",\n            \"description\": \"pointer to a component that can be instantiated with new and has an element defined on it.\"\n        }\n    ]\n}\n<\/code>\n<\/pre>\n<p>Extension points themselves are defined via the <code>extensionpoint<\/code> extension point. Seems a bit circular, but it works. We\u2019re creating an extension point called <code>tutorialtoolbaritem<\/code>. Bespin is designed to be introspectable, so we provide some documentation about the extension point via the <code>description<\/code> and <code>params<\/code> properties.<\/p>\n<p>We\u2019re going to make <code>tutorialtoolbaritem<\/code>s look a lot like Bespin\u2019s GUI components (an object with an <code>element<\/code> property).<\/p>\n<p>Now, we need to make our toolbar go out and find the registered <code>tutorialtoolbaritem<\/code>s. We\u2019ll change <code>index.js<\/code> to look like this:<\/p>\n<pre><code>var catalog = require(\"bespin:plugins\").catalog;\n\nexports.ToolbarView = function() {\n    var elem = document.createElement(\"menu\");\n    elem.setAttribute('class', \"tutorial-toolbar\");\n    elem.setAttribute('type', \"toolbar\");\n    this.element = elem;\n\n    this._items = [];\n\n    var extensions = catalog.getExtensions('tutorialtoolbaritem');\n    var self = this;\n    extensions.forEach(function(ext) {\n        ext.load().then(function(item) {\n            item = new item();\n            self._items.push(item);\n            elem.appendChild(item.element);\n        });\n    });\n};\n<\/code>\n<\/pre>\n<p>The first thing we\u2019re going to do is change the element we\u2019re creating into an <a href=\"http:\/\/www.w3.org\/TR\/html5\/interactive-elements.html#menus\">HTML5 menu element<\/a>. By giving it a type of <code>toolbar<\/code>, we\u2019re letting the browser know that we\u2019re creating a toolbar. Right now, the browsers don\u2019t render the toolbar in any special way, but we can use CSS to render the toolbar however we wish.<\/p>\n<p>Next, we\u2019ll make a list to keep track of the items we find. Speaking of finding the items, we need to talk to the plugin catalog about that. In the first line of the file, we imported the catalog. Now, we call getExtensions which will return all of the <code>tutorialtoolbaritem<\/code>s.<\/p>\n<p>We use <code>var self = this<\/code> because we\u2019ve got some nested callbacks and that\u2019s a bit nicer to look at than binding each function individually. Then, we loop through the list of extensions and call <code>load<\/code> on each one.<\/p>\n<p>The <code>load<\/code> function returns a <em>Promise<\/em>. Promises provide a convenient way to manage callbacks for asynchronous behavior. For our purposes here, the only difference between a Promise and a normal callback function is that we don\u2019t pass the callback directly to <code>load<\/code>. Instead, we call <code>then<\/code> on the promise and pass the callback to that.<\/p>\n<p>Back in our metadata for the <code>tutorialtoolbaritem<\/code> extension point, we defined the <code>pointer<\/code> property as a <em>pointer to a component that can be instantiated with new and has an element defined on it.<\/em> So, when we <code>load<\/code> the extension, that\u2019s what we\u2019re going to get back. With the tutorialtoolbar item in hand, we can call <code>new<\/code> on it, add that new instance to our items list and then add the instance\u2019s element to our toolbar.<\/p>\n<p>And, with that, we have created a dynamically extendable toolbar.<\/p>\n<h2 id=\"adding_some_items\">Adding Some Items<\/h2>\n<p>Of course, if we reload our Bespin, the toolbar will be rather boring. We haven\u2019t added any toolbar items!<\/p>\n<p>With the infrastructure that we put in place in the last section, we can create <code>tutorialtoolbaritem<\/code>s in any plugin. For convenience, we\u2019ll just add a couple of items in the tutorialtoolbar plugin.<\/p>\n<p>We\u2019ll make a new module (file) for them in the <code>tutorialtoolbar<\/code> directory called <code>items.js<\/code>. At this stage, we\u2019re not going to make our toolbar items <em>do<\/em> anything, but we just want to have something to display. Here is our <code>items.js<\/code> file:<\/p>\n<pre><code>exports.Logo = function() {\n    var li = document.createElement('li');\n    li.innerHTML = \"Logo here\";\n    this.element = li;\n};\n\nexports.OpenFileIndicator = function() {\n    var li = document.createElement('li');\n    li.innerHTML = \"SampleProject &amp;mdash; readme.txt\";\n    this.element = li;\n};\n\nexports.Save = function() {\n    var li = document.createElement('li');\n    li.innerHTML = \"Save\";\n    this.element = li;\n};\n\nexports.PositionIndicator = function() {\n    var li = document.createElement('li');\n    li.innerHTML = \"Row 0, Column 0\";\n    this.element = li;\n};\n<\/code>\n<\/pre>\n<p>As you can see, each one of these functions does nothing but create an <code>li<\/code> element (as per the HTML5 spec for menus) and add that element to the instance on the <code>element<\/code> property.<\/p>\n<p>One more thing to do before we can see our toolbar. We need to register the extensions for our <code>tutorialtoolbar<\/code> extension point. Back in <code>package.json<\/code>, we\u2019re going to add these four more items to our <code>provides<\/code> property:<\/p>\n<pre><code>{\n    \"ep\": \"tutorialtoolbaritem\",\n    \"name\": \"logo\",\n    \"pointer\": \"items#Logo\"\n},\n{\n    \"ep\": \"tutorialtoolbaritem\",\n    \"name\": \"openfileindicator\",\n    \"pointer\": \"items#OpenFileIndicator\"\n},\n{\n    \"ep\": \"tutorialtoolbaritem\",\n    \"name\": \"save\",\n    \"pointer\": \"items#Save\"\n},\n{\n    \"ep\": \"tutorialtoolbaritem\",\n    \"name\": \"positionindicator\",\n    \"pointer\": \"items#PositionIndicator\"\n}\n<\/code>\n<\/pre>\n<p>You can see that each one of these refers to the <code>items<\/code> module in the pointer, since that\u2019s where we put these items.<\/p>\n<p>For reference, here\u2019s the complete <code>package.json<\/code> file at this stage:<\/p>\n<pre><code>{\n    \"provides\": [\n        {\n            \"ep\": \"factory\",\n            \"name\": \"tutorialtoolbar\",\n            \"action\": \"new\",\n            \"pointer\": \"#ToolbarView\"\n        },\n        {\n            \"ep\": \"extensionpoint\",\n            \"name\": \"toolbaritem\",\n            \"description\": \"Toolbar item views\",\n            \"params\": [\n                {\n                    \"name\": \"name\",\n                    \"description\": \"name of this toolbar item\",\n                    \"type\": \"string\"\n                },\n                {\n                    \"name\": \"pointer\",\n                    \"description\": \"pointer to a component that can be instantiated with new and has an element defined on it.\"\n                }\n            ]\n        },\n        {\n            \"ep\": \"tutorialtoolbaritem\",\n            \"name\": \"logo\",\n            \"pointer\": \"items#Logo\"\n        },\n        {\n            \"ep\": \"tutorialtoolbaritem\",\n            \"name\": \"openfileindicator\",\n            \"pointer\": \"items#OpenFileIndicator\"\n        },\n        {\n            \"ep\": \"tutorialtoolbaritem\",\n            \"name\": \"save\",\n            \"pointer\": \"items#Save\"\n        },\n        {\n            \"ep\": \"tutorialtoolbaritem\",\n            \"name\": \"positionindicator\",\n            \"pointer\": \"items#PositionIndicator\"\n        }\n    ]\n}\n<\/code>\n<\/pre>\n<p>With that, we\u2019ve done everything we need to do to see our toolbar. Let\u2019s reload the page. You should see something that looks like this:<\/p>\n<p><img decoding=\"async\" src=\"files\/2010\/07\/ToolbarAsList.png\" alt=\"Toolbar displayed as list\" \/><\/p>\n<p>It worked! It has our items in it! Doesn\u2019t look much like a toolbar, though, does it?<\/p>\n<h2 id=\"styling_your_plugins\">Styling Your Plugins<\/h2>\n<p>To make our toolbar look like a toolbar, we need to add some styles. In order to support themes properly, Bespin uses <a href=\"http:\/\/lesscss.org\">LESS<\/a>, which is an extended CSS syntax.<\/p>\n<p>In the <code>tutorialtoolbar<\/code> directory, create a <code>resources<\/code> directory. In there, create a file called <code>toolbar.less<\/code>. We need to tell Bespin\u2019s theme manager about this file, so we need to add one more thing to the <code>provides<\/code> property in <code>package.json<\/code>:<\/p>\n<pre><code>{\n    \"ep\": \"themestyles\",\n    \"url\": [ \"toolbar.less\" ]\n}\n<\/code>\n<\/pre>\n<p><code>themestyles<\/code> extensions provide URLs (relative to the plugin\u2019s resources directory) to the LESS files that this plugin needs to have loaded. The theme manager will automatically load these files as needed.<\/p>\n<p>And what\u2019s in this mysterious <code>toolbar.less<\/code> file?<\/p>\n<pre><code>.bespin {\n    .tutorial-toolbar {\n        display: block;\n        background-color: #000;\n        border-bottom: solid #424038 1px;\n        -moz-box-shadow: 0px 0px 3px 3px #000;\n        -webkit-box-shadow: 0px 0px 3px 3px #000;\n        color: #ffffff;\n        font-family: Helvetica, Arial;\n        width: 100%;\n        margin: 0px;\n        padding: 0px;\n    }\n\n    .tutorial-toolbar li {\n        display: inline;\n        padding: 0px 24px 0px 0px;\n    }\n}\n<\/code>\n<\/pre>\n<p>This looks like fairly normal CSS, right? <em>Except<\/em> what\u2019s that <code>.bespin<\/code> doing surrounding those other rules? When LESS expands this out to standard CSS, the result would be something like this:<\/p>\n<pre><code>.bespin .tutorial-toolbar {\n}\n\n.bespin .tutorial-toolbar li {\n}\n<\/code>\n<\/pre>\n<p>So, the surrounding <code>.bespin<\/code> is a nice little shorthand, and it prevents CSS from leaking out onto the page. When you consider the use of Bespin Embedded on other sites, it\u2019s a good idea to ensure that all of our styles are scoped for use only within Bespin.<\/p>\n<p>With these styles in place, let\u2019s reload the page. Much nicer, eh?<\/p>\n<h2 id=\"wrapping_up\">Wrapping Up<\/h2>\n<p>On the one hand, this tutorial didn\u2019t give us a <em>functioning<\/em> toolbar. It\u2019s all static text. On the other hand, look at everything we <em>did<\/em> cover:<\/p>\n<ul>\n<li>multi-file plugin structure<\/li>\n<li>creating a component that Bespin can display<\/li>\n<li>the awesomeness that is CSS3\u2019s Flexible Box Model<\/li>\n<li>configuring our component for display in Bespin<\/li>\n<li>defining and using our own extension points<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>In this tutorial, we&#8217;ll learn how to add on to Bespin&#8217;s graphical user interface with a new, custom component.<\/p>\n","protected":false},"author":456,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"_links":{"self":[{"href":"https:\/\/blog.mozilla.org\/labs\/wp-json\/wp\/v2\/posts\/5187"}],"collection":[{"href":"https:\/\/blog.mozilla.org\/labs\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.mozilla.org\/labs\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.mozilla.org\/labs\/wp-json\/wp\/v2\/users\/456"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.mozilla.org\/labs\/wp-json\/wp\/v2\/comments?post=5187"}],"version-history":[{"count":0,"href":"https:\/\/blog.mozilla.org\/labs\/wp-json\/wp\/v2\/posts\/5187\/revisions"}],"wp:attachment":[{"href":"https:\/\/blog.mozilla.org\/labs\/wp-json\/wp\/v2\/media?parent=5187"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.mozilla.org\/labs\/wp-json\/wp\/v2\/categories?post=5187"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.mozilla.org\/labs\/wp-json\/wp\/v2\/tags?post=5187"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}