My name is Ismael Chang Ghalimi. I build the STOIC platform. I am a stoic, and this blog is my agora.
It really wasn’t easy, but Florian and I managed to complete the first phase of our record view redesign (old on top, new underneath). Many controls need to be adjusted and many parts of the user interface are broken as a result, but the baseline is in place, and it looks a lot better than the previous version. We hope to have a stable user interface by the middle of next week.
It really wasn’t easy, but Florian and I managed to complete the first phase of our record view redesign (old on top, new underneath). Many controls need to be adjusted and many parts of the user interface are broken as a result, but the baseline is in place, and it looks a lot better than the previous version. We hope to have a stable user interface by the middle of next week.

It really wasn’t easy, but Florian and I managed to complete the first phase of our record view redesign (old on top, new underneath). Many controls need to be adjusted and many parts of the user interface are broken as a result, but the baseline is in place, and it looks a lot better than the previous version. We hope to have a stable user interface by the middle of next week.

Take a look at our current record view. This morning, that’s pretty much what we did, and we came to the conclusion that it became too complex. Too many fonts, too many buttons, too many lines, borders, margins, and paddings. So we decided to trim it down, a lot… Florian is working on it right now, and I hope to have a first couple of screenshots later tonight. It should take another day to get this new design back to the level of robustness that we had so far, but it should make it a lot more usable. After that, we’ll work on the JSON editor control, as well as a WYSIWYG markdown control based on Hallo. This one will come with a positively mind-blowing feature that will demonstrate the full power of the platform in just a few keystrokes.

Stay tuned…

We’re starting to get very decent support for multiple relationships (aka N-N or many-to-many). For example, you can see on the top screenshot the 28 regions that make the European Union. Note how we display it on the object view (“28 regions”) and on the record view (list of records). For its part, the bottom screenshot shows what is displayed when we click on the corresponding cell in the object view. It will eventually be replaced by the multiple relationship control, but it shows how multiple relationships are currently serialized in JSON. Internally to the database, we’re actually storing them through dedicated lookup tables, but Pascal needs to make a few changes to mapper before we can use them directly. For the time being, we’re relying on temporary data being stored in the record itself.
We’re starting to get very decent support for multiple relationships (aka N-N or many-to-many). For example, you can see on the top screenshot the 28 regions that make the European Union. Note how we display it on the object view (“28 regions”) and on the record view (list of records). For its part, the bottom screenshot shows what is displayed when we click on the corresponding cell in the object view. It will eventually be replaced by the multiple relationship control, but it shows how multiple relationships are currently serialized in JSON. Internally to the database, we’re actually storing them through dedicated lookup tables, but Pascal needs to make a few changes to mapper before we can use them directly. For the time being, we’re relying on temporary data being stored in the record itself.

We’re starting to get very decent support for multiple relationships (aka N-N or many-to-many). For example, you can see on the top screenshot the 28 regions that make the European Union. Note how we display it on the object view (“28 regions”) and on the record view (list of records). For its part, the bottom screenshot shows what is displayed when we click on the corresponding cell in the object view. It will eventually be replaced by the multiple relationship control, but it shows how multiple relationships are currently serialized in JSON. Internally to the database, we’re actually storing them through dedicated lookup tables, but Pascal needs to make a few changes to mapper before we can use them directly. For the time being, we’re relying on temporary data being stored in the record itself.

View meta-model

Following the MVC pattern, we’ve properly separated our record view’s View from its Model, and we’re linking the two through bindings from the View and through control flow statements from the Controller. But we went a step further by also defining a meta-model for the view itself.

The justification for it is the following: the record view’s job is to display a form for the record of an object, plus additional information such as workflow status and changes timeline. For the form part, fields can be organized across multiple sections, which themselves are displayed through multiple tabs. While our current record view only supports the canonical generation of a form based on the model of the record’s object, a future version will let users create their own custom forms for any object.

