ECAS project - tech postmortem

Published at 02-07-2019

I recently worked on a project I really enjoyed :
The porting of a UNIT application into a Web version for the ECAS university.
You can find the project postmortem here.

In the article I detail a bit the technic behind the project and giving few tips for anyone who would like to tackle a similar project.

I gonna talk about :
  • Not using a Javascript framework
  • Javascript events
  • Viewport (device/window) size
  • Raphael JS

About (no) framework

I usually (totally) recommend the usage of a strong and well maintained framework.
However, this project is bit off the road, few basic forms and a large central graphical editor.
Experience will show you that frameworks have their own logic and don't like to be mixed with different ones. Plus they come with the cost of a lot of dependencies handling problematic you may not be facing at all (IE extra app weight). Finally this application is not meant to evolve so much in term of size. It's not a startup prototype that will grow in a thousand new more features.

So I opted for not using a framework but build a micro one instead. So what do you need ? What are the fundamentals you need to work efficiently ?
Based on my experience with BackboneJS and AnglarJS, I would tell something like :
  • Tools for handling Events, the core and glue for any Javascript application
  • A routing system, for navigation
  • A sort of MVC, MVVm system
  • A forms support
That would be a decent base to start.

Events everywhere

Coming from AngularJS, I enjoy RxJS. So I kind of copy the necessary behaviors.
A created a basic Event object equipped with the usual methods :
  • bind(method)
  • unbind(method)
  • fire(data)

Then I added above it, various trigger filtering configurations :
  • DebouncedEvent object, to avoid spamming for instance on window resize events
  • ValueChangedEvent, that is triggered only when fired data are changing
  • ... Check RxJS really

My little version is nothing to compare to RxJS, but certainly lighter.
I should try to extract and compare the both size later.

Routing for the win

A router is just a glorified configurable [state machine].
In my case the configuration is, a collection of states with :
  • A list of allowed states to transition to.
  • An optional object, implementing to `stage` / `unstage` methods, to handle current state without having to handle the events manually
  • And once again, events binding options :
    • onTransition(fromStateName, toStateName, handler)
    • onState(stateName, handler)

With that, you can handle your navigation and sub-navigation quite efficiently.

MVC is still a thing ?

I aimed to a MVC system at the beginning.
You will need anyway data (models), view (UI) and controllers (logic), right ?
I ended with the following mapping :
  • app/apis - Ajax call, sending models to the server
  • app/models - Objects handling some light logic and event bindings
  • app/templates - Html templates
  • app/controllers

I ended with a blurry line line between Controllers and Views.
So I stop distinguish them, and put them all in the controllers folder.
My controllers can :
  • Load an HTML template
  • Handle directly the UI via RaphaelJS
  • Act as a Proxy above multiple controllers to glue behaviors and layer extra logic
All of them expose of course various events.
And also implementing the `stage` / `unstage` twin methods required by the router.
Those methods define an expected passage for events `binding` / `unbinding` and other DOM related things, such as the injection of HTML templates.
Every controller is responsible to propagate the `stage` / `unstage` to his own sub items.

Basic, simple, just what I need.

Forms support forever

As mentioned, my app required few forms.
Form means :
  • Validations
  • Error display
  • Dirty/Pristine field display

Nothing complicated to DIY here, but required anyway.
Try to get it playing nice with the API layer to avoid unnecessary extra coding.

About javascript Events

If feel at ease with JS, you probably already know what I gonna detail below.

Events does not pop on demand.
Let's say you set an interval like so `setInterval(myFunction, 10)`.
You will not get an event every 10ms, that is unlikely to happen.
Instead you will have an event AT BEST every 10ms.
See, it's your browser that decide when events will be triggered, and lag is to be expected. In that case, the trick is to check the clock and adapt to whatever gap you may encounter.

If desktop browsers have grown up to show quite similar behavior now, this is not the case for mobile ones, where you'll find similar disparities as 10 years ago on desktop.
For instance, for the event trinity touchStart, touchMove, touchEnd; you have no guaranty (depending on the browser) to receive any touchEnd (or touchCancel depending on the case). If you don't use any framework, you have to set your own layer of abstraction to avoid this inconsistency.

It's a warning about going off-piste ...

About windows size

Full screen and centering are two hard topic in the web world.
It's a lasting battle to get the latest trick that will do what you want, in a elegant way, compatible with previous browser versions ...
Mobile browsers forced the introduction of the `View port`, that can be related as the user screen. In fact, it's smaller. The navigation bars are removed, designating the strict displaying area.

For this app, I needed to get :
  • A centered area
  • With a fix - hight/width- ratio
  • So, black lines on the left/right side or top/bottom side. Think about the same movie played on a phone held vertically or horizontally.

There are two kind of CSS units : Fixed and Relatives.
In the fixed ones you gonna find our usual `px` for pixel.
In the relatives, you gonna get `%` relative to the parent element.
The W3C introduced also the `vw` (as % of view port width) along `vh`, `vmin` and `vmax`. However, for that recent feature, you have to check browser compatibility.

Using the css property position (relative/absolute), I placed the various elements using `%` sizes to the css sizes and positioning properties : width, height, top, left, bottom and right. So when the screen size change, everything scale accordingly.
No `flexbox` or `v*` here for better retro-compatibility.

The centering of the main block was done with some JS code plugged on `$(window).resize(myCustomResizeCallbackFunction)`. I think it's totally possible to get the same result without JS but I need to watch carefully resizes because of RaphaelJS.

About RaphaelJS RJS

If you don't know this library, check it out, it's awesome.
It's an interface for SVG drawing. If you need to draw/animate/interact with complex graphical objects like graphs, curves, ... You need such a tool.
And that's exactly what I was asked to do, letting user draw a curve, drag and drop along that curve, stretch / rotate items on it ...

There are bunch of alternatives such as :

If you go for such a library, I advice you to centralize all the various styles, rather than letting peace of design code floating here and there and generating duplication.

The two small gap I faced with RJS were :
  • Minor API difference between objects
  • Window scaling

In RJS, a circle exposes its center as a [cx/cy] coordinate. You will not find any center for a rectangle, instead it exposes its top left corner as a [x,y] coordinate. I ended wrapping all the produced item to provide some desired shared behaviors.

For window scaling, I just redraw the full picture anytime I got a debounced resize window event. To do so, I just send a force "replay" to the router that would `unstage` / `stage` the current items, using the fresh window height / width properties.
Here you see how our simple design patterns play nicely together to make some problematic easy to handle in few lines of code.