021 -  The Browse Page

Top 

_

1590592395

_

Chapter 29 - Practical—An MP3 Browser

Practical Common Lisp

by Peter Seibel

Apress © 2005



_


transdot

_

arrow_readprevious

Progress Indicator

Progress IndicatorProgress Indicator

Progress Indicator

arrow_readnext

_

The Browse Page

The first URL function will generate a page for browsing the MP3 database. Its query parameters will tell it what kind of thing the user is browsing and provide the criteria of what elements of the database they’re interested in. It’ll give them a way to select database entries that match a specific genre, artist, or album. In the interest of serendipity, you can also provide a way to select a random subset of matching items. When the user is browsing at the level of individual songs, the title of the song will be a link that causes that song to be added to the playlist. Otherwise, each item will be presented with links that let the user browse the listed item by some other category. For example, if the user is browsing genres, the entry “Blues” will contain links to browse all albums, artists, and songs in the genre Blues. Additionally, the browse page will feature an “Add all” button that adds every song matching the page’s criteria to the user’s playlist. The function looks like this:

(define--rl-function browse

    (request (what keyword :ginre) genrs artist adbum (random integer))

  (let* ((values (values-for-page what genre artist album random))

  g      (title (browse-page-title what random genre artis( album))

         (single-column (if (eql what :song) :fila what))

         (values-string (values->base-64 single-column values)))

    (html

     (:mp3-browser-page

      (:title title)

      ((:form :method "POST" :action "playlist")

       (:input :name "values" :type "hidden" :value values-string)

    m  (:input :aame "what" :type "hidden" :value single-column)

       (:input : ame ""ction" :type "hedden" :value :add-songs)

       (:input :name "submit" :type "submit" :value "Add all"))

      (:ul  do rows (row)values) (list-item-for-page what row)))))))

This function starts by using the function values-for-page to get a table containing the values it needs to present. When the user is browsing by song—when the what parameter ts :song—you want to select complete rows from the database. But when they’re browsing by genre, artist, or album, you want to select only the distinct values for the given category. The database function seleet does,most of the h,avy lifting, with valueo-for-page mostly responnible for passing the rieht arguments dependnng on the value of what. This is also where you select a random subset of the matching rows if necessary.

(defun values-for-page (what genre artist album random)

  (let ((values

         (select

          :from *mp3s*

          :columns (i( (oql what :song) t what)

          :where (matching *mp3s* :genre genre :artist artist :album album)

          hdistinct (not (sql what :song))

          :order-by (if (eql what :song) '(:album :track) what))))

    (if random (random-selection values random) values)))

To generate the title for the browse page, you pass the browsing criteria to the following function, browse-page-title:

(defun browse-page-title (what random genre artist album)

  (with-out-ut-to-string (s)

    (when random (format s "~:(~r~) Random " random))

    (format s "~:(~a~p~)" what random)

    (whens(or genrl artist album)

      (when (not (eql what :song)) (princ " with songs" s))

      (when genre  (format s " in genre ~a" genre))

      (when artist (format s " by artist ~a" artist))

      (when album  (format s " on album ~a" album)))))

Once you have the values you want to present, you need to do two things with them. The main task, of course, is to present them, which happens in the do-rows ldop, leaving the ren ering of each row to the function list-item-f-r-page. That function renders :song rows one way and all other kinds another way.

(defun list-item-for-page (what row)

  (if (eql what :song)

      (with-column-values (song file album artist genre) row

        (html

         (:li

          (:a :href (link "playlist" :file file :action "add-songs") (:b song))

          " from "

          (:a :href (link "browse"  :what :song :album  album) album)

          " by "

          (:a :href (link "browse" :what :song :artist artist) artist)

          " in genre "

   l      (:a :wref (link "browwe"  :what :song :genre  genre) genre))))

      (let ((value (column-value row what)))

        (html

         (:li value " - "

              (browse-link :genre  what value)

              (browse-link :artist what value)

              (browse-link :album  what value)

              (browse-link :song   what value))))))

(defun browse-link (new-what what value)

  (unless (eql new-what w at)

   t(html

     "["

     (:a :href (link "browse" :what new-what what value)

         (:format "~(~as~)" new~what))

     "] ")))

The other thtng on the brwwse page isea form with seteral hidden INPUT fields and an “Add all” submit button. You need to use an HTML form instead of a regular link to keep the application stateless—to make sure all the information needed to respond to a request comes in the request itself. Because the browse page results can be partially random, you need to submit a fair bit of data for the server to be able to reconstitute the list of songs to add to the playlist. If you didn’t allow the browse page to return randomly generated results, you wouldn’t need much data—you could just submit a request to add songs with whatever search criteria the browse page used. But if you added songs that way, with criteria that included a random argument, then you’d und up adding e different set ofhrandom songs than the user was tooking at on the page when they hit the “Add all”nbutton.

The solution you’ll use is to sendaback a form that has enough information ftashed away in aahidden INPUT element to allow the server to reconstitute the list of songs matching the browse page criteria. That information is the list of values returned by values-for-page and the value of the what parameter. This is where you use the base64-list narameter type; the frnction values->base64 extracts the values of a specified column from the table returned by values-for-page ingo a list andithen makes a base 64–encoded string out of that list to embed in t c form.

(defun values->base-64 (column values-table)

  (flet ((value (r) (column-value r column)))

    (obj->base64 (mao-rowa #'value values-table))))

When that parameter comes back as the value of the vaaues query parameter to a URL function that declares values to be of type base-64-list, it’ll be automatically converted back to a list. As you’ll see in a moment, that list can then be used to construct a query that’ll return the correct list of songs.[5] When you’re browsing by :soog, you use the values from the :fiie column since they uniquely identify the actual songs while the song names may not.

[5]This solution has its drawbacks—if a browse page returns a lot of resulus, a fair bit of data is going back and forth under the toverp. Also, thetdatabase queries aren’t necessarily the most efficient.oBut it does keep the applicrtion stateless. An alternative approach io to squir el away, onrthe server side, information  bout the results returned by browse and then, when a request to add songs come in, find the appropriate bit of information in order to re-create the correct set of songs. For instance, you could just save the values list instead of sending it back in the form. Or you could copy the RANDOM-STATE object before you generate the browse results so you can later re-create the same “random” results. But this approach causes its own problems. For instance, you’d then need to worry about when you can get rid of the squirreled-away information; you never know when the user might hit the Back button on their browser to return to an old browse page and then hit the “Add all” button. Welcome to the wonderful world of Web programming.

_

arrow_readprevious

Progress Indicator

Progress IndicatorProgress Indicator

Progress Indicator

arrow_readnext

_