diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 4a14c9e..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "vendor/plugins/more"] - path = vendor/plugins/more - url = git://github.com/cloudhead/more.git diff --git a/Gemfile b/Gemfile index f03398e..f706288 100644 --- a/Gemfile +++ b/Gemfile @@ -4,12 +4,12 @@ gem 'rake' #, '0.8.7' gem 'rails', '3.0.5' gem 'jquery-rails', '>= 0.2.6' -gem 'jrails' # templating / styling gem 'haml-rails' gem "sass" +gem "youtube_it" # for using last.fm api gem 'typhoeus' diff --git a/Gemfile.lock b/Gemfile.lock index fb45ec0..8291c49 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -57,7 +57,6 @@ GEM jquery-rails (1.0.9) railties (~> 3.0) thor (~> 0.14) - jrails (0.6.0) json (1.5.1) mail (2.2.19) activesupport (>= 2.3.6) @@ -147,7 +146,6 @@ DEPENDENCIES haml-rails hashie jquery-rails (>= 0.2.6) - jrails json mongrel (= 1.2.0.pre2) oa-oauth diff --git a/app/controllers/artists_controller.rb b/app/controllers/artists_controller.rb index 49e70c2..6d29e5e 100644 --- a/app/controllers/artists_controller.rb +++ b/app/controllers/artists_controller.rb @@ -28,25 +28,30 @@ class ArtistsController < ApplicationController def more_tracks - more params - @tracks = LastFM::Artist.getTopTracks params[:artist], @@limit[:tracks], (params[:size] / @@limit[:tracks] + 1) + prepare params + @tracks = LastFM::Artist.getTopTracks params[:q], @@limit[:tracks], (params[:size] / @@limit[:tracks] + 1) LastFM::LastFMRequest.run_queue! render :partial=>"more_tracks", :locals=>{:tracks=>@tracks} end def more_albums - more params - @albums = LastFM::Artist.getTopAlbums params[:artist], @@limit[:albums], (params[:size] / @@limit[:albums] + 1) + prepare params + @albums = LastFM::Artist.getTopAlbums params[:q], @@limit[:albums], (params[:size] / @@limit[:albums] + 1) LastFM::LastFMRequest.run_queue! render :partial=>"more_albums", :locals=>{:albums=>@albums} end - - def more params - params[:artist] = CGI.unescape params[:artist] - params[:size] = params[:size].to_i - params + def album_info + @album = LastFM::Album.getInfo CGI.unescape(params[:album]), CGI.unescape(params[:artist]) + LastFM::LastFMRequest.run_queue! + + render :partial=>"album_info", :locals=>{:album=>@album} end -end + + def prepare params + params[:q] = CGI.unescape params[:q] + params[:size] = params[:size].to_i + end +end \ No newline at end of file diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 275920b..817c9a8 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -1,11 +1,35 @@ class SearchController < ApplicationController - def index - @artists = LastFM::Artist.search(params[:q].to_s.lstrip, 8) - @tracks = LastFM::Track.search(params[:q].to_s.lstrip, 24) - @albums = LastFM::Album.search(params[:q].to_s.lstrip, 16) + @@limit = { artists: 5, tracks: 10, albums: 5 } + + def show + @q = params[:id].to_s.lstrip + + @artists = LastFM::Artist.search @q, @@limit[:artists] + @tracks = LastFM::Track.search @q, @@limit[:tracks] + @albums = LastFM::Album.search @q, @@limit[:albums] LastFM::LastFMRequest.run_queue! + + render :partial=>"show", :locals=>{ + :artist=>@artist, + :tracks=>@tracks, + :albums=>@albums + } end + + def autocomplete + q = params[:q].to_s.lstrip + @artists = $sevendigital_client.artist.search q, { :pagesize=>5 } + + render :partial=>"autocomplete", :locals=>{ :artists=>@artists } + end + + def more_artists + @artists = LastFM::Artist.search params[:q].to_s.lstrip, @@limit[:artists], (params[:size].to_i / @@limit[:artists] + 1) + LastFM::LastFMRequest.run_queue! + + render :partial=>"more_artists", :locals=>{:artists=>@artists} + end end \ No newline at end of file diff --git a/app/controllers/tracks_controller.rb b/app/controllers/tracks_controller.rb index 215cc97..08eed4a 100644 --- a/app/controllers/tracks_controller.rb +++ b/app/controllers/tracks_controller.rb @@ -2,4 +2,11 @@ class TracksController < ApplicationController def index end + def search_video + + @video = @youtube_client.videos_by(:query => "#{CGI.unescape(params[:artist])} #{CGI.unescape(params[:track])} official", :page => 1, :per_page => 1, :order_by => "relevance") + @id = @video.videos.first.unique_id + + render :partial=>"video_id", :locals=>{:id=>@id} + end end \ No newline at end of file diff --git a/app/views/artists/_album_info.html.haml b/app/views/artists/_album_info.html.haml new file mode 100644 index 0000000..d1265c1 --- /dev/null +++ b/app/views/artists/_album_info.html.haml @@ -0,0 +1 @@ +#album_box -#%span -# .headline -# = image_tag validate_img_url(@album.image, "album", :large) -# %h4 -# = @album.name -# %br<> -# %span= @album.artist .wrap - @album.tracks.track.each do |track| %ul %li %span= track["@attr"].rank+"." = track.name - artist = track.artist.respond_to?(:name) ? track.artist.name : track.artist \ No newline at end of file diff --git a/app/views/artists/_more_albums.html.haml b/app/views/artists/_more_albums.html.haml index 68581f9..2e76489 100644 --- a/app/views/artists/_more_albums.html.haml +++ b/app/views/artists/_more_albums.html.haml @@ -1,7 +1,8 @@ -- albums.album.each do |album| - %li +- @albums.album.each do |album| + - artist = album.artist.respond_to?(:name) ? album.artist.name : album.artist + %li{:album=>"#{CGI.escape(album.name)}",:artist=>"#{CGI.escape(artist)}"} .img= image_tag validate_img_url(album.image, "album", :medium), :class=>"cover" %p.name< = trimString(album.name, 48) %br<> - %span.artist>= trimString(album.artist.name, 80) \ No newline at end of file + %span.artist>= trimString(artist, 80) \ No newline at end of file diff --git a/app/views/artists/_more_tracks.html.haml b/app/views/artists/_more_tracks.html.haml index 9cc8de7..ed48fe4 100644 --- a/app/views/artists/_more_tracks.html.haml +++ b/app/views/artists/_more_tracks.html.haml @@ -1,5 +1,6 @@ - @tracks.track.each do |track| - %li + %li{:artist=>track.artist.name,:track=>track.name} = track.name %span by - = link_to trimString(track.artist.name, 50), "#!/artists/#{CGI.escape(track.artist.name)}" \ No newline at end of file + - artist = track.artist.respond_to?(:name) ? track.artist.name : track.artist + = link_to trimString(artist, 50), "#!/artists/#{CGI.escape(artist)}" \ No newline at end of file diff --git a/app/views/artists/_show.html.haml b/app/views/artists/_show.html.haml index a8124c2..8e54022 100644 --- a/app/views/artists/_show.html.haml +++ b/app/views/artists/_show.html.haml @@ -12,7 +12,7 @@ .content #artist_info %h2 About - =raw artist.bio.summary + =raw artist.bio.summary.empty? ? "No information available!" : artist.bio.summary #artist_links %ul = image_tag "bing.png", :class=>"bing" @@ -26,7 +26,7 @@ %li songs %li albums %li related - .content{:artist=>"#{CGI.escape(artist.name)}"} + .content{:q=>"#{CGI.escape(artist.name)}"} %div %ul.list.songs.andmore{:tile=>"more_tracks"} = render :partial=>"more_tracks", :locals=>{:tracks=>@tracks} @@ -43,6 +43,7 @@ %span= "#{(artist.match.to_f*100).to_i}" .img = image_tag validate_img_url(artist.image, "artist", :extralarge), :alt=>artist.name, :title=>artist.name + %div.clean :javascript $(document).ready(function(){ diff --git a/app/views/layouts/_player.html.haml b/app/views/layouts/_player.html.haml new file mode 100644 index 0000000..7447193 --- /dev/null +++ b/app/views/layouts/_player.html.haml @@ -0,0 +1,7 @@ +.player + = link_to image_tag("icons/player/backward.png"), "javascript:void(0);", :class=>"backward" + = link_to image_tag("icons/player/play.png"), "javascript:void(0);", :class=>"play" + = link_to image_tag("icons/player/forward.png"), "javascript:void(0);", :class=>"forward" + = link_to image_tag("icons/player/volume.png"), "javascript:void(0);", :class=>"volume" + = link_to image_tag("icons/player/repeat.png"), "javascript:void(0);", :class=>"repeat" + = link_to image_tag("icons/player/fullscreen.png"), "javascript:void(0);", :id=>"full" \ No newline at end of file diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 2de736a..41421c8 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -3,12 +3,11 @@ %head %title Nineminutes = stylesheet_link_tag "9minutes" - =# javascript_include_tag "http://www.google.com/jsapi" + = javascript_include_tag "http://www.google.com/jsapi" = javascript_include_tag :defaults = csrf_meta_tag %body - %a#full{:style=>"position:absolute;z-index:999;color:white;"} FULL #loading Loading #error Error #block @@ -23,16 +22,16 @@ =link_to "Sign Out", destroy_user_session_path #head %h1= link_to image_tag("nine-minutes.png"), "/#!/home" + = render :partial=>"layouts/player" #navigation %li.active= link_to "home", "/#!/home" %li= link_to "charts", "/#!/charts" %li= link_to "about", "#" %li= link_to "register", "#" %li.search - = form_tag '/search', :method => 'get', :id => "search_form" do + = form_tag '/search', :method => 'get', :id => "search_form", :remote=>true do = text_field_tag 'q', params[:q], :class => "text", :placeholder => "Search here ..." - = link_to image_tag("icons/search-navi.png", :class=>"button"), "javascript:void(0);", :onclick=>"submit_redirect(this);" - = submit_tag '', :class=>"submit", :name => nil + = link_to image_tag("icons/search-navi.png", :class=>"button"), "/#!/search" %br.clean #content %br.clean @@ -47,6 +46,32 @@ #fullscreen %h2 Avril Lavigne %h1 Alice (In Wonderland) - = image_tag "examples/video-big.jpg", :class=>"left video" + #video Loading .sidebar - .player \ No newline at end of file + .top + = render :partial=>"layouts/player" + %ul.play_list + %li + %h3 + %div= "Firework" + %span= "Katie Perry" + %li + %h3 + %div= "Firework" + %span= "Katie Perry" + %li + %h3 + %div= "Firework" + %span= "Katie Perry" + %li + %h3 + %div= "Firework" + %span= "Katie Perry" + %li + %h3 + %div= "Firework" + %span= "Katie Perry" + %li + %h3 + %div= "Firework" + %span= "Katie Perry" \ No newline at end of file diff --git a/app/views/search/_autocomplete.html.haml b/app/views/search/_autocomplete.html.haml new file mode 100644 index 0000000..cf7b3ad --- /dev/null +++ b/app/views/search/_autocomplete.html.haml @@ -0,0 +1,2 @@ +- @artists.each do |artist| + = artist.name \ No newline at end of file diff --git a/app/views/search/_more_artists.html.haml b/app/views/search/_more_artists.html.haml new file mode 100644 index 0000000..5e2aec3 --- /dev/null +++ b/app/views/search/_more_artists.html.haml @@ -0,0 +1,8 @@ +- @artists.artist.each do |artist| + %li + - @link = "#!/artists/#{CGI.escape(artist.name)}" + .img= link_to image_tag(validate_img_url(artist.image, "artist", :large)), @link + .text + %h2= link_to artist.name, @link + %p + = artist.listeners+" Listeners" \ No newline at end of file diff --git a/app/views/search/_search_form.html.haml b/app/views/search/_search_form.html.haml new file mode 100644 index 0000000..df58ab9 --- /dev/null +++ b/app/views/search/_search_form.html.haml @@ -0,0 +1,4 @@ +.box#search_bar + = form_tag '/search', :method => 'get', :id => "search_bar_form", :remote=>true do + = text_field_tag 'q', params[:q], :class => "text", :placeholder => "Search here ..." + = link_to image_tag("icons/search.png", :class=>"button"), "/#!/search" \ No newline at end of file diff --git a/app/views/search/_show.html.haml b/app/views/search/_show.html.haml new file mode 100644 index 0000000..357b011 --- /dev/null +++ b/app/views/search/_show.html.haml @@ -0,0 +1,25 @@ +.tabs.tabs_js.w03 + %ul.nav + %li artists + %li songs + %li albums + %li youtube + .content{:q=>"#{@q}"} + %div + = render :partial=>"sidebar/index" + = render :partial=>"search_form" + %ul.list.artists.andmore{:tile=>"more_artists"} + = render :partial=>"more_artists" + %div + = render :partial=>"sidebar/index" + = render :partial=>"search_form" + %ul.list.songs.andmore{:tile=>"more_tracks"} + = render :partial=>"artists/more_tracks" + %div + = render :partial=>"sidebar/index" + = render :partial=>"search_form" + %ul.list.albums.andmore{:tile=>"more_albums"} + = render :partial=>"artists/more_albums" + %div + = render :partial=>"sidebar/index" + = render :partial=>"search_form" \ No newline at end of file diff --git a/app/views/sidebar/_index.html.haml b/app/views/sidebar/_index.html.haml index 47b45a8..5e09883 100644 --- a/app/views/sidebar/_index.html.haml +++ b/app/views/sidebar/_index.html.haml @@ -1,4 +1,4 @@ #sidebar - .advertisment + .box.advertisment %sup Advertisment - = image_tag "advertisment-placeholder.png" \ No newline at end of file + = image_tag "placeholder/advertisment.png" \ No newline at end of file diff --git a/app/views/tracks/_list.html.haml b/app/views/tracks/_list.html.haml index 53e4f55..0fcd6e9 100644 --- a/app/views/tracks/_list.html.haml +++ b/app/views/tracks/_list.html.haml @@ -7,4 +7,4 @@ = image_tag "ico/play.png", :class=>"play", :videoid=>id.videos.first.unique_id = image_tag "ico/add.png", :class=>"add" %span= track.name - = link_to trimString(track.artist, 50), artist_path(CGI.escape(track.artist)) \ No newline at end of file + = link_to trimString(track.artist, 50), "#!/artists/#{CGI.escape(track.artist)}" \ No newline at end of file diff --git a/app/views/tracks/_video_id.html.haml b/app/views/tracks/_video_id.html.haml new file mode 100644 index 0000000..e382295 --- /dev/null +++ b/app/views/tracks/_video_id.html.haml @@ -0,0 +1 @@ += @id \ No newline at end of file diff --git a/config/application.rb b/config/application.rb index aad14a1..cb46f52 100644 --- a/config/application.rb +++ b/config/application.rb @@ -41,14 +41,21 @@ module Nineminutes plugins/path ajax/requests ajax/routes + store plugins/bing plugins/coloranimations plugins/nivoslider plugins/imageresize + plugins/autocomplete + plugins/scrolling + plugins/player animations/animations animations/fullscreen layout + player ajax/loadmore + ajax/album + search ) # Configure the default encoding used in templates for Ruby 1.9. diff --git a/config/initializers/sevendigital.rb b/config/initializers/sevendigital.rb index 52699c2..0de5f5f 100644 --- a/config/initializers/sevendigital.rb +++ b/config/initializers/sevendigital.rb @@ -1 +1 @@ -#require "sevendigital" #$sevendigital_client = Sevendigital::Client.new("config/sevendigital.yml", :country => "US") \ No newline at end of file +require "sevendigital" $sevendigital_client = Sevendigital::Client.new("config/sevendigital.yml", :country => "US") \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 4d33ff0..a0194b6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -14,13 +14,18 @@ Nineminutes::Application.routes.draw do match "/artists/:artist", :to => "artists#show" match "/more_tracks", :to => "artists#more_tracks" match "/more_albums", :to => "artists#more_albums" + match "/more_artists", :to => "search#more_artists" match "/more_charts", :to => "charts#more" get "artists/", :to => "artists#index" + match "/autocomplete", :to => "search#autocomplete" + match "/album_info", :to => "artists#album_info" + match "/search_video", :to => "tracks#search_video" + #resources :artists, :constraints => { :id => /.*/ } - resources :search resources :users resources :sidebar + resources :search get "home/", :to => "home#show" diff --git a/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/9minutes.scssc b/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/9minutes.scssc index d0ece72..a8dfc75 100644 Binary files a/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/9minutes.scssc and b/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/9minutes.scssc differ diff --git a/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_album.scssc b/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_album.scssc new file mode 100644 index 0000000..179c343 Binary files /dev/null and b/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_album.scssc differ diff --git a/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_artist.scssc b/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_artist.scssc index 7e3ef7f..08faaad 100644 Binary files a/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_artist.scssc and b/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_artist.scssc differ diff --git a/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_autocomplete.scssc b/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_autocomplete.scssc new file mode 100644 index 0000000..99f7b24 Binary files /dev/null and b/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_autocomplete.scssc differ diff --git a/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_charts.scssc b/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_charts.scssc index 3e7b150..f42ccf9 100644 Binary files a/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_charts.scssc and b/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_charts.scssc differ diff --git a/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_fullscreen.scssc b/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_fullscreen.scssc index 15f84ca..5661e82 100644 Binary files a/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_fullscreen.scssc and b/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_fullscreen.scssc differ diff --git a/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_layout.scssc b/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_layout.scssc index c6f14ea..3652e9c 100644 Binary files a/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_layout.scssc and b/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_layout.scssc differ diff --git a/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_lists.scssc b/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_lists.scssc index 42b6fa9..f0ac477 100644 Binary files a/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_lists.scssc and b/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_lists.scssc differ diff --git a/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_player.scssc b/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_player.scssc new file mode 100644 index 0000000..16e9d19 Binary files /dev/null and b/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_player.scssc differ diff --git a/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_scrolling.scssc b/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_scrolling.scssc new file mode 100644 index 0000000..9c15d14 Binary files /dev/null and b/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_scrolling.scssc differ diff --git a/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_search.scssc b/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_search.scssc new file mode 100644 index 0000000..592fc0e Binary files /dev/null and b/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_search.scssc differ diff --git a/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_sidebar.scssc b/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_sidebar.scssc index d9d2ad4..51c55ef 100644 Binary files a/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_sidebar.scssc and b/public/.sass-cache/1309dacbc55d6dce7d378b0f7d2fbb9c912bce2e/_sidebar.scssc differ diff --git a/public/images/backgrounds/overall.png b/public/images/backgrounds/overall.png index a1a6912..7249486 100644 Binary files a/public/images/backgrounds/overall.png and b/public/images/backgrounds/overall.png differ diff --git a/public/images/icons/arrow.png b/public/images/icons/arrow.png new file mode 100644 index 0000000..ca3be99 Binary files /dev/null and b/public/images/icons/arrow.png differ diff --git a/public/images/icons/player/backward.png b/public/images/icons/player/backward.png new file mode 100644 index 0000000..a5040aa Binary files /dev/null and b/public/images/icons/player/backward.png differ diff --git a/public/images/icons/player/forward.png b/public/images/icons/player/forward.png new file mode 100644 index 0000000..cdca4c1 Binary files /dev/null and b/public/images/icons/player/forward.png differ diff --git a/public/images/icons/player/fullscreen.png b/public/images/icons/player/fullscreen.png new file mode 100644 index 0000000..3a29e77 Binary files /dev/null and b/public/images/icons/player/fullscreen.png differ diff --git a/public/images/icons/player/play.png b/public/images/icons/player/play.png new file mode 100644 index 0000000..87dac19 Binary files /dev/null and b/public/images/icons/player/play.png differ diff --git a/public/images/icons/player/repeat.png b/public/images/icons/player/repeat.png new file mode 100644 index 0000000..8314e13 Binary files /dev/null and b/public/images/icons/player/repeat.png differ diff --git a/public/images/icons/player/volume.png b/public/images/icons/player/volume.png new file mode 100644 index 0000000..a8131ae Binary files /dev/null and b/public/images/icons/player/volume.png differ diff --git a/public/images/icons/search.png b/public/images/icons/search.png new file mode 100644 index 0000000..f7ef6dc Binary files /dev/null and b/public/images/icons/search.png differ diff --git a/public/images/placeholder/advertisment.png b/public/images/placeholder/advertisment.png new file mode 100644 index 0000000..dfa1c39 Binary files /dev/null and b/public/images/placeholder/advertisment.png differ diff --git a/public/images/placeholder/artist-big.png b/public/images/placeholder/artist-big.png new file mode 100644 index 0000000..89c2da0 Binary files /dev/null and b/public/images/placeholder/artist-big.png differ diff --git a/public/images/placeholder/artist.png b/public/images/placeholder/artist.png new file mode 100644 index 0000000..bc9a0b6 Binary files /dev/null and b/public/images/placeholder/artist.png differ diff --git a/public/images/placeholder/related.png b/public/images/placeholder/related.png new file mode 100644 index 0000000..73ee510 Binary files /dev/null and b/public/images/placeholder/related.png differ diff --git a/public/javascripts/ajax/album.js b/public/javascripts/ajax/album.js new file mode 100644 index 0000000..5dee1a2 --- /dev/null +++ b/public/javascripts/ajax/album.js @@ -0,0 +1,34 @@ + +function load_songs_from_album(obj){ + if($(obj).attr("loaded") == "true") + { + $('#album_box').fadeOut(200); + $(obj).removeAttr("loaded"); + } + else + { + $(obj).parent().parent().children().each(function(){ + $(this).children("a").removeAttr("loaded"); + }); + $(obj).attr("loaded", "true"); + artist = $(obj).parent().attr("artist"); + album = $(obj).parent().attr("album"); + + url = "/album_info"; + params = "artist="+artist+"&album="+album; + + album_request(url, params, obj); + } +} + +function show_songs_from_album(data, obj){ + $('#album_box').remove(); + $(obj).parent().prepend(data); + $('#album_box').fadeIn(200); + $('#album_box .wrap').jScrollPane(); + + $('#album_box li').each(function(){ + if(!$(this).has(".play").length) + $(this).prepend(''); + }); +} \ No newline at end of file diff --git a/public/javascripts/ajax/loadmore.js b/public/javascripts/ajax/loadmore.js index 69fb1b1..07aa490 100644 --- a/public/javascripts/ajax/loadmore.js +++ b/public/javascripts/ajax/loadmore.js @@ -17,11 +17,12 @@ function loadmore(obj){ size = $(obj).parent().find('li').size(); tile = $(obj).parent().children('.list').attr("tile"); artist = $(obj).parent().parent(".content").attr("artist"); + q = $(obj).parent().parent(".content").attr("q"); disable_more_button(obj); url = "/"+tile; - params = "size="+size+"&artist="+artist; + params = "size="+size+"&artist="+artist+"&q="+q; load_more_request(url, params); } diff --git a/public/javascripts/ajax/requests.js b/public/javascripts/ajax/requests.js index 4f639ba..8a08717 100644 --- a/public/javascripts/ajax/requests.js +++ b/public/javascripts/ajax/requests.js @@ -16,6 +16,7 @@ } function load_site_request(link, params){ + hide_flash(true); $.ajax({ type:"GET", dataType:"html", @@ -33,4 +34,39 @@ function load_site_request(link, params){ }); } }); +} + +function album_request(link, params, obj){ + hide_flash(true); + $.ajax({ + type: "GET", + dataType: "html", + url: link, + data: params, + error: function(){ + hide_flash(false); + show_flash(true); + //enable_more_button(); + }, + success: function(data){ + show_songs_from_album(data, obj); + } + }); +} + +function video_request(link, params, obj){ + $.ajax({ + type: "GET", + dataType: "html", + url: link, + data: params, + error: function(){ + hide_flash(false); + show_flash(true); + //enable_more_button(); + }, + success: function(data){ + playTrack(data, obj); + } + }); } \ No newline at end of file diff --git a/public/javascripts/ajax/routes.js b/public/javascripts/ajax/routes.js index 3ad256f..637f179 100644 --- a/public/javascripts/ajax/routes.js +++ b/public/javascripts/ajax/routes.js @@ -17,4 +17,10 @@ $(document).ready(function(){ show_flash(false); load_site_request(charts_path, ""); }); + + // Search Page + Path.map(hashbang+search_path+":q").to(function(){ + show_flash(false); + load_site_request(search_path, this.params['q']); + }); }); \ No newline at end of file diff --git a/public/javascripts/animations/animations.js b/public/javascripts/animations/animations.js index 3df729d..9df9b9f 100644 --- a/public/javascripts/animations/animations.js +++ b/public/javascripts/animations/animations.js @@ -40,4 +40,14 @@ function more_hover_out(obj){ backgroundColor: "#f4f6f9", color: "#15191d" }, 100); +} + +function init_playlist_scrollbar(){ + $('#fullscreen .play_list').jScrollPane(); + + $('#fullscreen .play_list').hover(function(){ + $('#fullscreen .jspVerticalBar').stop().fadeTo(300, 1); + }, function(){ + $('#fullscreen .jspVerticalBar').stop().fadeTo(200, 0.02); + }); } \ No newline at end of file diff --git a/public/javascripts/animations/fullscreen.js b/public/javascripts/animations/fullscreen.js index 33cf40f..bdc370f 100644 --- a/public/javascripts/animations/fullscreen.js +++ b/public/javascripts/animations/fullscreen.js @@ -2,11 +2,11 @@ $('#full').live("click",function(){ if($(this).hasClass("open")) { $("#fullscreen").animate({ - marginTop: 700, + marginTop: -300, opacity: 0 }, 500, function(){ $(this).hide(); - $("#site, #bg_stripe").animate({ + $("#site, #bg_stripe").show().animate({ marginTop: 0, opacity: 1 }, 500); @@ -16,13 +16,15 @@ } else { $("#site, #bg_stripe").animate({ - marginTop: -700, + marginTop: 400, opacity: 0 }, 500, function(){ - $("#fullscreen").css("marginTop", 700).show().animate({ + $(this).hide(); + $("#fullscreen").css("marginTop", -100).show().animate({ marginTop: 120, opacity: 1 }, 500); + init_playlist_scrollbar(); $("#fullscreen_wrap").fadeTo(1000, 0.5); }); $(this).addClass("open"); diff --git a/public/javascripts/config.js b/public/javascripts/config.js index e8cc93f..c575a2d 100644 --- a/public/javascripts/config.js +++ b/public/javascripts/config.js @@ -5,6 +5,8 @@ const hashbang = "#!"; const artist_path = "/artists/"; const home_path = "/home"; const charts_path = "/charts"; +const search_path = "/search/"; +const search_autocomplete_path = "/autocomplete/"; $(document).ready(function(){ Path.root(hashbang+home_path); diff --git a/public/javascripts/layout.js b/public/javascripts/layout.js index 70a50cd..ccdc6b5 100644 --- a/public/javascripts/layout.js +++ b/public/javascripts/layout.js @@ -54,8 +54,8 @@ function submit_redirect(obj){ function add_controls(){ $('.list.songs').each(function(){ $(this).children("li").each(function(){ - if(!$(this).has(".play").length) - $(this).prepend(''); + if(!$(this).has(".playsong").length) + $(this).prepend(''); }); }); } @@ -112,7 +112,8 @@ function new_albums(){ function album_hover(){ $('.albums .img').hover(function(){ - $(this).append('
'); + if(!$(this).children(".playall").hasClass("playall")) + $(this).append('
'); $(this).find('.playall').fadeIn(200); }, function(){ $(this).find('.playall').stop().fadeOut(300, function(){ $(this).remove(); }); @@ -120,15 +121,16 @@ function album_hover(){ } function album_add_show_songs_button(){ - $('.albums li').each(function(){ + $('.albums > li').each(function(){ if(!$(this).has(".songsbtn").length) - $(this).append('Songs'); + $(this).append('Songs'); }); } function init_site(link){ Path.listen(); + onYouTubePlayerReady(); switch(link) { case artist_path: @@ -143,10 +145,14 @@ function init_site(link){ set_active_navigation(home_path); break; case charts_path: + case search_path: init_tabs(); add_more_button(); + add_controls(); + init_autocomplete(); set_active_navigation(charts_path); break; + } } diff --git a/public/javascripts/player.js b/public/javascripts/player.js new file mode 100644 index 0000000..4c7f2bb --- /dev/null +++ b/public/javascripts/player.js @@ -0,0 +1,116 @@ +$(document).ready(function(){ + loadPlayer(); + initPlaylist(); + makeTracksAddable(); // bind click-event to "add" button (for each track) + initPlayer(); + initPlayTrack(); +}); + + +function initPlayer(){ + $('.play').live('click', function(){ + if($(this).hasClass('pause')) pauseVideo(); + else playVideo(); + + $(this).toggleClass('pause'); + }); +} + + +function initPlayTrack(){ + $('.playsong').live('click',function(){ + url = "/search_video"; + params = "artist="+$(this).parents('li').attr("artist")+"&track="+$(this).parents('li').attr("track"); + video_request(url, params, this); + }); +} + +function playTrack(data, obj){ + loadVideo(data); + $('#fullscreen h2').text($(obj).parents('li').attr("artist")); + $('#fullscreen h1').text($(obj).parents('li').attr("track")); +} + + +function initPlaylist() { + // TODO: reload Playlist form localStorage + storage = $.store.get("9minutesPlaylist"); + if(storage !== undefined) { + size = storage.length; + for(i = 0; i < size; i++) { + $('.play_list').append('
  • '+ storage[i].title + '
    ' + storage[i].artist + '

  • '); + $listElement = $('.play_list li:eq('+i+')'); + //$listElement.prepend(''); + //$listElement.append(''); + } + } +/* + $('ul#playlist').dragsort({ + dragSelector: '.drag-handle', + dragEnd: savePlaylist, + placeHolderTemplate: '
  • ', + scrollContainer: '#playlist-container' + }); + + $('#playlist .delete-handle').live("click", onRemoveTrackFromPlaylist); + + $('#player #icons .playlist').live("click", function(){ + $('#playlist-container').toggle(); + }); + + $('#playlist-navigation .clear').live('click', function(){ + $('#playlist li').remove(); + $.store.clear(); + });*/ +} + +function savePlaylist() { + var tracks = []; + $('.play_list li').each(function(i) { + tracks[i] = { + title : $(this).attr("track"), // TODO + artist : $(this).attr("artist"), + videoid : $(this).attr('videoid') + } + }); + + //console.log(tracks); + $.store.set("9minutesPlaylist", tracks); + //console.log("playlist saved ;)"); +} + +function makeTracksAddable() { + $('.add').live("click", onAddTrackToPlaylist); +} + +function onAddTrackToPlaylist(event) { + // if already in playlist + $listElement = $(event.target).parents('li').clone(); // TODO + $vuid = $listElement.children('.play').attr('videoid'); + + + if ($('#playlist li[videoid='+$vuid+']').html() != null) + { + alert("Already in playlist!"); + return; + } + + $listElement.children('img').remove(); + $listElement.attr('data-itemidx', 0); // important for dragsort plugin + $listElement.attr('videoid', $vuid); + // $listElement.prepend(''); + // $listElement.append(''); + $listElement.appendTo('#playlist'); + + savePlaylist(); +} + +function onRemoveTrackFromPlaylist(event) { + $listElement = $(event.target).parent(); + $listElement.fadeTo(300, 0.1, function() { + $(this).hide("blind", function() { + $(this).remove(); + savePlaylist(); + }); + }); +} \ No newline at end of file diff --git a/public/javascripts/plugins/autocomplete.js b/public/javascripts/plugins/autocomplete.js new file mode 100644 index 0000000..73ccbb6 --- /dev/null +++ b/public/javascripts/plugins/autocomplete.js @@ -0,0 +1,843 @@ +/* + * jQuery Autocomplete plugin 1.2.1 + * + * Copyright (c) 2009 Jörn Zaefferer + * + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + * With small modifications by Alfonso Gómez-Arzola. + * See changelog for details. + * + */ + +;(function($) { + +$.fn.extend({ + autocomplete: function(urlOrData, options) { + var isUrl = typeof urlOrData == "string"; + options = $.extend({}, $.Autocompleter.defaults, { + url: isUrl ? urlOrData : null, + data: isUrl ? null : urlOrData, + delay: isUrl ? $.Autocompleter.defaults.delay : 10, + max: options && !options.scroll ? 10 : 150 + }, options); + + // if highlight is set to false, replace it with a do-nothing function + options.highlight = options.highlight || function(value) { return value; }; + + // if the formatMatch option is not specified, then use formatItem for backwards compatibility + options.formatMatch = options.formatMatch || options.formatItem; + + return this.each(function() { + new $.Autocompleter(this, options); + }); + }, + result: function(handler) { + return this.bind("result", handler); + }, + search: function(handler) { + return this.trigger("search", [handler]); + }, + flushCache: function() { + return this.trigger("flushCache"); + }, + setOptions: function(options){ + return this.trigger("setOptions", [options]); + }, + unautocomplete: function() { + return this.trigger("unautocomplete"); + } +}); + +$.Autocompleter = function(input, options) { + + var KEY = { + UP: 38, + DOWN: 40, + DEL: 46, + TAB: 9, + RETURN: 13, + ESC: 27, + COMMA: 188, + PAGEUP: 33, + PAGEDOWN: 34, + BACKSPACE: 8 + }; + + var globalFailure = null; + if(options.failure != null && typeof options.failure == "function") { + globalFailure = options.failure; + } + + // Create $ object for input element + var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass); + + var timeout; + var previousValue = ""; + var cache = $.Autocompleter.Cache(options); + var hasFocus = 0; + var lastKeyPressCode; + var config = { + mouseDownOnSelect: false + }; + var select = $.Autocompleter.Select(options, input, selectCurrent, config); + + var blockSubmit; + + // prevent form submit in opera when selecting with return key + $.browser.opera && $(input.form).bind("submit.autocomplete", function() { + if (blockSubmit) { + blockSubmit = false; + return false; + } + }); + + // only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all + $input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) { + // a keypress means the input has focus + // avoids issue where input had focus before the autocomplete was applied + hasFocus = 1; + // track last key pressed + lastKeyPressCode = event.keyCode; + switch(event.keyCode) { + + case KEY.UP: + if ( select.visible() ) { + event.preventDefault(); + select.prev(); + } else { + onChange(0, true); + } + break; + + case KEY.DOWN: + if ( select.visible() ) { + event.preventDefault(); + select.next(); + } else { + onChange(0, true); + } + break; + + case KEY.PAGEUP: + if ( select.visible() ) { + event.preventDefault(); + select.pageUp(); + } else { + onChange(0, true); + } + break; + + case KEY.PAGEDOWN: + if ( select.visible() ) { + event.preventDefault(); + select.pageDown(); + } else { + onChange(0, true); + } + break; + + // matches also semicolon + case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA: + case KEY.TAB: + case KEY.RETURN: + if( selectCurrent() ) { + // stop default to prevent a form submit, Opera needs special handling + event.preventDefault(); + blockSubmit = true; + return false; + } + break; + + case KEY.ESC: + select.hide(); + break; + + default: + clearTimeout(timeout); + timeout = setTimeout(onChange, options.delay); + break; + } + }).focus(function(){ + // track whether the field has focus, we shouldn't process any + // results if the field no longer has focus + hasFocus++; + }).blur(function() { + hasFocus = 0; + if (!config.mouseDownOnSelect) { + hideResults(); + } + }).click(function() { + // show select when clicking in a focused field + // but if clickFire is true, don't require field + // to be focused to begin with; just show select + if( options.clickFire ) { + if ( !select.visible() ) { + onChange(0, true); + } + } else { + if ( hasFocus++ > 1 && !select.visible() ) { + onChange(0, true); + } + } + }).bind("search", function() { + // TODO why not just specifying both arguments? + var fn = (arguments.length > 1) ? arguments[1] : null; + function findValueCallback(q, data) { + var result; + if( data && data.length ) { + for (var i=0; i < data.length; i++) { + if( data[i].result.toLowerCase() == q.toLowerCase() ) { + result = data[i]; + break; + } + } + } + if( typeof fn == "function" ) fn(result); + else $input.trigger("result", result && [result.data, result.value]); + } + $.each(trimWords($input.val()), function(i, value) { + request(value, findValueCallback, findValueCallback); + }); + }).bind("flushCache", function() { + cache.flush(); + }).bind("setOptions", function() { + $.extend(true, options, arguments[1]); + // if we've updated the data, repopulate + if ( "data" in arguments[1] ) + cache.populate(); + }).bind("unautocomplete", function() { + select.unbind(); + $input.unbind(); + $(input.form).unbind(".autocomplete"); + }); + + + function selectCurrent() { + var selected = select.selected(); + if( !selected ) + return false; + + var v = selected.result; + previousValue = v; + + if ( options.multiple ) { + var words = trimWords($input.val()); + if ( words.length > 1 ) { + var seperator = options.multipleSeparator.length; + var cursorAt = $(input).selection().start; + var wordAt, progress = 0; + $.each(words, function(i, word) { + progress += word.length; + if (cursorAt <= progress) { + wordAt = i; + return false; + } + progress += seperator; + }); + words[wordAt] = v; + // TODO this should set the cursor to the right position, but it gets overriden somewhere + //$.Autocompleter.Selection(input, progress + seperator, progress + seperator); + v = words.join( options.multipleSeparator ); + } + v += options.multipleSeparator; + } + + $input.val(v); + hideResultsNow(); + $input.trigger("result", [selected.data, selected.value]); + return true; + } + + function onChange(crap, skipPrevCheck) { + if( lastKeyPressCode == KEY.DEL ) { + select.hide(); + return; + } + + var currentValue = $input.val(); + + if ( !skipPrevCheck && currentValue == previousValue ) + return; + + previousValue = currentValue; + + currentValue = lastWord(currentValue); + if ( currentValue.length >= options.minChars) { + $input.addClass(options.loadingClass); + if (!options.matchCase) + currentValue = currentValue.toLowerCase(); + request(currentValue, receiveData, hideResultsNow); + } else { + stopLoading(); + select.hide(); + } + $inval = escape($input.val()); + $input.next("a").attr("href", hashbang+search_path+$inval); + }; + + function trimWords(value) { + if (!value) + return [""]; + if (!options.multiple) + return [$.trim(value)]; + return $.map(value.split(options.multipleSeparator), function(word) { + return $.trim(value).length ? $.trim(word) : null; + }); + } + + function lastWord(value) { + if ( !options.multiple ) + return value; + var words = trimWords(value); + if (words.length == 1) + return words[0]; + var cursorAt = $(input).selection().start; + if (cursorAt == value.length) { + words = trimWords(value) + } else { + words = trimWords(value.replace(value.substring(cursorAt), "")); + } + return words[words.length - 1]; + } + + // fills in the input box w/the first match (assumed to be the best match) + // q: the term entered + // sValue: the first matching result + function autoFill(q, sValue){ + // autofill in the complete box w/the first match as long as the user hasn't entered in more data + // if the last user key pressed was backspace, don't autofill + if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) { + // fill in the value (keep the case the user has typed) + $input.val($input.val() + sValue.substring(lastWord(previousValue).length)); + // select the portion of the value not typed by the user (so the next character will erase) + $(input).selection(previousValue.length, previousValue.length + sValue.length); + } + }; + + function hideResults() { + clearTimeout(timeout); + timeout = setTimeout(hideResultsNow, 200); + }; + + function hideResultsNow() { + var wasVisible = select.visible(); + select.hide(); + clearTimeout(timeout); + stopLoading(); + if (options.mustMatch) { + // call search and run callback + $input.search( + function (result){ + // if no value found, clear the input box + if( !result ) { + if (options.multiple) { + var words = trimWords($input.val()).slice(0, -1); + $input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") ); + } + else { + $input.val( "" ); + $input.trigger("result", null); + } + } + } + ); + } + }; + + function receiveData(q, data) { + if ( data && data.length && hasFocus ) { + stopLoading(); + select.display(data, q); + autoFill(q, data[0].value); + select.show(); + } else { + hideResultsNow(); + } + }; + + function request(term, success, failure) { + if (!options.matchCase) + term = term.toLowerCase(); + var data = cache.load(term); + // recieve the cached data + if (data && data.length) { + success(term, data); + // if an AJAX url has been supplied, try loading the data now + } else if( (typeof options.url == "string") && (options.url.length > 0) ){ + + var extraParams = { + timestamp: +new Date() + }; + $.each(options.extraParams, function(key, param) { + extraParams[key] = typeof param == "function" ? param() : param; + }); + + $.ajax({ + // try to leverage ajaxQueue plugin to abort previous requests + mode: "abort", + // limit abortion to this input + port: "autocomplete" + input.name, + dataType: options.dataType, + url: options.url, + data: $.extend({ + q: lastWord(term), + limit: options.max + }, extraParams), + success: function(data) { + var parsed = options.parse && options.parse(data) || parse(data); + cache.add(term, parsed); + success(term, parsed); + } + }); + } else { + // if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match + select.emptyList(); + if(globalFailure != null) { + globalFailure(); + } + else { + failure(term); + } + } + }; + + function parse(data) { + var parsed = []; + var rows = data.split("\n"); + for (var i=0; i < rows.length; i++) { + var row = $.trim(rows[i]); + if (row) { + row = row.split("|"); + parsed[parsed.length] = { + data: row, + value: row[0], + result: options.formatResult && options.formatResult(row, row[0]) || row[0] + }; + } + } + return parsed; + }; + + function stopLoading() { + $input.removeClass(options.loadingClass); + }; + +}; + +$.Autocompleter.defaults = { + inputClass: "ac_input", + resultsClass: "ac_results", + loadingClass: "ac_loading", + minChars: 1, + delay: 400, + matchCase: false, + matchSubset: true, + matchContains: false, + cacheLength: 100, + max: 1000, + mustMatch: false, + extraParams: {}, + selectFirst: true, + formatItem: function(row) { return row[0]; }, + formatMatch: null, + autoFill: false, + width: 0, + multiple: false, + multipleSeparator: " ", + inputFocus: true, + clickFire: false, + highlight: function(value, term) { + return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "$1"); + }, + scroll: true, + scrollHeight: 180, + scrollJumpPosition: true +}; + +$.Autocompleter.Cache = function(options) { + + var data = {}; + var length = 0; + + function matchSubset(s, sub) { + if (!options.matchCase) + s = s.toLowerCase(); + var i = s.indexOf(sub); + if (options.matchContains == "word"){ + i = s.toLowerCase().search("\\b" + sub.toLowerCase()); + } + if (i == -1) return false; + return i == 0 || options.matchContains; + }; + + function add(q, value) { + if (length > options.cacheLength){ + flush(); + } + if (!data[q]){ + length++; + } + data[q] = value; + } + + function populate(){ + if( !options.data ) return false; + // track the matches + var stMatchSets = {}, + nullData = 0; + + // no url was specified, we need to adjust the cache length to make sure it fits the local data store + if( !options.url ) options.cacheLength = 1; + + // track all options for minChars = 0 + stMatchSets[""] = []; + + // loop through the array and create a lookup structure + for ( var i = 0, ol = options.data.length; i < ol; i++ ) { + var rawValue = options.data[i]; + // if rawValue is a string, make an array otherwise just reference the array + rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue; + + var value = options.formatMatch(rawValue, i+1, options.data.length); + if ( value === false ) + continue; + + var firstChar = value.charAt(0).toLowerCase(); + // if no lookup array for this character exists, look it up now + if( !stMatchSets[firstChar] ) + stMatchSets[firstChar] = []; + + // if the match is a string + var row = { + value: value, + data: rawValue, + result: options.formatResult && options.formatResult(rawValue) || value + }; + + // push the current match into the set list + stMatchSets[firstChar].push(row); + + // keep track of minChars zero items + if ( nullData++ < options.max ) { + stMatchSets[""].push(row); + } + }; + + // add the data items to the cache + $.each(stMatchSets, function(i, value) { + // increase the cache size + options.cacheLength++; + // add to the cache + add(i, value); + }); + } + + // populate any existing data + setTimeout(populate, 25); + + function flush(){ + data = {}; + length = 0; + } + + return { + flush: flush, + add: add, + populate: populate, + load: function(q) { + if (!options.cacheLength || !length) + return null; + /* + * if dealing w/local data and matchContains than we must make sure + * to loop through all the data collections looking for matches + */ + if( !options.url && options.matchContains ){ + // track all matches + var csub = []; + // loop through all the data grids for matches + for( var k in data ){ + // don't search through the stMatchSets[""] (minChars: 0) cache + // this prevents duplicates + if( k.length > 0 ){ + var c = data[k]; + $.each(c, function(i, x) { + // if we've got a match, add it to the array + if (matchSubset(x.value, q)) { + csub.push(x); + } + }); + } + } + return csub; + } else + // if the exact item exists, use it + if (data[q]){ + return data[q]; + } else + if (options.matchSubset) { + for (var i = q.length - 1; i >= options.minChars; i--) { + var c = data[q.substr(0, i)]; + if (c) { + var csub = []; + $.each(c, function(i, x) { + if (matchSubset(x.value, q)) { + csub[csub.length] = x; + } + }); + return csub; + } + } + } + return null; + } + }; +}; + +$.Autocompleter.Select = function (options, input, select, config) { + var CLASSES = { + ACTIVE: "ac_over" + }; + + var listItems, + active = -1, + data, + term = "", + needsInit = true, + element, + list; + + // Create results + function init() { + if (!needsInit) + return; + element = $("
    ") + .hide() + .addClass(options.resultsClass) + .css("position", "absolute") + .appendTo(document.body) + .hover(function(event) { + // Browsers except FF do not fire mouseup event on scrollbars, resulting in mouseDownOnSelect remaining true, and results list not always hiding. + if($(this).is(":visible")) { + input.focus(); + } + config.mouseDownOnSelect = false; + }); + + list = $("