Indico front-end

A culinary journey from spaghetti to lasagna

The spaghetti

Validation

(server-side)

wvars["usersLimit"] = i18nformat("""
    <tr>
        <td nowrap class="displayField"><b> _("Max No. of registrants"):</b></td>
        <td width="100%%" align="left">%s</td>
    </tr>
""") % regForm.getUsersLimit()

HTML

(python-embedded)

html = "<table bgcolor=\"gray\"><tr><td bgcolor=\"white\">\n<font size=\"+1\"" +
       "color=\"red\"><b>%s</b></font>\n</td></tr></table>"%self._params["msg"]

Forms

(inconsistent)

Components

(inconsistent)

CSS

(messy)

ul.UIPeopleList li.listMessage:hover, ul.UISuggestedPeopleList li.listMessage:hover
{
    background-color: transparent !important;
}
ul.UIPeopleList li .nameLink, ul.UIAuthorList li .nameLink {
    padding: 0px 0px 4px 30px;
}

ul.UIPeopleList li .foundUserEmail, ul.UISuggestedPeopleList li .foundUserEmail {
    font-style: italic;
    font-size: 8pt;
    padding-left: 5px;
    line-height: 15px;
    display:none;
}

ul.UIPeopleList li:hover > .foundUserEmail, ul.UISuggestedPeopleList li:hover > .foundUserEmail {
    display:inline;
}

1 single file. +8000 LOC.

JavaScript

(unscoped)

<script type="text/javascript">
    var recordingSwitch = false;
    var toggleRecordingCapableRooms = function () {
        IndicoUI.Effect.toggleAppearance($E('recordingCapableRoomsDiv'));
        if (recordingSwitch) {
            $E("recordingRoomsText").dom.innerHTML = $T("See list of record-able rooms.");
        } else {
            $E("recordingRoomsText").dom.innerHTML = $T("Hide list of record-able rooms.");
        }
        recordingSwitch = !recordingSwitch;
    }
</script>
            $E("recordingRoomsText").dom.innerHTML

Icons!

(image-based)

loading.png

meeting.png

slides.png

group.png

delete.png

diskettes.png

key.png

logo.png

admins.png

Ingredients for

front-end spaghetti

  • 1tbps of             server-side validations
  • 1 whole              HTML-stuffed Python
  • 500g of             inconsistent components
  • 1l of                    hard-to-trace JS code
  • 78kg of              long, entangled CSS
  • Assortment of  image icons

A better recipe

Validation & Rendering

class UserForm(Form):
    name = StringField("First name", [DataRequired()])
    l_name = StringField("Last name", [DataRequired()])
    enabled = BooleanField("Enabled")

Forms and field definition

form = UserForm()
form.name(class_="main")
<input type="text" name="name" value="" 
       class="main" id="name" required>

Rendering

class PersonForm(UserForm):
    age = IntegerField("Event", [DataRequired()])

    def validate_age(form, field):
        if field.data < 13:
            raise ValidationError("You must be >13")

Inheritance, validation

form = PersonForm()
if form.validate_on_submit():
    # handle request

Template engine

{% extends "layout.html" %}

{% block body %}
  <ul>
  {% for user in users %}
    <li>
      <a href="{{ user.url }}">
        {{ user.username }}
      </a>
    </li>
  {% endfor %}
  </ul>
{% endblock %}

Inheritance, loops, interpolation

{% macro input(name, value='', type='text', size=20) -%}
  <input type="{{ type }}" 
         name="{{ name }}" 
         value="{{ value|e }}" 
         size="{{ size }}">
{%- endmacro %}

Macros

{{ input('jetons', 1, type='number') }}

CSS with superpowers

$font: Helvetica;
$color: #333;

h1 {
  font: $font;
  color: #fff;
}

a {
  color: $color;
}

.button {
  font: $font;
  color: $color;
}
h1 {
  font: Helvetica;
  color: #fff;
}

a {
  color: #333;
}

.button {
  font: Helvetica;
  color: #333;
}

Variables

nav {
  ul {
    margin: 0;
    padding: 0;
    list-style: none;
  }

  li { 
    display: inline-block; 
  }

  a {
    display: block;
    padding: 6px 12px;
    text-decoration: none;
  }
}
nav ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

nav li {
  display: inline-block;
}

nav a {
  display: block;
  padding: 6px 12px;
  text-decoration: none;
}

Nesting

@mixin radius($r) {
  -webkit-border-radius: $r;
  -moz-border-radius: $r;
  -ms-border-radius: $r;
  border-radius: $r;
}

.box1 { 
  @include radius(4px);
}

