Assignment Task
Your task is to write a simple blogging application. Unlike the earlier blog assignments, this time you will build a rich, client-side application using Flapjax.
Your blog must meet the following requirements:
Implement your blog as a single-page application (that is, a single
.html
file). Loading an entire page on each click would not provide a good user experience.A blog post consists of a title, a post-time, and the body text. All posts should be stored on a server so that killing the browser window does not lose content.
Your application will display a list of posts and a form to enter new posts.
The list of posts should be no more than 20 seconds out-of-date, so that viewing the blog from different tabs/windows/machines/... gets essentially the same content.
To discourage prolixity, you should display a word-count (defined in some reasonable way) that is updated as the user types.
The user should have the ability to save drafts on the server by clicking a
Save Draft
button. Drafts are not published (and thus not visible to other readers of the blog) until the author clicks on aPublish
button.To avoid accidental loss of content, you should save drafts every fifteen seconds even if the user has not clicked
Save Draft
. In a production version we would save drafts to users' accounts; for this application, we will associate drafts with IP addresses. The storage service has an API for associating data with an IP address.The blog should display when the last draft was saved and include a
Restore Draft
button to load the last saved draft.The blog and the saved drafts should not contain any embedded JavaScript or HTML.
Setting Up the Web Server
We provide a PLT Web Server application to provide server-side storage. Download and extract the application. To run it, from the root directory of the extracted files, execute:
mzscheme -tmv run.ssVisit
http://localhost:8080/
to ensure the web server is
running.
The package includes:
A web-server that provides storage for Flapjax applications:
run.ss
andflapjax-dispatcher.ss
,two Flapjax demos:
web-root/htdocs/index.html
andweb-root/htdocs/save.html
, andthe Flapjax library:
web-root/htdocs/*.js
.
Writing Flapjax Applications
Other than server-side storage (detailed below), your blog is a client-side
application. Therefore, your code should reside in web-root/htdocs
as "static" content (from the server's perspective).
You must write your blog with Flapjax as a library (i.e. do not use
the Flapjax compiler). You may use the demos in web-root/htdocs
as
templates for your application.
More examples are available online. There are multiple versions of the source code for these examples. Since you are not using the Flapjax compiler, you should see the code that is "unobtrusive JavaScript with the Flapjax library." The full Flapjax API is documented online.
Server Storage
You must store your blog on the server with the persistence service that the web server provides. Note that the service does not commit data to disk, so information is lost if you restart the server.
The best way to learn how to use the storage service is by studying the demo
web-root/htdocs/save.html
found in the support code. You should
read the following description of the storage service along with the demo.
getWebServiceObject_e
The genericgetWebServiceObject_e
allows you to treat a web service
as an event stream transformer. That is, the client sends the server a stream of
requests, the server processes them and returns a stream of responses:
getWebServiceObject_e :: requestE -> responseE
A request has the form
{ url: url-string, fields: { field-name: field-value, ... }, request: 'post', serviceType: 'jsonLiteral', asynchronous: true | false }The
url
specifies the address of the service you are accessing.
fields
is an object that specifies the "arguments" to
send the service. The field-value
s must be flat Javascript values.
(If you set a field-value
to an object, it will be marshalled as
the string "[Object object]"
.) You may specify 'get'
instead of 'post'
as the request type. However, HTTP GET requests are prone to caching, which is usually unacceptable for reactive applications.
responseE
is an event stream of responses from the server. A
response is an arbitrary Javascript value produced by the service.
PLT Web Server Persistence Service
The web server provides a simple persistence service for storing values and lists of values. Values on the server are referenced by a user-defined identifier. The shape of requests to store / retrieve values and lists is detailed below.
To store a flat value, send the request
{ url: 'fxobj/set/' + object-name, fields: object-value, request: 'post', serviceType: 'jsonLiteral', asynchronous: true | false }
To retrieve a stored value, send the request
{ url: 'fxobj/get/' + object-name, fields: default-value, request: 'post', serviceType: 'jsonLiteral', asynchronous: true | false }
Ifobject-name
does not exist,default-value
is returned.To cons an element onto a stored list, send the request
{ url: 'fxlist/cons/' + list-name, fields: object-value, request: 'post', serviceType: 'jsonLiteral', asynchronous: true | false }
Iflist-name
does not exist, a singleton list is created on the server.To retrieve a stored list, send the request
{ url: 'fxlist/get/' + object-name, fields: { }, request: 'post', serviceType: 'jsonLiteral', asynchronous: true | false }
Iflist-name
does not exist, the empty list ([ ]
) is returned.
Note that you are free to alter the storage interface or to augment it to
store different data structures. If you are interested,
flapjax-dispatcher.ss
contains the implementation of server
storage and there are 10 lines in run.ss
that link services' urls
with their implementations.
Associating Stored Data with IP Addresses
The web service also allows you to associate values with IP addresses. The
interface for doing so is essentially the same as the interface for storing
values and lists. The only difference is that we prefix 'ip/'
to
the URLs of the requests above to store / retrieve values from address-bound
storage.
To store a flat value, send the request
{ url: 'ip/fxobj/set/' + object-name, fields: object-value, request: 'post', serviceType: 'jsonLiteral', asynchronous: true | false }
To retrieve a stored value, send the request
{ url: 'ip/fxobj/get/' + object-name, fields: default-value, request: 'post', serviceType: 'jsonLiteral', asynchronous: true | false }
Ifobject-name
does not exist,default-value
is returned.To cons an element onto a stored list, send the request
{ url: 'ip/fxlist/cons/' + list-name, fields: object-value, request: 'post', serviceType: 'jsonLiteral', asynchronous: true | false }
Iflist-name
does not exist, a singleton list is created on the server.To retrieve a stored list, send the request
{ url: 'ip/fxlist/get/' + object-name, fields: { }, request: 'post', serviceType: 'jsonLiteral', asynchronous: true | false }
Iflist-name
does not exist, the empty list ([ ]
) is returned.
Handin
A single member of your team should handin the assignment. From the directory containing the files for the assignment you wish to hand in, execute
/course/cs173/bin/cs173handin flapjax