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

Single Page Applications with CoffeeScript

Single Page Applications with CoffeeScript

The slides from a "Single Page Applications with CoffeeScript" workshop at the Eurucamp 2012 conference.

Andrzej Krzywda

August 17, 2012
Tweet

More Decks by Andrzej Krzywda

Other Decks in Programming

Transcript

  1. Required tools and repos 1. install node.js (http://nodejs.org/#download) 2. npm

    install -g coffee-script 3. npm install -g coffee-toaster 4. git clone [email protected]:gameboxed/todomvc.git 5. git checkout -b minimal remotes/origin/minimal 6. cd todomvc; toaster -dw 7. open index.html and see if it works AGENDA 1. Presentation 2. Introducing the TodoMVC project 3. Look at the minimal implementation 4. Implementing “delete a task” feature - you follow me 5. Exercise: Implementing “complete/uncomplete a task” 6. Exercise: Implementing “complete all tasks” 7. Exercise: Implementing “Edit a task” 8. Summary Saturday, August 18, 12
  2. “I have no idea what I’m doing” A random Ruby

    programmer doing JS frontend, 2007-2012 Saturday, August 18, 12
  3. http://mlomnicki.com/javascript/2012/08/13/from-backend-to-frontend.html “As a backend developer I used to treat JavaScript

    as a toy language. Frontend programming was just a little addition. The “Real Work” was done on backend. It has changed. Mainly thanks to Single-Page- Applications. Nowadays JavaScript and CoffeeScript are one of the most important languages. Frontend programming is as important as backend. Recently I’ve dived into frontend and I don’t regret it at all. CoffeeScript and Gameboxed Engine are the most brilliant things since Rails.” Michał Łomnicki, Ruby programmer the maintainer of schema_plus, automatic_foreign_key, exceptioner gems Saturday, August 18, 12
  4. HTML on the frontend is the same as on the

    backend Saturday, August 18, 12
  5. <p> Your wonderful shooting skill let you score:<br> <b>{{playerMaxScore}} points.</b>

    </p> <a class="button ok_button">NEXT</a> handlebar Saturday, August 18, 12
  6. Mental transition • Faza 1: No JavaScript • Faza 2:

    JQuery explosion • Faza 3: Page/Widget object • Faza 4: Single Page Application Which phase is your project in? Saturday, August 18, 12
  7. Models • Game • Player • GameSession • Round •

    Prize • Friend (Invitation) • Life (LifeRequest...) • Team Saturday, August 18, 12
  8. pickCardAndNotifyIfAny: (cell, callback) => console.debug "pickCardAndNotifyIfAny #{cell.position}" card = @drawCard(cell)

    if not card console.debug "no card picked" callback?() return console.debug "card found: #{card.identifier}" card.onPicked(@) if card.onPicked? @eventBroker.trigger("player:picked_card:#{card.identifier}", card, callback) @eventBroker.trigger("player:picked_card", card) Saturday, August 18, 12
  9. class engine.monopoly.controllers.CardItemBargainContoller constructor: (@services, @game) -> @helper = new CardHelperForUsecases(@services)

    setup: => @services.eventBroker.bind('player:picked_card:CardItemBargain', @execute) execute: (card, callback) => @popup = @helper.showCardGenericPopupAndBindOnOK( (=> @applyFormDataToBargain(card, callback)), null) @popup.bind("popup:opened", => @popup.find('input').focus()) applyFormDataToBargain: (card, callback) => offer = @popup.find('input').val() new CardItemBargain(@services, @game).execute(card, offer, callback) Saturday, August 18, 12
  10. class engine.shooter.components.StageResultWon constructor: (@eventBroker) -> _.extend(@, Backbone.Events) super() @templateId =

    "stage_result_won" addMeToScreen: (root, me) => $("#gameArea").append(me) configureElement: (me) => me.find('.okButton').mousedown (event) => @hide() @eventBroker.trigger('stage:result:shown') Saturday, August 18, 12
  11. class engine.shooter.models.Game constructor: (@serverSide, @eventBroker) -> super(@eventBroker) @levels = []

    @guns = [] registerEvents: (eventBroker) => eventBroker.bind('game:start:requested', @start) eventBroker.bind("player:clicked:inside-target", @playerTriggeredShotInsideTarget) eventBroker.bind("player:clicked:magazine:reload", @playerWantsToReload) eventBroker.bind("stage:start:clicked", @startStageClicked) eventBroker.bind('countdown:stage:finish', @finishCurrentStage) eventBroker.bind("stage:result:shown", @loadNextStageOrFinishGame) Saturday, August 18, 12
  12. class engine.invite_and_win.GameUseCase constructor: (@game, @player) -> ObjectHelper.addRole(@player, engine.shared.models.PlayerWithFriends) @facebookHQ =

    new engine.invite_and_win.FacebookHQ() tryToEnterGameArea: () => if @amIEnteringGameFirstTime() if @amICommingFromInvitation() @tellPlayerHeIsPartOfTeam(@facebookHQ.friendsInviting) @teachPlayerHowToPlay() else #n-th time... if @amICommingFromInvitation() @tellPlayerHeIsPartOfTeam(@facebookHQ.friendsInviting) if not @playerLikesFanpage() @askPlayerToLikeFanpage() if @haveNotYetPickedFavPizzaCountry() @askPlayerToDeclareHisFavCountry() Saturday, August 18, 12
  13. class engine.invite_and_win.GameUseCase constructor: (@game, @player) -> ObjectHelper.addRole(@player, engine.shared.models.PlayerWithFriends) tryToEnterGameArea: ()

    => if @amIEnteringGameFirstTime() if @amICommingFromInvitation() @tellPlayerHeIsPartOfTeam(@facebookHQ.friendsInviting) @teachPlayerHowToPlay() else #n-th time... if @amICommingFromInvitation() @tellPlayerHeIsPartOfTeam(@facebookHQ.friendsInviting) if not @playerLikesFanpage() @askPlayerToLikeFanpage() if @haveNotYetPickedFavPizzaCountry() @askPlayerToDeclareHisFavCountry() a role Saturday, August 18, 12
  14. class engine.shared.models.PlayerWithFriends extends Mixin setup: => @friends = [] @invitedFriends

    = [] @acceptedFriends = [] setInvitedFriends: (facebookUids) => for facebookUid in facebookUids friend = new Friend({facebookUid: facebookUid}) @invitedFriends.push(friend) setFriends: (friends) => @friends = friends addFriend: (friend) => existing = @getFriendByFacebookUid(friend?.facebookUid) if not existing? @friends.push(friend) Saturday, August 18, 12
  15. class engine.invite_and_win.GameGuiConfiguration constructor: (@gameUseCase, @game, @gui, @services, @sharedComponents) -> execute:

    () => Around(@gameUseCase, 'tryToEnterGameArea', @checkFbInvitation) After (@gameUseCase, 'tryToEnterGameArea', @showTeamArea) After (@gameUseCase, 'tryToEnterGameArea', @showButtonInviteOrPostPicture) Around(@gameUseCase, 'tellPlayerHeIsPartOfTeam', @showTeamPopup) Around(@gameUseCase, 'askPlayerToLikeFanpage', @showLikePopup) Around(@gameUseCase, 'teachPlayerHowToPlay', @showTutorialPopup) Around(@gameUseCase, 'playerWantsToKnowWinnersWithPrize', @showWinnersPopup) Around(@gameUseCase, 'playerWantsToKnowPrizes', @showPrizesPopup) Around(@gameUseCase, 'askPlayerToDeclareHisFavCountry', @showDeclareCountryPopup) Around(@gameUseCase, 'iAcceptMyFriendInvitationToATeam', @onIAcceptMyFriendInvitationToATeam) Saturday, August 18, 12
  16. class engine.shared.server.ServerSide constructor: (@gameBasicDetails) -> @gameEngineUrl = "/engine/games/#{@gameBasicDetails.id}" @gameUrl =

    "/games/#{@gameBasicDetails.id}" @errors = [] gameDetailsLoaded: (gameDetails, callback) => callback(gameDetails) fetchGameDetails: (callback, errback) => $.ajax( type: "GET" url: "#{@gameEngineUrl}.json" success: (gameDetails) => @gameDetailsLoaded(gameDetails, callback) error: errback ) Saturday, August 18, 12
  17. Very useful for API, but it’s not part of the

    domain! Saturday, August 18, 12
  18. Rails is already a lot of magic. Any framework on

    top of Rails can be very quick at the start, but very difficult later on. Disconnect from the frameworks. Saturday, August 18, 12
  19. tl;dr • The frontend is a separate application • Use

    CoffeeScript • Be sceptical about frameworks • Write use cases in the code • Read about DCI • Create nice frontends Saturday, August 18, 12
  20. Use-case roundtrip • gui -> glue -> use-case -> glue

    -> gui • dataSource -> glue -> use-case -> glue -> gui Saturday, August 18, 12
  21. Deleting a task • Use case - destroyTask • Gui

    - destroyTaskClicked, destroyTask • Glue - 2 Afters Saturday, August 18, 12
  22. complete/uncomplete a task • Use case - toggleTask, completeTask, uncompleteTask

    • Gui - toggleTaskClicked, completeTask, uncompleteTask • Glue - 3 Afters Saturday, August 18, 12
  23. edit/update a task • Use case - editTask, updateTask •

    Gui - taskDoubleClicked, editTask, updateTask • Glue - 4 Afters Saturday, August 18, 12
  24. More exercises? • Start a pomodoro timer for a task,

    show desktop notification when pomodoro finishes • Implement undo/redo • Server-side storage • Multi-user support - with Pusher Saturday, August 18, 12