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

Single Page Applications with CoffeeScript (at ...

Single Page Applications with CoffeeScript (at DRUG, August 2012)

The slides from my talk at the August 2012 DRUG meetup.

Andrzej Krzywda

August 20, 2012
Tweet

More Decks by Andrzej Krzywda

Other Decks in Programming

Transcript

  1. “I have no idea what I’m doing” A random Ruby

    programmer doing JS frontend, 2007-2012 Tuesday, August 21, 12
  2. 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 Tuesday, August 21, 12
  3. HTML on the frontend is the same as on the

    backend Tuesday, August 21, 12
  4. <p> Your wonderful shooting skill let you score:<br> <b>{{playerMaxScore}} points.</b>

    </p> <a class="button ok_button">NEXT</a> handlebar Tuesday, August 21, 12
  5. Mental transition • Phase 1: No JavaScript • Phase 2:

    JQuery explosion • Phase 3: Page/Widget object • Phase 4: Single Page Application Which phase is your project in? Tuesday, August 21, 12
  6. Models • Game • Player • GameSession • Round •

    Prize • Friend (Invitation) • Life (LifeRequest...) • Team Tuesday, August 21, 12
  7. 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) Tuesday, August 21, 12
  8. 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) Tuesday, August 21, 12
  9. 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') Tuesday, August 21, 12
  10. 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) Tuesday, August 21, 12
  11. 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() Tuesday, August 21, 12
  12. 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 Tuesday, August 21, 12
  13. 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) Tuesday, August 21, 12
  14. 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) Tuesday, August 21, 12
  15. Use case View Glue Domain 1. omg, deleteClicked 2. deleteTask

    3. user.deleteTask 4. all fine (no exception) 5. task deleted 6. remove task DOM element 7. change task stats Storage 8. remove task from storage Tuesday, August 21, 12
  16. Use case View Glue Domain 1. omg, deleteClicked 2. deleteTask

    3. user.deleteTask 4. all fine (no exception) 5. task deleted 6. remove task DOM element 7. change task stats Storage 8. remove task from storage View Controller Model Tuesday, August 21, 12
  17. 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 ) Tuesday, August 21, 12
  18. Very useful for API, but it’s not part of the

    domain! Tuesday, August 21, 12
  19. 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. Tuesday, August 21, 12
  20. 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 Tuesday, August 21, 12