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