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

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)
} 
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.

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.

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, and List.

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
}
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 H2 Database Engine in file

db Set db=h2 to enable H2 Database Engine instead of the default MySQL.

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

dbmode Same as for MySQL.

Database Configuration H2 Database Engine in memory

db Set db=h2mem to enable in-memory H2 Database Engine instead of the default MySQL.

dbmode Same as for MySQL, although effectively the tables are always dropped after a restart with in-memory database

Database Configuration through JNDI

db Set db=jndi to retrieve a JDBC resource from the application server, rather than providing the configuration in the web application.

dbjndipath JNDI path to the JDBC resource. On Apache Tomcat this is typically prefixed by ‘java:comp/env’. An example may be: ‘java:comp/env/jdbc/mydatabase’

dbmode Same as for MySQL.

Apart from settings in the application.ini, also a Context XML file must be provided for Apache Tomcat. An example may be:

<Context>
    <Resource name="jdbc/mydatabase"
        auth="Container"
        type="javax.sql.DataSource"
        driverClassName="com.mysql.jdbc.Driver"
        maxActivate="100" maxIdle="30" maxWait="10000"
        username="root" password="dbpassword"
        url="jdbc:mysql://localhost:3306/mydatabase?useServerPrepStmts=false&amp;characterEncoding=UTF-8&amp;useUnicode=true&amp;autoReconnect=true" />
</Context>

This XML file must be stored in: $TOMCAT_BASE/conf/Catalina/localhost/<appname>.xml

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.

Search Configuration

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

searchstats Enable/disable search statistics, which can be displayed using template showSearchStats(). Default is false.

Optional Configuration

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

wikitext-hardwraps wikitext-hardwraps=true will enable so-called hard wraps in markdown. This way, each newline which isn’t followed by 2 white spaces is also rendered as new line. Default is false. See http://yellowgrass.org/issue/WebDSL/818

appurlforrenderwithoutrequest (as of WebDSL 1.3.0) Sets the URL to be used when links to pages are to be rendered outside a request. Normally, WebDSL will construct links using the request URL as a base. In case pages or templates with links are to be rendered outside a request (e.g. using a background task), WebDSL will use this property value as the base 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

Generated Functions for Entities

For defined entities, a number of global functions are automatically generated. Replace Entity with the defined entity name below.

Property with id annotation

If the Entity has an id annotation on a property, the following functions are generated (idtype is the type of the id property):

getUniqueEntity

getUniqueEntity(id : idtype) : Entity

If the Entity with the given id already exists, it is returned. If it did not exist, it is created once and a flush to the database is performed (this will commit any changes made to the entities in memory, e.g. the changes from data binding of input fields), repeated calls to this function with the same argument will keep returning that created Entity.

isUniqueEntity

isUniqueEntity(ent : Entity) : Bool

This function returns false when the value of the id property of ent is already taken. The function returns true when the id property is not taken, but will do so only once, subsequent calls with different entities but the same id will then return false (which makes this function suitable for processing a batch of entities in an action).

isUniqueEntityId

isUniqueEntityId(id : idtype, ent : Entity) : Bool

This function returns false when the entity would not be unique when given the id argument. The function returns true when the entity would be unique, but will do so only once for a given id, checking a different entity with the same id will return false in the rest of the action handling.

isUniqueEntityId(id : idtype) : Bool

This function returns false when the given id is not available for the Entity type. The function will return true only once, to cope with batch processing.

Note that these functions use one collection per entity to determine whether an id is available, so a call to isUniqueUserId(id) can influence the result of isUniqueUser(ent).

findEntity

findEntity(id : idtype) : Entity

This function returns the Entity with the given id value, null if it does not exist.

String property

For each String property in an Entity, a find function is generated (repace Property with the property name):

findEntityByProperty

findEntityByProperty(val : String) : List<Entity>

This function returns a list of all Entitys with the exact given Property value, an empty list if there are none.

findEntityByPropertyLike

findEntityByPropertyLike(val : String) : List<Entity>

This function returns a list of all Entitys with the given Property value as substring, an empty list if there are none.

Entity Name

Every entity has a name, which is always a string. This name can be retrieved by the automatically generated getName() function.

The name of an entity is determined as follows:

  1. If a property of the entity has the name annotation, the name of the entity equals this property. This property must be of type String.

  2. If a property of the entity is called ‘name’ and is of type String, this property determines the entity name.

  3. Otherwise, the id of the entity (converted to its string-value) is used.

Example

A typical scenario where these functions come in handy is a create/edit page for an entity. In the following example the isUniquePage function is used to verify that the new page has a unique identifier property:

entity Page {
  identifier :: String  (id, validate(isUniquePage(this), "Identifier is taken")
}
define page createPage(){ 
  var p := Page{}
  form{
    label("Identifier"){input(p.identifier)}
    action("save",save())
    action save(){
      p.save();
      message("New page created.");
      return home();
    }
  }  
}