(standard-header)

(when ,header (html (:h1 :class 'title' ,header)))

,@body

(standard-footer))))

You should define standard-header and standard-footer as separate functions for two reasons. First, during development you can redefine those functions and see the effect immediately without having to recompile functions that use the :mp3-browser-page macro. Second, it turns out that one of the pages you'll write later won't be defined with :mp3-browser- page but will still need the standard header and footers. They look like this:

(defparameter *r* 25)

(defun standard-header ()

(html

((:p :class 'toolbar')

'[' (:a :href (link '/browse' :what 'genre') 'All genres') '] '

'[' (:a :href (link '/browse' :what 'genre' :random *r*) 'Random genres') '] '

'[' (:a :href (link '/browse' :what 'artist') 'All artists') '] '

'[' (:a :href (link '/browse' :what 'artist' :random *r*) 'Random artists') '] '

'[' (:a :href (link '/browse' :what 'album') 'All albums') '] '

'[' (:a :href (link '/browse' :what 'album' :random *r*) 'Random albums') '] '

'[' (:a :href (link '/browse' :what 'song' :random *r*) 'Random songs') '] '

'[' (:a :href (link '/playlist') 'Playlist') '] '

'[' (:a :href (link '/all-playlists') 'All playlists') ']')))

(defun standard-footer ()

(html (:hr) ((:p :class 'footer') 'MP3 Browser v' *major-version* '.' *minor-version*)))

A couple of smaller HTML macros and helper functions automate other common patterns. The :table- row HTML macro makes it easier to generate the HTML for a single row of a table. It uses a feature of FOO that I'll discuss in Chapter 31, an &attributes parameter, which causes uses of the macro to be parsed just like normal s-expression HTML forms, with any attributes gathered into a list that will be bound to the &attributes parameter. It looks like this:

(define-html-macro :table-row (&attributes attrs &rest values)

`(:tr ,@attrs ,@(loop for v in values collect `(:td ,v))))

And the link function generates a URL back into the application to be used as the HREF attribute with an A element, building a query string out of a set of keyword/value pairs and making sure all special characters are properly escaped. For instance, instead of writing this:

(:a :href 'browse?what=artist&genre=Rhythm+%26+Blues' 'Artists')

you can write the following:

(:a :href (link 'browse' :what 'artist' :genre 'Rhythm & Blues') 'Artists')

It looks like this:

(defun link (target &rest attributes)

(html

(:attribute

(:format '~a~@[?~{~(~a~)=~a~^&~}~]' target (mapcar #'urlencode attributes)))))

To URL encode the keys and values, you use the helper function urlencode, which is a wrapper around the function encode-form-urlencoded, which is a nonpublic function from AllegroServe. This is—on one hand—bad form; since the name encode-form-urlencoded isn't exported from NET.ASERVE, it's possible that encode-form-urlencoded may go away or get renamed out from under you. On the other hand, using this unexported symbol for the time being lets you get work done for the moment; by wrapping encode-form-urlencoded in your own function, you isolate the crufty code to one function, which you could rewrite if you had to.

(defun urlencode (string)

(net.aserve::encode-form-urlencoded string))

Finally, you need the CSS style sheet mp3-browser.css used by :mp3-browser- page. Since there's nothing dynamic about it, it's probably easiest to just publish a static file with publish-file.

(publish-file :path '/mp3-browser.css' :file filename :content-type 'text/css')

A sample style sheet is included with the source code for this chapter on the book's Web site. You'll define a function, at the end of this chapter, that starts the MP3 browser application. It'll take care of, among other things, publishing this file.

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-url-function browse

(request (what keyword :genre) genre artist album (random integer))

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

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

(single-column (if (eql what :song) :file 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)

(:input :name 'what' :type 'hidden' :value single-column)

(:input :name 'action' :type 'hidden' :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 is :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 select does most of the heavy lifting, with values-for-page mostly responsible for

Вы читаете Practical Common Lisp
Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

Вы можете отметить интересные вам фрагменты текста, которые будут доступны по уникальной ссылке в адресной строке браузера.

Отметить Добавить цитату