Found Pages

Ajax

WebDSL provides ajax operations which allow you to easily replace an element or group of elements in a page without reloading the entire page. These operations can be used as statements inside actions or functions.

Ajax Operations

    replace(target, templatecall);
    append (target, templatecall);
    clear  (target);

    restyle (target, String);
    relocate(pagecall);
    refresh();

    visibility(target, show/hide/toggle);

    runscript(String);

The most commonly used operation is replace, for example:

action someaction() {
  replace(body, showUser(user)); 
}

This will replace the contents of an element or placeholder (see ajax targets section below) that has id ‘body’ with the output of the templatecall showUser(user).

runscript provides a way to interface with arbitrary Javascript code. Put .js files in a javascript directory in the root of your project and include it using includeJS in a template, e.g. includeJS("sdmenu.js").

Example: Moving a div around using JQuery animate:

runscript("$('"+id+"').animate({left:'"+x+"', top:'"+y+"'},1000);");

The refresh action is the default action; when no other interface changing operations are executed the browser will just refresh the current page. For example an input form which submits to a completely empty action results in the data being saved (default behavior) and the page being refreshed (default behavior).

Ajax Targets

There are three ways to target an ajax operation. target can either be

  • the name of an existing template
  • the name of a placeholder
  • or the id attribute of an object.
  • a String expression that creates the id, e.g. using a related entity object’s id property.

Placeholder with non-ajax default content:

placeholder leftbar { "default content" /* non-ajax default elements */ }

Placeholder with ajax default content:

placeholder leftbar ajaxTemplateCall() /* call to ajax template*/

Id attribute example:

table[id := myfirsttable] { /* non-ajax default elements */ }

When a template is used as target in an ajax operation, it must be declared with the ajax modifier.

Example:

define testtemplate(p:Person){
  placeholder testph{ "no details shown" }
  submit("show details",show())[ajax]
  action show(){
    replace(testph,showDetails(p));
  }
}
define ajax showDetails(person:Person){
  " name: " output(person.name)
}

Since an ajaxtemplate results in an extra entry point at the server, it must be explicitly opened when access control is enabled:

rule ajaxtemplate showDetails(p:Person){true}

DOM Event Handling

To invoke actions when an HTML event is fired, for example when pressing a key, event attributes can be defined on elements. The syntax of such an attribute is:

    <event name> := <action call>

W3schools.com provides an overview of all available events.

Example:

    "quicksearch: " 
    input(search)[onkeyup := updatesearch(search)]

The result is that the updatesearch action is invoked on the server.

Forms and Ajax

Typically you should not make a form cross an ajax placeholder. The server considers ajax templates as self-contained components similar to pages.

Example of proper usage:

define demo(){
  placeholder test()
}
define ajax test(){
  form{ input(someGlobal.name) submit action{} {"save"} }
}

Example of incorrect usage (the submit will be contained in a form on the client but not on the server):

define demo(){
  form{
    placeholder test()
  }
}
define ajax test(){
  input(someGlobal.name) submit action{} {"save"}
}

In some cases interaction between a regular form and ajax operations is not an issue, e.g. when the ajax template does not contain any input elements. The most common case is rendering validation messages in the form, this behavior is provided in the WebDSL library, see next section.

Ajax Input Validation

There are prebuild library components for creating inputs with ajax validation responses.

A simple example:

define demo(){  
  var s := "test"
  form{    
    inputajax(s)
    submit action{ log(s); } {"log"}
  }
}

See Ajax Validation

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

include Javascript

The includeJS(String) template call allows you to include a javascript file in the resulting page. Javascript files can be included in your project by placing them in a javascript/ directory in the project root.

Example 1:

define page root() {
  includeJS("sdmenu.js")
}

It is also possible to include a Javascript file using an absolute URL.

Example 2:

define page root() {
  includeJS("http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js")
}
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)
  }     
}
About

WebDSL is a domain-specific language for developing dynamic web applications with a rich data model.

Features

  • Domain modeling
  • User interfaces
  • Action code
  • Access control
  • Data validation
  • Ajax
  • Search
  • Email
  • Styling
  • Recurring tasks
  • Java class interface

Software

  • WebDSL applications are translated to Java web applications.
  • The WebDSL generator is implemented using Stratego/XT, SDF, and Spoofax.
  • IDE support for developing WebDSL applications is provided by the Eclipse plugin.

Developers

WebDSL is being developed by Eelco Visser and Ph.D./M.Sc. students in the context of the Model-Driven Software Evolution project at Delft University of Technology.


Active developers:


Non-active developers:

Native Class

Native Java classes can be declared in a WebDSL application in order to interface with existing libraries and code. If you want to use just one native function, see native function interface.

The supported elements are properties, (static) methods, and constructors. The supported types are

  • WebDSL type - Java type
  • Int - int or Integer
  • Bool - boolean or Boolean
  • Float - float or Float
  • String - String

Both the primitive type and the object types such as int and Integer can be produced by the WebDSL call (so overloading between these types is a problem here).

Add Java classes to a nativejava/ dir next to your app file and jar files in lib/.

Example:

native class nativejava.TestSub as SubClass : SuperClass {
  prop :String
  getProp():String
  setProp(String)
  constructor()
}

native class  nativejava.TestSuper as SuperClass  {
  getProp():String
  static getStatic(): String
  returnList(): List<SubClass>
}

define page root() {
  var d : SuperClass := SubClass()  
  output(d.getProp())

  var s : SubClass := SubClass()
  init{
    s.setProp("test");
  }
  output(s.prop)

  output(SuperClass.getStatic())

  for(a: SubClass in d.returnList()){
    output(a.prop)
  } 
}

(Example taken from compiler tests, source

https://svn.strategoxt.org/repos/WebDSL/webdsls/trunk/test/succeed/native-classes.app
https://svn.strategoxt.org/repos/WebDSL/webdsls/trunk/test/succeed/nativejava/TestSub.java
https://svn.strategoxt.org/repos/WebDSL/webdsls/trunk/test/succeed/nativejava/TestSuper.java

)

Passing a native java instance as page argument

If you want an instance of your defined native java class to be passed as page (or ajax template) argument, the class should be serializable for WebDSL. From WebDSL version 1.3.0 and on, support is added for doing this by implementing the following 2 methods in your java class. (If you use a class defined in a library, you may need to extend this class with the following methods)

public static YourClass fromParamMap( Map<String,String> paramMap )
public final Map<String,String> toParamMap()

note: The keys in the returned Map may only consist of character classes [A-Z][a-z][0-9], values may hold any value as they get filtered. On deserialization, the static fromParamMap method is invoked and its result is cast to the type as defined in the page/java template definition.

Examples can be found (notice the link ;)) in WebDSL’s source code itself.

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.