UI on the web is generally built with three technologies:
- HTML (specifically the DOM parts)
- CSS
- JS
In theory, the last two are optional and are just really there to make things fancy. In practice, fancy is a basic requirement.
So when you draw a piece of user interface on a users browser you do so using a mix of these 3 technologies.
Take a simple login form.
DOM (html):
<form id="login_form" action="/login" method="post">
<label>Email: <input type="text" name="email" /></label>
<label>Password: <input type="password" name="password" /></label>
<button type="submit">Gimme</button>
</form>
CSS:
#login_form {
width: 500px;
background: #eee;
}
#login_form input {
padding: 2px;
}
#login_form button {
text-transform: uppercase;
}
JS:
document.
getElementById('login_form').
getElementsByTagName('input')[0].focus();
Except we're fancy ruby developers so we'd probably do something like:
DOM (haml):
= form(:login_form, '/login') do
= labelled_field :email
= labelled_field :password, :type => :password
= submit_button "Gimme"
CSS (sass):
#login_form
:width 500px
:background #eee
input
:padding 2px
button
:text-transform uppercase
JS (jquery):
$(function() { $('#login_form input[name="email"]').focus() })
Well, not exactly. Sure that's what the code would look like. But each chunk would have to go in it's special spot. In Rails each chunk may part (but not all) of these files
- dom - /app/views/sessions/new.html.haml
- css - /public/stylessheets/sass/screen.sass
- js - /public/javascript/application.js
In Sinatra it's common to keep the files a little closer:
- dom - /views/login_form.haml
- css - /views/screen.sass
- js - /views/application.js
... which is a slight improvement.
Interestingly, raw html allows you to keep the individual parts right next to each other:
<styles>
....
<styles>
<form>
....
</form>
<script>
...
</script>
... but this is discouraged because it mixes the content part of the document with style and behavior instructions that should be in the header.
There are two main problems with the way current UI code is split up:
-
When you remove this UI you have to remove it from three different places
-
The styles and js are located in a file that by default will be included in other pages. This encourages two bad practices:
-
Premature generalisation. This is path to the dark side: "Seeing as I'm styling this button I should come up with a generic style for all buttons. I wonder if there is a button framework ..." and "Maybe I should create a class called 'focusFirst' and then add that class name to the form and then create a document ready event handler that automatically focuses the first element of the first form that has the 'focusFirst' class. I wonder if I can use a custom html attribute, maybe
... yeah, maybe I should start a thread on the WSG mailing list." -
Unnecessary specificity. For example the developer might realise that the #login_form styles will apply to any element with the id #login_form on any other page, including future no-yet-written pages. This may cause said developer to do something defensive like change the id to #session_new_login_form or they may decide to make the controller and action name a class of the body name like
<body class='session_new'>
and then target the form with a more specific selector:body.session_new #login_form
.
Good code hygiene would be keeping code that is related close together (modularization) while isolating it from unrelated code (encapsulation).
I propose we implement a new view architecture that takes this into account. We could do it for Rails or Sinatra with basically the same interface so let's start with Sinatra because it's fancier and has a pretty decent extension api.
The public interface for the initial implementation would consist of two parts:
-
HAML filters for capturing styles and script for rendering later by the layout (think content_for in rails). For example:
= form(:login_form, '/login') do = labelled_field :email = labelled_field :password, :type => :password = submit_button "Gimme"
styles: #login_form :width 500px :background #eee
input :padding 2px button :text-transform uppercase
script:
$(function() { $ ('#login_form input[name="email"]').focus() })
would be a valid view file.
-
The following helper methods:
view(name) # renders a view, captures any styles or script defined in the views, this is also the same method you would use for "partials" ... which are just views.
view_styles # call this from the header, will render out all the view styles in a <style> tag view_script # same as view_styles but for js
n.b. hidden markdown support activated