-
Notifications
You must be signed in to change notification settings - Fork 6
/
leftparen.scrbl
260 lines (178 loc) · 11.9 KB
/
leftparen.scrbl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
#lang scribble/doc
@(require scribble/manual)
@title{LeftParen 0.51 Documentation}
Website: @link["http://leftparen.com"]{http://leftparen.com}
LeftParen is a framework for quickly creating web apps. It runs on
PLT Scheme v4.1.3.9 or greater. (@bold{This is not-yet-officially-released}, so you'll have to download it from the @link["http://pre.plt-scheme.org/installers/"]{PLT Pre-Relese Software page}.) LeftParen is released under an @link["http://github.com/vegashacker/leftparen/tree/master/MIT-LICENSE.txt"]{MIT License}. The source is available on @link["http://github.com/vegashacker/leftparen/tree/master"]{github}.
@section{Installing LeftParen}
You'll need PLT Scheme v4.1.3@bold{.9} or greater installed.
Make sure that @scheme[mzscheme] is in your path. You should be ready
to go if you can do this:
@verbatim{
% mzscheme
Welcome to MzScheme v4.1.3.9...
>
}
Installing LeftParen is done automatically when you perform various @link["http://planet.plt-scheme.org/"]{PLaneT} @scheme[require] commands. See @secref{tutorials} for examples of this. On my system, starting a LeftParen web app for the first time (with a freshly installed version of PLT Scheme), takes about an hour. (The time is spent downloading and installing a bunch of files into your local PLaneT repository.)
At any point, if you see an error like
@verbatim{
make-directory: cannot make directory: /home/rob/.plt-scheme/planet/300/4.1.3.9 (Permission denied; errno=13)
}
try re-running the command with @tt{sudo} (you won't need to do this everytime--just the once).
@section[#:tag "tutorials"]{Tutorials}
@subsection{Hello, World}
We're going to make a project called @tt{hello-world}. Change to the directory that you'd like to create the project in. Then issue
@verbatim{% mzscheme -e '(require (planet vegashacker/leftparen:4:=1/generate))' hello-world}
This will create a @tt{hello-world} project directory for you. In this directory you'll find the @tt{script} directory, which contains some useful scripts. All paths are relative to this project directory, so when calling scripts, you always want to be at the project root.
@verbatim{% cd hello-world}
We need to make the scripts executable:
@verbatim{% chmod u+x script/server}
LeftParen has automatically generated everything we need to run our web app---we just need to start the server (again, you should be at the project root directory):
@verbatim{
% ./script/server
Populating caches...
Done populating cache.
Server is ready at http://localhost:8765/ (ctrl-c to stop it).
}
Point your browser to @link["http://localhost:8765"]{http://localhost:8765} and you should see a familiar greeting:
@tt{Hello, World!}
@subsection{Blogerton the Blog}
Now let's try implementing the true "hello world" of web apps---a blog. First, execute the following commands from the directory in which you want to create your project:
@verbatim{
% mzscheme -e '(require (planet vegashacker/leftparen:4:=1/generate))' blogerton
% cd blogerton
% chmod u+x script/server
}
@subsubsection{Changes to @tt{app.scm}}
We need to register a couple of pages in our app. The @scheme[index-page] was already set up for you, but you'll need to add a page to create new posts, and one to view them. Make the @scheme[define-app] call look like this:
@schemeblock[
(define-app my-app
(index-page (url "/"))
(create-post-page (url "/post"))
(view-post-page (url "/view/" (string-arg))))
]
@subsubsection{Changes to @tt{main.scm}}
Now we need to define those pages that we declared in @tt{app.scm}.
@schemeblock[
(define-page (index-page req)
(** `(h1 "Blogerton")
`(p ,(web-link "Create a new post" (page-url create-post-page)))
`(ul ,@(map (lambda (p) `(li ,(paint-blog-post p)))
(load-where '((type . blog-post))
#:sort-by 'created-at #:compare >)))))
(define-page (create-post-page req)
(form '((title "Title" text) (body "Body" long-text))
#:init '((type . blog-post))
#:on-done (lambda (post) (redirect-to-page view-post-page (rec-id post)))))
(define-page (view-post-page req post-id)
(paint-blog-post (load-rec post-id #:ensure '((type . blog-post)))))
(define (paint-blog-post post)
`(div (h2 ,(rec-prop post 'title))
(p ,(rec-prop post 'body))))
]
@subsubsection{Launch Blogerton}
You're ready for launch. Start the server with
@verbatim{% ./script/server}
and you should have a basic blogging app, with persistent data, in 19 lines of code.
@section{Reference}
@subsection{Forms}
Most web applications make some use of web forms. The @scheme[form] function lets easily you get and process input from your users.
@defproc[(form (field-specs (listof field-spec?))) xexpr?]
@defthing[field-spec? (list symbol? string? field-type?)]
@defthing[field-type? (or/c 'text 'long-text 'number 'password 'image 'checkbox
'radio 'drop-down)]
You create a form by listing, in order, "field specifications". For example, you might want a title field, followed by a description text box, followed by a photo upload field. Note that the many keyword arguments available to the @scheme[form] function aren't documented yet.
Each field spec is of the form @scheme[(field-name label field-type)]. For example, you create a title field, you might use the spec @scheme[(title "Enter a title" text)]. The entire example metioned above might look like this:
@schemeblock[
(form '((title "Title" text)
(description "Description" long-text)
(photo "Your photo" image)))
]
@subsubsection{Image uploads}
By default, uploaded images are stored in the @tt{uploaded-files} directory in your project directory. You can customize this with the @scheme[*PATH_TO_UPLOADED_FILES*] setting. When images are saved, their original filenames are used with a 5-character code pre-pended to make filenames unique.
@subsection{Sessions}
A session is an object that allows you to easily store state about individual visitors to your web app. Sessions are stored on the server as a record with a virtually impossible-to-guess id. A cookie is left in the user's web browser, which contains a pointer to a particular session id. These cookies expire one month after creation and, currently, this can't be changed.
@subsubsection{Creating sessions}
@defform[(define-session-page
(page-name request-iden session-iden page-args ...)
body ...)]
This is an alternate to @scheme[define-page], most commonly used in @scheme[main.scm]. The only difference is that after the request identifier, you must provide a session identifier. For example, to keep a counter (unique to each user), you could write:
@schemeblock[
(define-session-page (foo-page req sesh)
(let ((c (session-get-val sesh 'counter 0)))
(session-put-val! sesh 'counter (+ 1 c))
(number->string c)))
]
When you define a session page, the session is automatically fetched for you (and created if necessary), and bound to the session identifier you provided.
@subsubsection{Accessing sessions}
@defproc[(session-get-val (session session) (key symbol) (missing-val any #f)) any]
@defproc[(session-put-val! (session session) (key symbol) (val any)) session]
@subsection{Users}
LeftParen provides built-in functionality for dealing with users, including registering users, logging users in and out, and storing persistent data about users. To get up-and-running quickly, you can use the high-level @scheme[welcome-message] function:
@defproc[(welcome-message (session session?) (#:on-success success-fn (or/c (-> user? xexpr?) #f) #f) (#:no-register no-register boolean? #f)) xexpr]{The function @scheme[welcome-message] produces a small area of text and links (commonly found in the top-right area of a web app). If the user is not currently logged in, login and register links are presented. If the user is logged in, a message welcoming them is displayed, along with a link to log out.}
@defproc[(current-user (session session?)) (or/c user? #f)]{The function @scheme[current-user] returns the current user record, or @scheme[#f] if no user is available in the current session.}
As an example, here is the complete page code for a web app that allows users to register, login and logout, and which prints a secret message if the user is logged in:
@schemeblock[
(define-session-page (index-page req sesh)
(** (welcome-message sesh)
(aif (current-user sesh)
(format "The secret, ~A, is 42." (rec-prop it 'username))
"No secret for you.")))
]
@subsection{Feeds}
You can create Atom or RSS feeds in your web app. A feed in LeftParen is just a page crafted in a paricular way. The core functions involved are @scheme[atom-feed] and @scheme[rss-feed]:
@defproc[(atom-feed (atom-feed-page page?)
(#:feed-title feed-title string?)
(#:feed-updated/epoch-seconds updated-seconds integer?)
(#:author-name author-name string?)
(#:feed-description feed-description (or/c #f string?) #f)
(#:feed-id feed-id string? THE_URL_OF_THE_GIVEN_ATOM_FEED_PAGE)
(#:related-content-link related-content-link string? THE_LINK_TO_YOUR_WEB_APP)
(#:items atom-items (list-of atom-item?) '()))
response/full?]
@defproc[(rss-feed (rss-feed-page page?)
(#:feed-title feed-title string?)
(#:feed-description feed-description string?)
(#:related-content-link related-content-link string? THE_LINK_TO_YOUR_WEB_APP)
(#:items rss-items (list-of rss-item?) '()))
response/full?]
The @scheme[#:items] argument in each of these functions is a list of items constructed with @scheme[atom-item] and @scheme[rss-item]:
@defproc[(atom-item (#:title title string?)
(#:url url string?)
(#:updated-epoch-seconds updated-seconds integer?)
(#:content content (or/c #f string?) #f))
atom-item?]
@defproc[(rss-item (#:title title string?)
(#:url url string?)
(#:content content (or/c #f string?) #f))
rss-item?]
Here's an example Atom feed page:
@schemeblock[
(define-page (article-feed-page req)
#:blank #t
(atom-feed article-feed-page
#:feed-title "LeftParen blog"
#:feed-description "On LeftParen..."
#:feed-updated/epoch-seconds (current-seconds)
#:author-name "LP staffers"
#:items (list
(atom-item #:title "Status update..."
#:url "http://blog.../50308696"
#:updated-epoch-seconds
(current-seconds)
#:content "I’m nearing a...")
(atom-item #:title "LeftParen 0.3..."
#:updated-epoch-seconds
(current-seconds)
#:url "http://blog.../51814971"
#:content "Tonight I..."))))
]
Note that while using @scheme[current-seconds] for timestamps does satisfy the interface, it's not really appropriate since these times are supposed to indicated freshness of the data. If basing your feed off of records, you might consider using @scheme[created-when].
@section{About/Acknowledgements}
LeftParen was initially written by @link["http://robhunter.org"]{Rob Hunter}. It makes use of @link["http://planet.plt-scheme.org/display.ss?package=dispatch.plt&owner=untyped"]{Dispatch} by @link["http://untyped.com/"]{Untyped}, @link["http://planet.plt-scheme.org/display.ss?package=with-bindings.plt&owner=jaymccarthy"]{with-bindings} by @link["http://jay-mccarthy.blogspot.com/"]{Jay McCarthy}, and of course, @link["http://www.plt-scheme.org/"]{PLT Scheme}.
LeftParen used to use @link["http://planet.plt-scheme.org/display.ss?package=web.plt&owner=soegaard"]{WEB} by @link["http://scheme.dk/blog/"]{Jens Axel Soegaard} and @link["http://planet.plt-scheme.org/display.ss?package=instaservlet.plt&owner=untyped"]{Instaservlet} (now deprecated) by @link["http://untyped.com/"]{Untyped}, which were both very helpful in getting the project started.
@subsection{Code Contributors}
@itemize[
@item{Bill Hager}
@item{Joshua Herman}
]