This is part 1 of a series exploring how to build a full-pledged UML editor in pure JavaScript. 

 

Why JavaScript

I really love Python & its wonderful frameworks - they brought me pure joy & satisfaction for the past 6 years.

However, I'm forced to acknowledge that the times they are a-changin: logic moves back to the client-side, leaving the server-side to do almost nothing (just data access, usually). Further more, the server side turned into a series of disporate API's, which the client consumes, provided by different vendors - so now even the data access moves to the client, which talks directly with datastore providers.

 

The lingua-franca of the client-side, available anywhere there's a browser, is JavaScript. And, when I needed to write some very scalable server-side service, I also found JavaScript (node.js) doing a really great job even there.

With HTML5, there's no limitation to the abilities of JavaScript on the client-side.

 

In addition, the platform of computing is going thru an even larger change: mobile devices talking with cloud back-ends. I don't know whether the mobile-specific platforms will remain the mobile client-side technology, or the open-stack of the Web will arrive to the client. Time will tell.

 

The application

So, in this series I will develop an application in pure JavaScript, for Web & Mobile platforms. 

I do design very often, but don't have a UML editor that works for me: either they're not cloud-based & don't support collaboration, or they're too unusable, or just costing too much money.

So, I'll try to develop in this series a simple UML editor, which is

  • Cloud-based
  • Supports real-time collaboration
  • Works well on tablet & mobile
  • Free & open-source

 

Mock-up of the application:

 

 

 

Regarding the application name: the main consideration for a name should obviously be SEO - how easy it will be to find it in Google. So, given this imperative consideration, I came up with the name: model 

 

It's going to be pure client-side app - all static files downloaded from CDN to the client The app will consume remote 3rd-party API's, for example, a remote storage for persistence. Using a tool such as PhoneGap, I'll try to make it usable also as a Mobile app. Of course a pure MVC will be enforced: clear separations between data, business logic & presentation .

 

If you were given the task of writing a UML editor, which languages would you use?

 

 

Initial survey

 

Before starting out I've sent a couple of colleagues a survey, asking them about their activities & needs in respect to UML. The idea is to try understand who will be the 1st users of the editor (except for myself) & how to make it useful for them.

You can see (& fill out) the survey here.

 

 

I received some 7 responses, from which I've learnt that:
 

  •  Usage: A third of the responders use it often (every week). A third uses it once a month or few. Another third don't use UML at all.
  •  Tools: Half of the users use desktop tools (StarUML, Sparx, OpenOffice, Violet). Another hald uses online tools (WebSequenceDiagrams, Google Docs). Many said they also use paper & whiteboard.
  •  Diagram types: Most use Class diagram, Sequence diagram & Use-case. Also: Activity, State, Relationship, Work-flow.
  •  Activities: Almost everyone: Embed diagrams in design documents, Present them in design reviews, Collaborate on them with others. Also: Save them in the repository, Revise them when design changes. 
  •  Problems & needs: People expressed the need for online editor. Another requested feature is reverse engineering from existing source repository                      

 

Decisions

 

Possible frameworks & technologies

 

I made a check-list of frameworks & technologies that I'd like to use in this app:

  • CoffeeScript - a high-level language that compiles into JavaScript, & abstracts some of the difficult & bad parts of the language
  • BrowserID - an email-based authentication mechanism by Mozilla
  • RemoteStorage - a mechanism to store data from JavaScript on remote providers
  • Datastore providers: IrisCouch
  • Backbone.js - a popular MVC framework for javascript
  • Backbone.dualStorage - for Backbone storage on both local (HTML5) & remote storage
  • Backbone.boilerplate - template utilities for Backbone
  • Underscore.js - clean & feature-rich JavaScript framework
  • d3.js - data-driven visualization framework
  • Bootstrap2 - HTML5 template
  • Uijet - UI widgets framework
  • Faye & Kue- for real-time collaboration
  • Selenium2 - unit-tests framework for JavaScript * Sauce Labs - hosted service for cross-browser testing using Selenium
  • PhoneGap - framework for generatubg mobile editions of a Web app
  • Google Page Speed- for optimizing loading & performance
  • Google AnalyticsProvide the analytics on a client-side app
  • CloudBees - Continuous Integration & Testing
  • ??? - Hosting & continuous deployment

 

 

Code hosting & IDE

 

BitBucket - source hosting
Since I prefer mercurial, I usually host my projects on BitBucket. The source code can be found in: https://bitbucket.org/dibau/model

 

WebStorm - IDE
This full-pledged JavaScript IDE is based on JetBrains' Java IDE: IntelliJ IDEA. 

The main advantage of using it is the amazing intelligence it has in understanding code, detecting errors & enabling refactoring

I prefer it on any other IDE on this sole ground: especially with dynamic languages, it's crucial to have an editor that understands your code, & can validate it intelligently

 

