application example: a simple blog

143 views
Skip to first unread message

Evan Monroig

unread,
Mar 7, 2008, 8:21:38 PM3/7/08
to weblocks
So here it is. A simple application example in the form of a
tutorial.

I don't yet know weblocks a lot and I am not a professional developer
so I would appreciate any comments and suggestions.

Oh, and the application is by no way complete. It doesn't even have
comments or login or ways to add posts except using the "admin"
interface which is not hidden, etc etc... But I hope that it can show
what is a view, a presentation, a parser and a widget and where to
look to make your own.

You can find everything here [1]: tarballs, git repository (each
tarball is from a tag of the same name). Since there is some code
from cl-weblocks (mainly CSS and Javascript for a default weblocks
application), I'll release this under the same license for now, LLGPL
[2,3].

I'll post the steps in reply to this post.

Evan

[1] http://obakechan.net/lisp/blog-app/
[2] http://www.cliki.net/LLGPL
[3] http://opensource.franz.com/preamble.html

Evan Monroig

unread,
Mar 7, 2008, 8:26:54 PM3/7/08
to weblocks
;;;; blog-v0: Start up

;;;; Follow
;;;; http://trac.common-lisp.net/cl-weblocks/wiki/UserManual#BreakingtheIce
;;;; and start a "blog" project
;;;;
;;;; In the REPL

(in-package :cl-user)

