Anatomy of a Mobile App development project

Screen Shot 2015-09-23 at 5.14.22 PM“Being the first to respond to a deal on Axial gives you a 19% greater chance to closing it.” – Peter Lehrman – Bloomberg Business 2015-09-11

It is based on this insight that, for the past 6 months, Axial has assembled a team dedicated to putting together a solution that gives the investors the power to control and direct deal flow within the palm of their hands. Introducing Axial Mobile for iOS!

Native vs Hybrid

Over the past few years, a recurring topic has been whether to develop mobile apps using native languages (Swift/Objective-C for iOS or Android SDK) or hybrid frameworks relying on platforms’ browsers (UIWebView for iOS or WebView in Android).

While the native approach allows for a closer integration with each platform’s abilities and greater hardware responsiveness, the decision to go with the hybrid approach was motivated by several factors:

  • one unique codebase:
    • ease of maintenance,
    • multi platform deployment simplified,
    • simplified testing
  • ability to use a familiar framework:
    • smaller learning curve
    • supporting resources immediately available
    • ease of development scaling if required
  • maturity of the Ionic framework:

The choice of Ionic Framework

Over the last two years, Ionic has grown to become the reference framework in the hybrid community, providing custom industry standard components built on the Angular framework

Relying on Apache Cordova to enable developers to access native device function such as the camera or accelerometer directly from JavaScript, Ionic has created a free and open source library of mobile-optimized HTML, CSS and JS components, gestures and tools for building interactive apps from JavaScripts.

Moreover, the Ionic.io platform comes with a complete hybrid-focused suite of mobile backend services to integrate powerful features such as push notifications and rapid deployment.

Since we have adopted Angular as our reference front-end framework, it is only natural for Axial to leverage many of Ionic components and services in order to deliver the most embedded experience to its members.filter

Push notification integration

Using Ionic.io’s push service, we have the ability to notify members of deals they receive instantly. Our members also have the ability to adjust their notification preferences:settings

We can also very efficiently trigger updates in the background so that it ensures a smooth user experiences by preloading updated data using silent notifications.

Rapid deployment process

The Apple App Store release process takes approximately a week. However, in order to fix critical issues or release changes rapidly, 7 days can seem like an eternity.

For this reason, Ionic.io’s deploy service allows us to release any non binary affecting changes on the go. As a result, having this flexibility allows us to ship code more frequently, enhancing our members experience without forcing them to go through painful, often delayed (or forgotten) updates via the App Store.

Contributions to Ionic

Despite numerous features and wildly embraced standard components, Ionic may have some gaps. As we have discovered during the implementation of Axial Mobile, while the right-to-left swipe gesture on a collection repeat item was available, the opposite left-to-right gesture was not.

Thankfully, building a wildly adopted Angular framework, it was simple enough to implement our own left-to-right swipe gesture and submit a pull request on Github in order to contribute to the framework and give back to the open source project.swipes

Future Developments

Having shipped our Axial Mobile version 1.0 on September 21st, we are now awaiting feedback from users.

Our members now have the ability, in the palm of their hand, accessible from anywhere, to pursue or decline deals and create conversation around the ones that catch their interests.
Soon, we will be integrating HelloSign services to provide a way for our members to sign NDAs and other documentation directly on their smartphones screen, allowing them to engage one step further in the deal making process.

The case for styleguides + class spokes for more agile development

As a UX designer, one of the biggest challenges to agile development and processes I’ve found is that iterations and new features often get designed and developed with less regard to previous iterations and styles than there should be. If your organization or site doesn’t have a well defined and comprehensive style guide to work from, or at the very least, established and reusable stylesheets, the problem often grows from a minor annoyance into serious technical debt that must be resolved.

If your process has been agile for long enough, and you haven’t focused on that level of detail enough, you end up with multiple css files with inconsistent styles that conflict with each other. You also accumulate a lot of duplication of the same things (and a lot of leftovers that should be removed). Embarrassingly, we recently audited our front-end code and discovered that we have over 50 different font-family strings, when only 3 font-family strings are really necessary. We could be so much more efficient about this

