Found Pages

Ajax Example Custom Validation

This example will show a complex form that uses Ajax for custom data validation.

The full project source of this example is located here:

https://svn.strategoxt.org/repos/WebDSL/webdsls/trunk/test/succeed-web/manual/ajax-form-validation/


Important Note 1: inverse annotations

Inverse annotations can cause problems due to save cascading in WebDSL, if an inverse is made with an entity in the database, then your temporary entity will be automatically saved in the database as well.

Important Note 2: data validation

Don't use WebDSL's data validation described on the Validation page in combination with this example, because validation is done with custom code here. Data validation will be integrated with ajax to more easily get the result that is implemented in this example.



We're going to create an edit page for a Person entity:

entity Person {
  fullname :: String
  username    :: String (name)
  parents     -> Set<Person>
  children    -> Set<Person>
}

The name annotation indicates that the username is used to refer to the Person entity in select inputs, such as those for the parents and children property, see name property page.

The root page includes a personedit template and passes it a new Person object.

define page root() {
  main()
  define body() {
    personedit(Person{})

  }
}

The personedit template provides the form that checks values whenever changes occur. The various placeholder elements provide a location to insert error messages. The actual checks are encapsulated in functions, this allows the save action to easily do a server-side check before saving the new Person object.

define personedit(p:Person){
  form{
    par{
      label("username: "){ input(p.username)[onkeyup := action{ checkUsername(p); checkUsernameEmpty(p); checkFullname(p); }] }
      placeholder pusernameempty { }
      placeholder pusername { }
    }
    par{
      label("fullname: "){ input(p.fullname)[onkeyup := action{ checkFullname(p); checkFullnameEmpty(p); } ] }
      placeholder pfullnameempty { }
      placeholder pfullname { }
    }
    par{
      label("parents: "){ input(p.parents)[onchange := action{ checkParents(p); } ] }
      placeholder pparents { }
    }
    par{
      label("children: "){ input(p.children)[onchange := action{ checkParents(p); } ] }
    }
    submit save() [ajax] {"save"} //explicit ajax modifier currently necessary in non-ajax templates to enable replace. A warning is shown in the log if this is missing.
  } 
  action save(){ 
    // made an issue requesting & operator :)
    var checked := checkUsernameEmpty(p);
    checked := checkUsername(p) && checked;
    checked := checkFullname(p) && checked;
    checked := checkParents(p) && checked;
    checked :=  checkFullnameEmpty(p) && checked;
    if(checked){
      p.save();
      return root();
    } 
  }
}

The function definitions perform the check, and also update the placeholders (note that placeholder names are currently global in the application). They also return the result as a boolean, so the functions can be reused in the save action. replace calls perform the insertion of templates into placeholders, in this case the templates are just creating messages.

function checkUsernameEmpty(p:Person):Bool{
  if(p.username != ""){ 
    replace(pusernameempty, empty());
    return true;
  } 
  else {
  replace(pusernameempty, mpusernameempty());
  return false; 
  }
}
function checkUsername(p:Person):Bool{
  if((from Person as p1 where p1.username = ~p.username).length == 0)){ 
    replace(pusername, empty());
    return true;
  } 
  else {
  replace(pusername, mpusername(p.username));
  return false; 
  }
}

function checkFullnameEmpty(p:Person):Bool{
  if(p.fullname != ""){ 
    replace(pfullnameempty, empty());
    return true;
  } 
  else {
  replace(pfullnameempty, mpfullnameempty());
  return false; 
  }
}
function checkFullname(p:Person) :Bool{
  if(p.username != p.fullname) { 
    replace(pfullname, empty());
    return true;
  } 
  else{
    replace(pfullname, mpfullname());
    return false; 
  }
}

The templates for the messages are shown below. An errorclass template is used to wrap all the errors in the same div tag with special error class, to provide a hook for CSS styling. Templates that are used in replace actions have to be declared as ajax template. When access control is enabled the ajax templates can be protected with the ajaxtemplate rule type.

