Building Bulletproof Views
Transcript of Building Bulletproof Views
* BUILDING BULLETPROOF VIEWSJOHN ATHAYDE & BRUCE WILLIAMS, LivingSocial RAILS CONF 2011 BALTIMORE, MARYLAND
*
JOHNa designer(who also develops)
*
BRUCEa developer(who also designs)
WE WORK AT
WE ARE WRITING A BOOKfor
HOW MANY HERE ARE
DEVELOPERS??
HOW MANY HERE ARE
DESIGNERS??
BOTH?http://www.flickr.com/photos/clydeorama/5451312303/
AGENDA
HOUR 1
*
Intro, SurveysViews are ComplexMarkup RefresherBuilding a Layout
Questions
AGENDA
HOUR 2
*
The Art of Template WritingQuestions
AGENDA
HOUR 3
*
Nailing NavigationMaintainable FormsDon’t Fear the Object
Going MobilePackaging Assets
* 1.VIEWS ARECOMPLEX
Views are still...
THE WILD WEST
<ol class="narrow_playlist"><% playlist.listings.each_with_index do |listing, i| -%><% if i < show %>
<% composition = listing.composition %> <% position = i %>
<li class="playlist_item"> <table class="playlist_listing"> <tr> <td class="listing_rank_col"> <%= position+1 %> </td> <td class="listing_title_col"> <h6><%#= link_to awesome_truncate(composition.title,30), label_release_track_path(composition.label.slug, composition.release.slug), :title => composition.title %></h6> <h6><%= link_to awesome_truncate(composition.title,30), composition_path(composition), :title => composition.title %></h6> </td> <td class="listing_icon_col" rowspan="3"> <ul class="playlist_site_actions"> <li class="preview"> <a class="sm2_link" href="<%= composition.preview_url %>"> <span class="hide_me">Preview</span> </a> <%= link_to_function(image_tag("icon_speaker_preview.png", :alt => "Preview", :title => "Preview"), "") %> </li>
<% if !composition.member_contributed %> <li class="add_to_cart"> <% if composition.network? %> <%= link_to image_tag("button_playlist_download.gif", :alt => "Download"), downloads_path(:audio_item_id => composition.audio_items.first.id), :method => :post%> <% else %> <%= link_to(image_tag("cart.gif", :alt => "Add to cart", :title => "Add to cart"), selections_path(:salable_id => composition.id, :salable_type => composition.class.name), :method => :post) %> <% end -%> </li> <% end -%> </ul> </td> </tr> <tr> <td></td> <td><h6 class="artist"><%= linked_profiles(composition) %></h6></td> </tr> <tr> <td></td> <td> <ul class="playlist_social_actions"> <%= render :partial => "/ratings/rating_list", :locals => {:class_name => composition.class.superclass.name, :ratable => composition}%> <li> <%= link_to_function(image_tag("icon_load_in_player.png", :alt => "Add to Playlist",:title => "Add to Playlist"), "addCompositionToPlayerPlaylist(#{composition.id})") %> </li> </ul> </td> </tr> </table></li>
<% end %><% end -%></ol>
QUICKLY CLEANEDMuch simpler and could be better...
<ol class="latest_uploads"> <% playlist.listings.each_with_index do |listing, i| -%> <% if i < show %> <% composition = listing.composition %> <% position = i %> <li class="playlist_item"> <p class="track_title"><%= link_to awesome_truncate(composition.title,30), composition_path(composition), :title => composition.title %></p> <p class="sm2_link"><a href="<%= composition.preview_url %>"><span class="hide_me">Preview</span> </a> <% if composition.network? %> <%= link_to image_tag("button_playlist_download.gif", :alt => "Download"), downloads_path(:audio_item_id => composition.audio_items.first.id), :method => :post%> <% else %> <%= link_to(image_tag("cart.gif", :alt => "Add to cart", :title => "Add to cart"), selections_path(:salable_id => composition.id, :salable_type => composition.class.name), :method => :post) %> <% end -%></p> <p class="artist_name"><%= linked_profiles(composition) %></p> <ul class="playlist_social_actions"> <%= render :partial => "/ratings/rating_list", :locals => {:class_name => composition.class.superclass.name, :ratable => composition}%> <li> <%= link_to_function(image_tag("icon_load_in_player.png", :alt => "Add to Playlist",:title => "Add to Playlist"), "addCompositionToPlayerPlaylist(#{composition.id})") %> </li> </ul> </li><% end %><% end -%></ol>
Views are still...
THE RED-HEADEDSTEPCHILD
MULTIPLE LANGUAGESMuch simpler and could be better...
➡ ERB/Ruby
➡ HTML
➡ CSS
➡ JavaScript/CoffeeScript
➡ Serialization formats
LACK OF FAMILIARITY & RULES
“This is really long. I should break it into partials.”
“This is really complex. I should make it a helper.”
http://www.flickr.com/photos/lastyearsgirl_/2882447041
DESIGNERISSUES
* 2.MARKUPREFRESHER
HOW MANY HERE
USE ANDUNDERSTANDHTML5?
?
WHICH VERSION?HTML5 vs HTML4 vs XHTML
Newest (under development) standard. More semantic elements and other technologies
HTML5
Pretty standard.HTML4
XML version of HTML4, most browsers didn’t respect itXHTML 1.0
DECLARE A DOCTYPEAnd then develop to it
HTML 4.01 StrictHTML 4.01 Transitional
HTML 4.01 FramesetXHTML 1.0 Strict
XHTML 1.0 TransitionalXHTML 1.0 Frameset
XHTML 1.1HTML (5)
www.alistapart.com/articles/doctype/
XHTML 4 & HTML 4HAVE THESE ELEMENTS
html, body, div, span, applet, object, iframe,h1, h2, h3, h4, h5, h6, p, blockquote, pre,a, abbr, acronym, address, big, cite, code,
del, dfn, em, font, img, ins, kbd, q, s, samp,small, strike, strong, sub, sup, tt, var,
dl, dt, dd, ol, ul, li,fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td
HTML5HAS SOME NEW SEMANTIC TOYS
section, article, header, hgroup, nav,footer, aside, time, figure, figcaption
Legacy support requires Modernizr or a javascript shiv.
and removes things like frameset and consolidatessome other elements together (acronym/abbr)
HIERARCHYWhere it’s at.
<div class="headline">This is a page headline.</div><div class="subhead">This is a subhead</div><div class="body">This is body text and it goes on for miles and miles. I like cheese.</div><div class="list">This is going to be a list of items:<br />- Item 1<br />- Item 2<br />- Item 3<br /></div>
NO HIERARCHYNot semantic, everything is the same.
NO HIERARCHYNot semantic, everything is the same.
<hgroup> <h1>This is a page headline.</h1> <h2>This is a subhead</h2></hgroup><section id=”page”> <p>This is body text and it goes on for miles and miles. I like cheese.</p> <p>This is going to be a list of items:</p> <ul> <li>Item 1</li> <li>Item 2</li> <li>Item 3</li> </ul></section>
“SEMANTIC”HTML Tags used for meaning
“SEMANTIC”HTML Tags used for meaning
USE THE ELEMENTS!Lists are lists. Not divs with bullets.
<% @unit.unit_client_scans.each do |client_scan| %> • <%= client_scan.value %><br /><% end %>
<ul class=”scans”><% @unit.unit_client_scans.each do |client_scan| %> <li><%= client_scan.value %></li><% end %></ul>
!TIP:
Mark up content for the meaning, NOT the layout. Use the semantic elements.
VALIDATE!Valid HTML makes for better coding
validator.w3.org
STYLECSS Overview and Legacy Hacks
http://www.flickr.com/photos/prettycooljewels/4351797285/
<div class="b"> <div class="l"> <div class="r"> <div class="bl"> <div class="br"> <div class="tl"> <div class="tr box"> <%= content %> </div> </div> </div> </div> </div> </div> </div>
ROUNDED CORNERSVintage like...
ROUNDED CORNERS<div class=”box-to-be-rounded”> <%= content %></div>
CSS3 Solution
.box-to-be-rounded { border: 1px solid #ccc; -webkit-border-radius: 5px; /* Safari, Chrome */ -moz-border-radius: 5px; /* Firefox */ border-radius: 5px; /* IE9, Opera 10.5 */}
REQUIRED FIELDS
<%= f.label :first_name %> <span class=”required”>*</span><br /><%= f.text_field :first_name %>
Love the form you’re with
span.required {color: red;}
REQUIRED FIELDS<%= f.label :first_name, :class => “required” %><%= f.text_field :first_name %>
Progressively enhance!
label { display: block; }
label.required { color: red; }
label.required:after { content: “*”;}
Not supported in MSIE 7 and below, 8 does not accept images for content
www.quirksmode.org/css/beforeafter.html
DON’T FIGHT THE BROWSEREmbrace it!
html { overflow-y: scroll;}
ID vs. CLASSThere should be a standard
#this_is_an_id
.this-is-a-class
!TIP:
Embrace the browser and its flaws. Design to the power of the medium. (SCROLLING IS NOT A FLAW)
SHORTENINGLearn to write concise css
http://www.flickr.com/photos/tomsaint/3456155628/
CSS SHORTHANDCombine pieces together
margin-top: 4px;margin-right: 2px;margin-bottom: 4px;margin-left: 8px;
becomes...
margin: 4px 2px 4px 8px;
application.css
CSS SHORTHANDCombine pieces together
background-color: #333;background-image: url(“../images/texture.png”)background-position: top left;background-repeat: repeat;
becomes...
background: #333 url(“../images/texture.png”) repeat-x top left;
application.css
CSS SHORTHANDCombine pieces together
fontbackgroundmarginborderpaddinglist
CAN BE USED FOR:
Reduce stylesheet size and improve readability.
AND WILL HELP YOU
HOW TO WRITE CSS.button-install { margin-left: 30px; font-family: "Helvetica Neue", helvetica, arial, sans-serif; display: block; width: 250px; margin-bottom: 15px; text-align: left; height: 48px; font-size: 20px; font-weight: bold; padding:4px 0 8px 0; background-color: #67c5e6; background-image: -webkit-gradient( linear, left top, left bottom, color-stop(0.23, rgb(103,197,230)), color-stop(0.81, rgb(94,149,167)) ); background-image: -moz-linear-gradient( center top, rgb(103,197,230) 23%, rgb(94,149,167) 81% ); border-radius: 8px; -webkit-border-radius: 8px; -moz-border-radius: 8px;}
Hard to read and find what you’re looking for...
HOW TO WRITE CSS.button-install { background-color: #67c5e6; background-image: -webkit-gradient( linear, left top, left bottom, color-stop(0.23, rgb(103,197,230)), color-stop(0.81, rgb(94,149,167)) ); background-image: -moz-linear-gradient( center top, rgb(103,197,230) 23%, rgb(94,149,167) 81% ); -moz-border-radius: 8px; -webkit-border-radius: 8px; border-radius: 8px; display: block; font: bold 20px "Helvetica Neue", helvetica, arial, sans-serif; height: 48px; margin: 0 0 15px 30px; padding:4px 0 8px 0; text-align: left; width: 250px;}
Alpha order your properties, use shorthand, one per line
!TIP:
Write effective and concise CSS. Properties in alpha order, be specific, browser experimental calls before standard calls, and don’t use browser hacks.
BUT GUYS,
WHY DOESTHIS MATTER?
?
WEB ACCESSIBILITYTake care of your users
GOOGLE IS A BLIND USER
START SMALLCover the basics all the time.
www.w3.org/TR/WCAG10/full-checklist.html
WEB ACCESSIBILITYERB Examples
link_to @product.name, product_path(@product), :title => “Take a look at #{@product.name}”
image_tag “product_12758.png”, :alt => “#{@product.name}”
link_to (image_tag “product_12758.png”, :alt => “#{@product.name}”), product_path(@product), :title => “Take a look at #{@product.name}”
WEB ACCESSIBILITY=Well Formed HTML
Not just an afterthought.
!TIP:
HTML is code. Don’t be afraid to write it. Well written HTML helps users of all types access and interact with our content or application.
* 3.BUILDINGA LAYOUT
A STORY OF A LAYOUTDefault
<!DOCTYPE html><html> <head> <title></title> <%= stylesheet_link_tag :all %> <%= javascript_include_tag :defaults %> <%= csrf_meta_tag %> </head> <body> <%= yield %> </body></html>
application.html.erb
CHARSETCombine pieces together
<!DOCTYPE html><html> <head> <meta charset=”utf-8”> <title></title> <%= stylesheet_link_tag :all %> <%= javascript_include_tag :defaults %> <%= csrf_meta_tag %> </head> <body> <%= yield %> </body></html>
application.html.erb
TARGET MSIEBy version
<!DOCTYPE html><!--[if lt IE 7 ]> <html lang="en" class="ie ie6"> <![endif]--><!--[if IE 7 ]> <html lang="en" class="ie ie7"> <![endif]--><!--[if IE 8 ]> <html lang="en" class="ie ie8"> <![endif]--><!--[if IE 9 ]> <html lang="en" class="ie ie9"> <![endif]--><!--[if (gt IE 9)|!(IE)]><!--> <html lang="en"> <!--<![endif]-->
Extracted into a gem by Bruce
http://codefluency.com/post/1100393830/ie-conditional-tag-plugin
<%= ie_conditional_tag :html %>
application.html.erb
TARGET MSIEBy version
section#page { margin: 0 auto; width: 960px;}
.ie7 section#page { overflow: hidden;}
http://codefluency.com/post/1100393830/ie-conditional-tag-plugin
application.css
RECOGNIZE NEW ELEMENTSHTML5 Shiv (or Shim, same thing)
<!--[if lt IE 9]><script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
Enables the elements and fixes printing on IE
code.google.com/p/html5shiv/
modernizr.com
RESET.CSSGet your browser on the same page
meyerweb.com/eric/tools/css/reset/
First showed up in 2007
Put forth by Eric Meyer
NOT about defaults
Brings browsers to the same base page
Makes you specify everything
RESET.CSSGet your browser on the same page
html5doctor.com/html-5-reset-stylesheet/
tml, body, div, span, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
abbr, address, cite, code,
del, dfn, em, img, ins, kbd, q, samp,
small, strong, sub, sup, var,
b, i,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, figcaption, figure,
footer, header, hgroup, menu, nav, section, summary,
time, mark, audio, video {
margin:0;
padding:0;
border:0;
outline:0;
font-size:100%;
vertical-align:baseline;
background:transparent;
}
....etc...
reset.css
CALLING STYLESHEETSPitfalls of :all
<%= stylesheet_link_tag :all %>
Can load things in alpha order, meaning reset happens after
Xapplication.html.erb
CALLING STYLESHEETSManually calling them
<%= stylesheet_link_tag ‘reset’, ‘application’, ‘buttons’ %>
application.html.erb
CALLING STYLESHEETSDefining :defaults
class Application < Rails::Application config.action_view.stylesheet_expansions[:defaults] = %w( nav tipsy iconic ui-lightness/jquery-ui-1.8.11.custom jquery.customSelect dashboard shadows dataTables/css/demo_table dataTables/css/demo_page application )end
config/application.rb
FRAMEWORKS/BOILERPLATECombine pieces together
html5boilerplate.com
Build Your Own
Framework
BUILD YOUROWN
FRAMEWORK
http
://www.flick
r.com/p
hoto
s/dysto
pos/3
15312
589/
HIERARCHY OF H TAGSYou can reuse the H1. Really.
html|-body| |-header| | |-h1| |-article| | |-h1| | |-section| | | |-header| | | | |-h1| | | |-p| | | |-h2
QUESTIONSAND ANSWERS
AGENDA
HOUR 2
*
The Art of Template WritingQuestions
*
WELCOMEBACK!
* 4.THE ART OF TEMPLATEWRITING
IMPROVINGREADABILITY
http://www.flickr.com/photos/bluefootedbooby/389279217/
WHY WE REFACTOR
➡ Templates can be verbose
➡ Display logic can be very complex
➡ More people need to read, understand, and modify them.
➡ They need to change more frequently, as they’re more subjective
FORMATTING
<html> <head> <title>...
Don’t indent with hard tabs
FORMATTING<% pets.each do |pet| %><li> <%= pet.name %></li><% end %>
Indent
Better:
<% pets.each do |pet| %> <li> <%= pet.name %> </li><% end %>
FORMATTING
<p> We believe that many dogs are unnecessarily abandoned because of easily solved, unwanted behavior. To combat this, we offer behavior classes to <strong>all<strong> adopters, and all individuals requiring help with their dogs.</p>
Clean up big blocks of text
Still try to keep line length <= 80 characters per line
TIDYBecause templating code gets messy.
http://www.flickr.com/photos/muehlinghaus/3564021462/
FORMATTING
$ gem install rack-tidy$ gem install rack-tidy-ffi
Output formatting isn’t your job
Use one of these (or similar) as middleware
http://www.flickr.com/photos/miltonkeynesman/5359120312/
Zen CodingUse a tool, not a generator
div#page>img.logo+ul#navigation>li*5>a
<div id="page"> <img src=”” class="logo" /> <ul id="navigation"> <li><a href=""></a></li> <li><a href=""></a></li> <li><a href=""></a></li> <li><a href=""></a></li> <li><a href=""></a></li> </ul></div>
PROJECT FILES:code.google.com/p/zen-coding/
!TIP:
Know your team. YTMV
TOOLSWhat do we use for each situation?
http://www.flickr.com/photos/anotherphotograph/3571242832/
REFACTORING TOOLS
Extract part of a template (usually for reuse)PARTIALS
Extract complex logicHELPERS
Extract complex, wrap content, View DSLsBLOCK HELPERS
Reduce helper spaghetti code, encapsulate state, ease testingVIEW CLASSES
QUERYING, ORDERING, LIMITING
<% Pet.all.order(:adoption_date).each do |pet| %> <%# Use pet %><% end %>
Don’t do this.
X
QUERYING, ORDERING, LIMITING
def index @pets_by_adoption = Pet.all.order(:adoption_date)end
Instead, set an instance variable
pets_controller.rb
<% @pets_by_adoption.each do |pet| %> <%# Use pet %><% end %>
_adoption_list.html.erb
QUERYING, ORDERING, LIMITING
def pets_by_adoption @pets_by_adoption = Pet.all.order(:adoption_date)end
Or instead, use a helper
pets_helper.rb
<% pets_by_adoption.each do |pet| %> <%# Use pet %><% end %>
_adoption_list.html.erb
!TIP:
Avoid capital letters in your template code (configuration constants and model classes).
LOCAL ASSIGNMENT
<% number_available = Pet.available.count %><% available, unavailable = pets_by_status %><% next_month = Date.today >> 1 %>
Don’t do this, ever. Never. At no time...
Messy, noisy, undocumentable, easy to accidentally remove
X_availability.html.erb
LOCAL ASSIGNMENT
def index # ... other stuff @number_available = Pet.available.countend
Instead, use an instance varaible
pets_controller.rb
LOCAL ASSIGNMENT
def number_available @number_available ||= Pet.available.countend
Or instead, use a helper
pets_helper.rb
CONDITIONAL CONTENT
<% if @pet.at_risk? <%# Lots of content %><% else %> <%# Different lots of content %><% end %>
Avoid long if/else clauses
If lengthy, this makes it hard to get a quickfeeling of what the page contains.
X
CONDITIONAL CONTENT
def risk_assessment if @pet.at_risk? render partial: 'at_risk' else render partial: 'not_at_risk' endend
Better, split to files
pets_helper.rb
<%= risk_assesment %>
show.html.erb
CONDITIONAL CONTENT
<% if current_user.admin? || current_user.can?(:edit_pet) || pet.owner == current_user %> <%= render partial: 'form', locals: {pet: pet} %><% end %>
Avoid long and complex conditions
If lengthy, this makes it hard to get a quickfeeling of what the page contains.
X
CONDITIONAL CONTENT
<% if can_edit_pet?(pet) %> <%= render partial: 'form', locals: {pet: pet}<% end %>
Better, extract condition toa meaningful helper
_pet.html.erb
MANUAL LOOPS
<ul id='pets'> <% @pets.each do |pet| %> <li> <%= image_tag pet.image.url(:thumb) %> <%= pet.name %> <%= pet_stats(pet) %> <li> <% end %></ul>
Minimize theseindex.html.erb
X
MANUAL LOOPS
<ul id='pets'> <%= render @pets %></ul>
Break into partials
index.html.erb
<li> <%= image_tag pet.image.url(:thumb) %> <%= pet.name %> <%= pet_stats(pet) %><li>
_pet.html.erb
http://www.flickr.com
/photos/lambdacz/4
725437313
RENDERING(There are many ways to do it.)
RENDERING
render partial: 'pet'
Without an object
Renders #{controller_name}/_pet.html.erb
render partial: 'pets/pet'
Always renders pets/_pet.html.erb
RENDERING
render partial: 'pet', locals: {pet: pet}
With an object
Using :locals
<p><%= pet.description %></p>
Renders _pet.html.erbAssigns pet local variable
RENDERING
render partial: 'pet', object: comment
With an object
Using :object
<p><%= pet.description %></p>
Also renders _pet.html.erbAssigns pet local variable
RENDERING
render pet
With an object
Using an implicit partial
Checks the object’s .class.name (“Pet”), renderspets/_pet.html.erb, and assigns the object to
the local pet variable.
RENDERING
<% @pets.each do |pet| %> <%= render pet %><% end %>
With a collection of objects
With a manual loop:
RENDERING
render partial: 'pet', collection: @pets
With a collection of objects
Using :collection
Renders _pet.html.erb for each object in @pets,assigning each as the pet local variable
RENDERING
render @pets
With a collection of objects
Using an implicit partial
Checks the object’s .class.name (“Pet”), renderspets/_pet.html.erb, and assigns each object to
the pet local variable.
RENDERING
render [a_cat, a_dog, another_dog]
With a collection of objects
If we had Cat, and Dog single table inheritancesubclasses of Pet, with this render:
cats/_cat.html.erb (with cat set to a_cat)dogs/_dog.html.erb (with dog set to a_dog)dogs/_dogs.html.erb (with dog set to another_dog)
!TIP:
Using implicit partial rendering means you don’t have to CARE which partial gets rendered.
SETTING THE PAGE TITLEThe Instance Variable Method
Xdogs_controller.rb
def show @dog = Dog.find(params[:id]) @page_title = @dog.nameend
<section id='content'> <header id=‘page_header’> <h1><%= @page_title %></h1> </header> <%= yield %></section>
application.html.erb
SETTING THE PAGE TITLE
<% content_for :page_header do %> <h1><%= @pet.name %></h1><% end %>
show.html.erb
<section id='content'> <% if content_for?(:page_header) %> <header id=‘page_header’><%= yield :page_header %></header> <% end %> <%= yield %></section>
application.html.erb
The content_for Method
SETTING THE PAGE TITLE
def page_title(title) content_for(:page_header, content_tag(:h1, title))end
pets_helper.rb
<% page_title @pet.name %>
show.html.erb
The content_for Method
!TIP:
Use content_for in your action templates to define content that is conceptually related to the action, but structurally located elsewhere in the layout.
BLOCK HELPERS
http://www.flickr.com
/photos/icco/5237319
440/
!TIP:
Don’t code generate before you have determined what your common design elements are.
QUESTIONSAND ANSWERS
AGENDA
HOUR 3
*
NavigationForming Forms
Don’t Fear the ObjectGoing Mobile
Packaging Assets
*
WELCOMEBACK!
* 5.NAILINGNAVIGATION
WHAT NAVIGATION DOES
➡ Tells users where they can go
➡ Reminds them where they are
WHERE ARE WE?The Instance Variable Method
Xdogs_controller.rb
def show @dog = Dog.find(params[:id]) @current_tab = :dogsend
<ul> <li class=’<%= nav_class(:dogs) %>’> <%= link_to ‘Dogs’, dogs_path %> </li> ...</ul>
def nav_class(tab) ‘active’ if tab == @current_tabend
application_helper.rb
_nav.html.erb
WHERE ARE WE?The Manual Method
X_nav.html.erb
<ul> <li> <%= link_to_unless_current("Home", action: ‘home’, controller: ‘pages’) %> </li> ...</ul>
WHERE ARE WE?The CSS Method
Xapplication.html.erb
<body class="<%= controller_name %> <%= action_name %>">
nav.css
body.dogs li#dogs_tab { background: #fff;}
body.dogs li#dogs_tab a { color: #000;}
_nav.html.erb<ul> <li id=‘dogs_tab’> <%= link_to("Dogs", dogs_path) %> </li> ...</ul>
nav.css
IT’S THE ACTION TEMPLATE’S
RESPONSIBILITY.
*
GOOSEa simple navigation gem
GITHUB:github.com/bruce/goose
USING GOOSE
<% content_for :main_nav do %> <nav id='main-nav'> <ul> <%= nav_to 'Home', root_path %> <%= nav_to 'Dogs', dogs_path %> <%= nav_to 'Cats', dogs_path %> <%= nav_to 'Contact Us', about_path %> </ul> </nav><% end %>
nav/_main.html.erbHooking it in
<header> <%= yield :main_nav %></header>
application.html.erb
USING GOOSE
nav.css
Styling
nav#main_nav ul { height: 30px; overflow: hidden;}
nav#main_nav ul li { color: #999; float: left; margin-right: 3px; padding: 5px 10px;}
nav#main_nav ul li.active { color: #000; font-weight: bold;}
USING GOOSE
show.html.erb
In Action Templates
<% nav_at ‘Dogs’ %>
!TIP:
Driving the state of the page from the action template gives you flexibility and means you know exactly where to look to make changes.
* 6.MAINTAINABLEFORMS
FORM_FOR vs FORM_TAG
SHOW ME!
HTML5 FORM ELEMENTS
Will affect keyboard layout on iOS devices
<input type=”email” name=”email”>
diveintohtml5.org/forms.html
Search
HTML5 FORM ELEMENTS<input type=”search” name=”search”>
diveintohtml5.org/forms.html
Date
HTML5 FORM ELEMENTS
Only in Opera right now
<input type=”date” name=”date”>
diveintohtml5.org/forms.html
FORMTASTIC
<%= form_for @adoption_application do |f| %> <fieldset> <legend>Your Name</legend> <ol> <li> <%= f.label :first_name, "First Name" %> <%= f.text_field :first_name %> </li> <li> <%= f.label :last_name, "Last Name" %> <%= f.text_field :last_name %> </li> </ol> </fieldset><% end %>
Before...
FORMTASTIC
<%= semantic_form_for @adoption_application do |f| %> <%= f.inputs “Your Name” do %> <%= f.input :first_name, label: ‘First Name’ %> <%= f.input :last_name, label: ‘Last Name’ %> <% end %> ...<% end %>
After
* 6.DON’T FEARTHE OBJECT
YOU MAKE CLASSES FOR THE MODEL
LAYER
YOU MAKE CLASSES FOR THE
CONTROLLER LAYER
WHAT ABOUT THE VIEW LAYER?
ENCAPSULATION
http://www.flickr.com
/photos/shuffle-art/28102475
09/
HELPER CLASSEncapsulates state, accesses template
class PetHistory
def initialize(template, pet) @template = template @pet = pet end
# ...
def to_s # @template.render, etc end
end
def history @history ||= PetHistory.new(self, @pet)end
lib/pet_history.rb
pets_helper.rb
HYBRIDController -> View
class PetSort delegate :params, to: :@controller delegate :each, to: :records
def initializer(controller) @controller = controller end
def field params[:field] || :name end
def order params[:order] || :desc end
private
def records # TODO: make sure field & order are on whitelist Pet.order("#{field} #{order}") end end
lib/pet_sort.rb
HYBRIDController -> View
def sort @sort ||= PetSort.new(self)endhelper_method :sort
pets_controller.rb
!TIP:
View classes allow you to more easily test complex logic (instantiate, stub out the controller, template, etc).
* 8.GOINGMOBILE
MOBILE USERINTERACTION
http://www.flickr.com
/photos/yourdon/359975
3183/
MOBILE USER INTERACTION
➡ Task focused
➡ What are they trying to do?
➡ Fat-fingering (easy targets)
➡ Sometimes low bandwidth
CSS3 MEDIA QUERIES
Client-side smart rendering via conditional CSS
RESPONSIVE DESIGN
www.alistapart.com/articles/responsive-web-design
Client-side smart rendering via conditional CSS
RESPONSIVE DESIGN
@media screen and (max-width: 800px) { }
@media screen and (max-width: 640px) { }
@media only screen and (min-device-width: 320px) and (max-device-width: 480px) { }
If lengthy, this makes it hard to get a quickfeeling of what the page contains.
application.css
www.mediaqueri.es
@media QUERIES PITFALLS
➡ All content is loaded, even if it’s not shown
➡ Interaction is different on mobile
➡ Doesn’t get into the capabilities of the mobile device
MOBILE SPECIFIC
TEMPLATEShttp://www.flickr.com
/photos/goincase/49738
479
49/
Is it a mobile request?
MOBILE-SPECIFIC TEMPLATES
def mobile_request? request.user_agent =~ /iP(?:one|ad|od)/endhelper_method :mobile_request?
application_controller.rb
SET A MIME TYPE
Mime::Type.register_alias "text/html", :mobile
mime_types.rb
FILTER THE REQUEST
before_filter :prepare_mobile_request!
def prepare_mobile_request! if mobile_request? request.format = :mobile endend
application_controller.rb
Specific to mobile
LAYOUT FILE
<!DOCTYPE html> <html lang="en"> <head> <title>Dam App: Mobile Edition!</title> </head> <body> <%= yield %> <%= render partial: 'layouts/footer' %> </body> </html>
application.mobile.erb
LET USERS DECIDE
before_filter :set_preferred_view! before_filter :prepare_mobile_request!
def set_preferred_view! if mobile_request? case params[:prefer_view] when 'standard' session[:preferred_view] = :standard when 'mobile' session[:preferred_view] = :mobile end endend
application_controller.rb
LET USERS DECIDEdef prepare_mobile_request! if mobile_request? && preferred_view == :mobile request.format = :mobile endend
def preferred_view if mobile_request? session[:preferred_view] || :mobile else :standard endend
helper_method :preferred_view
application_controller.rb
LET USERS DECIDE
def link_to_prefer_view(name) link_to_unless(preferred_view == name, "#{name.capitalize} View", prefer_view: name)end
application_helper.rb
<% if mobile_request? %> <%= link_to_prefer_view :mobile %> | <%= link_to_prefer_view :standard %><% end %>
_footer.html.erb
MOBILE FU
http://www.flickr.com
/photos/mfl/2
978
0314
49/
!TIP:
Figure out what your users need and then build to that. The technology follows the use case.
* 9.PACKAGINGASSETS
WHY DOES IT LOAD SLOW?YSlow and PageSpeed
UNUSED SELECTORSUI side: Dust Me Selectors
sitepoint.com/dustmeselectors
UNUSED SELECTORSGem: Deadweight
# lib/tasks/deadweight.rake
require 'deadweight'
Deadweight::RakeTask.new do |dw| dw.mechanize = true
dw.root = 'http://staging.example.com'
dw.stylesheets = %w( /stylesheets/style.css )
dw.pages = %w( / /page/1 /about )
dw.pages << proc { fetch('/login') form = agent.page.forms.first form.username = 'username' form.password = 'password' agent.submit(form) fetch('/secret-page') }
dw.ignore_selectors = /hover|lightbox|superimposed_kittens/ end
www.github.com/aanand/deadweight
A MORE ROBUST METHODthat covers javascript too...
“Jammit is an industrial strength asset packaging library for Rails, providing both the CSS and JavaScript concatenation and compression that you'd expect,
as well as YUI Compressor and Closure Compiler compatibility, ahead-of-time gzipping, built-in JavaScript template support, and optional Data-URI /
MHTML image and font embedding.”
documentcloud.github.com/jammit
Concat, Convert, Minify assets
RAILS 3.1 SPROCKETS
vendor/assetsapp/assets
FINAL Q&AFIRE WHEN READY.
*SLIDES:www.therailsview.com/railsconf2011
THANKYou
BOOK:www.therailsview.com/book