How does this happen?

As I’m designing a new feature or changes to existing functionality, there are often new elements that no existing style fully covers. Sometimes there is also a need to improve the existing design to make it more visually appealing, functional, or obvious to the users. These are real-world needs and an important part of the iterative process, but also open the door for problems to arise. Often in providing specifications for these new designs, I’ll try to specify an existing classname of something else that matches the style of what I’m wanting, or state “make it like this other class we use here”. Without a style-guide to refer to and good habits to pay close attention to overlap with existing styles, most of the time I just end up detailing the individual styles in the specs, and verifying that they look right upon implementation. For our engineers, this makes it easy to develop without having to care too much about prior styles, but it ALWAYS either causes problems during QA, or later on when a style changes or stylesheet doesn’t load and hunting down the offending conflict is like finding a needle in a haystack.

We recognized and complained about the mess, but couldn’t prioritize fixing it. It’s like when you know your car is a mess, but you’re just too tired after driving it every day to get it clean, and you know its going to rain tomorrow and mess it up again… The problem is, the longer we let it get dirty, the more work we created for ourselves for design, implementation, testing, and reusability.

LETS DO THIS!

We cycled through the Kübler-Ross Model of Five Stages of Grief:

  1. Denial and Isolation: what got us here in the first place
  2. Anger: what we experience when things break during implementation, testing, or later on.
  3. Bargaining: what we do back and forth to get around it when it breaks, but we don’t really solve the problem.
  4. Depression: sinks in when you realize how far you have to go, how many places you’ll have to pick through to clean up, and how much work you have to put into documenting the right way.
  5. Acceptance: Is this sustainable? No? Then LETS DO THIS:

So, how do you fix it?

We started with a comprehensive audit of the most common elements of our application that had the most variation, and identified the two lowest-hanging fruits we could start resolving: Buttons and text styles.

Buttons:

We had old button styles from bootstrap, buttons that still referenced bootstrap but overrode certain things, and then several generations of newer buttons. We identified the buttons, the pages they lived on, and then went about defining a NEW button style that would cover all button implementations. With a style defined for every kind of button, we started building a button spoke that contained everything every button needed. Now, we have a plan of action of which buttons on which pages we can replace using the single reusable spoke. Every new button a designer designs must now match (or make a case for adding an additional button style, which can then be added to the spoke). Every new button an engineer implements must use the styles from the button spoke. While we’ve only just recently implemented the button spoke and started slowly converting existing buttons to use it, both the product and engineering teams have a much higher degree of confidence that the new buttons will be the easiest part of a design to implement.

Before: (provided in every single design spec & mockup every time a button is used)
“Here is the button, and the specs for this button”
button-spec-example

After: (stated in implementation spec)
“use Regular Primary Icon Button” from the button spoke.

Text Styles

We currently have over 50 different font-family strings, and countless variations on weight, size, and line-heights. We only need 3 font families specified, and 10 size variations with specific weights and line-heights for each. Our next project will be to define a type spoke that specifies these, and then begin converting pages to refer to this. Already as we provide specs to the engineers, we’re referencing text by the proper family name and size, weight, making documentation and testing much easier.

Takeaways

For Engineers: ask your designers to use existing class names already available to them. If they don’t know them, work with them to develop a library or reference. And when implementation calls for styles, either add them to the stylesheets in a re-usable way, or try to reference as much of the original style as possible.

For Designers: look at the code and figure out what you currently have, and what you actually want. Define your styles methodically and with reason, and if something needs to be different, communicate WHY.

Make it Responsive

One of the most important considerations when building modern application is how to preserve the presentation of your features across a variety of different screen sizes. Consider the following designs from a recent search project I worked on. On a large screen, the feature might look like this:

responsive_blog1

Versus a smaller format screen, like a tablet, where it makes more sense for it to look like this:

responsive_blog4