define errorclass(){
  <div class="error"> elements() </div>
}
define ajax empty(){ "" }
define ajax mpusername(name: String){ errorclass{ "Username " output(name) " has been taken already" } }
define ajax mpusernameempty(){ errorclass{ "Username may not be empty" } }
define ajax mpfullname(){ errorclass{ "Username and fullname should not be the same" } }
define ajax mpfullnameempty(){ errorclass{ "Fullname may not be empty" } }
define ajax mpparents(pname:String,names : List<String>){ 
  errorclass{ 
    "Person" 
    if(names.length > 1){"s"}
    " " 
    for(name: String in names){
      output(name)
    } separated-by {", "}
    " cannot be both parent and child of " output(pname)
  }
}

This app includes some CSS for top-aligned labels (http://css-tricks.com/label-placement-on-forms/), the errors are shown on new lines and in red.

label {
  clear:both;
  float:left;
  margin:10px 0 2px 0;
}
input, select {
  clear:both;
  float:left;
}
#errorclass {
  color: red;
  clear: both;
  float: left;
  margin:2px 0 0 0;
}
input[type="button"]{
  clear: both;
  float: left;
  margin: 20px 0 10px 0; 
}

Edit instead of Create

Since the example used a new Person entity, the save controls whether the object is persisted. If the entity was already in the database, and this is an edit page, then the save wouldn't be necessary to persist changes. Unfortunately this has the side-effect that all intermediate submits (on every change) already persist the changes automatically. One way to work around this issue create a copy of the entity and use that for data binding. Instead of the save() call, the code needs to put the changes back into the real persisted entity.

The editpage, in this case a global entity var is passed in, to demonstrate changing an entity that is persisted.

define page edit(){
  main()
  define body() {
    personedit(pAlice)
  }
}

The template is shown below, unchanged parts are left out. A template var that copies the original values is used for data binding, in the save action the changes are placed in the real person object. The save call is not necessary for edits, but now the template works correctly for both edit and create actions.

