-
Notifications
You must be signed in to change notification settings - Fork 4
/
step6.html
129 lines (92 loc) · 6.23 KB
/
step6.html
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
<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 6</h2>
<p><a href="step5.html">Previous</a> | <a href="step7.html">Next</a></p>
<p>In this step of the tutorial we are going to introduce one of the rather unique features of Ur/Web. <em>Metaprogramming</em> over row types allows us to write programs that are able to perform limited computation over the structure of rows. In practical terms, this means that we can write code that operates independently of the particular table or form inputs, and which can therefore be reused in many different situations. We're going to demonstrate this by taking a pre-existing component (the <em>Crud</em> -- create, read, update, delete -- example, slightly modified) from the Ur/Web site and apply it to our application to enable the creation and editing of blog entries.</p>
<p>How does this differ from components parameterised on table structure, you might ask? Well, that's a reasonable way of thinking about some of what this metaprogramming enables, but in Ur/Web it goes further. We can use familiar programming constructs (e.g. map and fold) to compute new types from old ones. We can invoke appropriate display functions based upon the types of the fields in a row. This gives much of the same flexibility that dynamic web languages can provide; however, Ur/Web does it in a way that is statically checked at compile time.</p>
<p>The Crud component is reasonably simple. We'll skip over most explanation of how it actually works, because you can divert back to that later. The component consists of two files -- a signature and an implementation.</p>
<h3>urblog.urp</h3>
<p>Now we need to include our new Crud module:</p>
<pre class="code">
allow url http://expdev.net/urtutorial/step6/style.css
rewrite style Urblog/blogEntry blogEntry
rewrite style Urblog/blogComment blogComment
rewrite style Urblog/commentForm commentForm
database dbname=urblog
sql urblog.sql
crud
urblog
</pre>
<h3>urblog.ur</h3>
<p>Including our Crud module is pretty simple, if you're accustomed to the use of functors in SML. Briefly, a functor takes a module (structure) as input and returns another structure. In this instance we are obligated to provide a structure with a table, a title field and a record mapping columns to functions that know how to display them on the page:</p>
<pre class="code">
open Crud.Make(struct
val tab = entry
val title = "Blog Admin"
val cols = {Title = Crud.string "Blog Title",
Created = Crud.time "Created",
Author = Crud.string "Author",
Body = Crud.string "Entry Body"}
end)
</pre>
<h3>urblog.urs</h3>
<p>We used <span class="code">open</span> in the last example to import everything from the structure returned by <span class="code">Crud.Make</span> into the current namespace. As a result, we need to make our brand new <span class="code">admin</span> function visible to the outside world:</p>
<pre class="code">
val list : unit -> transaction page
val detail : int -> transaction page
val main : unit -> transaction page
val admin : unit -> transaction page
</pre>
<h3>Running the example</h3>
<p>If you go through the process of compiling this example as usual and importing the generated SQL file, you should be able to view your brand new admin interface at this URL:</p>
<p><a href="http://localhost:8080/Urblog/admin">http://localhost:8080/Urblog/admin</a></p>
<h2>Making it a little prettier</h2>
<p>So, the Crud interface works, but it's not very pretty. Particularly, the way in which body text is displayed and edited is somewhat inconvenient. The default 'string' column transformations are inappropriate. We'll go ahead and define a custom transformation for the <span class="code">Body</span> field:</p>
<pre class="code">
val cols = {Title = Crud.string "Blog Title",
Created = Crud.time "Created",
Author = Crud.string "Author",
Body = {Nam = "Entry Body",
Show = fn b => <xml>{[String.length b]} characters</xml>,
Widget = fn [nm :: Name] => <xml><textarea{nm}/></xml>,
WidgetPopulated =
fn [nm :: Name] b => <xml><textarea{nm}>{[b]}</textarea></xml>,
Parse = readError,
Inject = _}
}
</pre>
<p>Now we are using a text area instead of a single-line text field to create and update our entry text. Obviously you could add styling or custom formatting for the other fields too.</p>
<p>One function provided by Ur/Web to draw attention to is <span class="code">readError</span>. This is a helpful function that will throw a runtime error if the submitted data is incorrectly formatted with respect to the expected type for that field.</p>
<h3>urblog.urp</h3>
<p>We've made use of the <span class="code">String.length</span> function, which means we need to tell the compiler where to find the built-in <span class="code">String</span> module. We do this by adding a line to the urp file:</p>
<pre class="code">
[...]
$/string
crud
urblog
</pre>
<p>This syntax will be familiar to users of various Standard ML build tools. The <span class="code">$/</span> preceeding the filename of the module basically says "look in the standard places for this", where the "standard places" are defined more carefully in the Ur/Web manual. In a properly-configured installation this should do the right thing without any intervention on your part.</p>
<p><a href="step5.html">Previous</a> | <a href="step7.html">Next</a></p>
</body>
</html>