.box2 { 
  @include radius(4px); 
}
.box1 {
  -webkit-border-radius: 4px;
  -moz-border-radius: 4px;
  -ms-border-radius: 4px;
  border-radius: 4px;
}

.box2 {
  -webkit-border-radius: 4px;
  -moz-border-radius: 4px;
  -ms-border-radius: 4px;
  border-radius: 4px;
}

Mixins

.message, .success, .error {
  border: 1px solid #ccc;
  padding: 10px;
  color: #333;
}

.success {
  border-color: green;
}

.error {
  border-color: red;
}
.message {
  border: 1px solid #ccc;
  padding: 10px;
  color: #333;
}

.success {
  @extend .message;
  border-color: green;
}

.error {
  @extend .message;
  border-color: red;
}

Inheritance

 The good ol' JS library

$('button.continue').html('Next Step...')
var hiddenBox = $('#banner-message');
$('#button-container button').on('click', function(event) {
  hiddenBox.show();
});

DOM Traversal and Manipulation

Event Handling

$.ajax({
  url: '/api/getWeather',
  data: {
    zipcode: 97201
  },
  success: function(result) {
    var html = '<strong>' + result + '</strong> degrees';
    $('#weather-temp').html(html);
  }
});

AJAX

Pixel perfect icons

Icomoon

Icomoon App

The Noun Project

Ingredients for

front-end lasagna

  • WTForms for preventing wtf
  • Jinja          for HTML templates
  • Sass          for manageable CSS
  • jQuery      for taming the DOM
  • Icomoon  for 🎉

Putting everything toghether

class TextListField(TextAreaField):

  def process_formdata(self, valuelist):
    lines = valuelist[0].split('\n')
    self.data = [line for line in lines]

  def _value(self):
    return '\n'.join(self.data) if self.data else ''

Custom WTForms fields #1

class EmailListField(TextListField):

  def process_formdata(self, valuelist):
    v = valuelist
    super(EmailListField, self).process_formdata(v)
    self.data = map(sanitize_email, self.data)

Custom WTForms fields #2

class IndicoPasswordField(PasswordField):
    """Password field that shows/hides the password."""

    widget = PasswordWidget()

Custom WTForms fields #3

class FeaturesForm(IndicoForm):
    payment = BooleanField("Enabled", 
                           widget=SwitchWidget())

Custom WTForms fields #4

{% for field in form %}
    <tr>
    {% if field.type == "BooleanField" %}
        <td></td>
        <td>{{ field }} {{ field.label }}</td>
    {% else %}
        <td>{{ field.label }}</td>
        <td>{{ field }}</td>
    {% end %}
    </tr>
{% endfor %}

WTForms rendering in Jinja

{% macro message_box(type, message) %}
  <div class="{{ type }}-message-box">
    <div class="message-box-content">
      <span class="icon"></span>
      <div class="message-text">
        {{ message }}
      </div>
    </div>
  </div>
{% endmacro %}

UI components in Jinja

Color palette in Sass

$darker-blue: #005272;
$dark-blue: #007CAC;
$blue: #5D95EA;
$pastel-blue: #CDE1FF;
$light-blue: #EDF3FD;

$red: #AF0000;
$dark-red: #A76766;
$pastel-red: #A76766;
$light-red: #F2DEDE;

CSS magic with Sass

@mixin _semantic-outline($color) {
    @include border-all($color);
    color: $color;
}
%semantic-outline {
  &.success {
    @include _semantic-outline($green);
  }

  &.error {
    @include _semantic-outline($red);
  }

  // ...
}

Configurable components in Sass

.i-button
.i-button.success
.i-button.danger.outline
.i-button.highlight.dashed

Sass partials

sass
├── _base.scss
├── _custom.scss
├── _modules.scss
├── _partials.scss
├── _widgets.scss
├── base
│   ├── _defaults.scss
│   ├── ...
├── modules
│   ├── _abstracts.scss
│   ├── ...
├── partials
│   ├── _buttons.scss
│   ├── ...
├── screen.scss

11 directories, 140 files

Everything has its place

partials
├── _boxes.scss
├── _buttons.scss
├── _dialogs.scss
├── _dropdowns.scss
├── _forms.scss
├── _icons.scss
├── _links.scss
├── _lists.scss
├── _qtips.scss
├── _sidebars.scss
├── _spinner.scss
├── _timelines.scss
├── _toolbars.scss
└── ...

jQueryUI widgets

declarative.js

Health benefits of

front-end lasagna

  • Extensible, reusable forms
  • Consistent HTML snippets
  • Uniform UI with CSS
  • Isolated, easy-to maintain JS
  • Crisp, light-weight icons

The lasagna

Alejandro Avilés

@OmeGak

Made with Slides.com