diff --git a/info.rkt b/info.rkt index 3c7f8fd..3d1dacb 100644 --- a/info.rkt +++ b/info.rkt @@ -22,5 +22,6 @@ (define compile-omit-paths '("tests" "examples" "experiments" - "simulator")) + "simulator" + "tmp")) (define can-be-loaded-with 'all) diff --git a/scribblings/manual.scrbl b/scribblings/manual.scrbl index 1c2286c..7e6c2c1 100644 --- a/scribblings/manual.scrbl +++ b/scribblings/manual.scrbl @@ -3,6 +3,7 @@ planet/version planet/resolver scribble/eval + scribble/bnf racket/sandbox racket/port racket/list @@ -98,7 +99,6 @@ The GitHub source repository to Whalesong can be found at @url{https://github.com/dyoo/whalesong}. - Prerequisites: at least @link["http://racket-lang.org/"]{Racket 5.1.1}. If you wish to use the JavaScript compression option, you will need @link["http://www.java.com"]{Java 1.6} SDK. @@ -112,6 +112,79 @@ Prerequisites: at least @link["http://racket-lang.org/"]{Racket +@subsection{Examples} +Here are a collection of programs that use the @emph{web-world} library described +later in this document: +@itemize[ +@item{@link["http://hashcollision.org/whalesong/examples/attr-animation/attr-animation.html"]{attr-animation.html} [@link["http://hashcollision.org/whalesong/examples/attr-animation/attr-animation.rkt"]{src}] Uses @racket[update-view-attr] and @racket[on-tick] to perform a simple color animation.} + + +@item{@link["http://hashcollision.org/whalesong/examples/boid/boid.html"]{boid.html} [@link["http://hashcollision.org/whalesong/examples/boid/boid.rkt"]{src}] Uses @racket[update-view-css] and @racket[on-tick] to perform an animation of a flock of @link["http://en.wikipedia.org/wiki/Boids"]{boids}.} + + +@item{@link["http://hashcollision.org/whalesong/examples/dwarves/dwarves.html"]{dwarves.html} +[@link["http://hashcollision.org/whalesong/examples/dwarves/dwarves.rkt"]{src}] +Uses @racket[view-show] and @racket[view-hide] to manipulate a view. Click on a dwarf to make them hide. + } + +@item{@link["http://hashcollision.org/whalesong/examples/dwarves-with-remove/dwarves-with-remove.html"]{dwarves-with-remove.html} +[@link["http://hashcollision.org/whalesong/examples/dwarves-with-remove/dwarves-with-remove.rkt"]{src}] +Uses @racket[view-focus?] and @racket[view-remove] to see if a dwarf should be removed from the view. +} + +@item{@link["http://hashcollision.org/whalesong/examples/field/field.html"]{field.html} +[@link["http://hashcollision.org/whalesong/examples/field/field.rkt"]{src}] +Uses @racket[view-bind] to read a text field, and @racket[update-view-text] to change +the text content of an element. +} + +@item{@link["http://hashcollision.org/whalesong/examples/phases/phases.html"]{phases.html} +[@link["http://hashcollision.org/whalesong/examples/phases/phases.rkt"]{src}] +Switches out one view entirely in place of another. Different views can correspond to phases in a program. +} + + +@item{@link["http://hashcollision.org/whalesong/examples/tick-tock/tick-tock.html"]{tick-tock.html} +[@link["http://hashcollision.org/whalesong/examples/tick-tock/tick-tock.rkt"]{src}] +Uses @racket[on-tick] to show a timer counting up. +} + +@item{@link["http://hashcollision.org/whalesong/examples/redirected/redirected.html"]{redirected.html} +[@link["http://hashcollision.org/whalesong/examples/redirected/redirected.rkt"]{src}] +Uses @racket[on-tick] to show a timer counting up, and also uses @racket[open-output-element] to +pipe side-effecting @racket[printf]s to a hidden @tt{div}. +} + +@item{@link["http://hashcollision.org/whalesong/examples/todo/todo.html"]{todo.html} +[@link["http://hashcollision.org/whalesong/examples/todo/todo.rkt"]{src}] +A simple TODO list manager. +} + +@item{@link["http://hashcollision.org/whalesong/examples/where-am-i/where-am-i.html"]{where-am-i.html} +[@link["http://hashcollision.org/whalesong/examples/where-am-i/where-am-i.rkt"]{src}] +Uses @racket[on-location-change] and @racket[on-mock-location-change] to demonstrate location services. +} +] + + +I also gave a +@link["http://hashcollision.org/whalesong/racketcon"]{presentation} +of Whalesong at RacketCon 2011, including examples like: +@itemize[ + +@item{@link["http://hashcollision.org/whalesong/racketcon/rain.html"]{rain.html} +[@link["http://hashcollision.org/whalesong/racketcon/rain.rkt"]{src}] +Uses the image libraries to show droplets of water falling down.} + +@item{@link["http://hashcollision.org/whalesong/racketcon/pacman.html"]{pacman.html} +[@link["http://hashcollision.org/whalesong/racketcon/pacman.rkt"]{src}] +Pacman.} +] + + + + + @;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @section{Getting started} @@ -407,9 +480,11 @@ pass the name of the file to it: }| A @filepath{.html} and @filepath{.js} will be written to the current directory, as will any external resources that the program uses. -Almost all of the @tt{whalesong} commands support two command line options: -@itemize{ + +The @tt{whalesong} commands support these command line options: + +@itemize[ @item{@verbatim{--compress-javascript} Use Google Closure's JavaScript compiler to significantly compress the JavaScript. Using this @@ -418,7 +493,13 @@ currently requires a Java 1.6 JDK.} @item{@verbatim{--verbose} Write verbose debugging information to standard error.} @item{@verbatim{--dest-dir} Write files to a separate directory, rather than the current directory.} -} + +@item{@verbatim{--split-modules} Write each dependent module as a +separate file, rather than in one large @filepath{.js}. This may be +necessary if your browser environment prohibits large @filepath{.js} +files. The files will be numbered starting from @racket[1].} + +] @@ -452,17 +533,6 @@ All main modules will be executed when the JavaScript function @tt{plt.runtime.invokeMains()} is called. -@subsection{@tt{write-javascript-files}} -[NOT DONE YET] -(needs to write a MANIFEST file?) -(this almost seems like we need some concept of a JAR... ) - - -@subsection{@tt{write-resources}} -[NOT DONE YET] - - - @subsection{@tt{get-runtime}} Prints out the core runtime library that the files generated by @@ -504,6 +574,22 @@ which defines a variable named @racket[humpback.png] whose @tech{resource} is @filepath{humpback.png}. +If the resource given has an extension one of the following: +@itemize[ +@item{@filepath{.png}} +@item{@filepath{.gif}} +@item{@filepath{.jpg}} +@item{@filepath{.jpeg}}] +then it can be treated as an image for which @racket[image?] will be true. + +If the resource has the extension @filepath{.html}, then it will be +run through an HTML purifying process to make sure the HTML is +well-formed. + + + +@defproc[(resource? [x any]) boolean]{ +Returns @racket[#t] if @racket[x] is a @tech{resource}.} @defproc[(resource->url [a-resource resource?]) string?]{ Given a @tech{resource}, gets a URL. @@ -621,59 +707,6 @@ by @racket[on-tick], though because we're on the web, we can bind to many other kinds of web events (by using @racket[view-bind]).} ] -@subsection{More web-world examples} -Here are a collection of web-world demos: -@itemize[ -@item{@link["http://hashcollision.org/whalesong/examples/attr-animation/attr-animation.html"]{attr-animation.html} [@link["http://hashcollision.org/whalesong/examples/attr-animation/attr-animation.rkt"]{src}] Uses @racket[update-view-attr] and @racket[on-tick] to perform a simple color animation.} - - -@item{@link["http://hashcollision.org/whalesong/examples/boid/boid.html"]{boid.html} [@link["http://hashcollision.org/whalesong/examples/boid/boid.rkt"]{src}] Uses @racket[update-view-css] and @racket[on-tick] to perform an animation of a flock of @link["http://en.wikipedia.org/wiki/Boids"]{boids}.} - - -@item{@link["http://hashcollision.org/whalesong/examples/dwarves/dwarves.html"]{dwarves.html} -[@link["http://hashcollision.org/whalesong/examples/dwarves/dwarves.rkt"]{src}] -Uses @racket[view-show] and @racket[view-hide] to manipulate a view. Click on a dwarf to make them hide. - } - -@item{@link["http://hashcollision.org/whalesong/examples/dwarves-with-remove/dwarves-with-remove.html"]{dwarves-with-remove.html} -[@link["http://hashcollision.org/whalesong/examples/dwarves-with-remove/dwarves-with-remove.rkt"]{src}] -Uses @racket[view-focus?] and @racket[view-remove] to see if a dwarf should be removed from the view. -} - -@item{@link["http://hashcollision.org/whalesong/examples/field/field.html"]{field.html} -[@link["http://hashcollision.org/whalesong/examples/field/field.rkt"]{src}] -Uses @racket[view-bind] to read a text field, and @racket[update-view-text] to change -the text content of an element. -} - -@item{@link["http://hashcollision.org/whalesong/examples/phases/phases.html"]{phases.html} -[@link["http://hashcollision.org/whalesong/examples/phases/phases.rkt"]{src}] -Switches out one view entirely in place of another. Different views can correspond to phases in a program. -} - - -@item{@link["http://hashcollision.org/whalesong/examples/tick-tock/tick-tock.html"]{tick-tock.html} -[@link["http://hashcollision.org/whalesong/examples/tick-tock/tick-tock.rkt"]{src}] -Uses @racket[on-tick] to show a timer counting up. -} - -@item{@link["http://hashcollision.org/whalesong/examples/redirected/redirected.html"]{redirected.html} -[@link["http://hashcollision.org/whalesong/examples/redirected/redirected.rkt"]{src}] -Uses @racket[on-tick] to show a timer counting up, and also uses @racket[open-output-element] to -pipe side-effecting @racket[printf]s to a hidden @tt{div}. -} - -@item{@link["http://hashcollision.org/whalesong/examples/todo/todo.html"]{todo.html} -[@link["http://hashcollision.org/whalesong/examples/todo/todo.rkt"]{src}] -A simple TODO list manager. -} - -@item{@link["http://hashcollision.org/whalesong/examples/where-am-i/where-am-i.html"]{where-am-i.html} -[@link["http://hashcollision.org/whalesong/examples/where-am-i/where-am-i.rkt"]{src}] -Uses @racket[on-location-change] and @racket[on-mock-location-change] to demonstrate location services. -} -] - @subsection{@racket[big-bang] and its options} @@ -842,9 +875,29 @@ Move the focus to the parent.} @defproc[(view-down? [v view]) boolean]{ See if the view can be moved to the first child. } + @defproc[(view-down [v view]) view]{ Move the view to the first child.} + + +@defproc[(view-forward? [v view]) boolean]{ +See if the view can be moved forward.} + +@defproc[(view-forward [v view]) view]{ +Move the view forward, assuming a pre-order traversal. +} + +@defproc[(view-backward? [v view]) boolean]{ +See if the view can be moved backward.} + +@defproc[(view-backward [v view]) view]{ +Move the view backward, assuming a pre-order traversal. +} + + + + @defproc[(view-text [v view]) string]{ Get the textual content at the focus. } @@ -893,8 +946,32 @@ Get the form value of the node at the focus.} @defproc[(update-view-form-value [v view] [value String]) view]{ Update the form value of the node at the focus.} -@defproc[(view-append-child [d dom]) view]{ -Add the dom node @racket[d] as the last child of the focused node.} + + +Dom nodes can be created by using @racket[xexp->dom], which converts a +@tech{xexp} to a node, and attached to the view by using +@racket[view-append-child], @racket[view-insert-left], and +@racket[view-insert-right]. + + +@defproc[(view-append-child [v view] [d dom]) view]{ +Add the dom node @racket[d] as the last child of the focused node. +Focus moves to the inserted node.} + + +@defproc[(view-insert-left [v view] [d dom]) view]{ +Add the dom node @racket[d] as the previous sibling of the focused node. +Focus moves to the inserted node.} + +@defproc[(view-insert-right [v view] [d dom]) view]{ +Add the dom node @racket[d] as the next sibling of the focused node. +Focus moves to the inserted node.} + + + + + + @defproc[(view-remove [v view]) view]{ Remove the dom node at the focus from the view @racket[v]. Focus tries to move @@ -925,6 +1002,100 @@ Get an list of the event's keys. +@subsection{Dynamic DOM generation with xexps} +@declare-exporting/this-package[web-world] +We often need to dynamically inject new dom nodes into an existing +view. As an example where the UI is entirely in code: +@codeblock|{ +#lang planet dyoo/whalesong +(require (planet dyoo/whalesong/web-world)) + +;; tick: world view -> world +(define (tick world view) + (add1 world)) + +;; draw: world view -> view +(define (draw world view) + (view-append-child view + (xexp->dom `(p "hello, can you see this? " + ,(number->string world))))) + +(big-bang 0 (initial-view + (xexp->dom '(html (head) (body)))) + (on-tick tick 1) + (to-draw draw)) +}| + +Normally, we'll want to do as much of the statics as possible with +@filepath{.html} resources, but when nothing else will do, we can +generate DOM nodes programmatically. + + + +We can create new DOMs from an @tech{xexp}, which is a s-expression +representation for a DOM node. Here are examples of expressions that +evaluate to xexps: + +@racketblock["hello world"] + +@racketblock['(p "hello, this" "is an item")] + +@racketblock[ +(local [(define name "josh")] + `(p "hello" (i ,name)))] + +@racketblock[ + '(div (\@ (id "my-div-0")) + (span "This is a span in a div"))] + +@racketblock[ + `(div (\@ ,(fresh-id)) + (span "This is another span in a div whose id is dynamically generated"))] + + +More formally, a @deftech{xexp} is: +@(let ([open @litchar{(}] + [close @litchar{)}] + [at @litchar[(symbol->string '\@)]]) +@BNF[(list @nonterm{xexp} + @nonterm{string} + @nonterm{symbol} + @BNF-seq[open @nonterm{id} @kleenestar[@nonterm{xexp}] close] + @BNF-seq[open @nonterm{id} open at @kleenestar[@nonterm{key-value}] close @kleenestar[@nonterm{xexp}] close]) + (list @nonterm{key-value} + @BNF-seq[open @nonterm{symbol} @nonterm{string} close]) +]) + + +To check to see if something is a xexp, use @racket[xexp?]: +@defproc[(xexp? [x any]) boolean]{ +Return true if @racket[x] is a xexp. +} + + +@defproc[(xexp->dom [an-xexp xexp]) dom]{ +Return a dom from the xexp. +} + + +When creating xexps, we may need to create unique ids for the nodes. +The web-world library provides a @racket[fresh-id] form to create these. +@defproc[(fresh-id) string]{ +Return a string that can be used as a DOM node id. +} + + +We may also want to take a view and turn it back into an @tech{xexp}. +@defproc[(view->xexp [a-view view]) xexp]{ +Coerses a view into a @tech{xexp}. +} + + + + + + + @subsection{Tips and tricks: Hiding standard output or directing it to an element} @declare-exporting/this-package[web-world] @@ -974,6 +1145,9 @@ even if the id does not currently exist on the page. + + + @section{The JavaScript Foreign Function Interface} @defmodule/this-package[js]{ @@ -1493,6 +1667,8 @@ language. @defform[(eq? ...)]{} @defform[(equal? ...)]{} @defform[(void ...)]{} +@defform[(quote ...)]{} +@defform[(quasiquote ...)]{} diff --git a/web-world/impl.rkt b/web-world/impl.rkt index f0f8b27..5b1d8e1 100644 --- a/web-world/impl.rkt +++ b/web-world/impl.rkt @@ -74,9 +74,9 @@ update-view-form-value view-append-child - view-remove view-insert-right view-insert-left + view-remove xexp? xexp->dom