Modern Application Foundations: Underscore and Twitter Bootstrap

2,899 views

Published on

As presented at No Fluff Just Stuff San Antonio, April 14th 2012.

Published in: Technology, Business

Modern Application Foundations: Underscore and Twitter Bootstrap

  1. 1. Modern ApplicationFoundations: Underscoreand Twitter BootstrapHoward M. Lewis ShipTWD Consultinghlship@gmail.com@hlship © 2012 Howard M. Lewis Ship
  2. 2. Rich ClientChallenges Make It Work Make It Work in IE Make It Right Make It Pretty
  3. 3. Underscore Make it work, rightBootstrap Make it prettyBootstrap.js + jQuery Make it interact right Make it work right under IE
  4. 4. _
  5. 5. ❝Underscore is a utility-belt library for JavaScript …without extending any of the built-in JavaScript objects. Its the tie to go along with jQuerys tux, and Backbone.jss suspenders.❞
  6. 6. Functional Programming
  7. 7. Is JavaScript a Functional Language? © 2008 Hans Splinter – http://www.flickr.com/photos/67196253@N00/2941655917/
  8. 8. underscore.js 1.3.1 Great documentation 34 Kb / < 4 Kb 60+ built-in functions Uses native support where available Extensible
  9. 9. http://jsconsole.com/
  10. 10. Caution: CoffeeScript
  11. 11. Caution: CoffeeScript CoffeeScript ➠ ❝… a little language that compiles into JavaScript❞ Concise and readable Optional parenthesis Implicit returns Concise function definitions Great fit with Underscore!
  12. 12. CoffeeScript: Invoking Functions$(".x-cancel").tooltip "hide" $(".x-cancel").tooltip("hide")collection.add new Quiz(originalModel), at: 0 collection.add(new Quiz(originalModel), { at: 0 });
  13. 13. CoffeeScript: Defining Functions function (x, y) {(x,y) -> x * y return x * y; } function isBlank(str) {isBlank = (str) -> return _.isNull(str) || _.isNull(str) or _.isUndefined(str) || _.isUndefined(str) or str.trim() === ""; str.trim() is "" }_.map list, (x) -> 2 * x _.map(list, function(x) { return 2 * x; });
  14. 14. Simple Object / ValueUtilities
  15. 15. Useful PredicatesPredicate DescriptionisEmpty Is the value null, undefined, the empty string, an empty object, or an empty array?isElement Is the value a DOM element?isArray Is the value an array (but not arguments)?isArguments Is the value the special arguments object?isFunction Is the value a function?isString Is the value a string?isNumber Is the value a numberisBoolean Is the value a boolean?isDate Is the value a JavaScript Date?isRegExp Is the value a Regular ExpressionisNaN Is the value NaN (note: returns false for undefined)isNull Is the value null (but not undefined)isUndefined Is the value specifically undefined
  16. 16. _.isEmpty _.isEmpty null ➠ true _.isEmpty undefined ➠ true _.isEmpty [] ➠ true _.isEmpty "" ➠ true _.isEmpty {} ➠ true _.isEmpty [1] ➠ false _.isEmpty name:null ➠ false me = name:"Howard" delete me.name _.isEmpty me ➠ true
  17. 17. _.keys, _.values, _.has me = firstName: "Howard" lastName: "Lewis Ship" _.keys me ➠ ["firstName", "lastName"] _.values me ➠ ["Howard", "Lewis Ship"] Just keys for this object, not inherited Not sorted _.has me, "firstName" ➠ true _.has me, "middleName" ➠ false
  18. 18. _.functions_.functions me ➠ []_.functions _ ➠ ["after", "all", "any","bind", "bindAll", "chain", … Sorted
  19. 19. _.extend and _.defaults shape = type: "circle" ➠ {"type": "circle"} extend: last value wins _.extend shape, { radius: 20 }, { type: "spiral" } ➠ {"radius": 20, "type": "spiral"} Modifies and returns first parameter shape = type: "circle" defaults: first non-null value wins ➠ {"type": "circle"} _.defaults shape, { radius: 20 }, { type: "spiral" } ➠ {"radius": 20, "type": "circle"}
  20. 20. Collection Functions
  21. 21. _.each(list, iterator, [context]) this set to context before invoking iterator_.each ["alpha", "bravo", "charlie"], (value, index) -> console.log "#{value} is at #{index}"alpha is at 0bravo is at 1 More CoffeeScript goodnesscharlie is at 2➠ undefinedAlias: _.forEach
  22. 22. iterator function Callback function passed to Underscore functions Iterating arrays value index array being iterated Iterating objects value key object being iterated
  23. 23. context and this this is a side-effect of method invocation: anObject.aMethod() ➠ var fn = anObject["aMethod"]; fn.call(anObject) Sets this for new stack frame DOM and JQuery manipulate this ➠ Usually the DOM element that triggered event this not relevant to HOFs Functions, parameters, local scope Optional context parameter on many _ functions
  24. 24. _.each(object, iterator, [context])_.each me, (value, key) -> console.log "Value for #{key} is #{value}"Value for firstName is HowardValue for lastName is Lewis Ship➠ undefined
  25. 25. _.each(list) and undefinedsparse = []sparse[10] = "ten"sparse[20] = "twenty"_.each sparse, (value, index) -> console.log "#{value} is at #{index}""ten is at 10""twenty is at 20"➠ undefined
  26. 26. _.map(list, iterator, [context]) _.map [1, 2, 3], (value) -> 2 * value ➠[2, 4, 6] _.map [1, 2, 3, 40, 500], (value, index) -> value * index ➠[0, 2, 6, 120, 2000] _.map me,(value, key) -> "Value for #{key} is #{value}" ➠ ["Value for firstName is Howard", "Value for lastName is Lewis Ship"]Alias: _.collect
  27. 27. _.reduce(list, iterator, memo, [context]) aka "accumulator"_.reduce ["Howard", "Lewis Ship", "TWD Consulting"], (memo, name) -> memo + name.length 0➠ 30"HowardLewis ShipTWD Consulting".length➠ 30 A side effect!_.reduce me, (memo, value, key) -> memo[value] = key ; return memo {}➠ {"Howard": "firstName", "Lewis Ship": "lastName"} Aliases: _.inject, _.foldl
  28. 28. _.find(list, iterator, [context])_.find [1, 2, 3, 4, 5, 6], (x) -> x % 2 is 0➠ 2hand = [ { value:3, suit: "hearts" } { value:2, suit: "spades" } { value:7, suit: "spades" } { value:8, suit: "diamonds" }]➠ …_.find hand, (card) -> 8 + card.value is 15➠ {"suit": "spades", "value": 7}_.find hand, (card) -> 27 + card.value is 31➠ undefinedAlias: _.detect © 2009 scribbletaylor – http://www.flickr.com/photos/64958688@N00/3604756480/
  29. 29. _.all(list, iterator, [context])_.all hand, (card) -> card.value >= 2➠ true_.all hand, (card) -> card.suit is "hearts"➠ falseAlias: _.every
  30. 30. _.any(list, [iterator], [context]) _.any hand, (card) -> card.suit is "clubs" ➠ false _.any hand, (card) -> card.suit is "diamonds" ➠ true _.any [] Default iterator is _.identity ➠ false _.any [false] ➠ false _.any [true] ➠ trueAlias: _.every
  31. 31. _.filter(list, iterator, [context])_.filter hand, (card) -> card.suit is "spades"➠ [{"suit": "spades", "value": 2}, {"suit": "spades", "value": 7}]_.reject hand, (card) -> card.suit is "spades"➠ [{"suit": "hearts", "value": 3}, {"suit": "diamonds", "value": 8}] Alias: _.select
  32. 32. _.groupBy(list, iterator) _.groupBy hand, (card) -> card.suit ➠ {"diamonds": [{"suit": "diamonds", "value": 8}], "hearts": [{"suit": "hearts", "value": 3}], "spades": [{"suit": "spades", "value": 2}, {"suit": "spades", "value": 7}]} _.groupBy hand, (card) -> card.value ➠ {"2": [{"suit": "spades", "value": 2}], "3": [{"suit": "hearts", "value": 3}], "7": [{"suit": "spades", "value": 7}], "8": [{"suit": "diamonds", "value": 8}]} _.groupBy hand, "suit" ➠ {"diamonds": [{"suit": "diamonds", "value": 8}], "hearts": [{"suit": "hearts", "value": 3}], "spades": [{"suit": "spades", "value": 2}, {"suit": "spades", "value": 7}]}
  33. 33. _.sortBy(list, iterator, [context])_.sortBy hand, (card) ->  suitIndex = _.indexOf ["clubs", "diamonds", "hearts", "spades"],    card.suit  100 * suitIndex + card.value➠ [{"suit": "diamonds", "value": 8}, {"suit": "hearts", "value": 3}, {"suit": "spades", "value": 2}, {"suit": "spades", "value": 7}]_.sortBy hand, "propertyName"Object propertyName has no method call_.sortBy ["fred", "wilma", "barney"], _.identity➠ ["barney", "fred", "wilma"] (x) -> x
  34. 34. _.max(list, [iterator], [context]) _.max [-1, 2, 3] ➠ 3 _.max [] ➠ -Infinity _.max hand, (card) -> card.value ➠ {"suit": "diamonds", "value": 8}
  35. 35. _.min(list, [iterator], [context]) _.min [-1, 2, 3] ➠ -1 _.min [] ➠ Infinity _.min hand, (card) -> card.value ➠ {"suit": "spades", "value": 2} _.min ["fred", "wilma", "barney"] ➠ NaN
  36. 36. _.include(list, value) _.include [1, 2, 3], 2 ➠ true _.include [1, 2, 3], 99 ➠ false _.include [1, 2, 3], "2" ➠ false Uses === comparisonAlias: _.contains
  37. 37. _.pluck(list, propertyName)_.pluck hand, "value"➠ [3, 2, 7, 8]_.pluck hand, "suit"➠ ["hearts", "spades", "spades", "diamonds"]_.pluck hand, "score"➠ [undefined, undefined, undefined, undefined]
  38. 38. _.shuffle(list) _.shuffle hand ➠ [{"suit": "spades", "value": 2}, {"suit": "diamonds", "value": 8}, {"suit": "hearts", "value": 3}, {"suit": "spades", "value": 7}] _.shuffle null ➠ []
  39. 39. Object Oriented Style andChaining
  40. 40. _ is a function Every function on _ is also onReturns a wrapper object wrapper object _(hand).pluck "value" ➠ [3, 2, 7, 8] Wrapped object passed as first parameter _(hand).size() ➠ 4
  41. 41. Chaining Can Be Ugly _.map(_.first(_.sortBy(hand,cardSortValue).reverse(), 2),       (card) -> "#{card.value} of #{card.suit}") ➠ ["8 of diamonds", "7 of spades"]1.Sort the hand using the cardSortValue function2.Reverse the result3.Take the first two cards4.Convert each card to a string
  42. 42. _.chain(object) and _.value(object) _.chain(hand) .sortBy(cardSortValue) .reverse() .first(2) Each step returns a new wrapped object .map((card) -> "#{card.value} of #{card.suit}") .value() ➠ ["8 of diamonds", "7 of spades"]
  43. 43. Flow of Transformations© 2008 Manu Gómez – http://www.flickr.com/photos/manugomi/2884678938/
  44. 44. _.tap(object, interceptor) _.chain(hand) .sortBy(cardSortValue) .tap(console.log) .reverse() .first(2) .map((card) -> "#{card.value} of #{card.suit}") .value() [{"suit": "diamonds", "value": 8}, {"suit": "spades", "value": 7}, {"suit": "hearts", "value": 3}, {"suit": "spades", "value": 2}] ➠ ["8 of diamonds", "7 of spades"]
  45. 45. Array prototype methods Extra methods on wrapper not on _ _.([1, 2]).concat "three" ➠ [1, 2, "three"] concat shift join slice pop sort push splice reverse unshift
  46. 46. Array Functions
  47. 47. Name Description Aliasesfirst(array) Return first element in array, as single value headfirst(array, n) Return first elements of array as list headinitial(array, [n]) Return everything but last n (default = 1) elements of arraylast(array) Return last element in array, as single valuelast(array, n) Return last n (default = 1) elements of arrayrest(array, [n]) Return array from index n (default = 1) on tailindexOf(array, value, [isSorted]) Index of value inside array, or -1lastIndexOf(array, value) Last index of value inside array, or -1 Returns copy with all falsey (null, false, 0, undefined, NaN)compact(array) removedflatten(array, [shallow]) Collapses nested arrays down to a single array
  48. 48. Set Operations _.without [5, 4, 3, 2, 1], 4, 2 ➠ [5, 3, 1] _.difference [5, 4, 4, 3, 3, 2, 2, 1], [1, 2], [4, 10, 22] ➠ [5, 3, 3] _.union [3, 2, 1, 3, 4, 5], [101, 1], [3, 4], [500] ➠ [3, 2, 1, 4, 5, 101, 500] _.intersection [5, 4, 4, 3, 3, 2, 2, 1],   [1, 2, 3, 4, 5],   [4, 10, 22, 3] ➠ [4, 3]
  49. 49. _.uniq(array, [isSorted], [iterator]) _.uniq [1, 30, 50, 40, 30, 1] ➠ [1, 30, 50, 40] _.uniq [1, 30, 50, 40, 30, 1], false, (x) -> "#{x}".length ➠ [1, 30]
  50. 50. _.range([start], stop, [step]) _.range 5 ➠ [0, 1, 2, 3, 4] _.range 3, 5 ➠ [3, 4] _.range 1, 20, 3 ➠ [1, 4, 7, 10, 13, 16, 19] _.range 1, 20, -3 ➠ [] _.range 20, 1, -4 ➠ [20, 16, 12, 8, 4]
  51. 51. _.zip(*arrays)_.zip(["a", "b", "c"], [1, 2, 3], ["alpha", "bravo", "charlie", "delta"])➠ [["a", 1, "alpha"], ["b", 2, "bravo"], ["c", 3, "charlie"], [undefined, undefined, "delta"]]
  52. 52. Higher Order Underscore
  53. 53. _.bind(fn, context, [*args])greet = (greeting) -> "#{greeting}: #{this.name}"greet("Hello, unbound this")➠ "Hello, unbound this: "bgreet = _.bind greet, { name: "Howard" }bgreet "Hello"➠ "Hello: Howard"fgreet = _.bind greet, { name: "Mr. Lewis Ship" }, "Salutations"fgreet()➠ "Salutations: Mr. Lewis Ship"
  54. 54. _.defer(function) do invokes the function with no parameters do -> _.defer -> console.log "deferred" console.log "immediate" immediate ➠ undefined deferred
  55. 55. _.delay(fn, wait, [*arguments]) log = _.bind console.log, console do -> _.delay log, 1000, "delayed" log "immediate" immediate ➠ undefined delayed About 1 second later
  56. 56. _.wrap(function, interceptor) timerInterceptor = (wrapped, args...) -> start = Date.now() result = wrapped(args...) elapsed = Date.now() - start console.log "Function call took #{elapsed} ms." return result fib = (x) -> switch x when 0 then 0 when 1 then 1 else fib(x - 1) + fib(x - 2) tfib = _.wrap fib, timerInterceptor tfib 30 Function call took 23 ms. ➠ 832040 tfib 40 Function call took 2674 ms. ➠ 102334155
  57. 57. More Functions on Functions bindAll memoize throttle debounce once after compose
  58. 58. Underscore UtilitiesName Descriptionidentity(value) Returns its argument, a default iteratortimes(n, iterator) Invoke its iterator n times, passing the index to ituniqueId([prefix]) Creates a unique id, good for labelling DOM elementsescape(string) Converts & < > " / in string to HTML entities (&amp; &lt; … )template(string, [context]) Converts the string into a template function, using a <% … %> syntaxmixin(object) Adds new functions to _ and to the OOP wrapper, for use with _.chain()
  59. 59. Twitter Bootstrap
  60. 60. Quizzical Empire
  61. 61. Paperwork
  62. 62. Twitter Bootstrap Built by and for nerds All skill levels Desktop, tablets … even smartphones and IE Custom jQuery Plugins Based on LESS for customizability
  63. 63. Minimal Setup <!DOCTYPE html> <html> <head> <link rel="stylesheet" href="/bootstrap/css/bootstrap.css"> <script src="/bootstrap/js/bootstrap.js"></script> </head> <body> … </body> </html>
  64. 64. 12 Column Grid 1 1 1 1 1 1 1 1 1 1 1 1 4 4 4 4 8 6 6 12 940px
  65. 65. 12 Column Grid<div class="container"> <div class="row"> <div class="span2">Span 2</div> <div class="span8">Span 8</div> <div class="span2">Span 2</div> </div> <div class="row"> <div class="span6 offset1">Span 6 / Offset 1</div> <div class="span5">Span 5</div> </div></div>
  66. 66. 12 Column Grid – Jade.container .row .span2 Span 2 .span8 Span 8 .span2 Span 2 .row .span6.offset1 Span 6 / Offset 1 .span5 Span 5
  67. 67. Nested Rows .container .row .span2 Span 2 .span8 Span 8 - Level 1 Add up to .row container .span4 Span 4 - Level 2 span .span4 Span 4 - Level 2 .span2 Span 2
  68. 68. Fluid Layout .container-fluid .row-fluid .span2 Span 2 .span8 Span 8 - Level 1 .row-fluid Add up to 12 .span6 Span 6 - Level 2 .span6 Span 6 - Level 2 .span2 Span 2
  69. 69. General Bootstrap Approach Basic, semantic, markup Reasonable default look Simple CSS classes: "row", "span3", "offset1" Additiive: more CSS classes to tune
  70. 70. Tables .container table.span12 caption Bare Table thead:tr th.span8 Column A th Column B th.span2 Column C tbody each row in [1, 2, 3, 4] tr each cell in ["A", "B", "C"] td Cell #{row}-#{cell}
  71. 71. .table
  72. 72. .table.table-bordered
  73. 73. .table.table-bordered.table-condensed.table-striped
  74. 74. Buttons•Use with: • <a> • <button> • <input type="submit"> • <input type="button"> • <input type="reset">
  75. 75. Glyphicons <i class="icon-search"/>
  76. 76. Bootstrap Components
  77. 77. .containerTab ul.nav.nav-tabs li.active: a(href="#moe", data-toggle="tab") Moe li: a(href="#larry", data-toggle="tab") Larry li: a(href="#curly", data-toggle="tab") Curly .tab-content .tab-pane.active#moe h1 Moe Howard img(src="images/moe.jpg") .tab-pane#larry h1 Larry Fine img(src="images/larry.jpg") .tab-pane#curly h1 Curly Howard img(src="images/curly.jpg") Text Requires jQuery and Twitter Bootstrap.js
  78. 78. Dynamic Tabs ex7.jade .container ul.nav.nav-tabs .tab-content != js("ex7") ex7.coffee jQuery ($) -> tabs = $("ul.nav.nav-tabs") tabContent = $(".tab-content") _.each ["Moe Howard", "Larry Fine", "Curly Howard"], (name) -> … tabs.find("li:first").addClass "active" tabContent.find(".tab-pane:first").addClass "active in"
  79. 79. Dynamic Tabs _.each ["Moe Howard", "Larry Fine", "Curly Howard"], (name) -> firstName = name.split( )[0] uid = _.uniqueId "tab" tab = $(""" <li> <a href="##{uid}" data-toggle="tab"> #{firstName} </a> </li> """) content = $(""" <div class="tab-pane fade" id="#{uid}"> <h1>#{name}</h1> <img src="images/#{firstName.toLowerCase()}.jpg"> </div> """) content.appendTo tabContent tab.appendTo tabs
  80. 80. Modal Dialogs
  81. 81. Modal Dialogs ex8.jade .container button#danger.btn.btn-danger(data-toggle="modal" data-target=".x-danger-alert") Do Not Press .modal.x-danger-alert.fade .modal-header a.close(data-dismiss="modal") &times; h3 Why did you press it? .modal-body p You pressed the button marked strong Do Not Press . .modal-footer button.btn.btn-warning(data-dismiss="modal") Continue button.btn(data-dismiss="modal") Cancel
  82. 82. Alerts ex8.coffee jQuery ($) -> $(".x-danger-alert .btn-warning").on "click", -> alert = $(""" <div class="alert fade in"> <a class="close" data-dismiss="alert">&times;</a> <strong>Well, you pressed it!</strong> </div> """) alert.prependTo $(".container")
  83. 83. Other Plugins Popover Tooltip And more: Scrollspy Dropdown Button Collapse Carousel Typeahead
  84. 84. Wrap Up
  85. 85. Underscore Effective, functional JavaScriptBootstrap Sharp L&F out of the box Very customizableBootstrap JS Lots of UI pizzaz, effortlesslyCoffeeScript Concise, readable JavaScript
  86. 86. http://howardlewisship.com
  87. 87. Q&A

×