(asdf:operate 'asdf:load-op :weblocks)

(asdf:operate
'wop:make-app-op :weblocks
:name 'blog
:target "/home/evan/Documents/projects/info/lisp/webapps/")

(push #p"/home/evan/Documents/projects/info/lisp/webapps/blog/"
asdf:*central-registry*)

(asdf:operate 'asdf:load-op :blog)

(blog:start-blog :debug t)

Evan Monroig

unread,
Mar 7, 2008, 8:27:19 PM3/7/08
to weblocks
;;;; blog-v1: adding models and a gridedit interface

;;;; We will work inside the blog/ directory, which now has a data/
;;;; directory (will store the posts and user data), pub/ (for
;;;; javascript scripts and css stylesheets), conf/ for configuration
;;;; of the store (by default a prevalence store that stores data in
;;;; xml files under data/), and src/ which will contain the lisp
;;;; source code of the blog.
;;;;
;;;; So let's start working by creating a post and a user class:

;;; src/models.lisp
(in-package :blog)

(defclass user ()
((id :documentation "this is automatically assigned by cl-prevalence
when we persist the post object")
(name :accessor user-name
:initarg :name
:initform ""
:type string)))

(defclass post ()
((id)
(short-text :accessor post-short-text
:initarg :short-text
:initform ""
:type string
:documentation "short text of the post, to be shown on
the main page of the blog")
(text :accessor post-text
:initarg :text
:initform ""
:type string
:documentation "long text of the post, shown when the user
clicks on the link after the short text")
(time :accessor post-time
:initarg :time
:initform (get-universal-time)
:documentation "time at which the post was created")
(author :accessor post-author
:initarg :author
:initform nil
:type user)))

;;;; Now we'll follow the weblocks-demo and play a little with the
;;;; data by using the gridedit widgets.
;;;;
;;;; Gridedit needs three views for its data, corresponding to the
;;;; grid view, the data view to view the details of an item, and the
;;;; form view to edit an item.
;;;;
;;;; We put one gridedit widget for the USER and one for the POST
;;;; class on the same page for convenience, by putting them inside a
;;;; composite widget.
;;;;
;;;; Each of the gridedits has three associated views that we'll
;;;; define thereafter. A simple title is also added before the grid,
;;;; by using a lambda function and the :WIDGET-PREFIX-FN argument to
;;;; GRIDEDIT.

;;; src/layout.lisp
(in-package :blog)

(defun make-users-gridedit ()
(make-instance 'gridedit
:name 'users-grid
:data-class 'user
:view 'user-grid-view
:widget-prefix-fn (lambda (&rest args)
(declare (ignore args))
(with-html (:h1 "Users")))
:item-data-view 'user-data-view
:item-form-view 'user-form-view))

(defun make-posts-gridedit ()
(make-instance 'gridedit
:name 'posts-grid
:data-class 'post
:widget-prefix-fn (lambda (&rest args)
(declare (ignore args))
(with-html (:h1 "Posts")))
:view 'post-grid-view
:item-data-view 'post-data-view
:item-form-view 'post-form-view))

(defun make-admin-page ()
(make-instance 'composite
:widgets
(list (make-users-gridedit)
(lambda ()
;; gridedit widgets were probably
;; not intended to be put 2 on the
;; same page, so I add an HR tag
;; between the two
(with-html (:div (:hr :style "margin: 2em;"))))
(make-posts-gridedit))))


;;;; Now we need to modify the file src/init-session.lisp to call the
;;;; function MAKE-ADMIN-PAGE.

;;; src/init-session.lisp
(in-package :blog)

;; Define our application
(defwebapp 'blog
:description "A web application based on Weblocks")

;; Set public files directory to blog/pub
(setf *public-files-path* (compute-public-files-path :blog))

(defun init-user-session (comp)
(setf (composite-widgets comp)
(make-admin-page)))

;;;; Finally, we need to define the views to be used in the GRIDEDIT.
;;;; The most straightforward way is to scaffold them based on the
;;;; USER and POST slots, see what the result is and modify the views
;;;; after.

;;; src/views.lisp
(in-package :blog)

(defview user-grid-view (:type grid :inherit-from '(:scaffold user)))
(defview user-data-view (:type data :inherit-from '(:scaffold user)))
(defview user-form-view (:type form :inherit-from '(:scaffold user)))

(defview post-grid-view (:type grid :inherit-from '(:scaffold post)))
(defview post-data-view (:type data :inherit-from '(:scaffold post)))
(defview post-form-view (:type form :inherit-from '(:scaffold post)))


;;;; For completeness, here is the modified blog.asd file.

;;; blog.asd
;;;; -*- Mode: Lisp; Syntax: ANSI-Common-Lisp; Base: 10 -*-
(defpackage #:blog-asd
(:use :cl :asdf))

(in-package :blog-asd)

(defsystem blog
:name "blog"
:version "0.0.1"
:maintainer ""
:author ""
:licence ""
:description "blog"
:depends-on (:weblocks)
:components ((:file "blog")
(:module conf
:components ((:file "stores"))
:depends-on ("blog"))
(:module src
:components ((:file "init-session" :depends-on ("layout"))
(:file "layout" :depends-on ("models" "views"))
(:file "models")
(:file "views" :depends-on ("models")))
:depends-on ("blog" conf))))


;;;; Now compile the files or load them from asdf, and restart the
;;;; blog from the REPL.

(blog:stop-blog)
(blog:start-blog :debug t)

;;;; The two gridedit widgets show "no information available" but we
;;;; can add a user using the "Add" button.
;;;;
;;;; A few remarks:
;;;;
;;;; - For some reason unknown to me the title that I added in front
;;;; of each widget disappears.
;;;;
;;;; - The scaffold defines fields from all slots for which there is a
;;;; slot reader when in data/grid view, and all fields for which
;;;; there is a slot writer when in form view. So the ID slots are
;;;; not shown but all other slots are shown.
;;;;
;;;; - The data are validated by using the :TYPE information in the
;;;; slots. So you can add a user, but no post as there is no way
;;;; to enter a user object directly. Let's change this by
;;;; modifying POST-FORM-VIEW.


;;;; ChangeLog
blog-v1:

* src/views.lisp (user-grid-view, user-data-view, user-form-view)
(post-grid-view, post-data-view, post-form-view): scaffolded views
for the gridedit interface

* src/init-session.lisp (init-user-session): call MAKE-ADMIN-PAGE

* src/layout.lisp (make-users-gridedit, make-posts-gridedit)
(make-admin-page): add simple gridedit interface for the two
models

* src/models.lisp (user, post): USER and POST models

Evan Monroig

unread,
Mar 7, 2008, 8:27:52 PM3/7/08
to weblocks
(in-package :blog)

;;;; blog-v2: define views

;;;; We'll override some of the fields in the scaffolding. For each
;;;; field, we can specify some arguments to specify how to present,
;;;; obtain, write or parse the data. Note also that currently, if I
;;;; override a field then I lose the associated scaffolding
;;;; (e.g. that the field is required).

;;; added in src/views.lisp
(defview post-form-view (:type form :inherit-from '(:scaffold post))
(time :hidep t)
;; POST-AUTHOR-ID and ALL-USERS will be defined below
(author :reader #'post-author-id
:present-as (dropdown :choices #'all-users
:label-key #'user-name)
:parse-as (object-id :class-name 'user)
:requiredp t)
(short-text :present-as textarea
:requiredp t)
(text :present-as (textarea :cols 30)
:requiredp t))

;;;; Here we don't really need to manually edit the time as it should
;;;; be done automatically by the backend, so we hide the TIME field.
;;;;
;;;; We had better present the SHORT-TEXT and TEXT by using a TEXTAREA
;;;; presentation so that they're easier to edit. It is actually a
;;;; class named TEXTAREA-PRESENTATION located in the weblocks source
;;;; in the file
;;;; "cl-weblocks/src/views/types/presentations/textarea.lisp". Now
;;;; is probably a good time to have a look. You will see that it has
;;;; among others a COLS slot, which the DEFVIEW macro allows us to
;;;; set by using the syntax above.
;;;;
;;;; Now for the AUTHOR. As is done in the weblocks-demo application,
;;;; we present it as an HTML dropdown list. The idea is to show the
;;;; user a list of the names of users, and each name has for value
;;;; the ID of the user. We then map the selected user ID to the
;;;; actual user object.
;;;;
;;;; This is done by using a DROPDOWN presentation (in a file next to
;;;; the TEXTAREA one). To :CHOICES we assign a function that returns
;;;; a list of objects to choose from, and :LABEL-KEY the function
;;;; used to convert each object to a string. That's it for what
;;;; we'll see in the browser.
;;;;
;;;; Under the cover we need to pass the USER objects as their ID's,
;;;; and parse an ID into a USER object. The former is done by
;;;; specifying a function as :READER (takes a POST as argument), and
;;;; the latter by using a parser named OBJECT-ID. As for
;;;; presentation, this is actually a class named OBJECT-ID-PARSER
;;;; which you'll find in
;;;; cl-weblocks/src/views/types/parsers/common.lisp at the end of the
;;;; file. As before CLASS-NAME is one slot of OBJECT-ID-PARSER.
;;;;
;;;; Finally, before we make this work we need to define
;;;; POST-AUTHOR-ID and ALL-USERS,

;;; added in src/models.lisp:
(in-package :blog)

(defgeneric post-author-id (post)
(:method ((post post))
(when (post-author post)
(object-id (post-author post)))))

(defun all-users (&rest args)
"return all objects of class USER. ARGS is an added argument that
is ignored (needed for use in dropdown lists in views)."
(declare (ignore args))
(find-persistent-objects (class-store 'user) 'user))

;;;; Now we can add and delete users and posts using the gridedit
;;;; widgets. That should be enough of an introduction to views and
;;;; presentations.


;;;; ChangeLog
blog-v2

* src/models.lisp (post-author-id, all-users): functions used by
the views

* src/views.lisp (post-form-view): override some fields - textarea
for the texts, and dropdown list for the author

Evan Monroig

unread,
Mar 7, 2008, 8:28:15 PM3/7/08
to weblocks
;;;; blog-v3: Widgets
;;;;
;;;; Now let's make some widgets to show and edit the blog. The
;;;; simplest widget that we can probably make is to just present some
;;;; data using a data view. We specialize on RENDER-WIDGET-BODY to
;;;; render the widget.

;;; src/widgets/post.lisp
(in-package :blog)

(defwidget post-widget ()
((post :accessor post
:initarg :post
:initform nil)
(mode :accessor mode
:initarg :mode
:initform :short
:documentation "The post can be displayed in two
versions, :SHORT and :FULL.")
(short-view :accessor short-view
:initarg :short-view
:initform nil
:documentation "View to determine how the post is
displayed when in :SHORT mode.")
(full-view :accessor full-view
:initarg :full-view
:initform nil
:documentation "View to determine how the post is
displayed when in :SHORT mode."))
(:documentation "widget to handle a blog post"))

(defmethod render-widget-body ((obj post-widget) &key)
(ecase (mode obj)
(:short
(when (short-view obj)
(render-object-view (post obj) (short-view obj) :widget obj)))
(:full
(when (full-view obj)
(render-object-view (post obj) (full-view obj) :widget obj)))))

;;;; In the top of the admin page let's add a link that will display
;;;; the blog. For now, we'll have the function MAKE-BLOG-WIDGET just
;;;; return a composite widget with a POST-WIDGET inside, and we'll
;;;; later put a BLOG-WIDGET instead. Since we just want to check
;;;; that our home-made widget displays correctly, let's initialize it
;;;; with the POST that has ID=1 (create a post with the admin page).
;;;;
;;;; Note that I don't fully understand the continuation framework
;;;; yet, so I used DO-PAGE and ANSWER by trial and error and the
;;;; calls below work but I don't know if it's the best way to do
;;;; this.
(list (lambda ()
(render-link (lambda (&rest args)
(declare (ignore args))
(do-page (make-blog-widget)))
"view blog"))
(make-users-gridedit)
(lambda ()
;; gridedit widgets were probably not
;; intended to be put 2 on the same page, so
;; I add an HR tag between the two
(with-html (:div (:hr :style "margin: 2em;"))))
(make-posts-gridedit))))

(defun make-blog-widget ()
(let ((composite
(make-instance
'composite
:widgets (list
(make-instance 'post-widget
:short-view 'post-data-view
:full-view 'post-data-view
:post (post-by-id 0))))))
(push (lambda ()
(render-link (lambda (&rest args)
(declare (ignore args))
(answer composite))
"admin"))
(composite-widgets composite))
composite))

;;; added in src/models.lisp
(defun all-posts (&rest args)
"return all objects of class POST. ARGS is an added argument that
is
ignored (needed for use in dropdown lists in views)."
(declare (ignore args))
(find-persistent-objects (class-store 'post) 'post))

(defun post-by-id (id)
(first
(remove-if-not
(lambda (post)
(when (slot-boundp post 'id)
(= id (slot-value post 'id))))
(all-posts))))

;;;; Now you can go and check that the first post is indeed displayed
;;;; (you will need to restart your session or restart the blog
;;;; application), and so let's turn on to making a more useful
;;;; BLOG-WIDGET.


;;;; ChangeLog
blog-v3

* blog.asd (blog): updated for new files

* src/layout.lisp (make-blog-widget): create a composite widget
with a post widget and a link
(make-admin-page): add a link to MAKE-BLOG-WIDGET

* src/models.lisp (all-posts, post-by-id): backend functions

* src/widgets/post.lisp (post-widget): simple post widget
(render-widget-body): specialized method to render the post

Evan Monroig

unread,
Mar 7, 2008, 8:28:42 PM3/7/08
to weblocks
;;;; blog-v4: add a blog widget

;;;; One straightforward way to go is to have the BLOG-WIDGET contain
;;;; a list of POST-WIDGETs for each post, and one for the current
;;;; post. The weblocks COMPOSITE widget can already handle a list of
;;;; widgets for us, so we'll use it again.

;;; src/specials.lisp
(in-package :blog)

(defvar *blog-title* "Blog")

;;; src/widgets/blog.lisp
(in-package :blog)

(defwidget blog-widget ()
((current-post :accessor current-post
:initarg :current-post
:initform nil
:documentation "POST-WIDGET containing the current
post when the blog is in :POST mode")
(posts :accessor posts
:initarg :posts
:initform (make-instance 'composite)
:documentation "composite widget that contains a POST-WIDGET
for each post of the blog")
(mode :accessor mode
:initarg :mode
:initform :blog
:documentation "The blog can be in two modes, :BLOG
and :POST. In :BLOG mode to display a list of posts, and
in :POST mode to display an individual post.")
(post-short-view :accessor post-short-view
:initarg :post-short-view
:initform nil
:documentation "see SHORT-VIEW slot of POST-WIDGET")
(post-full-view :accessor post-full-view
:initarg :post-full-view
:initform nil
:documentation "see FULL-VIEW slot of POST-WIDGET"))
(:documentation "widget to handle a blog"))

(defgeneric blog-action-blog-mode (blog-widget)
(:documentation "return an action that will switch BLOG-WIDGET
into :BLOG
mode")
(:method ((blog-widget blog-widget))
(make-action
(lambda (&rest args)
(declare (ignore args))
(when (current-post blog-widget)
(setf (mode (current-post blog-widget)) :short))
(setf (mode blog-widget) :blog)
(reset-blog blog-widget)))))

(defgeneric blog-make-post-widget (blog-widget post)
(:documentation "make a POST-WIDGET containing POST. (called by
RESET-BLOG)")
(:method ((blog-widget blog-widget) (post post))
(make-instance 'post-widget
:post post
:short-view (post-short-view blog-widget)
:full-view (post-full-view blog-widget)
;;; we'll add a new slot to POST-WIDGET for this
:on-select (lambda (post-widget)
(setf (current-post blog-widget) post-widget)
(setf (mode blog-widget) :post)))))

(defgeneric reset-blog (blog-widget)
(:documentation "Reset the list of post widgets from the posts in
the database. This function is called by BLOG-ACTION-BLOG-MODE.")
(:method ((blog-widget blog-widget))
(setf (composite-widgets (posts blog-widget))
(mapcar (lambda (post)
(blog-make-post-widget blog-widget post))
(all-posts)))))

(defmethod initialize-instance :after ((obj blog-widget) &key)
(reset-blog obj))

(defgeneric render-blog (blog-widget mode)
(:documentation "render a blog widget in mode MODE. This function
is called by RENDER-WIDGET-BODY."))

(defmethod render-blog ((blog-widget blog-widget) (mode (eql :blog)))
(with-html (:h1 *blog-title*))
(render-widget (posts blog-widget)))

(defmethod render-blog ((blog-widget blog-widget) (mode (eql :post)))
(with-html
(:h1
;; link to come back to the blog
(render-link (blog-action-blog-mode blog-widget)
*blog-title*)))
(render-widget (current-post blog-widget)))

(defmethod render-widget-body ((obj blog-widget) &key)
(render-blog obj (mode obj)))

;;;; When we make the POST-WIDGET in the function
;;;; BLOG-MAKE-POST-WIDGET, we introduced a new :ON-SELECT initarg
;;;; which is a function that should be called when the post is
;;;; selected (to view it) to appropriately set the state of
;;;; BLOG-WIDGET. So we'll now make the corresponding changes to
;;;; POST-WIDGET.

;;; src/widgets/post.lisp
(in-package :blog)

(defwidget post-widget ()
(;; slots as before
(post :accessor post
:initarg :post
:initform nil)
(mode :accessor mode
:initarg :mode
:initform :short
:documentation "The post can be displayed in two
versions, :SHORT and :FULL.")
(short-view :accessor short-view
:initarg :short-view
:initform nil
:documentation "View to determine how the post is
displayed when in :SHORT mode.")
(full-view :accessor full-view
:initarg :full-view
:initform nil
:documentation "View to determine how the post is
displayed when in :SHORT mode.")
;; new slot
(on-select :accessor on-select
:initarg :on-select
:initform nil
:documentation "Function to be called when this post is
selected. It accepts POST-WIDGET as argument."))
(:documentation "widget to handle a blog post"))

;;; new function
(defgeneric post-action-select (post-widget)
(:documentation "return an action that selects POST-WIDGET")
(:method ((post-widget post-widget))
(make-action
(lambda (&rest args)
(declare (ignore args))
(setf (mode post-widget) :full)
(safe-funcall (on-select post-widget) post-widget)))))

(defmethod render-widget-body ((obj post-widget) &key)
(ecase (mode obj)
(:short
(when (short-view obj)
(render-object-view (post obj) (short-view obj)
:widget obj
;; after the fields of the POST object,
;; display a link to see the full post
:fields-suffix-fn
(lambda (&rest args)
(declare (ignore args))
(when (on-select obj)
(render-link (post-action-select obj) "more"))))))
(:full
(when (full-view obj)
(render-object-view (post obj) (full-view obj) :widget obj)))))

;;;; Now we'll change the MAKE-BLOG-WIDGET function in the layout to
;;;; make a BLOG-WIDGET instead of a POST-WIDGET. And provide two
;;;; specialized views POST-SHORT-VIEW and POST-FULL-VIEW to nicely
;;;; display the posts.

;;; modification in src/layout.lisp
(defun make-blog-widget ()
(let ((composite
(make-instance
'composite
:widgets (list
(make-instance 'blog-widget
:post-short-view 'post-short-view
:post-full-view 'post-full-view)))))
(push (lambda ()
(render-link (lambda (&rest args)
(declare (ignore args))
(answer composite))
"admin"))
(composite-widgets composite))
composite))

;;;; For the views, we'll have POST-DATA-VIEW display properly
;;;; formatted time and the author name instead of "User" that is the
;;;; default when converting an object of class USER to a string. We
;;;; can then inherit from this view and hide either the TEXT slot or
;;;; the SHORT-TEXT slot to define the short and full views.

;;; modification in src/views.lisp
(defview post-data-view (:type data :inherit-from '(:scaffold post))
(author :reader #'post-author-name)
(time :reader #'post-formatted-time))

(defview post-short-view (:type data :inherit-from 'post-data-view)
(text :hidep t))

(defview post-full-view (:type data :inherit-from 'post-data-view)
(short-text :hidep t))

;;; add in src/models.lisp
(defgeneric post-author-name (post)
(:method ((post post))
(when (post-author post)
(user-name (post-author post)))))

(defun post-formatted-time (post)
(multiple-value-bind (second minute hour date month year day
daylight-p zone)
(decode-universal-time (post-time post))
(declare (ignore second daylight-p zone date))
;; my format-foo is not very good so please improve if you can :)
(format nil "~d-~d-~d ~d:~d" year month day hour minute)))




;;;; ChangeLog
blog-v4

* blog.asd (blog): add new file

* src/models.lisp (post-author-name, post-formatted-time): backend
functions

* src/layout.lisp (make-blog-widget): make a BLOG-WIDGET instead
of POST-WIDGET
(make-blog-widget): use new views for the post

* src/views.lisp (post-data-view): modify to display formatted
time, and user name instead of "User"
(post-short-view, post-full-view): new views for used the two
states POST-WIDGET

* src/widgets/post.lisp (post-widget): add ON-SELECT slot so that
BLOG-WIDGET can set a call-back
(post-action-select): return an action that selects POST-WIDGET
(render-widget-body): modify to add a link to see the full post
[and call the ON-SELECT function if defined]

* src/widgets/blog.lisp:
(blog-action-blog-mode, blog-make-post-widget, reset-blog)
(render-blog, initialize-instance, render-widget-body): new blog
widget

* src/specials.lisp (*blog-title*): blog title

Benjamin Collins

unread,
Mar 9, 2008, 1:24:49 AM3/9/08
to weblocks
On Mar 7, 7:27 pm, Evan Monroig <evan.monr...@gmail.com> wrote:
> (in-package :blog)
>
> ;;;; blog-v2: define views
>
> ;;;; We'll override some of the fields in the scaffolding.  For each
> ;;;; field, we can specify some arguments to specify how to present,
> ;;;; obtain, write or parse the data.  Note also that currently, if I
> ;;;; override a field then I lose the associated scaffolding
> ;;;; (e.g. that the field is required).
>
> ;;; added in src/views.lisp
> (defview post-form-view (:type form :inherit-from '(:scaffold post))
>   (time :hidep t)
>   ;; POST-AUTHOR-ID and ALL-USERS will be defined below
>   (author :reader #'post-author-id
>           :present-as (dropdown :choices #'all-users
>                                 :label-key #'user-name)
>           :parse-as (object-id :class-name 'user)
>           :requiredp t)
>   (short-text :present-as textarea
>               :requiredp t)
>   (text :present-as (textarea :cols 30)
>         :requiredp t))

I was following along your steps, and all was well until this. The
dropdown does not get rendered at all when creating a new post, but
oddly enough, it *does* get rendered when modifying an existing post.
This is true of both the sources that I've been typing as I read as
well as the set of sources in blog-v2.tar

Evan Monroig

unread,
Mar 9, 2008, 4:16:51 AM3/9/08
to webl...@googlegroups.com

That's interesting. I just loaded the sources in blog-v2.tar in a
fresh lisp (using SBCL and running the three commands below) and could
not reproduce.

I'm not sure how to go debugging this one... Did you try in a fresh
lisp? Which lisp are you using? Which OS? Which browser? Do you
see the dropdown in the HTML sources (e.g. using Firebug in Firefox)?

What you could try also is insert an error in the function ALL-USERS,
for example by adding (error "this is a debugging error") somewhere in
the body to see whether it gets executed at all.

Sometimes I also find it useful to restart the browser session (using
the icon at the bottom-left in the browser or by stopping and
restarting the application), although for functions to render widgets
and views I experienced that just reloading the page in the browser is
enough.

Finally, I wrote the code for the drop-down after looking at the
similar code in the weblocks-demo example application, so you could
also check whether the dropdown works in this case.

Hope this helps,

Evan

- - -

(push #p"/path/to/blog-v2/" asdf:*central-registry*)

Richard Szopa

unread,
Mar 11, 2008, 7:37:01 AM3/11/08
to weblocks


On Mar 8, 2:28 am, Evan Monroig <evan.monr...@gmail.com> wrote:

> ;;; modification in src/views.lisp
> (defview post-data-view (:type data :inherit-from '(:scaffold post))
> (author :reader #'post-author-name)
> (time :reader #'post-formatted-time))

If I may add my 5 groszy: I think that text should be rather presented
as a `paragraph'.

(defview post-data-view (:type data :inherit-from '(:scaffold post))
(author :reader #'post-author-name)
(time :reader #'post-formatted-time)
(text :present-as paragraph))

Evan Monroig

unread,
Mar 11, 2008, 8:18:36 AM3/11/08
to webl...@googlegroups.com

You're right, this is nicer. I'll add this (and also for short-text as well).

Looking at the tickets in the track, I also thought that maybe it
would be a nice occasion to also define an additional presentation for
the universal-time format that lisp defines?

Just a thought, not enough time right now :).

Evan

Benjamin Collins

unread,
Mar 11, 2008, 6:14:49 PM3/11/08
to weblocks
On Mar 9, 3:16 am, "Evan Monroig" <e...@monroig.net> wrote:
> I'm not sure how to go debugging this one...  Did you try in a fresh
> lisp?  Which lisp are you using?  Which OS?  Which browser?  Do you
> see the dropdown in the HTML sources (e.g. using Firebug in Firefox)?

Yes; SBCL 1.0.9, Gentoo Linux, FF3b4, No.

> What you could try also is insert an error in the function ALL-USERS,
> for example by adding (error "this is a debugging error") somewhere in
> the body to see whether it gets executed at all.

I tried this and got nothing. In the repl, calling (all-uses)
produces the backtrace in sldb just like you'd expect, but on the
frontend, no change.

> Sometimes I also find it useful to restart the browser session (using
> the icon at the bottom-left in the browser or by stopping and
> restarting the application), although for functions to render widgets
> and views I experienced that just reloading the page in the browser is
> enough.

Also no luck with this.

> Finally, I wrote the code for the drop-down after looking at the
> similar code in the weblocks-demo example application, so you could
> also check whether the dropdown works in this case.

Actually, *none* of the fields work for me in the demo.

Evan Monroig

unread,
Mar 11, 2008, 6:57:20 PM3/11/08
to webl...@googlegroups.com
On Wed, Mar 12, 2008 at 7:14 AM, Benjamin Collins <aggi...@gmail.com> wrote:
>
> On Mar 9, 3:16 am, "Evan Monroig" <e...@monroig.net> wrote:
> > I'm not sure how to go debugging this one... Did you try in a fresh
> > lisp? Which lisp are you using? Which OS? Which browser? Do you
> > see the dropdown in the HTML sources (e.g. using Firebug in Firefox)?
>
> Yes; SBCL 1.0.9, Gentoo Linux, FF3b4, No.

Ok. I have SBCL 1.0.6 on Ubuntu but it shouldn't make a difference.
I tried firefox 3 beta 4 but couldn't reproduce.

> > Finally, I wrote the code for the drop-down after looking at the
> > similar code in the weblocks-demo example application, so you could
> > also check whether the dropdown works in this case.
>
> Actually, *none* of the fields work for me in the demo.

So maybe it is a problem with weblocks on your configuration?

What version of weblocks do you have? "darcs changes | head -n 1"
tells me that the last change was on February 22 on my version.

Also, do the unit tests in weblocks succeed?

Evan

Benjamin Collins

unread,
Mar 11, 2008, 7:24:03 PM3/11/08
to weblocks


Evan Monroig wrote:
> So maybe it is a problem with weblocks on your configuration?
>
> What version of weblocks do you have? "darcs changes | head -n 1"
> tells me that the last change was on February 22 on my version.
>
> Also, do the unit tests in weblocks succeed?

I've got Feb 27, and the 34 of the 631 unit tests fail.

Benjamin Collins

unread,
Mar 11, 2008, 7:50:22 PM3/11/08
to weblocks
I did 'darcs unpull --last 5' to get back to Feb 23, and then the unit
tests pass and the blog seems to work.

Evan Monroig

unread,
Mar 11, 2008, 8:01:12 PM3/11/08
to webl...@googlegroups.com

Ok, then it is my fault :). I will get the latest weblocks and update
my code for it. I'll let you know.

Evan

Richard Szopa

unread,
Mar 12, 2008, 6:56:43 AM3/12/08
to weblocks


On Mar 12, 1:01 am, "Evan Monroig" <evan.monr...@gmail.com> wrote:

> Ok, then it is my fault :). I will get the latest weblocks and update
> my code for it. I'll let you know.

Well, I have SBCL 1.0.12 and the Weblocks from February 27th and both
the tests pass and the blog works. Upgrading SBCL looks like the best
thing to do.

-- Richard

Vyacheslav Akhmechet

unread,
Mar 12, 2008, 12:34:35 PM3/12/08
to webl...@googlegroups.com
Hmm, all unit tests should be passing on the latest Weblocks. If
something fails it's a problem (either with Weblocks or the
environment).

Benjamin Collins

unread,
Mar 15, 2008, 2:37:24 AM3/15/08
to weblocks
Evan, thanks for you blog application walk-through. I finally got it
all working, and it will be very helpful as I try to build my own
application.

Thanks for putting your time into this and sharing it.
Message has been deleted

Evan Monroig

unread,
Apr 16, 2008, 6:39:29 AM4/16/08
to webl...@googlegroups.com
Hi,

Sorry for the delay, it is difficult for me to find time these days
with a newborn in my hands ;).

> ;;;; Now compile the files or load them from asdf
>

> Do you mean:
>
> (asdf:operate 'asdf:load-op :blog) ?

Yes, that's what I meant.

> I'm getting an error after typing in v.1 when trying to add a user.
> (Note that I called it myapp instead of blog.)
>
> Internal Server Error
>
> There is no applicable method for the generic function
> #<STANDARD-GENERIC-FUNCTION WEBLOCKS:WITH-VIEW-HEADER (2)>
> when called with arguments
> (#<WEBLOCKS:FORM-VIEW {ACA1219}> #<MYAPP::USER {BBCF191}>
> #<WEBLOCKS:DATAFORM #:G2359> #<CLOSURE #
> {BC7D3DD}> :METHOD :POST :ACTION
> "2361:497E46257AC964BCBA557F475C7B92D5" :VALIDATION-ERRORS NIL
> :INTERMEDIATE-VALUES NIL :WIDGET #<WEBLOCKS:DATAFORM #:G2359>).
>
> 0: (BACKTRACE 536870911 #<SB-IMPL::STRING-OUTPUT-STREAM {BC7DA19}>)
> 1: (HUNCHENTOOT:GET-BACKTRACE #<unavailable argument>)
> 2: ((LAMBDA (COND)) #<SIMPLE-ERROR {BC7D6D9}>)
> 3: ((LAMBDA (COND)) #<SIMPLE-ERROR {BC7D6D9}>)
> 4: (SIGNAL #<SIMPLE-ERROR {BC7D6D9}>)
> 5: (ERROR
> "~@<There is no applicable method for the generic function
> ~2I~_~S~
> ~I~_when called with arguments ~2I~_~S.~:>")
> 6: ((SB-PCL::FAST-METHOD NO-APPLICABLE-METHOD (T))
> #<unavailable argument>
> #<unavailable argument>
> #<STANDARD-GENERIC-FUNCTION WEBLOCKS:WITH-VIEW-HEADER (2)>)
> 7: ((SB-PCL::FAST-METHOD WEBLOCKS:WITH-WIDGET-HEADER (T "#<...>" .
> "#<...>"))
> #<unavailable argument>
> #<unavailable argument>
> #<WEBLOCKS:DATAFORM #:G2359>
> #<STANDARD-GENERIC-FUNCTION WEBLOCKS:RENDER-WIDGET-BODY (11)>)
> 8: (WEBLOCKS:RENDER-WIDGET #<WEBLOCKS:DATAFORM #:G2359>)
> 9: (WEBLOCKS:RENDER-WIDGET #<WEBLOCKS:GRIDEDIT MYAPP::USERS-GRID>)
> 10: ((LAMBDA (WEBLOCKS::W)) #<WEBLOCKS:GRIDEDIT MYAPP::USERS-GRID>)
> 11: (WEBLOCKS::RENDER-DIRTY-WIDGETS)

Actually I haven't looked at my sample blog app for some time, so I
wasn't sure if it was because of a change in weblocks in the
meanwhile, but I just checked blog-v0 through blog-v4 versions and it
is ok on my setup.

As for your error, I really have no idea what can be wrong, since the
generic function with-view-header in weblocks accepts just that: a
view, an object, a widget, a closure, and additional arguments.

Just in case, did you try to restart the lisp before loading the blog
app? Also, does weblocks pass all tests? Is it the latest one?

Cheers,

Evan

Sohail Somani

unread,
Apr 16, 2008, 12:01:00 PM4/16/08
to webl...@googlegroups.com
Evan Monroig wrote:
> Hi,
>
> Sorry for the delay, it is difficult for me to find time these days
> with a newborn in my hands ;).

I am now an expert one-hand typist due to my similar situation!

--
Sohail Somani
http://uint32t.blogspot.com

Sohail Somani

unread,
Apr 16, 2008, 12:01:15 PM4/16/08
to webl...@googlegroups.com
Evan Monroig wrote:
> Hi,
>
> Sorry for the delay, it is difficult for me to find time these days
> with a newborn in my hands ;).

I am now an expert one-hand typist due to my similar situation!

Reply all
Reply to author
Forward
0 new messages