OpenWGA 7.5 - OpenWGA Concepts and Features

Design and development » HDBModel framework » HDBModel in TMLScript

Event scripts

HDBModel Event Scripts are an important tool to define automatic functionalities, that should run on every create/update/delete operation in HDBModel. They help you to enforce the data schema you want to accomplish, enforce dependencies between documents and data fields as well as validate operations for a certain status.

Creating event script modules

You can create event script modules for either content documents of a given content class or storage documents of a given storage id.

All event scripts for either a single content class or single storage id are collected in an event script module. This is a regular TMLScript module that you place inside your designs system resources below a folder of name "wga/hdbmodel".

The name of the event script either matches the content class or the storage id, depending on what you want. So an event script module for contents of content class "project" from the example shown on page Basics would have this path:

/scripts/tmlscript/wga/hdbmodel/project.tmlscript

So the module name of this module, for referencing in WebTML (what you hardly ever do) , would be:

"hdbmodel:project"

An event script for the storage document of id "projects", the one which contains project documents, would simply have the path:

/scripts/tmlscript/wga/hdbmodel/projects.tmlscript

As you see, your content classes and the IDs of your storages should not collide, as they are used to identify the document type that is in charge.

This is all it takes. Once there is a TMLScript module that applies to the naming convention of event script modules for a certain content class or storage id it will automatically be used by HDBModel to execute event scripts for the respective document type.

Previous to OpenWGA 6.3 these scripts were placed in a folder "/scripts/tmlscript/hdbmodel" as the "wga" system resources folder did not exist back then. So on designs created for earlier OpenWGA versions you will find those resources there. OpenWGA still falls back to using this location for HDBModel event scripts.

Defining event scripts

The TMLScript code of an event script module gets actually used to create a custom TMLScript object. The event scripts need to be defined as public methods on this object.

The name of the method determines on which event it is called. For example, the "onCreate" event, discussed below, is defined the following way:

this.onCreate = function(e) {

  ... event code ...

}

Every event script receives a single parameter object of type HDBModelEvent. It offers the code any information about the currently running operation that it needs for its purpose.

Let's first discuss the event types that are available.

Simple (non-hierarchical) event scripts

Simple event scripts are available for both, content documents and storage documents.

There are two simple event scripts which can be defined:

onCreate gets called once a content of the given content class or a storage of the given storage id is created. The event is executed...
  • after the creation (obviously)
  • after HDBModel has initialized optional item defaults
  • after the data of an optional WebTML form that was given to the operation has been transferred
  • after an optional update process ran
  • but before any saving operation (unless the update process did one, obviously)

The purpose of onCreate therefor is to perform any initialisation actions on newly created contents and storages that need to be done programmatically. They for example could import some data from the parent content:

this.onCreate = function(e) {

  content().setItemValue("status", context("relation:parent-project").defaultStatus);

}

A frequent use case for of event scripts for storage documents is to prevent the storage from being visible on any navigator:

this.onCreate = function(e) {

  content().setHiddenFrom([content().DISPLAYTYPE_NAVIGATOR]);

}

onSave gets called every time a content of the given content class or a storage of the given storage id is saved. The event is executed.

  • before the data is actually saved to disk
  • but may be called multiple times on create/update processes. For example: Creating a content actually saves the document two times: Once after all data has been set on it and once after it has been published. This is due to the processes that run on the underlying WGAPI operations. So when onSave gets called it is sometimes possible that the current data has actually already been saved to disk by some earlier saving.

The purpose of onSave may be to:

  • Validate data modifications coming in from WebTML form or update processes. It is the right to determine if the document in its current status should actually get saved or is in invalid state.  if the script determines that it should not it should call the method hdbModelEvent.cancel() which cancels the operation and prevents persisting the changes:

this.onSave = function(e) {

  if (status == 'closed' || status == 'progress') {

    if (context("relation:assignedto", false) == null) {

e.cancel("You must assign the issue to a developer in this status");

    }

  }

}
  • Calculate derived data that should get stored on the  document as item or metadata. If for example the title of your content document should be built up from the data of one or multiple items then the onSave event is the place to calculate and set this title. For example, taking the title from the items "name" and "status":

this.onSave = function(e) {

  content().setTitle(name + "( " + status + ")");

}

As you see above, both simple event scripts onCreate and onSave run in the context of the created/saved document. Therefor you are able to directly read items and metadata in short form, also to follow relations of the document using context expressions.

Also both scripts should do no save operation on the created content itself. From their perspective saving happens implicitly.

Hierarchical event scripts

Hierarchical events run before and after a create/update/delete operation is performed. They are only available for content documents, not for storage documents.

Valid names for hierarchical events are built the following way:

  • Take the name of one of the available operations, starting with a capital letter: "Create", "Update" or "Delete"
  • Prefix it with a "pre" or a "post", depending on if the script should run before or after the operation

So resulting event names, and therefor event method names, are  "preCreate", "postUpdate" and the such.

The main purpose of "pre" events is the validation if the operation should happen at all given the current documents status. if the script determines that it should not it should call the method hdbModelEvent.cancel() which cancels the operation. Therefor these events are guaranteed to run before any data modification is done. However because of this they are not able to validate the data produced by the modifications. Validating these should be done on the simple script "onSave" instead.

The main purpose of "post" events is to execute subsequent functionalities that should be ran after the operation has been done. After the creation of a content you could for example create some default subcontent directly on it. Therefor these events are guaranteed to run after all modification is done, including automatic ones like building a storage substructure for new contents:

this.postCreate = function(e) {

   // Create default categories
  HDBModel.createContent("category", e.getContent(), {
      title: "Public",
      allowed: true,
     }
  );

  HDBModel.createContent("category", e.getContent(), {
      title: "Private",
      allowed: false,
    }
  );

}

As you see it is perfectly valid to create/update other documents in "post" event scripts via HDBModel methods. But you MUST NOT update the document on which the operation runs in any "post" event. Triggering an update would again invoke the event scripts which most likely would result in an endless recursion and stack overflow. You should avoid these situations, but as a "last resort" you could instead use the WGAPI method doc.save(), which does not trigger hierarchical events.

Events with the given names run directly on behalf of the content which is created/updated/deleted. 

However these events are called hierarchical because they also may be defined to run on behalf of any child content in the case that a content below the "current content" gets created/updated/deleted. For example: In the Basics example, you could define an event script for "project" documents which should get triggered when any "task" document below it gets created.

For this you simply have to suffix the desired event name with the content class of the child content, whose operations should trigger the script. This content class suffix should also be camel-cased and all characters not valid in JavaScript identifiers removed. So for the use case given above a method named "preCreateTask" would be defined in the "project" event module.

We could use this to validate, if the project is still "open" so new tasks make any sense:

this.preCreateTask = function(e) {

  if (content().getItemValue("status") == "closed") {

    e.cancel("Cannot create task for closed project");

  }

}

This would also work if "task" contents weren't immediate child contents of "project" contents and there were some other content class in-between.

Hierarchical event scripts normally run in the WebTML context of the content whose event script is called. So in the "preCreateTask" example above the "project" document is in context. The content on which the operation is actually done is available there from the event object under hdbModelEvent.content. In the "postCreate" example, where modified content and the content of the script are the same, the context of course lies on this document.

There are two exceptions to this WebTML context rule: "preCreate" and "postDelete". This is simply because the document of the script is not yet / not any more available at that time the event script runs. Therefor the following applies:

  • "preCreate" runs in the context of the reference document given to the create operation
  • "postDelete" runs in the context of the parent document of the deleted content

Order of execution

The order of event script execution is guaranteed to be the following:

  • Hierarchical "pre" event scripts in the ascending order or document hierarchy level
  • Optional functionalities, like transferring WebTML form data or executing a custom process
  • onCreate (if creation operation)
  • onSave (if creation/update operation. Maybe multiple times)
  • Hierarchical "post" event scripts in the descending order of document hierarchy level

So if a "task" document is created in the Basics example the following event scripts (if defined) get executed in the following order:

  • project.tmlscript : preCreateTask()
  • task.tmlscript: preCreate()
  • Document is created
  • WebTML Form data is transferred to the document (if form available)
  • Custom process is executed (if process available)
  • task.tmlscript: onCreate();
  • task.tmlscript: onSave()
  • Document is saved
  • task.tmlscript: postCreate()
  • project.tmlscript: postCreateTask()

The following script order would run for an update:

  • project.tmlscript : preUpdateTask()
  • task.tmlscript: preUpdate()
  • WebTML Form data is transferred to the document (if form available)
  • Custom process is executed (if process available)
  • task.tmlscript: onSave()
  • Document is saved
  • task.tmlscript: postUpdate()
  • project.tmlscript: postUpdateTask()
The following script order applies for deletion:

  • project.tmlscript : preDeleteTask()
  • task.tmlscript: preDelete()
  • Document is deleted
  • task.tmlscript: postDelete()
  • project.tmlscript: postDeleteTask()