With the right techniques you can make the page respond from your widest supported format to your most narrow, while looking graceful everywhere in between — and you can do it with relatively little code.

responsive_blog3

Liquid Inputs

It starts with the Revenue and EBITDA inputs, which are elastic down to a certain point, then those inputs reposition themselves under the Company Location and Select Industry filters. As we get even smaller the filters and inputs fall into a vertical line to make even more space. The first part of making this responsive is to make the button and search bar liquid:

<style>
  #keyword-button {
    width: 200px;
    float: right;
  }
  #keyword-wrapper {
    margin-right: 220px;
  }
  #tpsearch_keywords {
    width: 100%;
  }
</style>
<button type="submit" id="keyword-button">Search</button>
<div id="keyword-wrapper">
  <input id="tpsearch_keywords" type="text" value="Search the Network (e.g. specialty chemicals, family office new york)">
</div>

Because the button is floating right in the margin of the keyword-wrapper, we can resize the page and the search bar will expand to fill in space. We use this same floating in the margin pattern for the “Refine Search/Clear Filters” buttons.

Using Table-Layout

To allow the inputs to make-up the space between our widest and collapsed view, we use table layout for the input columns. To do this we make the inputs columns in a table layout, setting the middle cell’s width to 20px and setting the input widths to 100% each. Fixed sizes get priority and then percentages are use to fill in the rest. Because they are both 100% they get averaged across remaining size, causing them to scale as the page size changes:

<style>
  .axl-criteria-resp-container {
    width: 560px; 
    float:left;
    display: table;
  }
  .axl-criteria-wrapper {
    display: table-row
  }
  .axl-input {
    display:table-cell;
    width: 100%;
  }
  .axl-criteria-resp-container > span {
    width: 20px;
    display:table-cell;
  }
</style>

Now as we make the window larger and smaller the Revenue and EBIDTA fields scale to make the filters fill the page, but when the window narrows further, we eventually want them to break apart uniformly. We always want the filters pairs either both on the same line, or all on separate lines. As this happens, we also want to collapse the fields below, and allow the search bar to fit the newly available space:

<style>
  .tpsearch-criteria {
    width:270px;
    float:left;
    margin-right: 20px;
  }
  .axl-criteria-resp-container {
    float:left;
  }
  @media(max-width: 900px ) {
    .axl-criteria-resp-container,
    .axl-criteria-wrapper,
    .axl-input,
    .axl-criteria-wrapper > span {
      display: block;
    }
   .axl-input {
      float:left;
      width: 270px;
      margin-right: 20px;
   }
   .axl-criteria-wrapper > span {
      display:none;
   }
 }
 </style>
<div class="axl-filters-container">
  <button class="tpsearch-criteria" id="btn-filter-company-location">Company Location</button>
  <button class="tpsearch-criteria" id="btn-filter-industry">Select Industry...</button>
  <div class="axl-criteria-resp-container">
    <div class="axl-criteria-wrapper">
      <input type="text" class="axl-input currency" name="tpsearch_revenue" value="Revenue">
      <span></span>
      <input type="text" class="axl-input currency" name="tpsearch_ebitda" value="EBITDA">
    </div>
  </div>
</div>

Now the Revenue and EBIDTA inputs will fall squarely below the Company Location and Select industry filters because they are enclosed in a div, but as we get smaller than that we need to break further. Using one more max-width media query, and a little extra mark-up, we can do one more CSS adjustment to get down to our smallest screen format:

<style>
  @media(max-width: 540px){
    .axl-criteria-resp-container {
      width: 270px;
      float:none;
    }
    .tpsearch-criteria {
      display:block;
      float:none;
    }
  }
</style>

Kill Yr Features

Kim Gordon of Sonic Youth, walking on her bass, 1991. (Source: Wikipedia)

