Skip to content

Commit

Permalink
[doc] book: updated chat and wiki (back to sync)
Browse files Browse the repository at this point in the history
  • Loading branch information
François-Régis Sinot committed Sep 2, 2011
1 parent fc55a97 commit 343e1e0
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 81 deletions.
95 changes: 62 additions & 33 deletions doc/book/hello_chat/hello_chat.adoc
Expand Up @@ -44,6 +44,21 @@ In this listing, we define the communication infrastructure for the chatroom,
the user interface, and finally, the main application. In the rest of the
chapter, we will walk you through all the concepts and constructions introduced.

A bit of style
~~~~~~~~~~~~~~

Before exposing the real machinery, let's care from the start how the
apllication should look like, with this single import line:

[source,opa]
------------------------
import stdlib.themes.bootstrap
------------------------

This automatically brings http://twitter.github.com/bootstrap/[Bootstrap CSS
from Twitter] to your application, so you can use the predefined classes that
will just look nice.

Setting up communications
~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down Expand Up @@ -138,18 +153,20 @@ For starters, consider a possible skeleton for the user interface:
.Skeleton of the user interface (incomplete)
[source,opa]
------------------------
<div id=#header><div id=#logo></div></div>
<div id=#conversation></div>
<input id=#entry/>
<div class="button">Send!</div>
<div class="topbar"><div class="fill"><div class="container"><div id=#logo /></div></div></div>
<div id=#conversation class="container"></div>
<div id=#footer><div class="container">
<input id=#entry class="xlarge"/>
<div class="btn primary" >Post</div>
</div></div>
------------------------

If you are familiar with HTML, you will recognize easily that this skeleton defines
a few boxes (or +<div>+), called respectively +header+, +logo+ and +conversation+ and
one without a name but with a style class +button+,
as well as a text input zone (or +<input>+) called +entry+. We will use these names to add
interactions and style. If you are not familiar with HTML, it might be a good idea to
grab https://developer.mozilla.org/En/HTML[a good HTML reference] and check up the tags as you see them.
If you are familiar with HTML, you will recognize easily that this skeleton
defines a few boxes (or +<div>+), with some names (or +id+) and some classes, as
well as a text input zone (or +<input>+) called +entry+. We will use these names
to add interactions and style. If you are not familiar with HTML, it might be a
good idea to grab https://developer.mozilla.org/En/HTML[a good HTML reference]
and check up the tags as you see them.

Actually, for convenience, and because it fits with the rest of the library, we will
put this user interface inside a function, as follows:
Expand All @@ -159,10 +176,12 @@ put this user interface inside a function, as follows:
------------------------
start() =
(
<div id=#header><div id=#logo></div></div>
<div id=#conversation></div>
<input id=#entry/>
<div class="button">Send!</div>
<div class="topbar"><div class="fill"><div class="container"><div id=#logo /></div></div></div>
<div id=#conversation class="container"></div>
<div id=#footer><div class="container">
<input id=#entry class="xlarge"/>
<div class="btn primary" >Post</div>
</div></div>
)
------------------------

Expand Down Expand Up @@ -209,10 +228,12 @@ At this stage, we can already go a bit further and invent an author name, as fol
start() =
(
author = Random.string(8)
<div id=#header><div id=#logo></div></div>
<div id=#conversation></div>
<input id=#entry/>
<div class="button">Send!</div>
<div class="topbar"><div class="fill"><div class="container"><div id=#logo /></div></div></div>
<div id=#conversation class="container"></div>
<div id=#footer><div class="container">
<input id=#entry class="xlarge"/>
<div class="btn primary" >Post</div>
</div></div>
)
------------------------

Expand Down Expand Up @@ -270,17 +291,19 @@ denotes the manipulation of the contents of a web page once that page is display
in the browser.
=======================

