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