519 lines
12 KiB
Plaintext
519 lines
12 KiB
Plaintext
Design doc for web-world.
|
|
|
|
|
|
|
|
We want to take advantage of web development interfaces.
|
|
|
|
* We should be able to design and prototype web interfaces without
|
|
touching a programming environment.
|
|
|
|
* It should be easy to inject behavior separately from the static
|
|
representation of a view.
|
|
|
|
* This demands that the primary way to get a view is to use HTML
|
|
files directly.
|
|
|
|
|
|
Furthermore, we want to fix a particularly glaring issue with the
|
|
previous attempt of jsworld:
|
|
|
|
* The DOM nodes in jsworld were treated as values with implicit
|
|
state, making it very difficult to write UI's that asked what
|
|
the value was at a particular node.
|
|
|
|
* The DOM tree represents external state!
|
|
|
|
* Therefore, each world -> world function should take in, not just
|
|
the internal state of the world, but the external state of the
|
|
DOM tree.
|
|
|
|
|
|
|
|
We want to take the design ideas of JQuery.
|
|
|
|
* The view is a cursor into a DOM node.
|
|
|
|
* Operations refocus the cursor onto particular elements of the
|
|
dom.
|
|
|
|
* Methods on the view apply functional update on those nodes.
|
|
|
|
|
|
|
|
|
|
----------------------------------------------------------------------
|
|
|
|
Example 0 "hello world"
|
|
|
|
If we have an index.html as:
|
|
|
|
|
|
<html><head><title>Hello world</title></head>
|
|
<body><h1>Hello world</h1></body>
|
|
</html>
|
|
|
|
|
|
then it should be trivial to make a program that just shows
|
|
that page:
|
|
|
|
|
|
#lang planet dyoo/whalesong
|
|
(require (planet dyoo/whalesong/web-world))
|
|
|
|
(define-resource index.html)
|
|
|
|
(big-bang "don't care"
|
|
(initial-view index.html))
|
|
|
|
|
|
No reactivity means no changes to the view.
|
|
|
|
Comments: the initial-view can be a static resource.
|
|
|
|
|
|
|
|
In these examples, the ids I'm using for the resource and the file
|
|
name are matching. I will allow an abbreviated use of define-resource
|
|
to eliminate this kind of duplication. I'm planning to allow:
|
|
|
|
(define-resource index.html)
|
|
|
|
to macro-expand out to the more explicit:
|
|
|
|
(define-resource index.html "index.html")
|
|
|
|
which we talked about earlier. I will use the abbreviated forms in
|
|
the remainder of the examples.
|
|
|
|
|
|
|
|
----------------------------------------------------------------------
|
|
|
|
Example 1 "tick tock"
|
|
|
|
|
|
A student should be able to prototype a basic user interface in .html,
|
|
such as:
|
|
|
|
<html>
|
|
<head><title>My simple program</title></head>
|
|
<body>
|
|
<p>The current counter is: <span id="counter">fill-me-in</span></p>
|
|
</body>
|
|
</html>
|
|
|
|
|
|
|
|
and then, in the programming language, add behavior:
|
|
|
|
#lang planet dyoo/whalesong
|
|
(require (planet dyoo/whalesong/web-world))
|
|
|
|
(define-resource index.html)
|
|
|
|
;; draw: world view -> view
|
|
(define (draw w v)
|
|
(view-text (view-focus v "#counter")
|
|
w))
|
|
|
|
;; tick: world view -> world
|
|
(define (tick w v)
|
|
(add1 w)
|
|
|
|
|
|
(big-bang 0
|
|
(initial-view index.html)
|
|
(to-draw draw)
|
|
(on-tick tick))
|
|
|
|
|
|
to get a simple clock ticking application.
|
|
|
|
|
|
|
|
Comments:
|
|
|
|
view-text, when given a view and a string, is a functional update that
|
|
replaces the text at the focus. We're trying deliberately to match
|
|
JQuery. These should be functional updates, though.
|
|
|
|
|
|
In contrast to plain vanilla world programs, the tick function of a
|
|
web-world consumes both the world and the view. The draw function,
|
|
too, takes both the world and the currently-displayed view. Event
|
|
handlers, like on-tick, should be allowed to look at the state of the
|
|
view, because they may want to do things like look up an element's
|
|
value. The draw function does not reconstruct the entire DOM tree:
|
|
rather, it is responsible to producing functional updates of the
|
|
currently displayed view.
|
|
|
|
|
|
|
|
----------------------------------------------------------------------
|
|
|
|
Example 2 "the ticker"
|
|
|
|
|
|
|
|
We should be able to attach event handlers in the expected way to
|
|
elements of the DOM. For example, let's count the number of times a
|
|
user clicks on a particular DIV.
|
|
|
|
Here, we need to adjust the view and attach a click event.
|
|
|
|
If index.html contains:
|
|
|
|
<html>
|
|
<head>
|
|
<title>My simple program</title>
|
|
<link rel="stylesheet" href="style.css">
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<div id="my-button">Click me!</div>
|
|
|
|
<p>The current counter is: <span id="counter">fill-me-in</span></p>
|
|
</body>
|
|
</html>
|
|
|
|
|
|
with some appropriate CSS to make the DIV look good, then the program
|
|
will be:
|
|
|
|
#lang planet dyoo/whalesong
|
|
(require (planet dyoo/whalesong/web-world))
|
|
|
|
(define-resource index.html)
|
|
|
|
;; Declare style.css as a resource so it gets bundled.
|
|
(define-resource style.css)
|
|
|
|
|
|
;; draw: world view -> view
|
|
(define (draw w v)
|
|
(view-text (view-focus v "#counter")
|
|
w))
|
|
|
|
;; world view -> world
|
|
(define (on-click w v)
|
|
(add1 w))
|
|
|
|
|
|
(define my-initial-view
|
|
(view-bind (view-focus (resource->view index.html)
|
|
"#my-button")
|
|
"click"
|
|
on-click))
|
|
|
|
(big-bang 0
|
|
(initial-view my-initial-view)
|
|
(to-draw draw))
|
|
|
|
|
|
----------------------------------------------------------------------
|
|
|
|
Example 3 "field"
|
|
|
|
|
|
We want to make it easy to query from the view. That's why each
|
|
handler takes, not only the world, but the current view.
|
|
|
|
|
|
<html>
|
|
<head>
|
|
<title>My simple program</title>
|
|
<link rel="stylesheet" href="style.css">
|
|
</head>
|
|
|
|
<body>
|
|
<input type="text" id="text-field"/>
|
|
<input type="button" id="button"/>
|
|
|
|
<p>Hello <span id="template">fill-me-in</span>!</p>
|
|
</body>
|
|
</html>
|
|
|
|
|
|
|
|
#lang planet dyoo/whalesong
|
|
(require (planet dyoo/whalesong/web-world))
|
|
|
|
(define-resource index.html)
|
|
(define-resource style.css)
|
|
|
|
;; The world is a string which represents the name of the user.
|
|
|
|
|
|
;; on-click: world view -> world
|
|
;; When the user clicks on the button, grab at the text of the
|
|
;; text-field.
|
|
(define (on-click w v)
|
|
(view-text (view-focus v "#text-field")))
|
|
|
|
|
|
;; on-draw: world view -> view
|
|
;; Take the view, and replace the template with the world value.
|
|
(define (on-draw w v)
|
|
(view-text (view-focus v "#template")
|
|
w))
|
|
|
|
|
|
(define my-view (view-bind (view-focus (resource->view index.html)
|
|
"#button")
|
|
"click"
|
|
on-click))
|
|
|
|
(big-bang "Jane Doe"
|
|
(initial-view my-view)
|
|
(to-draw draw))
|
|
|
|
|
|
----------------------------------------------------------------------
|
|
|
|
Example 4 "dwarves!"
|
|
|
|
|
|
We need to be able to generate elements of views dynamically. We
|
|
should also be able to attach event handlers dynamically, too.
|
|
|
|
|
|
The following should show an empty list, and on every clock tick, a
|
|
new dwarf will show up in the list. If you click on a dwarf, it will
|
|
hide.
|
|
|
|
<html>
|
|
<head><title>Dwarves</title></head>
|
|
<body>
|
|
<ul></ul>
|
|
</body>
|
|
</html>
|
|
|
|
|
|
#lang planet dyoo/whalesong
|
|
(require (planet dyoo/whalesong/web-world))
|
|
(define-resource index.html)
|
|
|
|
;; make-item: string -> view
|
|
(define (make-item name)
|
|
(view-bind (sexp->view `(li ,name))
|
|
"click"
|
|
hide-on-click))
|
|
|
|
|
|
;; When a dwarf clicks, it hides!
|
|
(define (hide-on-click w v)
|
|
(view-hide v))
|
|
|
|
|
|
(define dwarf-names
|
|
'("Doc" "Grumpy" "Happy" "Sleepy" "Bashful" "Sneezy" "Dopey"))
|
|
|
|
|
|
;; Update the view so it shows the next dwarf on the scene,
|
|
;; until we're all done.
|
|
(define (draw w v)
|
|
(cond [(< w (length dwarf-names))
|
|
(view-append (view-focus v "ul")
|
|
(make-item (list-ref dwarf-names w)))]
|
|
[else
|
|
v]))
|
|
|
|
|
|
;; tick: world view -> world
|
|
(define (tick w v)
|
|
(add1 w))
|
|
|
|
|
|
(big-bang 0
|
|
(initial-view index.html)
|
|
(on-tick tick 1)
|
|
(to-draw draw))
|
|
|
|
|
|
|
|
----------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
Types
|
|
|
|
|
|
A view represents the DOM tree and its event handlers.
|
|
|
|
|
|
One way to create views is to take a resource and convert it to a view.
|
|
|
|
resource->view: resource -> view
|
|
|
|
Explicitly translate a resource into a view.
|
|
|
|
|
|
A more programmatic way to do this is with an s-expression representation.
|
|
|
|
sexp->view: s-expression -> view
|
|
|
|
Translate an s-expression into a view.
|
|
|
|
where the s-expression grammar is SXML.
|
|
|
|
|
|
|
|
|
|
|
|
A view is implicitly focused on a selection of its nodes. You can
|
|
always refocus the view to the top:
|
|
|
|
view-focus: view string -> view
|
|
|
|
Refocus the view, using JQuery selector syntax.
|
|
|
|
view-find: view string -> view
|
|
|
|
Refocus the view, using JQuery selector syntax. The search starts
|
|
from the context of the currently focused nodes.
|
|
|
|
view-top: view -> view
|
|
|
|
Refocus the view to the toplevel node.
|
|
|
|
view-up: view -> view
|
|
|
|
Move the focus of the view over to the parents of the currently focused nodes.
|
|
|
|
view-down: view -> view
|
|
|
|
Move the focus of the view over to the parents of the currently focused nodes.
|
|
|
|
view-next: view -> view
|
|
|
|
Move the focus of the view over to the next siblings of the
|
|
currently focused nodes.
|
|
|
|
view-prev: view -> view
|
|
|
|
Move the focus of the view over to the previous siblings of the
|
|
currently focused nodes.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The content of a view may be functionally queried or updated:
|
|
|
|
update-view-text: view string -> view
|
|
|
|
Replace the text at the focus with the given string.
|
|
|
|
view-text: view -> view
|
|
|
|
Grab at the text of the currently focused nodes.
|
|
|
|
|
|
update-view-css: view string string -> view
|
|
Update the CSS style value of the currently focused node.
|
|
|
|
view-css: view string -> view
|
|
Get at the CSS style value of the currently focused node.
|
|
|
|
|
|
|
|
|
|
update-view-attr: view string string -> view
|
|
Update the attribute of the currently focused node.
|
|
|
|
view-attr: view string -> view
|
|
Get at the attribute value of the currently focused node.
|
|
|
|
|
|
|
|
|
|
view-replace: view view -> view
|
|
view-replace: view (listof view) -> view
|
|
|
|
Replace the focused elements of the first view with the focused
|
|
elements of the second view.
|
|
|
|
|
|
view-delete: view -> view
|
|
|
|
Remove the focused elements of the view. The focus becomes empty.
|
|
|
|
|
|
view-append: view view -> view
|
|
view-append: view (listof view) -> view
|
|
|
|
Append the focused elements of the second view after the
|
|
focused elements of the first view.
|
|
|
|
|
|
view-count: view -> number
|
|
|
|
Count how many nodes are currently focused.
|
|
|
|
|
|
|
|
view-clone: view -> view
|
|
|
|
Do a deep clone of the currently focused elements. Those fresh
|
|
elements will focused.
|
|
|
|
|
|
view-hide: view -> view
|
|
|
|
Hide the selected focus.
|
|
|
|
view-show: view -> view
|
|
|
|
Show the selected focus.
|
|
|
|
|
|
|
|
We need to be able to bind events to elements of the view.
|
|
|
|
view-bind: view string (world event -> world) -> view
|
|
view-bind: view string (world -> world) -> view
|
|
|
|
Given the view, the name of the event type, and its world
|
|
handler, create a new view that binds that event to the focused
|
|
nodes.
|
|
|
|
The event will trigger the world-updating function, with the
|
|
view focused on the originating node.
|
|
|
|
If the bound function doesn't care about event-specific
|
|
information, allow it to ignore the value.
|
|
|
|
|
|
|
|
|
|
Configuration of a web big-bang
|
|
|
|
|
|
initial-view: resource | view -> handler
|
|
|
|
Given a resource, assume it's a resource into a web page, and
|
|
translate it directly into a view.
|
|
|
|
|
|
into-dom: dom-node -> handler
|
|
|
|
Use the given dom node as the toplevel parent.
|
|
|
|
|
|
|
|
|
|
Reactive handlers for a big bang:
|
|
|
|
|
|
to-draw: (world view -> view) -> handler
|
|
|
|
we need to be able to replace one view with another.
|
|
|
|
|
|
on-tick: (world -> world) -> handler
|
|
|
|
(world -> world) number -> handler
|