Found Pages
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
}
Storing data in the session context on the server is done using session entities. Example:
session shoppingcart {
products -> List<Product>
}
A session entity name is a globally visible variable in the application code. The entity object is automatically instantiated and saved, one for each browser session accessing the application.
Typically, session data is used for keeping track of authentication state, but it can also be used for temporarily storing data for anonymous users. A common oversight with session data is that it is shared between tabs in a browser.
Declaring an access control principle, e.g. principal is User with credentials name,password, automatically creates a securityContext session entity. For more information about access control see the Access Control section.
Session entities can also be extended with extra properties. Example:
extend session shoppingcart{
lastSearchQuery :: String
}
Session data times out by default, this timeout length can be adjusted in the application.ini file, e.g. sessiontimeout=10080. This time is specified in minutes. More information about application settings is shown on the Application Configuration page.
Entities can inherit properties and functions from other entities, like subclassing in Object-Oriented programming.
Example:
entity Sub : Super {
str :: String
}
entity Super {
i :: Int
}
function test(){
var e1 := Sub{ i := 1 str := "sdf" };
var e2 := Super{ i := 1 };
}
Subclass entities can be passed whenever an argument of one of its super types is expected.
Example:
function test(){
var e1 := Sub{ i := 1 str := "sdf" };
test(e1);
}
function test(s:Super){
log(s.i);
}
Checking the dynamic type of an entity can be done using isa and casting is performed using as.
Example:
function test(s:Super){
if(s isa Sub){
var su :Sub := s as Sub;
log(su.str);
}
}
When specifically want to call a function from the Superclass, use the ‘super’ keyword.
Example:
entity Sub : Super {
function foo() : Int {
return super.foo();
}
}
entity Super {
function foo() : Int {
return 42;
}
}
Instantiating new entity objects is done with the following expression:
Entity{ [property := value]* }
The entity name followed by an optional list of property assignments between curly brackets.
Example:
User{}
User{ name := "Alice" }
User{ name := "Bob" age := 34 }
Default initialization (what you would put into the constructor of an object in e.g. the Java programming language), can be added by extending the constructor function that is implicitly called.
Example:
entity A : B{
extend function A(){
name := name +"A";
}
}
entity B{
extend function B(){
name := name +"B";
}
}
test constructors {
var t := A{};
assert(t.name == "BA");
}
Creating an empty entity which doesn’t call the constructor extensions can be done using createEmptyEntity, e.g. createEmptyUser()
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)
}
Represents an ordered list of items of a certain type. Example:
var l : List<Int> := [1, 2, 3, 4];
Sorted output of lists can be created using the for loop filter in templates or actions:
for(u:User in [u1,u2,u3] order by u.name desc){
output(u)
}
Fields
length
Gives the number of items in the list.
List Creation Expressions
List()
Creates an empty list of type Entity.
List(…, Entity, …)
Creates a list of type Entity with the elements resulting from the comma separated argument expressions.
Example:
var list := List<User>(User{},SubSubUser{},SubUser{})
[Entity, …]
Creates a list of type Entity (type of first element) with the elements resulting from the comma separated expressions between the [ ].
Example:
var list := [User{ name := "test" },SubUser{},uservar]
Functions
add(Entity)
Adds the entity to this list.
remove(Entity)
Removes the first occurence of Entity in this list.
clear()
Removes all entities in this list.
addAll(List/Set)
Adds all entities of the List/Set to this list.
set() : Set
Creates a Set containing the unique elements in this list.
indexOf(Entity) : Int
Returns the index of the first occurence of Entity in this list. Returns -1 if the Entity is not in this list.
get(Int)
Returns the element at location Int in this list.
set(Int,Entity)
Sets the list element at Int to Entity. If the Int is not within bounds, nothing is set, and a warning is shown in the log.
insert(Int,Entity)
Inserts the Entity at location Int in this list. If the Int is not within bounds, nothing is set, and a warning is shown in the log.
removeAt(Int)
Removes the element at location Int in this list. If the Int is not within bounds, nothing is set, and a warning is shown in the log.
subList(from:Int,to:Int):List
Returns a portion of this list between the specified from, inclusive, and to, exclusive.
Represents an unordered collection of unique items of a certain type. Example
var s : Set<Int> := {1, 2, 3, 4};
Sorted output of sets can be created using the for loop filter in templates or actions.
for(u:User in [u1,u2,u3] order by u.name desc){
output(u)
}
Fields
length
Gives the number of items in the set.
Set Creation Expressions
Set()
Creates an empty set of type Entity.
Set(…, Entity, …)
Creates a set of type Entity with the elements resulting from the comma separated argument expressions.
Example:
var set := Set<Person>(Person{},SubSubPerson{},SubPerson{})
{Entity, …}
Creates a set of type Entity (type of first element) with the elements resulting from the comma separated expressions.
Example:
var set : Set<Person> := {Person{ name := "test" },personvar}
Functions
add(Entity)
Adds the entity to this set.
remove(Entity)
Removes Entity in this set.
clear()
Removes all entities in this set.
addAll(List/Set)
Adds all entities of the List/Set to this set.
list() : List
Creates a List containing the elements in this set.
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:
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.
If a property of the entity is called ‘name’ and is of type String, this property determines the entity name.
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();
}
}
}
input(<expression>) creates an input form element. Can be applied directly to the properties of an entity (e.g., input(user.name)) or to page variables.
Input widgets are determined by the type of the property passed to the
input template call:
- String, Email, Int, Float, URL, Patch -> textfield
- Text, WikiText -> textarea
- Bool -> checkbox
- Date, DateTime, Time -> date picker
- List, Set -> multiselect (bug: List actually requires
a different type of input, to allow duplicates and control ordering) - Entity -> select
For example, to get a checkbox, use:
define root(){
var x : Bool := false
form{
input(x)
submit action{ log(x); } { "log result" }
}
}
or:
entity TestEntity {
x :: Bool
}
define editTestEntity (e:TestEntity){
form{
input(e.x)
submit action{ } { "update entity" }
}
}
The ‘name’ property is special, it is declared for each entity. By default it is a derived property that simply returns the id of the entity (which is also a special property declared for each entity, id:UUID is set automatically).
The name can be customized by declaring a real name property:
name :: String
Or derived name property:
name :: String := firstname + lastname
Or by declaring a property as the name using an annotation:
someproperty :: String (name)
The name property is used in input and select template elements to refer to an entity. Example:
application exampleapp
init{
var u := User{};
u.save();
u := User{};
u.save();
u := User{};
u.save();
}
entity User{}
entity UserList{
users -> List<User>
}
var globalList := UserList{}
define page root(){
for(u:User in globalList.users){
output(u.name) //there is always a name property
}
form{
input(globalList.users) //this will show three UUIDs as options
submit("save",action{})
}
}
If the name is not a real property, you cannot create an input for it or assign to it.
