PageRenderTime 4ms CodeModel.GetById 2ms app.highlight 14ms RepoModel.GetById 2ms app.codeStats 0ms

/public/js/MusicPlayer.js

https://bitbucket.org/zappan/backbone.musicplayer
JavaScript | 326 lines | 233 code | 82 blank | 11 comment | 11 complexity | 22175a44a56f48e40cf31850ae108c8c MD5 | raw file
  1(function($) {
  2  
  3  // Prepare 'namespace'
  4  MusicPlayer = {};
  5
  6  MusicPlayer.Track = Backbone.Model.extend({ });      // model
  7  
  8  MusicPlayer.Library = Backbone.Collection.extend({   // this is collection
  9    model: MusicPlayer.Track,
 10    url: '/musiclibrary'
 11  });
 12  
 13  MusicPlayer.Playlist = MusicPlayer.Library.extend({  // this is playlist collection (notice extend from Library collection)
 14    isFirstTrack: function(index) {
 15      return (index == 0);
 16    },
 17    
 18    isLastTrack: function(index) {
 19      return (index == (this.models.length - 1));      // 'models' method on collection is legit - returns array of all models 
 20    },
 21    
 22    lastTrack: function() {
 23      return this.models.length - 1;
 24    },
 25    
 26    trackUrlAtIndex: function (index) {
 27      return this.models[index].attributes.url;
 28    }
 29    
 30  });
 31  
 32  
 33  MusicPlayer.Player = Backbone.Model.extend({         //this model uses playlist as a global object
 34    defaults: {
 35      'currentTrackIndex': 0,
 36      'state': 'stop'
 37    },
 38    
 39    initialize: function() {
 40      //this.thePlaylist = playlist;
 41    },
 42    
 43    play: function() {
 44      this.set({'state': 'play'});
 45    },
 46    
 47    pause: function() {
 48      this.set({'state': 'pause'});
 49    },
 50    
 51    isPlaying: function() {
 52      return (this.get('state') == 'play');
 53    },
 54    
 55    isStopped: function() {
 56      return (!this.isPlaying());
 57    },
 58    
 59    currentTrack: function() {
 60      return playlist.at(this.get('currentTrackIndex'));
 61    },
 62    
 63    currentTrackUrl: function() {
 64      return playlist.trackUrlAtIndex(this.get('currentTrackIndex'));
 65    },
 66    
 67    nextTrack: function() {
 68      var currentTrackIndex = this.get('currentTrackIndex');
 69      
 70      if(playlist.isLastTrack(currentTrackIndex)){
 71        this.set({'currentTrackIndex': 0});
 72      } else {
 73        this.set({'currentTrackIndex': currentTrackIndex + 1});
 74      }
 75      this.logCurrentTrack();
 76    },
 77    
 78    prevTrack: function() {
 79      var currentTrackIndex = this.get('currentTrackIndex');
 80      
 81      if(playlist.isFirstTrack(currentTrackIndex)){
 82        this.set({'currentTrackIndex': playlist.lastTrack()});
 83      } else {
 84        this.set({'currentTrackIndex': currentTrackIndex - 1});
 85      }
 86      this.logCurrentTrack();
 87    },
 88    
 89    logCurrentTrack: function() {
 90      console.log("Player track is: " + this.get('currentTrackIndex'), this);
 91    }
 92    
 93  });
 94  
 95  
 96  
 97  // views
 98  
 99  MusicPlayer.TrackView = Backbone.View.extend({
100    initialize: function () {
101      this.template = _.template($('#track-template').html());   // initializes the jQuery template
102    },
103    
104    render: function () {
105      var renderedContent = this.template (this.model.toJSON());  // fills the template with values
106      $(this.el).html(renderedContent);      // attachs to parent? => [TC] attaches rendered HTML to the view's top-level DOM element
107      return this;                           // enables chaining
108    },
109    
110    events: {
111      'click .queue.add': 'select',                    // calls select method on click event
112      'click .queue.remove': 'removeFromPlaylist'
113    },
114    
115    select: function () {                               //?? how does the view know what is the collection of its model?
116      this.collection.trigger('select', this.model);    // triggers select, passes this model as an argument
117      //console.log("I just triggered a select", this.model);
118    },
119    
120    removeFromPlaylist: function () {
121      this.options.playlist.remove(this.model);     //remove is triggered directly on playlist collection that is passed as a parameter to TrackView
122      //console.log("I just triggered a remove", this.model);
123    }
124      
125    
126  })
127  
128  
129  MusicPlayer.LibraryView = Backbone.View.extend({
130    initialize: function () {
131      _.bindAll(this,'render');     // this binds context to always be this view (not ajax something that calls it)
132      this.template = _.template($('#library-template').html());
133      //this.collection.bind('reset', this.render);  //  this should be commented when running tests, because this causes an error
134    },
135    
136    render: function () {
137      var $tracks,
138          collection = this.collection;
139
140      $(this.el).html(this.template({}));      // fills template with no data
141      $tracks = this.$(".tracks");             // sets the place for model instances
142                                               // => [TC] more precisely, it fetches a reference to the DOM element into which rendered child views HTML will be appended to
143                                               //    this.$() is a Backbone view's convenience method that says search for ".tracks" within my template only, not the whole DOM
144      collection.each(function(track) {
145        var view = new MusicPlayer.TrackView({
146          model: track,
147          collection: collection               // now track view can pass select trigger to collection
148        });
149        
150        $tracks.append(view.render().el)
151      })
152      return this;
153    }
154    
155  })
156  
157  
158  MusicPlayer.PlaylistView = Backbone.View.extend({
159    // tagName: 'section',
160    // className: 'playlist',     // taken from video but not necessary adds section tag with 'playlist' class
161    
162    initialize: function () {
163      _.bindAll(this, 'render', 'renderTrack', 'queueTrack', 'renderWithObjects', 'updateTrack');
164      this.template = _.template($('#playlist-template').html());
165      this.collection.bind('reset', this.render);
166      this.collection.bind('add', this.renderTrack);  // on collection add event, execute method
167      this.collection.bind('remove', this.renderWithObjects);   // calls a method to re-render the whole playlist with the rest of models in it
168      
169      this.library = this.options.library;             // library is given as a property of param object
170      this.library.bind('select', this.queueTrack);    // on library 'select' event, execute given method 
171      
172      this.player = this.options.player;
173      this.player.bind('change:currentTrackIndex', this.updateTrack);
174      this.player.bind('change:state', this.updateTrack);
175    },
176    
177    render: function () {
178      $(this.el).html(this.template());
179
180      return this;
181    },
182    
183    renderTrack: function(track) {            // track is passed as a param, see queueTrack method
184      var view = new MusicPlayer.TrackView({
185        model: track,
186        playlist: this.collection             // playlist is passed to this class as a parameter
187      });
188      this.$('ul').append(view.render().el);  // after track view is made, append it to playlist
189    },
190    
191    queueTrack: function(track) {      // this method adds to playlist collection (and triggers the add event)
192      this.collection.add(track);
193    },
194    
195    renderWithObjects: function() {    // re - renders the whole playlist
196      var $tracks,
197          collection = this.collection;
198      $(this.el).html(this.template({}));
199      $tracks = this.$(".tracks");
200      collection.each(function(track) {
201        var view = new MusicPlayer.TrackView({
202          model: track,
203          playlist: collection
204        });
205        $tracks.append(view.render().el);
206      });
207      
208      return this;
209    },
210    
211    updateTrack: function() {
212      var currentTrackIndex = this.player.get('currentTrackIndex');
213      this.$("li").each(function(index, el) {
214        $(el).toggleClass('current', index == currentTrackIndex);
215      });
216      console.log("TrackUpdated");
217    }
218    
219  })
220  
221  MusicPlayer.PlayerView = Backbone.View.extend({
222    initialize: function () {
223      _.bindAll(this, 'render', 'updateTrack', 'updateState');
224      this.template = _.template($('#player-template').html());
225      this.createAudio();
226      
227      this.player = this.options.player;
228      this.player.bind('change:state', this.updateState);
229      this.player.bind('change:currentTrackIndex', this.updateTrack);
230      
231    },
232    
233    render: function () {
234      $(this.el).html(this.template());
235      
236      this.$('button.play').toggle(this.player.isStopped());    // shows the play button if the player is stopped
237      this.$('button.pause').toggle(this.player.isPlaying());
238      return this;
239    },
240    
241    createAudio: function() {
242      this.audio = new Audio();
243    },
244    
245    updateState: function() {
246      this.updateTrack();
247      this.$("button.play").toggle(this.player.isStopped());
248      this.$('button.pause').toggle(this.player.isPlaying());
249    },
250    
251    updateTrack: function() {
252      this.audio.src = this.player.currentTrackUrl();
253      if (this.player.get('state') == 'play') {
254        this.audio.play();
255      } else {
256        this.audio.pause();
257      }
258    },
259    
260    events: {
261      'click .play': 'play',
262      'click .pause': 'pause',
263      'click .next': 'nextTrack',
264      'click .prev': 'prevTrack'
265    },
266    
267    play: function() {
268      this.player.play();
269    },
270    
271    pause: function() {
272      this.player.pause();
273    },
274    
275    nextTrack: function() {
276      this.player.nextTrack();
277    },
278    
279    prevTrack: function() {
280      this.player.prevTrack();
281    }
282  })
283  
284  
285  MusicPlayer.Router = Backbone.Router.extend({
286    routes: {
287      '': 'home'
288    },
289    
290    initialize: function(){  
291      library =  new MusicPlayer.Library();                         // new collection
292      libraryView = new MusicPlayer.LibraryView({collection: library});  // instantiates view (collection is a param)
293
294  	  player = new MusicPlayer.Player();
295  	  playerView = new MusicPlayer.PlayerView({player: player});
296
297  	  playlist = new MusicPlayer.Playlist();
298  	  playlistView = new MusicPlayer.PlaylistView({collection: playlist, library: library, player: player});
299    },
300
301    home: function(){
302  	  $('#music-library').append(libraryView.render().el);   // sticks collection view to hard coded html
303
304  	  $('#playlist').append(playlistView.render().el);       // this adds playlist view to hardcoded html
305
306  	  $('#player').append(playerView.render().el);           // adds player controls to view      
307    }
308    
309  });
310
311
312
313  $(function(){
314    window.App = new MusicPlayer.Router();
315    Backbone.history.start();
316  });
317
318  // this runs on page load and bootstraps the app
319  $(document).ready(function() {
320	  
321	  library.fetch();   // fetches elements and triggers collection view 'reset'
322
323  });
324  
325
326})(jQuery);