Clean
Client/Server
Code

About Me

Why are you here?

Front End Problems

What you'll be seeing

Agenda

  1. Writing and structuring CSS better

  2. JavaScript that reacts to workflow instead of guiding it

  3. How to drive client code with well written server code

CSS Troubles

Style Sheets != CSS

CSS != XPath

Bad:

.student-list li.active a { text-decoration: none; }

Good:

a.active-student-link { text-decoration: none; }

Poor separation

Mixing typography, colors, position, layout...

Brittle

As Peter showed us.

Files

Project Structure

GZip, Bundle, Minify, Cookieless

Right away, first thing, always...

Files (continued)

Serving Files (bad)

<link rel="stylesheet" href="/content/css/vendor/bootstrap/bootstrap.css">
<link rel="stylesheet" href="/content/css/custom/layout.css">
<link rel="stylesheet" href="/content/css/custom/typography.css">

Bundling (good)

<link rel="stylesheet" href="/content/css/all.css">

Bundling w/ cache buster (better)

<link rel="stylesheet" href="/content/css/all.css?_=198539853">

Bundling per version (best)

<link rel="stylesheet" href="/content/238d9b6/css/all.css">

Cascading Style Sheets

<div id="door" class="opened">
  <img src="icon-open.png" class="icon open" />
  <img src="icon-close.png" class="icon close" />
  ...
</div>
#door .open, #door .close {
  display: none;
}
#door.opened .open, #door.closed .close {
  display: block;
}
function (data) {
  $('#door').attr('class', data.state);
}

Cascading Walkthrough

<div id="door" class="{{ state }}">
  <img class="icon open" /><!-- default: none, opened: block -->
  <img class="icon close" /><!-- default: none, closed: block -->
  ...
</div>
#door .open, #door .close {
  display: none;
}
#door.opened .open, #door.closed .close {
  display: block;
}

Agenda

  1. Writing and structuring CSS better

  2. JavaScript that reacts to workflow instead of guiding it

  3. How to drive client code with well written server code

Giving your JavaScript fewer responsibilities

Why would you want to do less on the client side?

Testing JS is hard

Angular = Grunt + Protractor + Jasmine + Karma + PhantomJS + Selenium...

Less is more consistent

If you're testing business logic on the server, why test in the browser if you don't have to?

Confidence in executable outcome increases as lines of code approaches zero.

Chris Missal

AJAX – Part I

// Initialize the Ajax request
var xhr = new XMLHttpRequest();
xhr.open('get', 'send-ajax-data.php');
 
// Track the state changes of the request
xhr.onreadystatechange = function(){
    // Ready state 4 means the request is done
    if(xhr.readyState === 4){
        // 200 is a successful return
        if(xhr.status === 200){
            alert(xhr.responseText); // 'This is the returned text.'
        }else{
            alert('Error: '+xhr.status); // An error occurred
        }
    }
}
 
// Send the request to send-ajax-data.php
xhr.send(null);
Code sample stolen from Wikipedia

AJAX – Part II

$.ajax({
  type: "GET",
  url: "/api/endpoint",
  data: { id: 1024, location: "Austin, TX" }
  dataType: "json"

}).done(function() {
  // do something neat

}).fail(function() {
  // aw crap

});

AJAX – Part III

// some setup file
angular.factory('Cart', function ($resource) {
  return $resource('/api/cart/:id');
});
// a different file, i.e. CartCtrl.js
var cart = Cart.get(2048);

Reducing JavaScript Decisions – Part I

<div class="message-links">
  <a href="/messages/402/delete" class="confirm-action">Delete 402</a>
  <a href="/messages/176/delete" class="confirm-action">Delete 176</a>
</div>
define(['jquery'], function($) {
  $('.confirm-action').click(function (e) { // "who"
    if (!confirm('Delete this message?')) { // how
      e.preventDefault(); // what
    }
  });
});

Reducing JavaScript Decisions – Part II

<div class="delete-links">
  <a href="/messages/40132/delete"
     data-confirm="Delete this message?">Delete 40132</a>

  <a href="/links/98176/delete"
     data-confirm="Delete this blog post?">Delete Blog Post</a>
</div>
define('confirm', ['jquery'], function($) {
  var setup = function(selector) {
    $(selector).on('click', function (e) {
      var message = $(this).data('confirm');
      if (!confirm(message)) {
        e.preventDefault();
      }
    });
  };
  return { init: setup }
});
require(['confirm'], function (confirm) {
    confirm.init('.delete-links a');
});

Agenda

  1. Writing and structuring CSS better

  2. JavaScript that reacts to workflow instead of guiding it

  3. How to drive client code with well written server code

State Values in JSON

State Intro = new State(1, "welcome", "Welcome");
State EnterName = new State(2, "enter-name", "Enter Your Name");
State EnterAddress = new State(3, "enter-address", "Enter Your Address");

State Enumeration

public class State : Enumeration<State, string> {
  private State(int order, string value, string displayName)
    : base(value, displayName) {
    Order = order;
  }
  public int Order { get; private set; }
  public State Next {
    get { return GetAll().FirstOrDefault(x => x.Order == Order + 1); }
  }
  public State Previous {
    get { return GetAll().FirstOrDefault(x => x.Order == Order - 1); }
  }
  public decimal Progress {
    get {
      return Math.Round((Order - 1m)/(GetAll().Length - 1m), 2)*100m;
    }
  }
}

Cart Controller

[ActionName("next"), HttpPut]
public Cart Next(string id) {
    // if validation passes
    var cart = _cartProvider.Get(id);
    cart.State = cart.State.Next;
    return cart;
}
angular.module('TheApp').service('Presenter', ['$location', '$cookieStore', function($location, $cookieStore) {
  this.update = function ($scope, data) {
    // ...
    $scope.disableNext = !data.state.next;
    $scope.disableBack = !data.state.previous;
    $scope.disableDone = data.state.next;
    $scope.progressStyle = { 'width': data.state.progress + '%' };
    // ...
  }
}]);

Demo App

Thanks!

/