UI Documentation Working Draft

In Progress

This is a working document. If you are looking for documentation about the OpenLMIS-UI, refer to docs.openlmis.org.

No information on this page should be considered final or correct.

UI Application Architecture - 2017-09-28

NOTE: In the OpenLMIS-UI development, we have never had a formal architecture — rather we just pointed the ui.router and said "URL Driven". We are well past the point where we need a formal and documented architeture because (1) we have non-DRY code (2) patterns are not shared well across developers (3) architecture makes maintenance easier.

This is a working document — please comment, add, and change.

UI Overview

The OpenLMIS-UI is a progressive web application that is URL Driven. Our application architecture stresses modular Javascript and DRY HTML markup, so implementers can customize logic and workflows to meet their needs. An extendable codebase is a maintainable codebase.

We aim to support "the last mile" which means we do our best to:

  • OfflineFirst
  • Minimize HTTP Requests
  • Display meaningful application state

That said, these are goals and not mandates — we all have deadlines and do our best to support the communities that use OpenLMIS — get people the tools they need first, and make it better second.

Build Process

→ See the current build process documentation

HTML & CSS

→ Keep it simple, semantic, and shallow — fancy things are hard, and avoid complex designs → I'll add more here later

Javascript Application Architecture

Modules

In the OpenLMIS-UI we consider a module a directory that contains a single feature. There are three buckets of modules:

  • Model Modules which bring data into the application, store application state, and/or process data
    • I expect this bucket to be most of our application code
  • View Modules which are CSS styles, UI Components, and other pieces that make views simpler
  • Controller Modules which are controllers, HTML, and configuration to make a screen show up
    • These should be skinny and ideally look very simple to an implementer

Note: I wanted to call these things Data, View, and Pretty modules - but making up new terms is usually a bad idea

No module should ever fit in more than one of these buckets. 

Moderator: The routeProvider

Since the OpenLMIS-UI is URL-Driven, every screen and modal should be directly accessible by a URL. AngularJS and ui.router provide a great start for an application, but we are hitting the limits of what we can do with this initial framework without it turning into a writing nightmare that is hard to document and test.

The routeProvider is a facade pattern that will essentially wrap ui-router. This will help us divide layout issues from application logic, and provides logical places for extension.

Routes will be created with configuration objects with "strong defaults", not specialized methods, so implementers can drop or change functionality without needing to worry about breaking the application architecture.

We are using the name "routeProvider" because other libraries are nice enough to leave us this namespace — since all application logic should point here, it should have the most obvious name.

NOTE: Using ui.router and existing conventions is fine for now, we will be moving towards a routeProvider based infrastructure and clean up existing ui.router code ... eventually.

Events: eventService

Events are great for decoupling modules and providing extension points. AngularJS has an event system built in — but we have different patterns of using it and are not even using all of the features provided.

We will be adding an eventService which will only pass events at the top-level of our application architecture. Technically, this means we are only exposing $rootScope.$emit and $rootScope.$on — with slightly different names. Only model modules should be aware of the event service.

NOTE: UI Components should also use events, but since they rely on the AngularJS architecture, they will use $rootScope for communication. I'll try to add specifics for UI Components when we revise the conventions and documentation for them.

Main Application Data Services

These are the primary services that are used through the OpenLMIS-UI application. The services could be put behind the routeProvider facade, but since they are primary, its best if other modules depend on them directly.

  • authorizationService keeps track of the current user, and what rights/permissions that user has
  • messageService handles internationalizing strings. Behind this service are other services that maintain the current state and other internationaliztion preferences
  • offlineService keeps track of if the OpenLMIS-UI is currently offline, and will emit events as that state changes
  • openlmisUrlService keeps track of where the OpenLMIS services live.
    • I'd like feedback if this should be extended so it actually does HTTP requests, which would make some markup cleaner
  • loadingService keeps track of if the UI is loading data, so url state transitions can be orchestrated
    • This could be hidden inside the routeProvider — I'd like feedback on this


HTML Conventions: 2017-09-21

QuestionResolutionJIRA Ticket



HTML Markup Guidelines

Less markup is better markup, and semantic markup is the best.

This means we want to avoid creating layout specific markup that defines elements such as columns or icons. Non-semantic markup can be replicated by using CSS to create columns or icons. In some cases a layout might not be possible without CSS styles that are not supported across all of our supported browsers, which is perfectly acceptable.

Here is a common pattern for HTML that you will see used in frameworks like Twitter's Bootstrap (which we also use)

<li class="row">
    <div class="col-md-9">
        Item Name
    </div>
    <div class="col-md-3">
        <a href="#" class="btn btn-primary btn-block">
            <i class="icon icon-trash"></i>
            Delete
        </a>
    </div>
 </li>
 <div class="clearfix"></div>

The above markup should be simplified to:

<li>
    Item Name
    <button class="delete">Delete</button>
</li>

This gives us simpler markup, that could be restyled and reused depending on the context that the HTML section is inserted into. We can recreate the styles applied to the markup with CSS such as:

  • A ::before pseudo class to display an icon in the button
  • Using float and width properties to correctly display the button
  • A ::after pseudo class can replace any 'clearfix' element (which shouldn't exist in our code)

See the UI-Styleguide for examples of how specific elements and components should should be constructed and used.

HTML Views

Angular allows HTML files to have variables and simple logic evaluated within the markup.

A controller that has the same name will be the reference to vm, if the controller is different, don't call it vm

General Conventions

  • If there is logic that is more complicated than a single if statement, move that logic to a controller
  • Use filters to format variable output — don't format variables in a controller

Unit Testing

We want to unit test our HTML files to:

  • Ensure HTML views display the information described in a ticket
  • Verify that HTML views connect to the contract exposed by their controller
  • Dynamic AngularJS elements act as defined when scope variables change

Unit tests in HTML should never:

  • Test behavior of anything outside of the scope of the HTML view (ie page flow, data processing)
    • Error states should be handled in HTML Form unit tests, and the directives that expose form logic
  • Directly test the configuration of the HTML
  • Rely on the HTML markup or CSS used to display an element

Example tests to be defined

Extending a HTML View

Stub stub stub.

HTML Form Markup

A goal for the OpenLMIS-UI is to keep business logic separated from styling, which allows for a more testable and extendable platform. Creating data entry forms is generally where logic and styling get tangled together because of the need to show error responses and validation in meaningful ways. AngularJS has built-in features to help foster this type of separation, and OpenLMIS-UI extends AngularJS's features to a basic set of error and validation features.

The goal here is to attempt to keep developers and other implementers from creating their own form submission and validation - which is too easy in Javascript frameworks like AngularJS.

An ideal form in the OpenLMIS-UI would look like this:

<form name="exampleForm" ng-submit="doTheThing()">
    <label for="exampleInput">Example</label>
    <input id="exampleInput" name="exampleInput" ng-model="example" required />
    <input type="submit" value="Do Thing" />
</form>

This is a good form because:

  • There is a name attribute on the form element, which exposes the FormController
  • The input has a name attribute, which allow for validation passed to the FormController to be passed back to the correct input
  • ng-submit is used rather than ng-click on a button

Unit Testing

Stub stub stub

Extending a HTML Form


OpenLMIS: the global initiative for powerful LMIS software