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:
Hello world
Hello world
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:
My simple program
The current counter is: fill-me-in
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:
My simple program
Click me!
The current counter is: fill-me-in
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.
My simple program
Hello fill-me-in!
#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.
Dwarves
#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