In such a context, we need a way to define the structure of a form, by nesting references to fields within sections and sections within tabs. Then, we need to generate a canonical form structure for objects that do not have any custom form, or dynamically lookup a pre-defined form structure. Finally, we need to merge this form structure with the record’s data, the object’s model, and the definition of all datatypes used by fields of the object (a field’s datatype defines its widget).

This is pretty much what we did yesterday. At first, I was expecting this project to take a few days. We would have to develop a form meta-model from scratch, then implement a fairly sophisticated template (the View) to merge data and meta-data about four different entities (form, record, object, datatype). Instead, we were done in about three hours, and all it took fit within less than 60 lines of code:

  • HTML template (less than 40 lines of code, widgets excluded)
  • Canonical form generator (less than 20 lines of code)
  • Canonical form meta-model (simple JSON dynamically populated by the form generator)

How come? AngularJS works. Plain and simple…

Adding classes to elements within an iterator

AngularJS offers support for powerful expressions within templates. Unfortunately, you cannot write a control flow statement in an expression (conditionals, loops, or throw). According to the documentation, “the reason behind this is core to the angular philosophy that application logic should be in controllers, not in the view. If you need a conditional, loop, or to throw from a view expression, delegate to a JavaScript method instead.”

This is a bit of a shame, especially when trying to add a class to specific elements within an iterator (loop, using ng-repeat), either the first one, the last one, or any element in the middle. This is especially frustrating since AngularJS conveniently provides special properties for it ($index, $first, $middle, $last). Separation of concerns should be a philosophy, not a religion, and many control flow statements really belong to the view, not to the controller.

In order to work around this problem, we just created the following helper function:

$scope.isOn = function(elementPosition, className) {
  return elementPosition ? className : null;
}

We added this function to our controller and invoked it from our templates like that:

class="{{isOn($first, 'active')}}"

This will add the active class to the first element within a list. Of course, the very same function can be used with $middle and $last as well. We implemented this helper function in order to activate the first tab of our record view now that we’re dynamically generating the record view’s structure based on a JSON definition (more on this later).

UPDATE: We just learned that ternary operators are supported in expressions. This was documented in this stackoverflow answer and we tested it within our code. It removes the need for the helper function we implemented, and makes the code a lot more readable:

ng:class="{true:'active', false:''}[$first]"

Font Awesome

This morning, I’ve been working on adding support for icons in our new record view. Unfortunately, I quickly realized that Fontello (used for icon fonts) was suddenly interfering with Twitter Bootstrap’s icons, since both were using CSS classes with the .icon- prefix. I briefly considered modifying one of the two libraries, but decided against it for obvious maintenance reasons. While looking online for a solution, I stumbled upon Font Awesome, which did an awesome job (pun intended) of packaging an icon font for use with Twitter Bootstrap.

After I had it integrated within our framework, all I had to do was to change the icon references for a few datatypes, and I was up and running. I then applied some Twitter Bootstrap colors (@linkColor and @linkColorHover) and added a Twitter Bootstrap Tooltip to make it a bit more dynamic (when you mouse over an icon, its color gets darker and a tooltip is displayed).

It looks… awesome!

AngularUI and Bootstrap integration

Now that we’ve decided to go with AngularJS for our new user interface framework, we can start re-implementing our record view and query builder. For this purpose, we will need a few widgets, which we will get from AngularUI and Bootstrap.

On that front, I am pleased to report that things are looking pretty good. After a couple hours of work, I implemented field documentation using Bootstrap Tooltips and field masks using AngularUI Mask, which itself is based on jQuery Masked Input.

For the former, all I had to do was to activate tooltips with this jQuery statement:

$("[rel=tooltip]").tooltip();

And for the latter, I added the following line to my controller:

angular.module('sutoiku', ['ui']);