Sonic Youth fans may have missed one of the bands earliest gems called “Kill Yr Idols“. The lyrics are simple: don’t worry about impressing the critics, find out the new goal. For writers, the phrase “murder your darlings” by English author Sir Arthur Quiller-Couch applies in the same way:  remain objective and  strive for improvement regardless of sentiment or precedent. For technology, there is no shortage of  visionaries who’ve all reinforced the importance of pushing of ideas and starting over to get things just right. Every now and then you’re presented with the opportunity (or, the necessity) to take something born in a lot of blood, sweat and cursing, and tear it down to the foundation and start again.

Rebuilding pieces of your application to fit new needs is nothing new. It should be core to any team to want to build it better and scalable. It’s an opportunity to continually re-evaluate the UX and product decisions you made before and improve upon them. Here’s a perfect example of why anyone involved in planning or developing products should embrace that opportunity, every time.

1 idea, 3 wizards, too many design changes, too many bugs

It started about half a year ago as a grand plan for a single wizard style and function to help our users create and edit their company profile, investment and deal criteria. We designed it and built it from scratch with a new UI, validation engine, and branching logic to help guide users through the process. We covered all three types of wizards in the design, but we started small (or so we thought) by focusing on the first of the three wizards. We tried to keep scope manageable by iterating, and chose to maintain some of the original (older and rigid) architecture. As the first wizard began taking longer than we thought it would, we cut scope, and the end result, while well received, was sort of anemic, and we were still supporting 2 workflows. We immediately started on the next wizard on an even more complex and key piece of the application. What started as identical twins became fraternal twins, and then became bad dopplegängers. Each implementation had its own requirements and circumstances, and the result of our first two attempts was the only thing worse than two specific implementations … two competing generic implementations, neither of which was generic. By the beginning of this year, we were in the same position we started in: separate wizards, different methods of validation, with conflicting and replicated stylesheets. We needed to take a step back.

Time to Pull The Plug

Despite our best intentions, nothing we had built before was truly re-usable without an overly complex re-configuration. Rather than go down the same path (again) for the next round of improvements needed, we took a step back across the board. We used the two existing implementations, and the third proposed one to need consolidate on a more consistent and clear UX, graphic design and wizard architecture that would allow us to be generic in the database, and specific in the experience. Even though it meant revisiting everything we had just built, we started by revisiting the functionality we wanted all along and started from scratch, this time explicitly covering each of the three wizards we’d made before. Each and every single question, field and option was re-evaluated and compared. Similar functionality was combined, refined, and styled. Unique solutions we’d come up with while sweating each launch in isolation were rethought and some were ultimately scrapped completely. We totally rethought how we wanted to handle validation, in a way we could actually reuse, and in other places outside of the wizards too.

Damn the Roadmap, Full Speed Ahead!

Two weeks later, we had a master design for every element in every wizard. We had a working stylesheet and structure that covered every use-case, and we had every field in each existing and new wizard planned and accounted for. Despite a full roadmap, we made the case to revisit and rebuild, rather than just quadrupling-down on our previous work, further bifurcating the experience and code along the way, and we decided to spend the extra time to rebuild it all using what we’d learned along the way. We tackled the simplest wizard first, and quietly shipped a reboot without anyone really noticing. The newest and most complex wizard got all of the new functionality that the roadmap had called for, and along with it, replaced the bad decisions we’d made before, all in about half the time it took to re-build the first wizard. The third wizard is planned to take even less time, and will have taken us about 6 weeks of development. We’ll have redesigned and rebuilt in 2 months what originally took 6 to build, and resolved numerous headaches. Plus, our original goal of using the same logic and styling for all wizards has truly been realized.

The process has also gotten us thinking about other parts of the application that have suffered the same fate: development and design without enough focus on reusability. These pieces could, and more importantly, should be much more closely related. Thousands of lines of one-off code and design inconsistency and conflict could be eliminated. As a team, this experience has improved the communication between product and engineering, and encouraged us to think about reusability and application implications in a more specific, defined, and calculated way. Like listening to “Kill Yr Idols,” a harsh experience leads to serenity and desire for self-improvement. What better reason to take the machete to an idol of your past?

