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

Tastebuds Radio - a rapidly developed Ember.js app

Stephen Best
December 13, 2012

Tastebuds Radio - a rapidly developed Ember.js app

Stephen Best

December 13, 2012
Tweet

More Decks by Stephen Best

Other Decks in Programming

Transcript

  1. What is Tastebuds? ? 2 Marriages (we know of) ‘A

    dazzlingly slick and intuitive site with enormous potential for growth’ 2 Million messages 100,000 users Meet people who share your taste in music Thursday, 13 December 12
  2. We’ve made some waves One of ‘10 Exciting European Startups

    from 2011’ TV COVERAGE Thursday, 13 December 12
  3. Our Users 18-25: 58% 26-30: 21% 31+: 21% 48% new

    to online dating Thursday, 13 December 12
  4. Brand Notoriety “Coldplay fans least likely to have sex on

    first date” The Sun, Metro, NME.com, Time.com 12,000 Facebook Likes “Worst album covers of all time” 800,000 Facebook Likes 12,000 Tweets Class, style, sophistication, taste - its nice to see all of these absent. @stephenfry Thursday, 13 December 12
  5. Team Julian Keenaghan Co-Founder / Developer Previously Senior Developer at

    Spoonfed Media. Guitarist. Alex Parish Co-Founder / Designer Previously Technical Director at Edelman & Spook Media. Bass Guitarist. Stephen Best Developer Previously Head of Technology at various London startups. Metal Bassist. Alex and Julian met in September 2007 as part of the instrumental rock group, ‘Years of Rice and Salt’. After incorporating Tastebuds in November 2010, they took part in the Springboard Accelerator programme in Cambridge in Summer 2011. Mariusz Bari Community Manager Previously Community Manager at LD50 music community. Electro DJ Thursday, 13 December 12
  6. Tastebuds on Spotify • Spotify app launched May 2012 covered

    by Wired.co.uk, Mashable, TechCrunch, Bloomberg TV, The Metro • Meet new people using the music you listen to on Spotify Thursday, 13 December 12
  7. What is Tastebuds Radio? A new, fun way to explore

    and interact with the profiles and song selections of your most musically compatible peers. • Scrolling list of matched users • Each face plays a song • Like • Comment Thursday, 13 December 12
  8. Now the science part ... • Rails • Heroku •

    Postgres • MongoDB • Pusher Thursday, 13 December 12
  9. The Radio project began in late September Ember version //

    Last commit: 55d2d23 (2012-09-07 16:48:14 +0100) Design / planning ~ 1 week Prototype ~ 1 week Polished MVP ~ 1 week Feature complete ~ 2 weeks Thanks to Ember! Thursday, 13 December 12
  10. 1 2 Buds = {} 3 window.Buds = Buds 4

    5 Buds.Controller = Em.ObjectController.extend() 6 Buds.ArrayController = Em.ArrayController.extend() 7 Buds.View = Em.View.extend() 8 Buds.CollectionView = Em.CollectionView.extend() 9 Buds.ContainerView = Em.ContainerView.extend() 10 Buds.Router = Em.Router.extend() 11 Buds.O = Em.Object.extend() 12 Buds.Mixin = Em.Mixin 13 Buds.Noop = Em.K 14 Base Classes This is your app first and an Ember app second. Define your own objects and APIs where possible Thursday, 13 December 12
  11. 2 3 Buds.Radio.Playlist = Buds.O.extend 4 profileSongs: {} 5 users:

    {} 6 orderedSongIds: [] 7 likeIds: [] 8 favouriteIds: [] 9 _cards: [] 10 _fetcher: null 11 12 init: -> 13 @_super() 14 @set('_fetcher', Buds.Radio.SongFetcher.create()) 15 16 fetchMoreSongs: (options) -> 17 @get('_fetcher').fetch(this, options) 18 19 getPlayer: (options) -> 20 throw new Error("Must provide a song to create pl 21 Buds.Radio.Player.create(options) 22 23 favouriteUser: (user) -> 24 return if @isUserFavourited(user) 25 @get('favouriteIds').pushObject(user.get('id')) 26 user.set("isFavourited", true) Core Objects • Playlist - Hides raw data - Gateway to other core objects Thursday, 13 December 12
  12. 1 2 Buds.Radio.Player = Buds.O.extend 3 song: null 4 htmlElementId:

    'budsPlayer' 5 isPlaying: null 6 scrobbler: null 7 songEndedCallback: Buds.Noop 8 9 init: -> 10 @_super() 11 @set('scrobbler', Buds.Radio.Scrobbler.create(son 12 @set('viewLogger', Buds.Radio.Player.ViewLogger.c 13 22 insertFlashElement: -> 23 unless @get('song') 24 throw new Error("must set song before inserting 25 31 embedUrl = "http://www.youtube.com/apiplayer?vide 32 window.onYouTubePlayerReady = => @youTubeReady.ap 33 window.swfobject.embedSWF(embedUrl, "yt-player", 34 35 play: -> 36 @get('htmlElement').playVideo() 37 38 pause: -> 39 @get('htmlElement').pauseVideo() Core Objects • Playlist - Hides raw data - Gateway to other core objects • Player - Handles playstate - Abstracts YouTube API Thursday, 13 December 12
  13. 1 2 Buds.Radio.Player = Buds.O.extend 3 song: null 4 htmlElementId:

    'budsPlayer' 5 isPlaying: null 6 scrobbler: null 7 songEndedCallback: Buds.Noop 8 9 init: -> 10 @_super() 11 @set('scrobbler', Buds.Radio.Scrobbler.create(son 12 @set('viewLogger', Buds.Radio.Player.ViewLogger.c 13 22 insertFlashElement: -> 23 unless @get('song') 24 throw new Error("must set song before inserting 25 31 embedUrl = "http://www.youtube.com/apiplayer?vide 32 window.onYouTubePlayerReady = => @youTubeReady.ap 33 window.swfobject.embedSWF(embedUrl, "yt-player", 34 35 play: -> 36 @get('htmlElement').playVideo() 37 38 pause: -> 39 @get('htmlElement').pauseVideo() Core Objects • Playlist - Hides raw data - Gateway to other core objects • Player - Handles playstate - Abstracts YouTube API Card ( user info / pictures ) SongFetcher Scrobbler / ViewLogger Song/Artist/User Factories Thursday, 13 December 12
  14. 1 Buds.Radio.Router = Buds.Router.extend 2 3 showSong: (router, event) ->

    4 router.transitionTo('show', event.context) 5 6 skipSong: (router, event) -> 7 song = event.context 8 Buds.Radio.SkipLogger.create({ song: song }).log() 9 router.send('nextSong', event) 10 11 nextSong: (router, event) -> 12 currentSong = event.context 13 nextSong = App.playlist.nextSong(currentSong) 14 15 if nextSong instanceof Buds.Radio.NoMoreSongsError 16 router.send('fetchMoreCards', callback:-> 17 router.send('nextSong', event) 18 ) 19 else 20 router.transitionTo('show', nextSong) 21 The Router • Actions Thursday, 13 December 12
  15. 29 likeSong: (router, event) -> 30 song = event.context 31

    App.playlist.likeSong(song, "radio_bottom") 32 33 favouriteUser: (router, event) -> 34 user = event.context 35 App.playlist.favouriteUser(user) 36 37 fetchMoreCards: (router, options) -> 38 return if router.get('cardsController').get('loading', true 39 40 options = options || {} 41 callback = options.callback || Buds.Noop 42 43 router.get('cardsController').set('loading', true) 44 App.playlist.fetchMoreSongs(callback: -> 45 router.get('cardsController').set('loading', false) 46 callback.apply(window) 47 ) The Router • Actions Thursday, 13 December 12
  16. 49 root: Em.Route.extend 50 root: Em.Route.extend 51 route: '/' 52

    connectOutlets: (router) -> 53 applicationController = router.get('applicationControll 54 applicationController.connectOutlet 55 outletName: 'player' 56 name: 'player' 57 58 applicationController.connectOutlet 59 outletName: 'userProfile' 60 name: 'userProfile' 61 62 applicationController.connectOutlet 63 outletName: 'playerControls' 64 name: 'playerControls' 65 66 applicationController.connectOutlet 67 outletName: 'cards' 68 name: 'cards' 69 The Router • Actions • Setup Thursday, 13 December 12
  17. 69 70 index: Em.Route.extend 71 route: '/' 72 connectOutlets: (router)

    -> 73 firstSong = App.playlist.get("firstSong") 74 router.transitionTo('show', firstSong) 75 76 show: Em.Route.extend 77 route: '/:id' 78 connectOutlets: (router, context) -> 79 if context instanceof Buds.ProfileSong 80 song = context 81 else 82 song = App.playlist.getSong(context.id) 83 The Router • Actions • Setup • Routes Thursday, 13 December 12
  18. 84 85 applicationController = router.get('applicationController') 86 applicationController.connectOutlet 87 outletName: 'userProfile'

    88 name: 'userProfile' 89 context: song.get("user") 90 91 player = App.playlist.getPlayer 92 song: song 93 songEndedCallback: -> router.send("nextSong", context: song) 94 95 @set('player', player) 96 97 applicationController.connectOutlet 98 outletName: 'player' 99 name: 'player' 100 The Router • Actions • Setup • Routes • Connect outlets Thursday, 13 December 12
  19. 100 101 router.get('cardsController').set('currentSong', song) 102 router.get('playerController').set('content', song) 103 router.get('playerController').set('player', player)

    104 router.get('playerControlsController').set('content', song) 105 router.get('playerControlsController').set('player', player) 106 107 router.get('userProfileController').set('isLoggedIn', App.isLo 108 router.get('playerController').set('isLoggedIn', App.isLoggedI 109 router.get('playerControlsController').set('isLoggedIn', App.i 110 111 buds.helper.replaceTitle(song.get("title")) The Router • Actions • Setup • Routes • Connect outlets • Pass data Thursday, 13 December 12
  20. Controllers and Views Often tightly coupled and therefore I like

    to keep them together in xxx_controller_view.coffee No Rails 3.1 asset pipeline so we put templates in server-side partials and rendered with the page. (not ideal but quick to develop) Thursday, 13 December 12
  21. Controller Responsibility Receives data from router Data transformation (like a

    presenter) Sends messages to router (outside context of the action helper) Thursday, 13 December 12
  22. View Responsibility Events from DOM Micro-state - selected item -

    scroll position - hover state Activate legacy / jQuery plugins - Call functions / plugins on didInsertElement Thursday, 13 December 12
  23. Stuff that was hard Interaction with one view must tell

    another view to resize - We sent a message via the view -> controller -> router -> other controller -> other view Dependency Injection vs contentBindings Integrating with legacy jQuery code / plugins Thursday, 13 December 12
  24. Stuff I would recommend Server responsibility Initial state ( javascript

    and HTML assets ) Persistence ( JSON API ) Minimise coupling to Ember - Use your own classes - Define your own APIs - Keep your logic out of the router - Make porting your app possible Thursday, 13 December 12