Adam Gomaa avatar Adam Gomaa committed 03d8a70

Remove search, upgrade underscore/backbone versions, use collection in albums/songs view.

Comments (0)

Files changed (3)

webmusic/templates/music-body.html

 </div>
 <div class="right height-100">
   <div id="controls">
-    <input class="search" data-target=".artist" type="text">
     <div class="controls-row">
       <div id="play-pause">P</div>
       <div id="time">
   </div>
   <div id="console"></div>
 </div>
+<div id="templates" style="display:none;">
+<div id="album-template">
+  <span class="album-name"><%= text %></span>
+</div>
+<div id="song-template">
+  <span class="song-name"><%= text %></span>
+  <span class="duration"><%= duration %></span>
+</div>
+</div>

webmusic/templates/music.html

   <title>webmusic</title>
   <link rel="stylesheet" href="{{ music_url }}getmedia/__music.css">
   <link rel="stylesheet" href="//ajax.googleapis.com/ajax/libs/jqueryui/1.8.17/themes/vader/jquery-ui.css">
-{% if false %}
+{% if false %}{# for debugging JS stuff #}
   <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.js"></script>
   <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jqueryui/1.8.17/jquery-ui.js"></script>
 {% else %}
   <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
   <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jqueryui/1.8.17/jquery-ui.min.js"></script>
 {% endif %}
-  <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.2.2/underscore-min.js"></script>
-  <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/backbone.js/0.5.3/backbone-min.js"></script>
+{% if false %}{# for debugging JS stuff #}
+  <script type="text/javascript" src="/lib/underscore-1.3.1.js"></script>
+  <script type="text/javascript" src="/lib/backbone-0.9.1.js"></script>
+{% else %}
+  <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.3.1/underscore-min.js"></script>
+  <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/backbone.js/0.9.1/backbone-min.js"></script>
+{% endif %}
 <script type="text/javascript">
 {# music_js is static, so define the urls here. #}
 _music_url = "{{ music_url }}";

webmusic/templates/music.js

 $(function(){
+    var get_template = function(selector){
+        return _.template($(selector).html().replace(/&lt;/g, "<").replace(/&gt;/g, ">"));
+    }
+
     var Artist = Backbone.Model.extend({});
     var Album = Backbone.Model.extend({});
-    var Song = Backbone.Model.extend({});
+    var Song = Backbone.Model.extend({
+        duration_display: function(){
+            var mins = Math.floor(this.get("duration") / 60);
+            var secs = Math.floor(this.get("duration") % 60);
+            if(secs<10){secs = "0" + secs}
+            return mins + ":" + secs;
+        }
+    });
 
     var ArtistCollection = Backbone.Collection.extend({model: Artist});
     var AlbumCollection = Backbone.Collection.extend({model: Album});
             var song_text = song["title"];
             var song_path = song['path'];
             $("title").text(song_text + " - webmusic");
-            var src = this.media_url + song._id.$oid;
+            var src = this.media_url + song.get("_id").$oid;
             this.log("src:" + src)
             this.$audio.attr("src", src);
             this.audio.load();
             this.audio.play()
         },
         seekRatio: function(ratio){
+            this.log("seeking to: " + ratio);
             this.seekTime(this.audio.duration * ratio);
         },
         seekTime: function(time){
             this.controls.$slider.slider("value", completion * 500);
         },
         audioEnded: function(){
+            this.log("audio ended");
             if(!this.loop){
                 $(this.songs.el).find(">.selected").next().trigger("click");
             }else if(this.loop=="song"){
             }
         },
         globalKeys: {
-            // "/" slash
-            191: function(evt){
-                evt.preventDefault();
-                app.controls.$search.trigger("focus").trigger("select");
-            },
             // " " space
             32: function(evt){
                 app.controls.playPauseClick();
         // I wasted more time naming this method than I care to admit.
         handleGlobalKey: function(evt){
             if(evt.which in this.globalKeys){
-                if(this.controls.$search.is(":focus")){return;}
                 this.globalKeys[evt.which](evt);
             }
         }
     });
     var ControlsView = AppView.extend({
         events: {
-            "keyup .search": "searchKeyup",
             "click #play-pause": "playPauseClick",
             "slidestop #slider": "slideSeek",
             "click .trigger-resize": "clickResize",
         initialize: function(options){
             this.constructor.__super__.initialize.apply(this, [options]);
             _.bindAll(this, "songStart", "slideSeek", "resizeControls",
-                      "playPauseClick", "searchKeyup", "clickResize",
+                      "playPauseClick", "clickResize",
                       "clickReindex",
                       "reloadArtists", "reloadCss", "reloadJs", "reloadHtml",
                       "cycleLoopStates");
             this.$duration = this.$("#duration");
             this.$remaining = this.$("#remaining");
             this.$current_song = this.$("#current-song");
-            this.$search = this.$(".search")
 
 
             this.$slider = this.$("#slider");
                 this.$(".ui-slider-handle").addClass("paused")
             }
         },
-        /* two questions. Can I build a client-side index or cache
-           quickly, as in without singificantly affecting
-           time-to-ready? Is is significanly more responsive? I should
-           test this, especially once I get to adding local storage.
-         */
-        searchKeyup: function(){
-            var start = new Date();
-            var s = this.$search;
-            var val = s.val().toLowerCase();
-            if(!s.data("targetobj")){
-                s.data("targetobj", $(s.data("target")));
-            }
-            var $target = s.data("targetobj");
-            if(!val||val===""){
-                /* search is empty, everything matches */
-                $target.removeClass("search-not-matched");
-                return;
-            }
-            $target.each(function(){
-                if($(this).text().toLowerCase().indexOf(val)==-1){
-                    $(this).addClass("search-not-matched");
-                }else{
-                    $(this).removeClass("search-not-matched");
-                }
-            });
-            this.app.log(val + ":" + (new Date() - start));
-
-        },
         clickReindex: function(){
             var postdata = {action: "reindex"};
             $.post(this.app.ajax_url, postdata, "json")
 
 
     var ArtistsView = AppView.extend({
+        events: {
+            "click .artist": "clickArtist"
+        },
         initialize: function(options){
             _.bindAll(this, "loadArtists", "render", 'artistNodes', 'clickArtist');
             this.constructor.__super__.initialize.apply(this, [options]);
                 $(this).width(col_width_perc + "%");
                 $(this).css("left", ((col_width_perc+margin_perc) * ii) + "%");
             });
-            this.$(".artist").bind("click", this.clickArtist);
+            //this.$(".artist").bind("click", this.clickArtist);
         },
         artistNodes: function(){
             return $(this.artists.map(function(a, i){
             var artist = $artist.data("artist");
             this.app.current_artist = artist;
             $.post(this.app.ajax_url, {action: "get-albums", artist: artist.get("_id").$oid}, "json")
-                .then(this.app.albums.showArtistAlbums)
+                .then(function(albums){app.albums.albums.reset(albums)});
         }
     });
     var AlbumsView = AppView.extend({
+        events: {
+        },
+        initialize: function(options){
+            _.bindAll(this, "resetAlbums", "addAlbum");
+            this.constructor.__super__.initialize.apply(this, [options])
+            this.albums = new AlbumCollection();
+            this.albums.bind("reset", this.resetAlbums);
+        },
+        resetAlbums: function(){
+            $(this.el).empty();
+            this.albums.each(this.addAlbum);
+        },
+        addAlbum: function(album){
+            var view = new SingleAlbumView({app: this.app, album: album});
+            $(this.el).append(view.render().el);
+        }
+    });
+    var SingleAlbumView = AppView.extend({
+        className: "album",
+        events: {"click": "handleClick"},
         initialize: function(options){
             this.constructor.__super__.initialize.apply(this, [options])
-            _.bindAll(this, "showArtistAlbums", "clickAlbum");
+            this.album = options.album;
+            _.bindAll(this, "render", "handleClick");
         },
-        showArtistAlbums: function(albums){
+        render: function(){
+            var album_text;
+            var album = this.album;
+            if(album.get("year")){
+                album_text = album.get("year") + " - " + album.get("name");
+            }else{
+                album_text = album.get("name");
+            }
+            var template_context = {text: album_text};
+            $(this.el).html(get_template("#album-template")(template_context))
+            return this;
+        },
+        handleClick: function(){
+            $(this.el).addClass("selected").siblings().removeClass("selected");
+            var album_id = this.album.get("_id")['$oid'];
+            var postdata = {action: "get-songs", album: album_id};
+            $.post(this.app.ajax_url, postdata, "json").then(function(songs){
+                app.songs.songs.reset(songs);
+            });
+        }
+    })
+
+    var SongsView = AppView.extend({
+        initialize: function(options){
+            _.bindAll(this, "resetSongs", "addSong");
+            this.constructor.__super__.initialize.apply(this, [options])
+            this.songs = new SongCollection();
+            this.songs.bind("reset", this.resetSongs);
+        },
+        resetSongs: function(){
             $(this.el).empty();
-            var view = this;
-            _(albums).each(function(album){
-                var album_text;
-                if(album.year){
-                    album_text = album.year + " - " + album.name;
-                }else{
-                    album_text = album.name;
-                }
-                var $node = $("<div></div>");
-                $node.data('album', album._id.$oid);
-                $node.text(album_text);
-                $node.bind("click", view.clickAlbum);
-                $(view.el).append($node);
-            });
+            this.songs.each(this.addSong);
         },
-        clickAlbum: function(event){
-            var $album = $(event.target)
-            this.$(".selected").removeClass("selected");
-            $album.addClass("selected");
-            var album_id = $album.data("album")
-            var postdata = {action: "get-songs", album: album_id};
-            $.post(this.app.ajax_url, postdata, "json").then(app.songs.showAlbumSongs);
+        addSong: function(song){
+            var view = new SingleSongView({app: this.app, song: song});
+            $(this.el).append(view.render().el);
         }
     });
 
-    var SongsView = AppView.extend({
+    var SingleSongView = AppView.extend({
+        className: "song",
+        events: {"click": "handleClick"},
         initialize: function(options){
-            this.constructor.__super__.initialize.apply(this, [options])
-            _.bindAll(this, "showAlbumSongs", "clickSong");
+            this.constructor.__super__.initialize.apply(this, [options]);
+            this.song = options.song;
+            _.bindAll(this, "handleClick", "render");
         },
-        showAlbumSongs: function(songs){
-            $(this.el).empty();
-            var view = this;
-            _(songs).each(function(song){
-                var $node = $("<div></div>");
-                $node.data('song', song);
-                $node.text(song.title);
-                $(view.el).append($node);
-            });
-            this.$(">div").bind("click", this.clickSong);
+        handleClick: function(event){
+            $(this.el).addClass("selected").siblings().removeClass("selected");
+            this.app.loadSong(this.song);
         },
-        clickSong: function(event){
-            this.$(".selected").removeClass("selected");
-            $(event.target).addClass("selected");
-            this.app.loadSong($(event.target).data("song"));
+        render: function(){
+            var template_context = {
+                text: this.song.get("title"),
+                duration: this.song.duration_display(),
+            }
+            $(this.el).html(get_template("#song-template")(template_context));
+            return this;
         }
-    });
+    })
 
     window.app = new MusicApp();
 });
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.