Step by step, I am re-implementing all the features of the record view, and while I’m still finding my way around all the capabilities offered by AngularJS, my first impression is that the code I’m writing is a lot smaller, and a lot more readable. It will be interesting to see how much smaller it ends up being compared to the previous version. As a reminder, here are the features offered by the record view:

  • User-defined tabs
  • User-defined sections
  • User-configured columns
  • User-ordered fields
  • User-extensible input widgets (Check Box, Text Box, Text Area, etc.)
  • Field labels (displaying the name of the field)
  • Field placeholders (displaying some sample content)
  • Field data masks (for datatypes like phone number)
  • Field documentation (in a popup window)
  • Field highlighting (highlighting of the field being edited)
  • Editable fields (uneditable fields are disabled)
  • Hidden fields (invisible to the user but still present in the form’s data)
  • Informative fields (disabled and displayed on a separate tab)
  • Required fields (with asterisk next to the field’s label)
  • Calculated fields (with real-time calculation, if we can implement it)
  • Conditional fields (for which editable, hidden, and required are set by rules)
  • Error messages (for validation errors and missing required fields)
  • Prefixes and suffixes (for currency amounts, percentages, and quantities)
  • Field styles (for customizing the way fields display data)
  • Field icons (for displaying field details)
  • Field details (for displaying details of target records referenced in relations)
  • Dynamic lookups (for relationship fields)
  • Formatters (for displaying data)
  • Parsers (for gathering data)
  • Validators (for validation)
  • Buttons (save and cancel)

Tabs

Refactoring our CSS stylesheet allowed us to refine the styling of our tabs by reusing the styling of our buttons. This makes the look and feel of our record view more consistent, and it reduces the amount of code we have to maintain. Here is the final result, cropped to fit on this blog.

PS: Yes, I know, we need to properly style this ugly checkbox…

CSS refactoring completed

Our CSS refactoring is done. It certainly was a lot harder than I had expected it to be, but it was worth the effort. The record view now scales gracefully with window dimensions and font sizes, while the CSS code is cleaner than it’s ever been, all thanks to the magic of jQuery and LESS.

Here is what it looks like with a 14px font size and 280px input width:

And here is the 18px/350px version:

And if that was not obvious already, the currency symbol stays where it should as you type:

Now, I just wish all web forms were like that…

I love LESS

I just discovered that LESS provides a full set of color functions, so I decided to try them out on our field detail icons (the pin and arrow shown on the following screenshot). I wanted to see if I could use these functions to dynamically change an icon’s color when the user mouses over it.

 

All it took are two lines of CSS code:

.suIcon:hover {color: darken(@blue, 20%);}
.suIconInvalid:hover {color: darken(@red, 20%);}

I love LESS, more and more.

CSS refactoring

Our CSS refactoring is almost done, but it took a lot longer than expected. A couple hours quickly turned into a couple days, as we discovered more and more areas that needed attention. Essentially, when you want a complex piece of user interace to gracefully scale with the default size of fonts, every single CSS rule needs careful crafting. Thanks to LESS, we could do that cleanly by using variables, mixins, parametric mixins, and operations. Ultimately, we managed to:

  • Reset CSS styles
  • Externalize all colors through variables
  • Externalize all fonts through variables
  • Parametrize all dimensions
  • Replace all static font sizes (using px) by dynamic ones (using em)
  • Make all complex rules (like border-radius) browser independant
  • Reduce the repetition of rules to the absolute minimum
  • Include copyright notices for all embedded fonts
  • Make CSS styles visible to jQuery

The last item deserves some explanation: in some cases, jQuery needs to know the style of some elements, and how this style materializes itself on the web browser. For example, in order to properly implement prefixes and suffixes in text boxes, jQuery needs to know the actual width (in pixel) of the monospaced font (Inconsolata in our case) used to display the quantity symbol of numerical values like Currency Amount, Percent, or Quantity.

Because this width depends on the default font size (which can be changed by the user) and the styles of parent elements, it cannot be known in advance. Instead, it has to be detected by jQuery, and for that to happen, a sample character needs to be displayed while the page is loading, measured, then removed before the page is actually displayed to the end-user.

Painful but necessary…

CSS reset

As we were cleaning up our CSS stylesheet, we realized that we had forgotten to reset CSS styles. We quickly filled that gap by including Eric Meyer’s Reset CSS, which instantly fixed quite a few nagging issues. We can now focus our efforts on fine tuning everything.