GLitter – A Simple WebGL Presentation Framework

Example GLitter Page

Example GLitter Page

A couple of weeks ago, I gave an Axial Lyceum talk on WebGL, a technology that allows hardware-accelerated 3D graphics to be implemented in the browser using pure javascript code. Before I came to Axial, I was doing a lot of 3D graphics work, which is how I came to learn WebGL. I had a moment of panic a few days before my talk when I realized it had been almost a year since I’d done any WebGL programming and I was feeling a little rusty. I was about to fire up powerpoint and start creating what probably would have been a boring presentation when I had a flash of inspiration– I could implement the slides for my WebGL talk directly in WebGL!

This both forced me to get back into the swing of WebGL and resulted in a much more engaging and interactive presentation. I used the fantastic Three.js library to make a simple framework for my presentation. After my talk, a few of the attendees asked if they could get a copy of the code that I used for the presentation, so I spent a little time making the code a bit more modular and it is now available at http://github.com/axialmarket/GLitter/. Before I explain how the presentation framework works, a little background on three.js is necessary.

WebGL is a very powerful technology, but it is also very complicated and has a steep learning curve. Three.js (http://threejs.org) makes creating 3D graphics with WebGL drastically simpler. While you still need some understanding of matrix mathematics and 3D geometry, Three.js abstracts away some of the highly technical details of WebGL programming like writing fragment shaders in GLSL or manipulating OpenGL buffers. In Three.js, you create a scene, a camera and a renderer object, and then use the renderer object to render each frame. To actually render something, you add 3D objects to the scene. These 3D objects have a geometry and materials, a transformation matrix that specifies translation, rotation and scaling relative to the object’s parent, and the ability to contain other 3D objects.

With GLitter, you define a “Page” object for each step in the presentation that creates the 3D objects and behaviors needed to implement that step. The GLitter “Scene” object manages the Three.js scene, camera and renderer and implements transition logic for switching between steps, and provides some common keypress handling functionality. One of the neat things about WebGL is that it renders inside of the HTML5 canvas so it is easy to composite the WebGL scene with HTML content overlayed on top. In GLitter, there are a few different types of HTML content you can overlay on top of the WebGL canvas. First, each Page provides a title and optionally some subtitle content. Second, GLitter uses the dat.GUI control framework to allow Page objects to easily add controls for properties of javascript objects. Lastly, GLitter provides an “info” interface that can be used to show dynamic content.

To see how this works in practice, let’s create a presentation with two steps. The first will show a spinning cube and provide controls to change the cube’s size, and the second will show a sphere with controls to change the camera’s field-of-view, aspect ratio and near and far clipping planes. On the first step, we will show the cube’s transformation matrix in the “info” overlay and in the second, we will show the camera’s projection matrix.

We first create GLitter Page objects CubePage and SpherePage:

var CubePage = new GLitter.Page({
    title: "Cube",
    subtitle: "Spinning!",
    initializor: function (scene) {
        var context = {};
        var cubeMaterial = new THREE.MeshLambertMaterial({ color: 0xee4444});
        var cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
        context.cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
        context.cube.position.z = -2.5;
        scene.add(context.cube);

        var spin = function() {
            new TWEEN.Tween(context.cube.rotation)
                     .to({y: context.cube.rotation.y - 2*Math.PI}, 2000)
                     .start()
                     .onComplete(spin);
        }
        spin();
        scene.add(new THREE.PointLight(0x999999));
        scene.camera.position.z = 2.5;
        return context;
    },
    finalizor: function() {
        GLitter.hideInfo();
    },
    updator: function (context) {
        return function (scene) {
            GLitter.showInfo(GLitter.matrix2html(context.cube.matrix));
            return ! context.STOP;
        }
    },
    gui: function (scene, context) {
        scene.gui.add(context.cube.scale, 'x', 0.1, 5);
        scene.gui.add(context.cube.scale, 'y', 0.1, 5);
        scene.gui.add(context.cube.scale, 'z', 0.1, 5);
    }
});
var SpherePage = new GLitter.Page({
    title: "Sphere",
    initializor: function (scene) {
        var context = {};
        var sphereMaterial = new THREE.MeshLambertMaterial({ ambient: 0xee4444 });
        var sphereGeometry = new THREE.SphereGeometry(1, 20, 20);
        context.sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
        context.sphere.position.z = -5;
        scene.add(context.sphere);

        scene.add(new THREE.AmbientLight(0x999999));
        return context;
    },
    finalizor: function() {
        GLitter.hideInfo();
    },
    updator: function (context) {
        return function (scene) {
            GLitter.showInfo(GLitter.matrix2html(scene.camera.projectionMatrix));
            return ! context.STOP;
        }
    },
    gui: function (scene, context) {
        var upm = function(){scene.camera.updateProjectionMatrix()};
        scene.gui.add(scene.camera, 'fov', 1, 179).onChange(upm);
        scene.gui.add(scene.camera, 'aspect', 0.1, 10).onChange(upm);
        scene.gui.add(scene.camera, 'near', 0.1, 10).onChange(upm);
        scene.gui.add(scene.camera, 'far', 0.1, 10).onChange(upm);
    }
});

The “title” and “subtitle” values are pretty self-explanatory. The “initializor” function is called to initialize the page when GLitter transitions to it. It adds the desired 3D objects to the GLitter Scene object, including lights, and returns a context object holding any objects that need to be referenced later. The “finalizor” function is called just before GLitter transitions away from this page. The “updator” function is called just before every frame is rendered. The “gui” function is used to update controls in scene.gui, which is a dat.GUI object.

Note that in CubePage, the spinning of the cube is handled by the TWEEN javascript library. TWEEN requires an update call to be made in every frame, but this is handled automatically by GLitter.

Also note that the “updator” functions return “! context.STOP”. The idea here is that if the “updator” function returns a false value, rendering is paused. The GLitter Scene object intercepts ‘keydown’ events, and will set context.STOP to true if Enter or Space is pressed. In addition, if “n” or “p” is pressed, GLitter transitions to the next or previous step, respectively. Page objects can add handling for other keypresses by defining an ‘onKeydown’ function. If this function returns a true value, then the standard GLitter keypress handling is skipped.

Now that these Page objects are defined, we can create an HTML page that sets up the basic structure GLitter needs and loads all of the required files. Currently GLitter is not a javascript module, so we load all of the files explicitly:

<!DOCTYPE html>
<html>
  <head>
    <title>GLitter Blog Example</title>
    <meta charset="utf-8"> 
    <link rel="stylesheet" type="text/css" href="example/example.css">
    <script src="//rawgithub.com/mrdoob/three.js/master/build/three.js"></script>
    <script src="lib/tween.js"></script>
    <script src="lib/dat.gui.min.js"></script>
    <script src="lib/OrbitControls.js"></script>
    <script src="lib/EffectComposer.js"></script>
    <script src="lib/MaskPass.js"></script>
    <script src="lib/RenderPass.js"></script>
    <script src="lib/ShaderPass.js"></script>
    <script src="lib/CopyShader.js"></script>
    <script src="lib/HorizontalBlurShader.js"></script>
    <script src="lib/VerticalBlurShader.js"></script>
    <script src="GLitter.js"></script>
    <script src="Page.js"></script>
    <script src="Scene.js"></script>
    <script src="example/CubePage.js"></script>
    <script src="example/SpherePage.js"></script>
    <script src="example/example.js"></script>
  </head>
  <body>
    <div id="content" class="content">
        <div id="title" class="title"></div>
        <div id="subtitle" class="subtitle"></div>
    </div>
    <div id="info" class="info">
    </div>
  </body>
</html>

The files in lib/ are third-party libraries, and GLitter itself is comprised of GLitter.js, Page.js and Scene.js. The contents of CubePage.js and SpherePage.js are shown above, so all that’s left is example.js:

window.addEventListener('load',
    function () {
        CubePage.nextPage = SpherePage;
        SpherePage.prevPage = CubePage;
        var scene = new GLitter.Scene({});
        scene.initialize();
        document.getElementById('content').appendChild(scene.domElement);
        scene.loadPage(CubePage);
    }
);

You can see the completed example at: http://axialmarket.github.io/GLitter/example.html

There’s still quite a bit of work to do to make GLitter more powerful and easier to use,  but as you can see, GLitter is already pretty easy to use, and doesn’t look too shabby either!

[Video] Having Fun with WebGL

On 2/25, our very own Ben Holzman stopped by to teach us all a little bit about 3D graphics on the Web, using WebGL. Ben’s presentation was 100% written in a WebGL presentation framework he calls “GLitter”, which he will be releasing shortly.

Out of everything Ben managed to pack into this Lyceum, perhaps the most impressive thing we learned was just how easy it’s becoming to do 3D graphics in the browser with tools like three.js.

SPOKES: Towards Reusable Front-End Components

Hub and Bespoke

For some time here at Axial we’ve been migrating a large monolithic app to a set of small and simple services. One challenge that has come up in this process is how to share front-end components without unnecessarily coupling services together, and without imposing too many restrictions on how these front-end components can be implemented. The solution we’re evolving involves an abstraction we call a “spoke“.

What is a spoke?

A spoke is an executable javascript file that can contain javascript, CSS and HTML templates. In addition, a spoke can have dependencies on other spokes, which allows us to partition front-end components into small discrete chunks and at build time create a single loadable javascript file for a page that includes just the functionality we need. Now, you may be wondering how we embed CSS and HTML templates into an executable javascript file. We’re using a somewhat unsophisticated approach; we URI-encode the CSS or HTML content into a string and embed it in some simple javascript that decodes the content and dynamically adds it to the DOM.

A simple example

Let’s create a spoke for rendering a user’s name. This perhaps sounds like it’s too simple a task, but there could be some complexity to the logic required:

  • To save space, if the user’s full name would be more than 20 characters, we will render just their first initial followed by their last name.
  • If the user is an internal user, we want to annotate their name with (internal).
  • If the user is an internal user masquerading as a regular user, we want to annotate their name with (masq).

For this example, we will use a Backbone model and view, and an Underscore template, but these are implementation choices and not imposed on us just because we are creating a spoke.

Here is the Backbone model we will use:

var UsernameModel = Backbone.Model.extend({
    defaults: { first_name: "",
                last_name: "",
                is_internal: false,
                is_masq: false }
});

The view is pretty straightforward:

var UsernameView = Backbone.View.extend({
    className: 'username',
    render: function() {
        this.$el.html(this.template(this.model.attributes));
        return this;
    },
    template: _.template($('#username-template').html())
 });

We will store the Underscore template in a <script> tag with type “text/template”:

<script id="username-template" type="text/template">
    <% if (first_name.length + last_name.length >= 20) { %>
        <%= first_name.substr(0,1) %>.
    <% } else { %>
        <%= first_name %>
    <% } %>
    <%= last_name %>
    <% if (is_internal) { %>(internal)<% } %>
    <% else if (is_masq) { %>(masq)<% } %>
</script>

In addition, we have a CSS file to control the styling of the username:

.username {
    font-size: 18px;
    color: #333;
    white-space: nowrap;
}

To turn this into a spoke, all we have to do is store these source files in the spoke source tree:

js/models/Username.js
js/views/Username.js
html/username.html.tpl
css/username.css

Then we add a definition for this spoke (which we will call, surprise, surprise, “username”) to a spoke config in /etc/spoke/, for use by the “spoke compiler”, which is a python script spokec:

    # /etc/spoke/username.cfg
    [username]
    js     = [ 'models/Username.js', 'views/Username.js' ]
    html   = 'username.html.tpl'
    css    = 'username.css'
    spokes = 'backbone'

Spokes do not need to have all of these types of files; a spoke might contain only CSS or only javascript content. Note, also, that we have made the “username” spoke dependent on the “backbone” spoke. The definition of the “backbone” spoke in turn references the “underscore” spoke. When we use spokec to generate a spoke, these dependencies are followed and included in the output. As you probably anticipate, if a spoke is referenced multiple times, it only gets included in the output once.

Now that we’ve defined this spoke, here’s how we would call spokec to generate it:

spokec username [additional spokes] path/to/output.js

Each invocation of spokec generates a single executable javascript file containing all of the specified spokes and their dependencies. So typically a service will create a single spoke file for all of its pages, or sometimes a few different spoke files if the pages that service provides are significantly different. Currently we apply minification and fingerprinting to the spokes after generating them, but we will probably add this functionality directly to spokec soon.

Now, because we specified that “backbone” is a requirement for the “username” spoke, the resulting output is somewhat too large to paste here, but spokec has a feature that allows you to exclude specific dependencies from the generated spoke file by specifying them on the command-line prefixed with a ‘-‘. So, for example,

spokec username -backbone path/to/output.js

would create a spoke file with *only* the “username” spoke in it, which looks like this:

$("<style type='text/css'>").appendTo("head").text(decodeURIComponent(".username%20%7B%0A%20%20%20%20font-size%3A%2018px%3B%0A%20%20%20%20color%3A%20%23333%3B%0A%20%20%20%20white-space%3A%20nowrap%3B%0A%7D%0A"));
$("<div style='display: none'>").appendTo("body").html(decodeURIComponent("%3Cscript%20id%3D%22username-template%22%20type%3D%22text/template%22%3E%0A%3C%25%20if%20%28first_name.length%20%2B%20last_name.length%20%3E%3D%2020%29%20%7B%20%25%3E%0A%3C%25%3D%20first_name.substr%280%2C1%29%20%25%3E.%0A%3C%25%20%7D%20else%20%7B%20%25%3E%0A%3C%25%3D%20first_name%20%25%3E%0A%3C%25%20%7D%20%3E%0A%3C%25%3D%20last_name%20%25%3E%0A%3C%25%20if%20%28is_internal%29%20%7B%20%25%3E%28internal%29%3C%25%20%7D%20%25%3E%0A%3C%25%20else%20if%20%28is_masq%29%20%7B%20%25%3E%28masq%29%3C%25%20%7D%20%25%3E%20%0A%3C/script%3E%0A"));
var UsernameModel = Backbone.Model.extend({
   defaults: {
       first_name: "",
       last_name: "",
       is_internal: false,
       is_masq: false }
});

;
var UsernameView = Backbone.View.extend({
   className: 'username',
   render: function() {
       this.$el.html(this.template(this.model.attributes));
       return this;
   },
   template: _.template($('#username-template').html())
});
;

As you can see, the implementation of spokec assumes that jQuery is already included on the page, which for us is a practical assumption but would be easy to change if we wanted to. It should also be clear that the spoke abstraction makes very few other assumptions about how a specific spoke is implemented, as long as they can be represented as a series of javascript, CSS and HTML files. This allows us the flexibility to change the tools and libraries we are using but maintain a consistent and logical structure to our reusable components.

Getting Started

To start using spoke today, install it using pip:

sudo pip install spoke

The Future of Spokes

One type of content that we do not yet support in spokes is images; thus far we have just been using data URLs when we’ve needed to include images, but particularly for larger images this may become somewhat impractical. Another future enhancement we’ve been considering would make it much safer and easier to create reusable components by providing a way to automatically prefix CSS/HTML classes (and perhaps IDs) with a spoke identifier so that we can create very generic CSS class names without fear of a conflict with CSS classes in a different spoke. Doing this for CSS and HTML content is relatively straightforward, but to do so in javascript is a little trickier, although we have some ideas. Look for a future blog post once we’ve got solutions to this problem that we’re happy with!