Speaking of types, it is generally a good idea to know the type of functions. Function +broadcast+
has type +string \-> void+, meaning that it takes an argument with type +string+ and produces a
value with type +void+. We could have written just as well:
Speaking of types, it is generally a good idea to know the type of
functions. Function +broadcast+ has type +string \-> void+, meaning that it
takes an argument with type +string+ and produces a value with type
+void+. Also, writing +{author=author text=text}+ is a bit painful, so we added
a syntactic sugar for this. We could have written just as well:

.Broadcasting a message to the room (variant)
[source,opa]
------------------------
broadcast(author: string): void =
(
text = Dom.get_value(#entry)
message = {author=author text=text}
message = {~author ~text}
do Network.broadcast(message, room)
Dom.clear_value(#entry)
)
Expand All @@ -301,9 +324,11 @@ that we want to be called whenever the network propagates a message:
------------------------
user_update(x: message) =
(
line = <div class="line">
<div class="user">{x.author}:</div>
<div class="message">{x.text}</div>
line = <div class="row line">
<div class="span1 columns userpic" />
<div class="span2 columns user">{x.author}:</div>
<div class="span13 columns message">{x.text}
</div>
</div>
do Dom.transform([ #conversation +<- line ])
Dom.scroll_to_bottom(#conversation)
Expand Down Expand Up @@ -354,10 +379,12 @@ Let us connect +broadcast+ to our button and our input. This changes function +s
start() =
(
author = Random.string(8)
<div id=#header><div id=#logo></div></div>
<div id=#conversation></div>
<input id=#entry onnewline={_ -> broadcast(author)}/>
<div class="button" onclick={_ -> broadcast(author)}>Send!</div>
<div class="topbar"><div class="fill"><div class="container"><div id=#logo /></div></div></div>
<div id=#conversation class="container"></div>
<div id=#footer><div class="container">
<input id=#entry class="xlarge" onnewline={_ -> broadcast(author)}/>
<div class="btn primary" onclick={_ -> broadcast(author)}>Post</div>
</div></div>
)
------------------------

Expand Down Expand Up @@ -389,10 +416,12 @@ the user loads the page, as follows:
start() =
(
author = Random.string(8)
<div id=#header><div id=#logo></div></div>
<div id=#conversation onready={_ -> Network.add_callback(user_update, room)}></div>
<input id=#entry onnewline={_ -> broadcast(author)}/>
<div class="button" onclick={_ -> broadcast(author)}>Send!</div>
<div class="topbar"><div class="fill"><div class="container"><div id=#logo /></div></div></div>
<div id=#conversation class="container" onready={_ -> Network.add_callback(user_update, room)}></div>
<div id=#footer><div class="container">
<input id=#entry class="xlarge" onnewline={_ -> broadcast(author)}/>
<div class="btn primary" onclick={_ -> broadcast(author)}>Post</div>
</div></div>
)
------------------------

Expand Down
4 changes: 3 additions & 1 deletion doc/book/hello_chat/hello_chat.opa
Expand Up @@ -50,7 +50,9 @@ user_update(x: message) =
* @param author The name of the author. Will be included in the message broadcasted.
*/
broadcast(author) =
do Network.broadcast({~author text=Dom.get_value(#entry)}, room)
text = Dom.get_value(#entry)
message = {~author ~text}
do Network.broadcast(message, room)
Dom.clear_value(#entry)

/**
Expand Down
37 changes: 18 additions & 19 deletions doc/book/hello_wiki/hello_wiki.adoc
Expand Up @@ -6,14 +6,6 @@ In itself, developing a simple wiki is error-prone but otherwise not very diffic
developing a rich wiki that both scales up and does not suffer from vulnerabilities caused by
the content provided by users is quite harder. Once again, Opa makes it simple.



//
// About this chapter:
// Main author: David
// Paired author:?
//

In this chapter, we will see how to program a complete, albeit simple, wiki
application in Opa. Along the way, we will introduce the Opa database, the
client-server security policy, the mechanism used to incorporate user-defined
Expand Down Expand Up @@ -42,6 +34,8 @@ safe format, we define the user interface and finally, the main application. In
the rest of the chapter, we will walk you through all the concepts and
constructions introduced.

As for the chat, we use http://twitter.github.com/bootstrap/[Bootstrap CSS from
Twitter] with a single import line.

Setting up storage
~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -95,13 +89,14 @@ makes manipulation of data easier:

[source,opa]
-----------------
db /wiki[_] = Template.text("This page is empty")
db /wiki[_] = Template.text("This page is empty. Double-click to edit.")
-----------------

The square brackets +[_]+ are a convention to specify that we are talking about
the contents of a map and, more precisely, providing a default value. Here,
the default value is +Template.text("This page is empty")+, i.e. a simple
text in our templating system. As expected, it has type +Template.default_content+.
the contents of a map and, more precisely, providing a default value. Here, the
default value is +Template.text("This page is empty. Double-click to edit.")+,
i.e. a simple text in our templating system. As expected, it has type
+Template.default_content+.

With these two lines, the database is set. Any data written to the database will
be kept persistent, versioned and snapshoted regularly. Should you stop and
Expand Down Expand Up @@ -255,9 +250,12 @@ As previously, we define a function to produce the user interface:
display(topic) =
(
Resource.styled_page("About {topic}", ["/resources/css.css"],
<div id=#header><div id=#logo></div>About {topic}</div>
<div class="show_content" id=#show_content ondblclick={_ -> edit(topic)}>{load_source(topic)}</>
<textarea class="edit_content" id=#edit_content style="display:none" cols="40" rows="30" onblur={_ -> save(topic)}></>
<div class="topbar"><div class="fill"><div class="container"><div id=#logo></div></div></div></div>
<div class="content container">
<div class="page-header"><h1>About {topic}</></>
<div class="well" id=#show_content ondblclick={_ -> edit(topic)}>{load_rendered(topic)}</>
<textarea clas="xxlarge" rows="30" id=#edit_content onblur={_ -> save(topic)}></>
</div>
)
)
-----------------
Expand Down Expand Up @@ -330,7 +328,7 @@ To get this to work, we need to define a function +start+ as follows:
start(uri) = (
match uri with
| {path = {nil} ...} -> display("Hello")
| {path = path ...} -> display(List.to_string_using("", "", "::", path))
| {path = path ...} -> display(String.concat("::", path))
)
----------------
Expand All @@ -349,13 +347,14 @@ pattern did not match, for instance on a request to "http://localhost:8080/hello
In both cases, we execute function +display+. The first case is trivial, while
in the second case, we first convert our list to a string, with separator +"::"+.
Actually, we will make it a tad nicer by also ensuring that the first letter is uppercase.
Along the way, we can make this function shorter:
Actually, we will make it a tad nicer by also ensuring that the first letter is
uppercase, while the other letters are lowercase. Along the way, we can make
this function shorter:
[source,opa]
----------------
start =
| {path = [] ...} -> display("Hello")
| {~path ...} -> display(String.capitalize(List.to_string_using("", "", "::", path))
| {~path ...} -> display(String.capitalize(String.to_lower(String.concat("::", path))))
----------------
In this new version, we use a shorter syntax for pattern-matching. We use +[]+
Expand Down
17 changes: 9 additions & 8 deletions doc/book/hello_wiki/hello_wiki.opa
Expand Up @@ -5,6 +5,9 @@
*/
import stdlib.themes.bootstrap

/**
* {1 Import templates}
*/
import stdlib.web.template

/**
Expand Down Expand Up @@ -41,7 +44,7 @@ db /wiki[_] = Template.text("This page is empty. Double-click to edit.")
*
* Note: This function does not perform any caching.
*/
@publish load_rendered(topic) = Template.to_xhtml( Template.default, /wiki[topic])
@publish load_rendered(topic) = Template.to_xhtml(Template.default, /wiki[topic])

/**
* Accept source code, save the corresponding document in the database.
Expand Down Expand Up @@ -107,12 +110,10 @@ save(topic) =
display(topic) =
Resource.styled_page("About {topic}", ["/resources/css.css"],
<div class="topbar"><div class="fill"><div class="container"><div id=#logo></div></div></div></div>
<div class="content container"><h1>About {topic}</h1>
<div id=#show_content ondblclick={_ -> edit(topic)}>
{load_rendered(topic)}
</>
<textarea class="edit_content" id=#edit_content style="display:none"
cols="40" rows="30" onblur={_ -> save(topic)}></>
<div class="content container">
<div class="page-header"><h1>About {topic}</></>
<div class="well" id=#show_content ondblclick={_ -> edit(topic)}>{load_rendered(topic)}</>
<textarea clas="xxlarge" rows="30" id=#edit_content onblur={_ -> save(topic)}></>
</div>
)

Expand All @@ -129,7 +130,7 @@ start =
| {path = [] ... } ->
display("Hello")
| {~path ...} ->
display(String.capitalize(String.to_lower(List.to_string_using("", "", "::", path))))
display(String.capitalize(String.to_lower(String.concat("::", path))))

/**
* Statically embed a bundle of resources
Expand Down
27 changes: 18 additions & 9 deletions doc/book/hello_wiki/hello_wiki_simple.opa
@@ -1,3 +1,13 @@
/**
* {1 Import standard classes of bootstrap css}
*
* see http://twitter.github.com/bootstrap/
*/
import stdlib.themes.bootstrap

/**
* {1 Import templates}
*/
import stdlib.web.template

/**
Expand All @@ -11,7 +21,7 @@ import stdlib.web.template
* Note: By definition, pages stored in the database are always well-formed.
*/
db /wiki: stringmap(Template.default_content)
db /wiki[_] = Template.text("This page is empty")
db /wiki[_] = Template.text("This page is empty. Double-click to edit.")


/**
Expand Down Expand Up @@ -99,12 +109,12 @@ save(topic) =
*/
display(topic) =
Resource.styled_page("About {topic}", ["/resources/css.css"],
<div id=#header><div id=#logo></div>About {topic}</div>
<div class="show_content" id=#show_content ondblclick={_ -> edit(topic)}>
{load_rendered(topic)}
</>
<textarea class="edit_content" id=#edit_content style="display:none"
cols="40" rows="30" onblur={_ -> save(topic)}></>
<div class="topbar"><div class="fill"><div class="container"><div id=#logo></div></div></div></div>
<div class="content container">
<div class="page-header"><h1>About {topic}</></>
<div class="well" id=#show_content ondblclick={_ -> edit(topic)}>{load_rendered(topic)}</>
<textarea clas="xxlarge" rows="30" id=#edit_content onblur={_ -> save(topic)}></>
</div>
)

/**
Expand All @@ -120,7 +130,7 @@ start =
| {path = [] ... } ->
display("Hello")
| {~path ...} ->
display(String.capitalize(String.to_lower(List.to_string_using("", "", "::", path))))
display(String.capitalize(String.to_lower(String.concat("::", path))))

/**
* Statically embed a bundle of resources
Expand All @@ -131,4 +141,3 @@ server = Server.of_bundle([@static_include_directory("resources")])
* Launch the [start] dispatcher
*/
server = Server.simple_dispatch(start)

14 changes: 3 additions & 11 deletions doc/book/hello_wiki/resources/css.css
Expand Up @@ -7,15 +7,7 @@
}
/***Editing area***/
.content {margin-top:60px;}
.edit_content {
background: #fcfcfc;
padding:0;
width:100%;
}
dt {
font-weight: bold;
}

dd {
text-indent: 5px;
#edit_content {
width:100%;
display: none; /* initially hidden */
}

0 comments on commit 343e1e0

Please sign in to comment.