WebStorm supports debugging of JavaScript ran on the browser from the IDE & comes with many plugins, such as CSS-X-Fire which enables you to update CSS attributes in your browser & automatically update them in your source files. For example, WebStorm can infer the type of function parameters (which aren't typed of course) & detect when a call passes parameters of a different type!

 

 

Debug & inspection

 

Browsers debug console
All modern browser have an interactive console, crucial for debugging & exploratory development

 

JSFiddle                

An online tool to experiment with JavaScript & share it with others                  

 

JSLint

This tool by Douglas Corkford (the main guru & reviver of modern JavaScript) to validate the quality & correctness of your JavaScript code

 

 

 

Templates

 

The project can be started with several templates:

 

Boilerplate
 Many nice features for HTML5 support. Comes with Google Analytics code & also script to install Chrome Frame
Initializr
Service that let's you customize the template, which is based on Boilerplate. Customizing it is a bit annoying - because there are incompatibilities between possible elements, & it takes time to succeed resolving the conflicts. Uses the Modernizr framework for detecting & leveraging HTML5 & CSS3
Bootstrap2
Best practices template by Twitter. Includes both resources & widgets.      

All of these templates support the infra to make the app responsive - adapt to different devices (Computer, Tablet, Mobile, TV)

 

I decided to start with Bootstrap2We'd also take stuff from Initializr/Biolerplate, such as: Chrome Frame & Google Analytics

 

 

 

 

Design & code

 

The initial version of the product is very simple - it let's you describe classes in text, and draws the corresponding class diagram. Here's the back-of-the-envelope design:

                 

Main page (app.html)

The application has 1 main page, using Bootstrap2 as templae. The code is straight-forward:

 

  <body>

    <div class="navbar navbar-fixed-top">
      <div class="navbar-inner">
        <div class="container-fluid">
          <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          </a>
          <a class="brand" href="#">Model</a>
          <div class="nav-collapse">
            <ul class="nav">
              <li class="active"><a href="#">Home</a></li>
              <li><a href="#about">About</a></li>
              <li><a href="#contact">Contact</a></li>
            </ul>
            <p class="navbar-text pull-right">Logged in as <a href="#">username</a></p>
          </div><!--/.nav-collapse -->
        </div>
      </div>
    </div>

    <div class="container-fluid">
      <div class="row-fluid">
        <div class="span3">
          <div class="well sidebar-nav">
            <ul class="nav nav-list">
              <li class="nav-header">Project 1</li>
              <li class="active"><a href="#">Class Diagram 1</a></li>
              <li><a href="#">Diagram 2</a></li>
              <li><a href="#">Diagram 3</a></li>
              <li><a href="#">Diagram 4</a></li>
              <li class="nav-header">Project 2</li>
              <li><a href="#">Diagram 1</a></li>
              <li><a href="#">Diagram 2</a></li>
              <li><a href="#">Diagram 3</a></li>
              <li><a href="#">Diagram 4</a></li>
              <li><a href="#">Diagram 5</a></li>
              <li><a href="#">Diagram 6</a></li>
              <li class="nav-header">Project 3</li>
              <li><a href="#">Diagram 1</a></li>
              <li><a href="#">Diagram 2</a></li>
              <li><a href="#">Diagram 3</a></li>
            </ul>
          </div><!--/.well -->
        </div><!--/span-->
        <div class="span9">
          <div class="hero-unit">
            <h2>Class Diagram 1</h2>
            <p>This is the diagram description.</p>
          </div>

            <ul id="tab" class="nav nav-tabs">
                <li class="active"><a href="#diagram" data-toggle="tab">Diagram</a></li>
                <li class=""><a href="#source" data-toggle="tab">Source</a></li>
            </ul>
            <div id="myTabContent" class="tab-content">
                <div class="tab-pane fade active in" id="diagram">
                    <div id="chart" style="width: 675px; height: 360px;"></div>
                </div>
                <div class="tab-pane fade" id="source">
                    <div>
                        <textarea id="source_input" rows="20" style="width: 675px; height: 341px;"></textarea>
                        <p class="muted" style="float: right;">
                            For example:<br/><br/>
                            Class1 : BaseClass1<br/>
                            +method1<br/>
                            +method2<br/>
                            -method3<br/>
                            <br/>
                            Class2 : BaseClass2<br/>
                            +method1<br/>
                            +method2<br/>
                            -method3<br/>
                        </p>
                    </div>
                </div>
            </div>

        </div><!--/span-->
      </div><!--/row-->

      <hr>

      <footer>
        <p>(cc) Model UML editor 2012</p>
      </footer>

    </div><!--/.fluid-container-->

   

JavaScript models (model-models.js)

The 1st interesting place is the JavaScript in which the Backbone.js models are defined. There's a simple base class (DiagramElement), a class that extend it (Class) for representing classes, & a collection of classes (ClassDiagram).

 

 

var DiagramElement = Backbone.Model.extend({
    defaults: {
        "x": 0,
        "y": 0,
        "width": 40,
        "height": 100
    }
});

var Class = DiagramElement.extend({
    defaults: {
        "name": "",
        "super_classes": [],
        "public_methods": [],
        "private_methods": []
    },
    get_all_members: function () {
        var result = [];
        var name = this.get('name');
        if (this.get('super_classes').length > 0) {
            name += " : ";
            name += this.get('super_classes').join(",")
        }
        result.push(name);
        _.each(this.get('public_methods'), function(n) { result.push(n); });
        _.each(this.get('private_methods'), function(n) { result.push(n); });
        return result;
    }
});


var ClassDiagram = Backbone.Collection.extend({
    model: Class,
    source: "",
    get_classes: function() {
        var result = [];
        _.each(this.models, function(model) {
            result.push(model.get("name"));
        });
        return result;
    }
});

var current_diagram = new ClassDiagram();

   

JavaScript controllers (model-controllers.js)

The contoller file contains the parser of the text in which you describe classes. The parser goes over the text & creates Backbone models out of it. Then the controller creates the Drawer, that renders the class diagram, & binds it to the add event of the diagram (triggered whenever a new Class is added to the collection).

 

 

var SourceParser = {

    parse: function() {
        var previous_source = "",
            current_class = "",
            current_super_classes = [];

        var members_map = {
            "+": [],
            "-": []
        };

        var add_class = function() {
            var cls = new Class({
                "name": current_class,
                "super_classes": _.clone(current_super_classes),
                "public_methods": _.clone(members_map["+"]),
                "private_methods": _.clone(members_map["-"])
            });
            var existing_class = current_diagram.find(function(c) { return c.get('name') == cls.name});
            if (existing_class) {
                existing_class.set(cls);
            }
            else {
                current_diagram.add(cls);
            }
            current_class = "";
            current_super_classes = [];
            members_map["+"] = [];
            members_map["-"] = [];
        };

        return function() {
            var source = $("#source_input").val();
            if (source != previous_source) {
                previous_source = source;
                var lines = source.split("\n");
                _.each(lines, function(line) {
                    if (line.trim().length == 0) return;
                    var first_char = line.charAt(0);
                    if (members_map[first_char]) {
                        members_map[first_char].push(line.substr(1));
                    }
                    else {
                        if (current_class != "") {
                            add_class();
                        }
                        if (line.indexOf(":") >= 0) {
                            var parts = line.split(":");
                            current_class = parts[0].trim();
                            current_super_classes = parts[1].trim().split(",");
                        }
                        else {
                            current_class = line;
                        }
                    }
                });
                add_class();
            }
        }
    }


};


$(function () {
    drawer.init();

    var parse = SourceParser.parse();

    $('a[data-toggle="tab"]').on('shown', function (e) {
        parse();
    });

    current_diagram.on("add", drawer.draw_class, drawer);
});

   

JavaScript widgets (model-widgets.js)

The final source file defines the ClassDiagramDrawer, which uses the d3.js library to tramsform the Backbone models into SVG elements. d3.js is a very powerful library, which we'll explore more deeply in following posts. The main idea is to associate a data array with DOM elements, & create DOM elements for new data.

 

var ClassDiagramDrawer = {
    svg: null,
    current_offset: 0,
    default_class_width: 150,
    default_member_height: 20,
    default_member_spacing: 20,
    class_containers: {},

    init: function() {
        var w = 675,
            h = 360;

        var pack = d3.layout.pack()
            .size([w - 4, h - 4])
            .value(function (d) {
                return d.size;
            });

        this.svg = d3.select("#chart").append("svg")
            .attr("width", w)
            .attr("height", h)
            .attr("class", "pack")
            .append("g")
            .attr("transform", "translate(2, 2)");        
    },
    
    draw_class: function(model) {
        var data = model.get_all_members(),
            x = this.current_offset,
            y = 50,
            margin = 10;

        var g = this.svg.append('g')
            .attr("x", x)
            .attr("y", y);
        this.class_containers[model.get('name')] = g;

        g.append('rect')
            .attr("x", x)
            .attr("y", y)
            .attr("width", this.default_class_width)
            .attr("height", data.length * this.default_member_height);

        var that = this;
        g.selectAll("text")
             .data(data)
           .enter().append("text")
             .attr("x", x + margin)
             .attr("y", function(d, i) { return y + margin + i * that.default_member_height; })
             .attr("dy", ".35em") // vertical-align: middle
             .text(String);

        g.append("line")
            .attr("x1", x)
            .attr("y1", y + this.default_member_height)
            .attr("x2", x + this.default_class_width)
            .attr("y2", y + this.default_member_height);

        this.current_offset += this.default_class_width + this.default_member_spacing;
    }

};

var drawer = ClassDiagramDrawer;

 

 

 

Screenshots

 

 

Finally, here's how the 1st version looks like:

 

 

 

 

 

Next..

In the next parts, I'll try to make a real application out of this prototype, & alsp adopt better frameworks & technologies, such as CoffeeScript.