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

Single Page Applications with CoffeeScript (at ...

Single Page Applications with CoffeeScript (at KGDNET in Krakow)

Andrzej Krzywda

September 26, 2012
Tweet

More Decks by Andrzej Krzywda

Other Decks in Programming

Transcript

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

    programmer doing JS frontend, 2007-2012 *backend = ruby | python | php | java | .net Thursday, September 27, 12
  2. backend = ruby | python | php | java |

    .net What do most of the backend developers have in common? Thursday, September 27, 12
  3. We’re all in this together. backend = ruby | python

    | php | java | .net Thursday, September 27, 12
  4. Andrzej Krzywda now: Ruby and Coffee programmer in the past:

    .Net (mostly IronPython and WinForms) Java (web and desktop) Thursday, September 27, 12
  5. HTML on the frontend is the same as on the

    backend Thursday, September 27, 12
  6. <p> Your wonderful shooting skill let you score:<br> <b>{{playerMaxScore}} points.</b>

    </p> <a class="button ok_button">NEXT</a> handlebar Thursday, September 27, 12
  7. 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? Thursday, September 27, 12
  8. Read “JavaScript: The Good Parts” by Douglas Crockford in order

    to understand JS, its 4 ways of calling a function and (most important) to learn about prototypical inheritance. Thursday, September 27, 12
  9. CoffeeScript is easy and awesome, start with that, even if

    you don’t fully get JS (but learn JS later). Thursday, September 27, 12
  10. Real MVC (model changes cause GUI changes) (Rails, Struts, Spring,

    .NET MVC - are not MVC!) Thursday, September 27, 12
  11. It all comes down to implementing the Observer pattern •

    Callbacks • Events • Aspects Thursday, September 27, 12
  12. Models • Game • Player • GameSession • Round •

    Prize • Friend (Invitation) • Life (LifeRequest...) • Team Thursday, September 27, 12
  13. 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) Thursday, September 27, 12
  14. 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) Thursday, September 27, 12
  15. 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') Thursday, September 27, 12
  16. 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) Thursday, September 27, 12
  17. 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() Thursday, September 27, 12
  18. 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 Thursday, September 27, 12
  19. 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) Thursday, September 27, 12
  20. 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) Thursday, September 27, 12
  21. Use case View Glue Domain 1. 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 Thursday, September 27, 12
  22. Use case View Glue Domain 1. 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 Thursday, September 27, 12
  23. You don’t need a backend to start working on a

    frontend Thursday, September 27, 12
  24. You can implement the backend API at the end, once

    you know what you need. Thursday, September 27, 12
  25. 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 ) Thursday, September 27, 12
  26. Very useful for API, but it’s not part of the

    domain! Thursday, September 27, 12
  27. 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 Thursday, September 27, 12