Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Accelerating Titanium Development with CoffeeScript, Compass, and Sass

Accelerating Titanium Development with CoffeeScript, Compass, and Sass

Wynn Netherland

September 20, 2011
Tweet

More Decks by Wynn Netherland

Other Decks in Programming

Transcript

  1. var foo = function () { } foo = ()

    -> I’d rather write this. JAVASCRIPT COFFEESCRIPT
  2. var button = Titanium.UI.createButton({ title: 'I am a Button', height:

    40, width: 200, top: 10 }); button.addEventListener('click', function(e) { alert("Oooh, that tickles!"); }); JAVASCRIPT
  3. button = Titanium.UI.createButton title: 'I am a Button' height: 40

    width: 200 top: 10 button.addEventListener 'click', (e) -> alert "Oooh, that tickles!" COFFEESCRIPT
  4. for own key, value of query uri += "#{ key

    }=#{ escape(value) }&" COFFEESCRIPT var key, value; var __hasProp = Object.prototype.hasOwnProperty; for (key in query) { if (!__hasProp.call(query, key)) continue; value = query[key]; uri += "" + key + "=" + (escape(value)) + "&"; } JAVASCRIPT
  5. for own key, value of query uri += "#{ key

    }=#{ escape(value) }&" COFFEESCRIPT Comprehensions
  6. for own key, value of query uri += "#{ key

    }=#{ escape(value) }&" COFFEESCRIPT Interpolation
  7. class GolfStatus.Models.Game constructor: (@owner, @course, @playingFor='brag', @scoringFormat='low_net') -> @players =

    {} @addPlayer @owner if @owner @green = @course.greens[0] if @course @currentHole = 1 @maxHolePlayed = 1 # elsewhere game = new GolfStatus.Models.Game(...) COFFEESCRIPT Simple inheritance pattern
  8. class GolfStatus.Models.Game constructor: (@owner, @course, @playingFor='brag', @scoringFormat='low_net') -> @players =

    {} @addPlayer @owner if @owner @green = @course.greens[0] if @course @currentHole = 1 @maxHolePlayed = 1 COFFEESCRIPT @
  9. class GolfStatus.Models.Game constructor: (@owner, @course, @playingFor='brag', @scoringFormat='low_net') -> @players =

    {} @addPlayer @owner if @owner @green = @course.greens[0] if @course @currentHole = 1 @maxHolePlayed = 1 COFFEESCRIPT Default values
  10. class GolfStatus.Models.Game constructor: (@owner, @course, @playingFor='brag', @scoringFormat='low_net') -> @players =

    {} @addPlayer @owner if @owner @green = @course.greens[0] if @course @currentHole = 1 @maxHolePlayed = 1 COFFEESCRIPT More human conditionals
  11. content presentation <ul class='wynning'> <li class='logo-mark'> <a href='/about'>Wynn Netherland</a> on

    design, development, and general geekery. </li> <li> <a class='gowalla' href='http://gowalla.com/pengwynn'>Gowalla</a> </li> <li> <a class='facebook' href='http://facebook.com/pengwynn'>Facebook</a> </li> <li> <a class='dribbble' href='http://dribbble.com/pengwynn'>Dribbble</a> </li> <li> <a class='linked-in' href='http://linkedin.com/in/netherland'>LinkedIn</a> </li> <li> <a class='github' href='http://github.com/pengwynn'>GitHub</a> </li> <li> <a class='twitter' href='http://twitter.com/pengwynn'>Twitter</a> </li> ... ✂ You gotta keep 'em separated.
  12. var buttonOne = Titanium.UI.createButton({ title:'I am a Button', height:40, width:200,

    top:10 }); var buttonTwo = Titanium.UI.createButton({ title:'I am also a Button', image:'../images/chat.png', width:200, height:40, top:60 }); JAVASCRIPT
  13. var buttonOne = Titanium.UI.createButton({ title:'I am a Button', height:40, width:200,

    top:10 }); var buttonTwo = Titanium.UI.createButton({ title:'I am also a Button', image:'../images/chat.png', width:200, height:40, top:60 }); Presentation JAVASCRIPT
  14. #buttonOne { title:'I am a Button'; width:200; height:40; top:10 }

    #buttonTwo { title:'I am also a Button'; image:'../images/chat.png'; width:200; height:40; top:60 } .button { height: 40; width: 200; } var buttonOne = Titanium.UI.createButton({ id: "buttonOne", className: "button" }); var buttonTwo = Titanium.UI.createButton({ id: "buttonTwo", className: "button" }); Behavior and composition in Presentation in .js .jss JSS JAVASCRIPT
  15. #buttonOne { title:'I am a Button'; width:200; height:40; top:10 }

    #buttonTwo { title:'I am also a Button'; image:'../images/chat.png'; width:200; height:40; top:60 } .button { height: 40; width: 200; } var buttonOne = Titanium.UI.createButton({ id: "buttonOne", className: "button" }); var buttonTwo = Titanium.UI.createButton({ id: "buttonTwo", className: "button" }); Style hooks JAVASCRIPT JSS
  16. #buttonOne { title: 'I am a Button'; width: 200; height:

    40; top: 10 } #buttonTwo { title: 'I am also a Button'; image: '../images/chat.png'; width: 200; height: 40; top: 60 } .button { height: 40; width: 200; } Is it JSS or Sassy CSS? Yes? JSS / SCSS
  17. #buttonOne title: 'I am a Button' width: 200 height: 40

    top: 10 #buttonTwo title: 'I am also a Button' image: '../images/chat.png' width: 200 height: 40 top: 60 .button height: 40 width: 200 I prefer Sass' original indented, whitespace aware syntax. SASS
  18. #buttonOne { title: 'I am a Button'; width: 200; height:

    40; top: 10 } #buttonTwo { title: 'I am also a Button'; image: '../images/chat.png'; width: 200; height: 40; top: 60 } .button { height: 40; width: 200; } SCSS
  19. #buttonOne title: 'I am a Button' width: 200 height: 40

    top: 10 #buttonTwo title: 'I am also a Button' image: '../images/chat.png' width: 200 height: 40 top: 60 .button height: 40 width: 200 SASS
  20. stylesheets ├── _activity.sass ├── _base.sass ├── _confirmation.sass ├── _course.scss ├──

    _courses.sass ├── _friends.scss ├── _gameplay.sass ├── _leaderboard.sass ├── _leaders.sass ├── _login.sass ├── _requests.sass ├── _tourcard.sass └── app.sass @import 'base' @import 'login' @import 'activity' @import 'course' @import 'courses' @import 'friends' @import 'leaderboard' @import 'leaders' @import 'requests' @import 'tourcard' @import 'confirmation' @import 'gameplay' Resources ├── app.js ├── app.jss ...
  21. stylesheets ├── _activity.sass ├── _base.sass ├── _confirmation.sass ├── _course.scss ├──

    _courses.sass ├── _friends.scss ├── _gameplay.sass ├── _leaderboard.sass ├── _leaders.sass ├── _login.sass ├── _requests.sass ├── _tourcard.sass └── app.sass @import 'base' @import 'login' @import 'activity' @import 'course' @import 'courses' @import 'friends' @import 'leaderboard' @import 'leaders' @import 'requests' @import 'tourcard' @import 'confirmation' @import 'gameplay' Resources ├── app.js ├── app.jss ... Mix scss with sass if you're so inclined.
  22. .user-info bottom: 1 color: #333 font-size: 11 font-weight: bold height:

    auto left: 0 shadowColor: #fff shadowOffset-x: 0 shadowOffset-y: -1 text-align: center width: 92 SASS
  23. .user-info bottom: 1 color: #333 font: size: 11 weight: bold

    height: auto left: 0 shadowColor: #fff shadowOffset: x: 0 y: '-1' text-align: center width: 92 DRY it up. Nesting SASS
  24. #buttonOne title: 'I am a Button' width: 200 height: 40

    top: 10 #buttonTwo title: 'I am also a Button' image: '../images/chat.png' width: 200 height: 40 top: 60 .button height: 40 width: 200 SASS
  25. =button height: 40 width: 200 #buttonOne +button title: 'I am

    a Button' top: 10 #buttonTwo +button title: 'I am also a Button' image: '../images/chat.png' top: 60 DRY it up. Mixins SASS
  26. =bottom-right($height: 40, $width: 200) height: $size width: $size right: 0

    bottom: 0 #buttonOne +bottom-right title: 'I am a Button' #buttonTwo +bottom-right(50, 300) title: 'I am also a Button' image: '../images/chat.png' DRY it up. Mixins with params SASS
  27. #buttonOne title: 'I am a Button' width: 200 height: 40

    top: 10 #buttonTwo title: 'I am also a Button' image: '../images/chat.png' width: 200 height: 40 top: 60 .button height: 40 width: 200 SASS
  28. .button height: 40 width: 200 #buttonOne @extend .button title: 'I

    am a Button' top: 10 #buttonTwo @extend .button title: 'I am also a Button' image: '../images/chat.png' top: 60 DRY it up. @extend SASS
  29. .button, #buttonOne, #buttonTwo { height: 40; width: 200; } #buttonOne

    { title: 'I am a Button'; width: 200; } #buttonTwo { title: 'I am also a Button'; image: '../images/chat.png'; top: 60 } DRY it up. @extend One less class in our .js JSS
  30. $button-base: #a7a7a7 #buttonOne color: $button-base title: "Button 1" #buttonTwo color:

    darken($button-base, 20%) title: "Button 2" color functions SASS
  31. $button-base: #a7a7a7 #buttonOne color: $button-base title: "Button 1" #buttonTwo color:

    darken($button-base, 20%) title: "Button 2" color functions SASS
  32. hue(#cc3) # => 60deg saturation(#cc3) # => 60% lightness(#cc3) #

    => 50% adjust-hue(#cc3, 20deg) # => #9c3 saturate(#cc3, 10%) # => #d9d926 desaturate(#cc3, 10%) # => #bfbf40 lighten(#cc3, 10%) # => #d6d65c darken(#cc3, 10%) # => #a3a329 grayscale(#cc3) # => desaturate(#cc3, 100%) = #808080 complement(#cc3) # => adjust-hue(#cc3, 180deg) = #33c mix(#cc3, #00f) # => #e56619 mix(#cc3, #00f, 10%) # => #f91405 mix(#cc3, #00f, 90%) # => #d1b72d more color functions SASS
  33. mix(rgba(51, 255, 51, 0.75), #f00) # => rgba(178, 95, 19,

    0.875) mix(rgba(51, 255, 51, 0.90), #f00) # => rgba(163, 114, 22, 0.95) alpha(rgba(51, 255, 51, 0.75)) # => 0.75 opacity(rgba(51, 255, 51, 0.75)) # => 0.75 opacify(rgba(51, 255, 51, 0.75), 0.1) # => rgba(51, 255, 51, 0.85) fade-in(rgba(51, 255, 51, 0.75), 0.1) # => rgba(51, 255, 51, 0.85) transparentize(rgba(51, 255, 51, 0.75), 0.1) # => rgba(51, 255, 51, 0.65) fade-out(rgba(51, 255, 51, 0.75), 0.1) # => rgba(51, 255, 51, 0.65) even more color functions SASS with alpha support!
  34. sass40 Save 40% and get early access! Sadly, sass100 is

    not a valid code. http://wynn.fm/ti-sass
  35. button = Titanium.UI.createButton title: 'I am a Button' height: 40

    width: 200 top: 10 button.addEventListener 'click', (e) -> alert "Oooh, that tickles!" COFFEESCRIPT Look familiar?
  36. MyApp.Views.createLoginWindow = (opts={}) -> window = Ti.UI.createWindow(opts) button = Titanium.UI.createButton

    title: 'I am a Button' height: 40 width: 200 top: 10 window.add button # methods say = (msg) -> alert(msg) # event handlers button.addEventListener 'click', -> say('hello') window COFFEESCRIPT
  37. MyApp.Views.createLoginWindow = (opts={}) -> window = Ti.UI.createWindow(opts) button = Titanium.UI.createButton

    title: 'I am a Button' height: 40 width: 200 top: 10 window.add button # methods say = (msg) -> alert(msg) # event handlers button.addEventListener 'click', -> say('hello') window Create the factory method in the appropriate namespace. COFFEESCRIPT
  38. MyApp.Views.createLoginWindow = (opts={}) -> window = Ti.UI.createWindow(opts) button = Titanium.UI.createButton

    title: 'I am a Button' height: 40 width: 200 top: 10 window.add button # methods say = (msg) -> alert(msg) # event handlers button.addEventListener 'click', -> say('hello') window Compose the view from Titanium types or others of your own COFFEESCRIPT
  39. MyApp.Views.createLoginWindow = (opts={}) -> window = Ti.UI.createWindow(opts) button = Titanium.UI.createButton

    title: 'I am a Button' height: 40 width: 200 top: 10 window.add button # methods say = (msg) -> alert(msg) # event handlers button.addEventListener 'click', -> say('hello') window Methods in familiar place COFFEESCRIPT
  40. MyApp.Views.createLoginWindow = (opts={}) -> window = Ti.UI.createWindow(opts) button = Titanium.UI.createButton

    title: 'I am a Button' height: 40 width: 200 top: 10 window.add button # methods say = (msg) -> alert(msg) # event handlers button.addEventListener 'click', -> say('hello') window And event handlers... COFFEESCRIPT
  41. MyApp.Views.createLoginWindow = (opts={}) -> window = Ti.UI.createWindow(opts) button = Titanium.UI.createButton

    title: 'I am a Button' height: 40 width: 200 top: 10 window.add button # methods say = (msg) -> alert(msg) # event handlers button.addEventListener 'click', -> say('hello') window Return your view COFFEESCRIPT
  42. class GolfStatus.Models.Game constructor: (@owner, @course, @playingFor='brag', @scoringFormat='low_net') -> serialize: ->

    deserialize: (data) -> save: -> resume: -> dataForSubmit: () -> submit: (error) -> ... COFFEESCRIPT
  43. ... # Build the full API URI for a request

    requestURI: (path, query={}) -> uri = "#{GolfStatus.API_ENDPOINT}#{path}.json?" for own key, value of query uri += "#{ key }=#{ escape(value) }&" uri COFFEESCRIPT URI building
  44. # Common request handling across all verbs request: (path, options,

    authenticated=true) -> # Default to GET options.method ?= 'GET' options.query ?= {} options.success ?= -> Ti.API.info options.error ?= -> Ti.API.error xhr = Ti.Network.createHTTPClient() xhr.onreadystatechange = (e) -> ... # Default event handlers # Basic auth # other common stuff ... if options.body data = JSON.stringify(options.body) Ti.API.debug data xhr.send(data) else xhr.send() COFFEESCRIPT HTTP Request building
  45. # High level method for GET requests get: (path, options,

    authenticated=true) -> options.method = 'GET' @request path, options, authenticated # High level method for POST requests post: (path, options, authenticated=true) -> options.method = 'POST' @request path, options, authenticated # High level method for DELETE requests delete: (path, options, authenticated=true) -> options.method = 'DELETE' @request path, options, authenticated COFFEESCRIPT High level methods for HTTP verbs
  46. # ### Authenticate the user ### authenticate: (options) -> Ti.API.debug

    "GolfStatus.API.authenticate" @get '/me', options # ### Logout the user ### logout: (options) -> Ti.API.debug "GolfStatus.API.logout" @delete '/logout', options # ### Forgot password forgotPassword: (email, options) -> Ti.API.debug "GolfStatus.API.forgotPassword" options.query = {} options.query.email = email @post '/passwords', options, false # ### Convenience method to get current user info ### me: (options) -> Ti.API.debug "GolfStatus.API.me" @authenticate options COFFEESCRIPT Higher level API methods
  47. . ├── Resources # Titanium root │ └── vendor #

    JavaScript frameworks ├── src # CoffeeScript root │ └── golf_status # App root │ ├── models │ └── views │ ├── account # App domains │ ├── activity │ ├── courses │ ├── leaderboard │ └── play └── stylesheets # Sass
  48. GolfStatus = Models: {} Views: Account: {} Activity: {} Courses:

    {} Leaderboard: {} Play: {} Ti.include('vendor/date.js') Ti.include('vendor/underscore.js') Ti.include('golf_status.js') GolfStatus.App.init() COFFEESCRIPT
  49. GolfStatus = Models: {} Views: Account: {} Activity: {} Courses:

    {} Leaderboard: {} Play: {} Ti.include('vendor/date.js') Ti.include('vendor/underscore.js') Ti.include('golf_status.js') GolfStatus.App.init() COFFEESCRIPT Set up your namespaces
  50. GolfStatus = Models: {} Views: Account: {} Activity: {} Courses:

    {} Leaderboard: {} Play: {} Ti.include('vendor/date.js') Ti.include('vendor/underscore.js') Ti.include('golf_status.js') GolfStatus.App.init() COFFEESCRIPT third party frameworks
  51. GolfStatus = Models: {} Views: Account: {} Activity: {} Courses:

    {} Leaderboard: {} Play: {} Ti.include('vendor/date.js') Ti.include('vendor/underscore.js') Ti.include('golf_status.js') GolfStatus.App.init() COFFEESCRIPT All of our app in just one file
  52. GolfStatus = Models: {} Views: Account: {} Activity: {} Courses:

    {} Leaderboard: {} Play: {} Ti.include('vendor/date.js') Ti.include('vendor/underscore.js') Ti.include('golf_status.js') GolfStatus.App.init() COFFEESCRIPT Fire up the app and first window
  53. run-iphone: & @DEVICE_TYPE=iphone make run test-iphone: & @DEVICE_TYPE=iphone make test

    run-ipad: & @DEVICE_TYPE=ipad make run test-ipad: & @DEVICE_TYPE=ipad make test run: & @if [ "${DEVICE_TYPE}" == "" ]; then\ & & echo "Please run \"make run-[iphone|ipad]\" instead.";\ & & exit 1;\ & fi & @mkdir -p ${PROJECT_ROOT}/${PROJECT_NAME}/Resources/test/ & @echo "" > ${PROJECT_ROOT}/${PROJECT_NAME}/Resources/test/enabled.js & @make launch-titanium http://wynn.fm/g9 guilhermechapiewski (Guilherme Chapiewski)
  54. def compile_sass puts "Compiling stylesheets".blue input = "stylesheets/app.sass" output =

    "Resources/app.jss" system "sass --compass -C -t expanded #{input} > #{output}" end RAKEFILE
  55. def compile_coffee paths = `find src/golf_status -name '*.coffee'`.split("\n") compilation =

    ( puts "Compiling CoffeeScript (golf_status.js)".blue output = "Resources/golf_status.js" system "coffee --join #{output} -b -c #{paths.join(' ')}" and puts "Compiling CoffeeScript (app.js)".blue system "coffee -p --bare src/app.coffee > Resources/app.js" ) if compilation puts "Successfully compiled CoffeeScript".green else puts "Error compiling CoffeeScript".red end compilation end RAKEFILE
  56. def compile_coffee paths = `find src/golf_status -name '*.coffee'`.split("\n") compilation =

    ( puts "Compiling CoffeeScript (golf_status.js)".blue output = "Resources/golf_status.js" system "coffee --join #{output} -b -c #{paths.join(' ')}" puts "Compiling CoffeeScript (app.js)".blue system "coffee -p --bare src/app.coffee > Resources/app.js" ) if compilation puts "Successfully compiled CoffeeScript".green else puts "Error compiling CoffeeScript".red end compilation end RAKEFILE Compile App namespaces to single file
  57. def compile_coffee paths = `find src/golf_status -name '*.coffee'`.split("\n") compilation =

    ( puts "Compiling CoffeeScript (golf_status.js)".blue output = "Resources/golf_status.js" system "coffee --join #{output} -b -c #{paths.join(' ')}" puts "Compiling CoffeeScript (app.js)".blue system "coffee -p --bare src/app.coffee > Resources/app.js" ) if compilation puts "Successfully compiled CoffeeScript".green else puts "Error compiling CoffeeScript".red end compilation end RAKEFILE Compile app.js which includes the app library
  58. def build(options={}) return unless compile options[:device] ||= 'iphone' puts "Building

    with Titanium... (DEVICE_TYPE:#{options[:device]})".blue sh %Q{bash -c "#{TI_BUILD} run #{PROJECT_ROOT}/ #{IPHONE_SDK_VERSION} #{APP_ID} #{APP_NAME} #{APP_DEVICE}" \ | perl -pe 's/^\\[DEBUG\\].*$/\\e[35m$&\\e[0m/g;s/^\\[INFO\\].*$/\\e[36m $&\\e[0m/g;s/^\\[WARN\\].*$/\\e[33m$&\\e[0m/g;s/^\\[ERROR\\].*$/\\e[31m $&\\e[0m/g;'} end RAKEFILE
  59. def build(options={}) return unless compile options[:device] ||= 'iphone' puts "Building

    with Titanium... (DEVICE_TYPE:#{options[:device]})".blue sh %Q{bash -c "#{TI_BUILD} run #{PROJECT_ROOT}/ #{IPHONE_SDK_VERSION} #{APP_ID} #{APP_NAME} #{APP_DEVICE}" \ | perl -pe 's/^\\[DEBUG\\].*$/\\e[35m$&\\e[0m/g;s/^\\[INFO\\].*$/\\e[36m $&\\e[0m/g;s/^\\[WARN\\].*$/\\e[33m$&\\e[0m/g;s/^\\[ERROR\\].*$/\\e[31m $&\\e[0m/g;'} end RAKEFILE Build with Titanium Python command line
  60. def build(options={}) return unless compile options[:device] ||= 'iphone' puts "Building

    with Titanium... (DEVICE_TYPE:#{options[:device]})".blue sh %Q{bash -c "#{TI_BUILD} run #{PROJECT_ROOT}/ #{IPHONE_SDK_VERSION} #{APP_ID} #{APP_NAME} #{APP_DEVICE}" \ | perl -pe 's/^\\[DEBUG\\].*$/\\e[35m$&\\e[0m/g;s/^\\[INFO\\].*$/\\e[36m $&\\e[0m/g;s/^\\[WARN\\].*$/\\e[33m$&\\e[0m/g;s/^\\[ERROR\\].*$/\\e[31m $&\\e[0m/g;'} end RAKEFILE Pipe to PERL for some colored terminal goodness
  61. ├── Coffeefile ├── Guardfile ├── LICENSE ├── Rakefile ├── Readme.mkd

    ├── Resources │ ├── app.js │ ├── app.jss │ ├── images │ │ ├── KS_nav_ui.png │ │ └── KS_nav_views.png │ ├── lsrc.js │ └── vendor ├── app │ ├── app.coffee │ └── lsrc │ ├── api.coffee │ ├── app.coffee │ ├── helpers │ │ └── application.coffee │ ├── models │ ├── stylesheets │ │ ├── app.sass │ │ └── partials │ └── views ├── build ├── config │ └── config.rb ├── docs ├── spec │ ├── app_spec.coffee │ ├── helpers │ ├── models │ └── views ├── tiapp.xml └── tmp
  62. XIB