-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathstep4.html
More file actions
170 lines (131 loc) · 7.36 KB
/
step4.html
File metadata and controls
170 lines (131 loc) · 7.36 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
<html>
<head>
<title>Ur/Web Tutorial</title>
<style>
.code {
background: #d0d0d0;
}
</style>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-10255629-6']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</head>
<body>
<img src="urheader.png" alt="Ur/Web Tutorial"/></br>
<hr width="99%"/>
<h1>Ur/Web Tutorial</h1>
<h2>Step 4</h2>
<p><a href="step3.html">Previous</a> | <a href="step5.html">Next</a></p>
<p>In this step, we're going to add a "detail" view for blog entries. This will demonstrate how links and parameters work in Ur/Web, as well as mutual recursion.</p>
<p>To create our detail links, we're going to want a new function that takes a parameter -- the id of the entry we want to view, in this case.</p>
<h3>urblog.ur</h3>
<p>We're going to create <span class="code">detail</span> as a <em>mutually recursive</em> function with <span class="code">list</span>. In order to link to something in Ur/Web, it's necessary that it be visible within the scope of the caller. If we want to be able to link from a detail page back to the main list, they each depend on the other being defined. We use the <span class="code">and</span> keyword to introduce mutually recursive definitions.
<pre class="code">
[...]
and detail (i:int) =
res <- oneOrNoRows (SELECT * FROM entry WHERE entry.Id = {[i]});
return
(case res of
None => <xml>
<head><title>Entry Not Found</title></head>
<body>
<h1>Entry not found</h1>
</body>
</xml>
| Some r => <xml>
<head>
<title>{[r.Entry.Title]}</title>
<link rel="stylesheet" type="text/css" href="http://expdev.net/urtutorial/step4/style.css"/>
</head>
<body>
<h1>{[r.Entry.Title]}</h1>
<h2>By {[r.Entry.Author]} at {[r.Entry.Created]}</h2>
<div class={blogEntry}>
<p>{[r.Entry.Body]}</p>
</div>
<a link={list ()}>Back to all entries</a>
</body>
</xml>)
[...]
</pre>
<p>To link to our new detail page, we add a link from the entry title within the list function:</p>
<pre class="code">
[...]
fun list () =
rows <- queryX (SELECT * FROM entry)
(fn row =>
<xml>
<div class={blogEntry}>
<h1><a link={detail row.Entry.Id}>{[row.Entry.Title]}</a></h1>
<h2>By {[row.Entry.Author]} at {[row.Entry.Created]}</h2>
<p>{[row.Entry.Body]}</p>
[...]
</pre>
<h3>urblog.urs</h3>
<p>To ensure our new detail function visible to the outside world, so we add its signature to the signature definition:</p>
<pre class="code">
val list : unit -> transaction page
val detail : int -> transaction page
val main : unit -> transaction page
</pre>
<p>Note that this is not <em>strictly</em> necessary, as Ur/Web automatically exports functions that previously-exported functions depend upon. In any case, it may be good practice to include these kinds of top-level pages into your signature file to take advantage of the "self-documenting" effect that these signature definitions may have upon your applications. My personal approach to learning about the operation of an unfamiliar Ur/Web application usually involves reading all the signature definitions first; this might guide you as to what is or isn't appropriate to include.</p>
<h2>Adding Comments</h2>
<p>So you'll notice that we have so far restricted ourselves to creating functions that generate complete pages suitable for serving directly to the user. What if we want to create a function that generates just part of a page, and then include that content on other pages? It turns out Ur/Web gives us plenty of scope within which to do this. We'll demonstrate this by creating a comment-displaying function that we can then include elsewhere.</p>
<h3>urblog.ur</h3>
<p>We need a table in which to store comments on entries. We define this table much like we did our original one:</p>
<pre class="code">
table comment : {Id : int, Entry : int, Created : time, Author : string, Body : string }
PRIMARY KEY Id,
CONSTRAINT Entry FOREIGN KEY Entry REFERENCES entry(Id)
</pre>
<p>We also introduce a new style <span class="code">blogComment</span>.</p>
<p>Our comment function is pretty simple. The only thing we have to do is specify a type annotation -- Ur/Web can't always figure out the type of functions on its own. In this case we specify that our function is <span class="code">transaction xbody</span>. This type (<span class="code">xbody</span>) is that of XML that is suitable for inclusion within a <span class="code">body</span> tag. The transaction, as before, indicates that this is part of a monad, and is therefore suitable for inclusion within other transactions (as all content that will be delivered to users must be).</p>
<pre class="code">
fun comments i : transaction xbody =
queryX (SELECT * FROM comment WHERE comment.Entry = {[i]})
(fn row =>
<xml>
<div class={blogComment}>
<p>{[row.Comment.Body]}</p>
<p>By {[row.Comment.Author]} at {[row.Comment.Created]}</p>
</div>
</xml>)
</pre>
<p>Now, we can't just call this function directly, because it is returning a transaction (which is necessary because it queries our database), so instead we must <em>bind</em> it within our entry detail function:
<pre class="code">
and detail (i:int) =
res <- oneOrNoRows (SELECT * FROM entry WHERE entry.Id = {[i]});
comm <- comments i;
[...]
<p>{[r.Entry.Body]}</p>
</div>
{comm}
[...]
</pre>
<h3>urblog.urp</h3>
<p>We need to update our project file and style sheet (adding the rewrite for the new style declaration):</p>
<pre class="code">
allow url http://expdev.net/urtutorial/step4/style.css
rewrite style Urblog/blogEntry blogEntry
rewrite style Urblog/blogComment blogComment
database dbname=urblog
sql urblog.sql
urblog
</pre>
<p>Before we get too carried away rewriting styles one by one, there's a convenient shorthand that will save us some typing:</p>
<pre class="code">
rewrite style Urblog/*
</pre>
<p>This could be used to replace both of the <span class="code">rewrite style</span> lines above.</p>
<h2>Running the example</h2>
<p>You will need to recompile as usual; however, because of the new comments table, you will need to import the generated <span class="code">urblog.sql</span> again, possibly dropping the existing entry table in the process. You might like to manually add an example comment to an entry.</p>
<p><a href="step3.html">Previous</a> | <a href="step5.html">Next</a></p>
</body>
</html>