Ur/Web Tutorial

Ur/Web Tutorial

Step 5

Previous | Next

Adding dynamic behaviour

So one of the tasks that tends to take up a lot of time in web development is those shiny little features that require wheeling out a Javascript library, and then debugging the numerous concurrency bugs that invariably arise from this style of asynchronous programming. We're going to add a totally unnecessary Javascript-enabled comment box to our blog entry detail page, primarily to demonstrate the use of the functional reactive programming style that Ur/Web enables.

Functional Reactive Programming in Ur/Web

Functional reactive programming as it appears in Ur/Web dictates that we define some data sources, and then the entire page generation becomes a pure function defined in terms of these sources. This contrasts favourably with the usual Javascript approach of applying (imperatively) a number of transformations to the page at runtime, both in terms of the amount of work you must do as a programmer, as well as in terms of the likelihood of your program being correct.

We define a source using the source value function, which defines a source that will continue to yield that value until such a time as we change it using the set function. Finally, we use the dyn pseudo-tag to include sources within our page. Whenever the source is updated, it will result in the the dyn tag being updated with this new value.

We're going to use this to add a button to every entry detail page that allows us to show or hide a comment form.


The basic process involves defining a function that returns a comment form XML fragment. We wrap the actual invocation of this function in a dyn tag that "subscribes to" a signal that determines whether the comment form is visible or not. Finally, we pass this signal as an argument to the comment form function, so that the 'Cancel' button can re-hide the form.

and mkCommentForm (id:int) s : xbody =
      <p>Your Name:<br/></p><textbox{#Author}/><br/>
      <p>Your Comment:<br/></p><textarea{#Body}/>
      <submit value="Add Comment" action={handler id}/>
      <button value="Cancel" onclick={ fn _ => set s False}/></form></xml>

In this example we also see the first instace of Ur/Web's handling of forms. The result of submitting a form is a row, much like the rows we are returned when we query the database. We assign names to these row fields using the {#Author} syntax, which defines an input form field, the value of which will be accessible as the field named Author in the row that will be passed to our handler function. We set the action attribute of the submit button to be some function named handler that takes an entry id as an argument. We'll give the definition of that function soon. The argument s is the data source that determines if the comment form should be visible or not. Data sources are first-class values, so they can be passed around in the usual way. This is one of the things that makes FRP in Ur/Web so exceptionally flexible.

Let's add a dummy handler for the form that just displays the detail page for the entry we're trying to post a comment to, and then wire in our comment form to a button on the entry.

and handler entry r = detail entry

And we can define our detail function:

style commentForm

and detail (i:int) =
   commentSource <- source False;
                  <button value="Add Comment" onclick={ fn _ => set commentSource True}/>
                  <div class={commentForm}>
                  <dyn signal={s <- signal commentSource;
                         if s then 
                            return (mkCommentForm i commentSource) 
                         else return <xml/>}/>

We also add a new style and add an appropriate rewrite for the style name.

Updating the database

You can compile and run the application as it is. The comment form will appear on the detail page when you click "Add Comment", and it will hide again when you press "Cancel". Pretty neat, huh? Let's go ahead now and actually save our comments to the database.

We are going to need to add a sequence in order to generate unique (and sequential) comment IDs in the database for the index of the comment table. We do this using the sequence keyword:

sequence commentS

Now to define our handler function, which will increment the sequence we've just created, and insert into the database all the fields we gathered from the comment form:

and handler entry r = 
   id <- nextval commentS;
   dml (INSERT INTO comment (Id, Entry, Author, Body, Created) VALUES ({[id]}, {[entry]}, {[r.Author]}, {[r.Body]}, CURRENT_TIMESTAMP));
   detail entry

The dml function performs updates on the database, and takes an SQL query as its argument. It is basically as simple as matching the fields of our submitted form (which reside in r, as the form values are passed as an argument to the handler function). Ur/Web is going to make sure we don't do anything silly like expect form values that don't exist, and ensure that we don't do anything completely silly like leave ourselves open to SQL injection attacks. Aside from allowing users to create arbitrarily many database rows that will be displayed, there would be no danger to placing this application directly into a public environment exactly as it is now.

Putting it all together

As ever, you can compile the application. Make sure you have at least one test blog entry you can work with in your database, and then you can test the application by going to:


Previous | Next