define personedit(realp:Person){
  var p := Person{ username := realp.username fullname := realp.fullname children := realp.children parents := realp.parents};
  form{
    par{
      label("username: "){ input(p.username)[onkeyup := action{ checkUsername(p,realp); checkUsernameEmpty(p); checkFullname(p); }] }

     ...

  action save(){ 
    // made an issue requesting & operator :)
    var checked := checkUsernameEmpty(p);
    checked := checkUsername(p,realp) && checked;
    checked := checkFullname(p) && checked;
    checked := checkParents(p) && checked;
    checked :=  checkFullnameEmpty(p) && checked;
    if(checked){
      realp.username := p.username;
      realp.fullname := p.fullname;
      realp.parents := p.parents;
      realp.children := p.children;
      realp.save(); // does nothing in the case of an update
      return root();
    } 
  }
}

One of the checks needs to change, because the entity might be already in the database now.

function checkUsername(p:Person, realp:Person):Bool{
  var matches := from Person as p1 where p1.username = ~p.username;
  if(matches.length == 0 || (matches.length == 1 && matches[0] == realp)){ 
    replace(pusername, empty());
    return true;
  } 
  else {
  replace(pusername, mpusername(p.username));
  return false; 
  }
}

The full project source of this example is located here:

https://svn.strategoxt.org/repos/WebDSL/webdsls/trunk/test/succeed-web/manual/ajax-form-validation/

Validation

Important Note: Data validation has not been fully integrated with ajax templates yet, look at the ajax validation example for a workaround.


Checking user inputs and providing clear feedback is essential for the usability of web applications. WebDSL allows declarative specification of such input validation rules using the validate feature.

Validation rules in WebDSL are of the form validate(e,s) and consist of a Boolean expression e to be validated, and a String expression s to be displayed as error message. Any globally visible functions or data can be accessed as well as any of the properties and functions in scope of the validation rule context.

Value well-formedness checks (e.g. whether the user enters a valid integer in an Int input) are added automatically to each input field.

Validation can be specified on entities in property annotations:

entity User { 
  username :: String (id, validate(isUniqueUser(this), "Username is taken")) 
  password :: Secret (validate(password.length() >= 8, "Password needs to be at least 8 characters") 
  , validate(/[a-z]/.find(password), "Password must contain a lower-case character") 
  , validate(/[A-Z]/.find(password), "Password must contain an upper-case character") 
  , validate(/[0-9]/.find(password), "Password must contain a digit")
  email :: Email)) 
} 
extend entity User { 
  username(validate(isUniqueUser(this),"Username is taken")) 
  password(validate(password.length() >= 8, "Password needs to be at least 8 characters") 
  ,validate(/[a-z]/.find(password), "Password must contain a lower-case character") 
  ,validate(/[A-Z]/.find(password), "Password must contain an upper-case character") 
  ,validate(/[0-9]/.find(password), "Password must contain a digit")) 
}

Validation can be specified directly in pages:

define page editUser(u:User) { 
  var p: Secret; 
  form { 
    group("User") { 
      label("Username") { input(u.username) } 
      label("Email") { input(u.email) } 
      label("New Password") { 
        input(u.password) 
      } 
      label("Re-enter Password") { 
        input(p) { 
          validate(u.password == p, "Password does not match") 
        } 
      } 
      action("Save", action{ } ) 
    } 
  }
}

Validation can be specified in actions:

define page createGroup() { 
  var ug := UserGroup {} 
  form { 
    group("User Group") { 
    label("Name") { input(ug.name) } 
    label("Owner") { input(ug.owner) } 
    action("Save", save()) } } 
    action save() { 
      validate(email(newGroupNotify(ug)),"Owner could not be notified by email"); 
      return userGroup(ug); 
   }
 }

Customizing Validation Output

Validation output can be customized by overriding the templates used to display validation messages. Currently, there are 4 global validation templates:


define ignore-access-control errorTemplateInput(messages : List<String>)

Displays validation message related to an input.


define ignore-access-control errorTemplateForm(messages : List<String>)

Displays validation message for validation in a form.


define ignore-access-control errorTemplateAction(messages : List<String>)

Displays validation message for validation in an action.


define ignore-access-control templateSuccess(messages : List<String>)

Displays validation message for success messages.


When overriding these validation templates, use a validateInput() templatecall to refer to the element being validated.

Example:

define ignore-access-control errorTemplateInput(messages : List<String>){
  validatedInput()
  for(ve: String in messages){
    output(ve)
  }     
}
Ajax Validation

WebDSL provides input components that validate the inputs using ajax.

built-in value types:

inputajax(String/Secret/URL/Email/Text/WikiText)
inputajax(Int/Bool/Float/Long)

reference types:

inputajax(Entity/List<Entity>/Set<Entity>)
selectajax(Entity/Set<Entity>)
radioajax(Entity)

provide selection options:

inputajax(Entity/List<Entity>/Set<Entity>,List<Entity>)
selectajax(Entity/Set<Entity>,List<Entity>)
radioajax(Entity,List<Entity>)

Selection options can also be provided using the allowed annotation on an entity property. Example:

entity Person{
  parent -> Person (allowed=from Person as p where p != this)
}
Pages

Pages in WebDSL can be defined using the following construct:

 define page [pagename]( [page-arguments]* ){ [page-elements]* }

There are basic output elements for structure and layout of the page, such as title and header.

Example:

define page root() {
  title { "Page title" }
  section {
    header{ "Hello world." }  
    "Greetings to you."
  }
}

Page Parameters

Pages can have parameters, and output is used for inserting data values.

Example:

define page user(u : User) {
  "The name of this user is " output(u.name)
}

Input Forms

The form element in combination with submit is used for submitting data. input elements perform automatic data binding upon submit. For more information about forms, go to the Form page.

Example:

define page editUser(u:User){
  form{
    input(u.name)
    submit action{} { "save" } 
  }
}

Templates

Pages can be made reusable by declaring them as template, and calling them from other pages or templates.

Example:

define common(){
  header{ "my page" }
}
define page root(){
  common()
}
Access Control

Minimal Access Control Example

A minimal access control example is shown here.

Configuration of the Principal

The Access Control sublanguage is supported by a session entity that holds information regarding the currently logged in user. This session entity is called the securityContext and is configured as follows:

principal is User with credentials name, password

This states that the User entity will be the entity representing a logged in user. The credentials are not used in the current implementation (the idea is to derive a default login template). The resulting generated session entity will be:

session securityContext
{
  principal -> User
  loggedIn :: Bool := this.principal != null
}

Note that this principal declaration is used to enable access control in the application.

It will also generate authentication() (both login and logout), login(), and logout() templates, and an authenticate function that takes the credentials as arguments and, if they are correct, returns true and sets the principal (only String/Email/Secret-type credential properties are allowed).

Authentication

Authentication can be added manually, instead of using the generated authentication templates. Here is a small example application with custom authentication:

 principal is User with credentials name, password

entity User{
  name :: String
  password :: Secret
}

define login(){
  var username := ""
  var password : Secret := ""
  form{ 
    label("Name: "){ input(username) }
    label("Password: "){ input(password) }
    captcha()
    submit login() {"Log In"}
  }
  action login(){
    validate(authenticate(username,password), 
      "The login credentials are not valid.");
    message("You are now logged in.");
  }
}

define logout(){
  "Logged in as " output(securityContext.principal)
  form{
    submitlink logout() {"Log Out"}
  }
  action logout(){
    securityContext.principal := null;
  }
}

define page root(){
  login()
  " "
  logout()
}

init{
  var u1 : User := 
    User{ name := "test" password := ("test" as Secret).digest() };
  u1.save();
}

access control rules

  rule page root(){
    true
  }

When storing a secret property you need to create a digest of it:

      newUser.password := newUser.password.digest();

This makes sure the secret property is stored encrypted. A digest can be compared with an entered string using the check method:

      us.password.check(enteredpassword)

Protecting Resources

The default policy is to deny access to all pages and ajax templates, the rules determine what the conditions for allowing access are. Regular templates are accessible by default, however, you can add additional access control rules on templates to limit their accessibility.

A simple rule protecting the editUser page to be only accessable by the user being edited looks like this:

    access control rules

      rule page editUser(u:User){
        u == principal     
      }

An analysis of this rule:

  • access control rules: a rules section is started with this declaration, multiple rules can follow. To go back to a normal section, use section some description.
  • rule: A keyword for Access Control rules
  • page: The type of resource being protected here, all the types available in the Access Control DSL for WebDSL are: page, action, template, function. The rules on pages protect the viewing of pages, action rules protect the execution of actions, template rules determine whether a template is visible in the including page, and finally rules on functions are lifted to the action invoking the function.
  • editUser: The name of the resource the rule will apply to.
  • (u:User): The arguments (if any) of the resource, the types of the arguments are used when matching. This also specifies what variables can be used in the checks.
  • u = principal: the check that determines whether access to this resource is allowed, this check is typechecked to be a correct boolean expression. The use of principal implies that the securityContext is not null and the user is logged in (these extra checks are generated automatically).

Matching can be done a bit more freely using a trailing * as wildcard character, both in resource name and arguments:

    rule page viewUs*(*){
      true
    }

When more fine-grained control is needed for rules, it is possible to specify nested rules. This implies that the nested rule is only valid for usage of that resource inside the parent resource. The allowed combinations are page - action, template - action, page - template. The next example shows nested rules for actions in a page:

      rule page editDocument(d:Document){
        d.author == principal
        rule action save(){
          d.author == principal
        }
        rule action cancel(){
          d.author == principal
        }      
      }

This flexibility is often not necessary, and it is also inconvenient having to explicitly allow all the actions on the page, for these reasons some extra desugaring rules were added. When specifying a check on a page or template without nested checks, a generic action rules block with the same check is added to it by default. For example:

      rule page editDocument(d:Document){
        d.author == principal     
      }

becomes

      rule page editDocument(d:Document){
        d.author == principal
        rule action *(*)
        {
          d.author == principal
        }    
      }

Reuse in Access Control rules

Predicates are functions consisting of one boolean expression, which allows reusing complicated expressions, or simply giving better structure to the policy implementation. An example of a predicate:

      predicate mayViewDocument (u:User, d:Document){
        d.author == principal
        || u in d.allowedUsers
      }
      rule page viewDocument(d:Document){
        mayViewDocument(principal,d)
      }
      rule page showDocument(d:Document){
        mayViewDocument(principal,d)
      }

Pointcuts are groups of resources to which conditions can be specified at once. Especially the open parts of the web application are easy to handle with pointcuts, an example:

      pointcut openSections(){
        page home(),
        page createDocument(),
        page createUser(),
        page viewUser(*)
      }
      rule pointcut openSections(){
        true
      }

Pointcuts can also be used with parameters:

      pointcut ownUserSections(u:User){
        page showUser(u),
        page viewUser(u),
        page someUserTask(u,*)
      }
      rule pointcut ownUserSections(u:User){
        u == principal
      }

Note that each parameter must be used in each pointcut element, this indicates the value to be used as argument for the pointcut check. A wildcard * can follow to indicate that there may be additional arguments.

Inferring Visibility

A disabled page or action redirects to a very simple page stating access denied. Since this is not very user friendly, the visibility of navigate links and action buttons/links are automatically made conditional using the same check as the corresponding resource. An example conditional navigate:

    if(mayViewDocument(securityContext.principal,d)){
      navigate(viewDocument(d)){ "view " output(d.title) } 
    }

When using conditional forms it is often more convenient to put the form in a template, and control the visibility by a rule on the template.

Using Entities

Access Control policies that rely on extra data can create new or extend existing properties. An example of extending an entity is adding a set of users property to a document representing the users allowed access to that document:

      extend entity Document{
        allowedUsers -> Set<User>
      }

Administration of Access Control

Administration of Access Control in WebDSL is done by the normal WebDSL page definitions. All the data of the Access Control policy is integrated into the WebDSL application. An option is to incorporate the administration into an existing page with a template. This example illustrates the use of a template for administration:

      define allowedUsersRow(document:Document){
        row{ "Allowed Users:" input(document.allowedUsers) }
      }

The template call for this template is added to the editDocument page:

      table{
        row{ "Title:" input(document.title) }
        row{ "Text:" input(document.text) }
        row{ "Author:" input(document.author) }  
        allowedUsersRow(document)
      }

By using a template the Access Control can be disabled easily by not including the access control definitions and the template. The unresolved template definitions will give a warning but the page will generate normally and ignore the template call.

App Configuration

In the application.ini file compile-, database- and deployment information is stored. Executing the webdsl command in a certain directory will look for a application.ini file to obtain compilation information. If no such file was found, it will start a simple wizard to create one.

Example application.ini:

backend=servlet
tomcatpath=/opt/tomcat
appname=hello
dbserver=localhost
dbuser=webdsluser
dbpassword=webdslpassword
dbname=webdsldb
dbmode=update
smtphost=localhost
smtpport=25
smtpuser=
smtppass=

Required Configuration

backend The back-end target platform of the application. Currently, the servlet back-end is only up-to-date.

appname The name of the application to compile. The compiler will look for a APPNAME.app file to compile. This name will also become the servlet name and show up as part of the URL. By renaming the generated APPNAME.war file to ROOT.war and then deploying it, the application name will not be in the URL.

tomcatpath This field should contain the root directory of the Tomcat installation. For example /opt/tomcat. It is used when executing 'webdsl deploy'.

Database Configuration MySQL

dbmode This field indicates if the application should try to create tables in a database, or try to sync it with the existing schema to avoid loss of data. Valid values are create-drop, update, and false. Update can lead to unpredictable results if data model is changed too much, if data needs to be properly migrated, use Acoda instead. For production deployment use 'export DBMODE=false'.

dbserver Location of the Mysql server, which will be used in the connection URL, e.g. 'localhost'.

dbuser User to be used for connecting to the MySQL database.

dbpassword Password for the specified user.

dbname Database name, note that the database needs to exist when the application is run. The 'webdsl' script will try to create the database in the wizard, but manually creating it via command-line or MySQL Administrator is also possible.

Database Configuration Sqlite

db Set 'db=sqlite' to enable Sqlite instead of the default MySQL.

dbfile Sqlite database file, an empty file will be populated with tables automatically, when using 'create-drop' or 'update' db modes.

dbmode Same as for MySQL.

Email Configuration

smtphost SMTP host for sending email, e.g. smtp.gmail.com

smtpport SMTP port for sending email, e.g. 465

smtpuser SMTP username

smtppass SMTP password

smtpprotocol 'smtpprotocol=smtps' [smtp/smtps] Use smtp or smtps as protocol.

smtpauthenticate 'smtpauthenticate=true' [true/false] Authenticate with a username and password.

Lucene Index Configuration

indexdir set the index directory, default is /var/indexes.

Optional Configuration

rootapp 'rootapp=true' will deploy the application as root application, it will not have the application name prefix in the URL.

sessiontimeout Sets the session timeout, specified in minutes.

javacmem 'javacmem=3G' set javac max memory for compilation of generated Java classes

debug 'debug=true' will show queries and Java exception stacktraces in the log.

verbose 'verbose=2' will show more info during compilation, mainly for developers.

fastpp 'fastpp=true' will make the compiler write Java code faster (writing files stage), however, it also becomes less readable. (only for C-based back-end of the WebDSL compiler)

Deploy with Tomcat Manager

For the 'webdsl tomcatdeploy' and 'webdsl tomcatundeploy' commands to work, a user has to be configured in Tomcat (tomcat/conf/tomcat-users.xml). For example:

<tomcat-users>
  <role rolename="manager"/>
  <user username="tomcat" password="tomcat" roles="manager"/>
</tomcat-users>

The tomcat manager URL and username and password can be set in the application.ini file (defaults are listed as examples):

tomcatmanager 'tomcatmanager=http:\localhost:8080\manager' URL to Tomcat manager

tomcatuser 'tomcatuser=tomcat' manager user declared in tomcat/conf/tomcat-users.xml

tomcatpassword 'tomcatpassword=tomcat' password for that user

XML Embedding

XML fragments can be embedded directly in templates. This allows easy reuse of existing XHTML fragments and CSS. For example:

define main() {
  <div id="pagewrapper">
    <div id="header">
      header()
    </div>
    <div id="footer">
      <p />"powered by " <a href="http://webdsl.org">"WebDSL"</a><p />
    </div>
  </div>
  <some:tag />
}

While the name and attribute names are fixed, the attribute values can be any WebDSL expression that produces a string:

define test(i : Int) {
  <div id="page" + "wrapper" + i />
}
Action Code

This section describes the expressions and statements available in WebDSL.

Entity

Data models in WebDSL are defined using entity definitions. An entity definition consists of the entity's name, possibly a super-entity from which it inherits, 0 or more properties and 0 or more entity functions:

entity User {
  name     :: String (length = 25)
  email    :: Email
  password :: Secret
  homepage :: URL
  pages    -> Set<Page>
  function checkPassword(String s) : Bool { 
    return password.check(s);
  }
  predicate sameUser(u:User){ this == u } 
}

A property consists of 4 parts:

  • a name
  • a property kind, which can either be value (::), reference (->) or composite (<>)

The difference between reference and composite property kinds is that composite indicates that the referred entity is part of the one referring to it. The only effect this currently has is that composite cascades delete (deleting the entity will also delete the referred entity).

  • a property type, e.g. value types String, Int, Long, Text or reference/composite types which refer to other entities, such as Person, Set<Person>, and List<Person>.

For a complete overview of the available types, see Types.

  • a set of annotations, for instance declaring inverse properties, lengths, validation.

An example data model for a blogging site:

entity Author {
  name     :: String
  email    :: Email
  password :: Secret
  posts    -> Set<Post> (inverse=Post.author)
}

entity Post {
  author   -> Author
  title    :: String
  text     :: Text
  comments -> Set<Comment> (inverse=Comment.post)
}

entity Comment {
  post     -> Post
  author   :: String
  text     :: Text
}
My First Web Application

Example Application: Keeping Track of Publications

In this tutorial we build a small web application for registering publications and writing reviews about them. This could be the start of a larger community site for sharing bibliographic references and opinions about these. Such a site could deal with the following entities:

  • Publication
  • Author
  • Review
  • User
  • Group
  • Bibliography
  • Collection
  • Citations

Data Models: Records

We start simply with the entity of publications. A first attempt is the following 'record' data model of Publication:

      entity Publication {
        title     :: String (name)
        authors   :: String
        abstract  :: String
        published :: String
        url       :: String
      }

According to this entity definition, a Publication has several String valued properties. The properties of an entity type store the information of entity objects of that type. If e denotes an object of type Publication, then e.title can be used to obtain the title of the the publication. The title property has a name annotation, this has the effect of adding a derived property name. By convention, any WebDSL object can be asked for its name, which provides a generic interface to naming an object, for instance when producing links.

Data Models: Associations

Instead of using a String to encode (the names of) the authors of a Publication, we'd rather like to use a list of references to actual Persons, i.e., objects represented by a proper entity definition in WebDSL. To this end we introduce a Person entity and make the authors property of Publication and association to a List of Person. Likewise, we'd like to an association from Person to the Set of Publications that the Person has authored. Moreover, we'd like this relation to be consistent. That is, whenever, a person is listed as author of a publication, the publication should be in the set of publications of that person. This is what the inverse annotation on the authors property achieves.

      entity Publication {
        title     :: String (name)
        authors   -> List<Person> (inverse=Person.publications)
        abstract  :: String
        published :: String
        url       :: String
      }
      entity Person {
        fullname     :: String (name)
        homepage     :: String
        email        :: String
        address      <> Address 
        publications -> Set<Publication> 
      }
      entity Address {
        street  :: String
        city    :: String
        country :: String
        phone   :: String
      }

The authors association is a reference association. In contrast, the address association of Person is a composite association, which indicates that the address is 'owned' by the person; if the person object is deleted, the corresponding address object can be deleted as well.

Data Models: Special Types

Entity associations provide a more informative type for a property than the plain string type. While (string) value types are fine for other properties such as title, abstract, and url, it would be useful to have a better distinction in the role of these values. Therefore, WebDSL provides a range of special value types such as Text, WikiText Date, URL, and Email that convey the sort of string value that is contained in the property, such that appropriate operations and user interfaces can be attached to these properties.

      entity Publication {
        title     :: String (name)
        authors   -> List<Person> (inverse=Person.publications)
        abstract  :: Text
        published :: Date
        url       :: URL
      }
      entity Person {
        fullname     :: String (name)
        homepage     :: URL   (optional)
        email        :: Email (optional)
        address      <> Address 
        publications -> Set<Publication> 
      }
      entity Address {
        street  :: String
        city    :: String
        country :: String
        phone   :: String
      }

Defining an Application (Mark 1)

Now we have a complete data model for publications and their authors. From such a data model we can already generate a prototype application for creating, viewing, and editing objects for the entity types. An application definition starts with the keyword application and then contains a number of descriptions and sections containing entity and other definitions.

    application org.researchr.www

    description { A community site about research. }

    section data model

      entity Publication { ... }
      entity Person { ... }
      entity Address { ... }

    section templates 

      define main() { body() manageMenu() }
      define body() {}
      define manageMenu() {}
      define page home() { main() }

Here is the complete code of this application.

The code: