Extension scripts

What are extensions?

Extensions are additional lumps of data associated with nodes. Extensions can be associated with the node itself, with a member type, or an individual member. A typical use of an extension is to return a version of the data formatted for display.

Extensions can be read by any user with the appropriate read authority (read over the node or member type).

How extensions are retrieved

Extensions are retrieved by the Get Node and Set Node services.

Within scripts, node and member type extensions are also accessible through the ScriptNode object using the getExtension() and getMemberTypeExtension(). Member extensions are not currently accessible through scripts.

What extensions are created

The creation of node extensions is controlled by the node type. The creation of member type and member extensions is controlled by the member type. By default, the extensions are empty. However, if the node controller class CustomNodeControllerWithExtensionsScripts is used, then you can use scripts to create extensions. The node controller class is set automatically if you use the library.core package for creating types and member types.

The CustomNodeControllerWithExtensionScripts supports six different extensions scripts:

Type extension scripts are coded on the node type, member type and member extension scripts are coded on the member type.

The Edit versions of the extension scripts are called from the SetNode service, or when the scripting methods are called from a NodeScriptWritableOriginal object.

Returning data from extension scripts

Extension scripts return a single parameter in the 'return' attribute. This should be a string, or an XML message created using the service message writer, or an XPath Evaluator (for example, returned from another script).

If an XML message is returned, it should have a high-level tag of 'Extension'. Use the newServiceResponse() method of the script application to create the service message writer and set the return attribute:

var extension = application.newServiceResponse('Extension');
extension.put('content','Hello World');

When returning data through a XML message, extensions cripts use the same conventions as other scripts. Extensions can return:

  • title - title for the page (node extensions only)
  • content - content for the page
  • navigation - content for the navigation section
  • preamble - content for the preamble area (node extensions only)
  • head - content for the head section
  • redirect - redirect to be sent to the browser (node extensions only)
  • message - user message (node extensions only, for use with redirect)

If the extension script returns a string, it is presumed to be content.

application.put('return','Hello World');

Passing data into extension scripts

The method application.getContext() provides the node, as an object of type ScriptNodeWritableDerived.

When an extension script runs, various application attributes are preset:

  • contextNode - the node itself, as a ScriptNode
  • contextMemberType - the member type (member type and member extensions only), as a ScriptNode
  • contextMemberList - list of members (member type extensions only), as ScriptMember objects.
  • contextMember - the member (member extensions only), as a ScriptMember
  • request - request object, as an XPathEvaluator (with credentials removed)
  • response - response object written so far, as an XPathEvaluator. This is only populated when invoked from GetNode and SetNode, and its contents are variable. Use with caution.
  • userCurrent - the current user as a Script User. Note that only the user identifier, user logon reference and user credentials are available on the ScriptUser - other methods will return null.
  • userIdentifierCurrent - the current user, as a number
  • userLogonReferenceCurrent - the logon reference of the current user
  • memberTypePrefix - when called from SetNode, prefix for member type field name (member type and member extensions only)
  • memberPrefix - when called from SetNode, prefix for member field name (member extensions only)
  • mayUpdate - when called from SetNode, set to the string "true" if the user can update the member type, null if the user cannot update the member type (member type and member extensions only). Unlike the mayUpdateMember() method on ScriptNode, this also tests for the use of the protected tag and protected mode.

The request object can contain anything. When invoked from the GetNode and SetNode services, this contains the full request from the service call. In this way, the extension script can respond to parameters passed in the URL, which are passed onto the GetNode and SetNode services. When an extension is invoked from a script, the request object can contain any XML message. Often it will contain a single JSON string.

Although retrieving extensions through scripts involves one script calling another, it is not similar to other script invocations (include, execute and call). There are no shared objects between the caller and the callee, and they only communicate using XML and/or strings. This ensures that there is no accidental leakage of authority, and also ensures compatibility with other types of extension that are not created using scripts.

Caching results

The node returned from application.getContext() is of type ScriptNodeWritableDerived. This means that the extension script can modify the derived data of the node for which it is an extension. This can be very useful for caching data between calls, as in the following snippet, which caches the result of calling the getContent() function.

var context = application.getContext();
var memberType = application.get('contextMemberType');
var content = context.getValue(memberType);
if ( content == null ) {
  content = getContent();
  context.setValue(memberType,content);
}
application.newServiceResponse('Extension').put('content',content);

Because it is derived data, the cached data is automatically deleted when the data in the node, or any node on which it depends, changes.

When GetNode is used, member type extension scripts are called before the members are accessed. The member type extension script can therefore be used to perform "late derivation". This can be useful when the data member is always access but get node is called, for example in deriving content files.

Extension script permissions

Extension scripts run under the authority of the node owner, rather than the current user.

The userIdentifierCurrent parameter can be used to tailor the extension data to a particular user, for example generating HTML which only shows links that the user is authorised to use.

If the node owner is authorised, the script can use the userIdentifierCurrent parameter to run scripts on behalf of the current user.

Rules for displaying extensions

When used from GetNode and SetNode, the interaction between display classes, the presence of the extension element, and the extension show members display property is relatively complicated.

For node extensions:

  • The node extension is only shown when the Display Class (system.PROPERTIES.displayClass) is set to node-extension and there is an Extension element present. Both the property and the Extension need to be in place for the extension to be used. The Extension element need not contain any content, but its presence shows that an extension is being used.
  • All member types are suppressed if the Extension Show Members (system.PROPERTIES.extensionShowMembers) property is set to false, even if there is no <Extension> element. Tabs are not shown if member types are not shown.

This is summarised in the table below.

Type display classNode <Extension> elementType extension show members propertyOutcomeNode extension shownMember types shown
not 'node-extension' present or absent true or false No node extensions, member shown.  
'node-extension' absent true No node extensions, members shown.  
'node-extension' absent false No node extensions, members not shown.    
'node-extension' present false Node extension shown, members not shown,  
'node-extension' present true Node extension shown, members shown.

Assuming they are being shown, the rules for member type and member extensions:

  • The extensions are only shown if the Display Class (system.PROPERTIES.displayClass) property is set to member-extension, and there is an Extension element present.
  • Members are suppressed if the Member Type Extension Show Members (system.PROPERTIES.extensionShowMembers) property is set to false.
  • Member extensions are show if any member of the member type has an <Extension> element, otherwise member content is shown.

This is summarised in the table below.

Member type display classMember type <Extension> elementMember Type Extension Show Members propertyMember <Extension> elementOutcomeMember type Extension shownMember extension shownMember content shown
not 'member-extension' present or absent true or false present or absent No extensions shown, member content shown.    
'member-extension' absent true present Member type extension not shown, member extension shown in place of member content.    
'member-extension' absent false present or absent Member type extension not shown, no member content or extension shown.      
'member-extension' absent true absent No extensions shown, member content shown.    
'member-extension' present true present Member type extension shown, member extension shown in place of member content.  
'member-extension' present true absent Member type extension shown, member content shown.  
'member-extension' present false present or absent Member type extension shown, no member extension or member content shown.    

SetNode considerations

For SetNode:

SetNode extensions follow the same conventions as GetNode extensions. For a type extension script, the preamble is inserted above the edit form, and the content between the control section and the members. If extension show members is false, the tabs and members are suppressed, but the control edit and buttons are retained. (Tabs are not shown because they would have no effect.) It does not usually make sense to have a type extension script and extension show members set to false.

For member type edit extension scripts, the attribute memberTypePrefix is populated. This provides a prefix to add to member type fields so that they are correctly prefixed for the node update process. For example, the prefix might be "MemberType[4]/" for the fourth member type on the page.

Member edit extension scripts are also passed the memberTypePrefix, and a memberPrefix. The memberPrefix can be added to member fields so that they are correctly prefixed for the node update process. For example, the member prefix might be "MemberType[4]/Member[1]/" for the first member of the fourth member type.

Generally, it is difficult to envisage how a member edit extension script could work in anything but a simple case, and any use of the member edit extension script should be considered experimental. If new node edit UI structures are required, it is probably best to code them into a member type edit extension script.

If an extension with a top-level element of TargetList is created, which follows the same format as the target list in the node edit structure, then this will be used in addition to the target list for the member type. This can be used to provide node-specific or user-specific target lists.

In the example below, all the nodes in the same package as the node being edited are added to the target list.

/**
 * Add all the nodes in the same package to the target list.
 */

function main() {
  var context = application.getContext();
  // If context node is not set, do not create the extension
  if ( context == null ) {
    return;
  }
  var packageNode = context.getPackage();
  var packageContent = packageNode.listPackageContent();

  var targetList = application.newServiceResponse('TargetList');

  for ( var i = 0; i < packageContent.length; i++ ) {
    var siblingNode = packageContent[i];
    var fieldPrefix = 'Target[' + (i + 1) + ']/';
    targetList.put(fieldPrefix + 'nodeVersionIdentifierTarget',siblingNode.getNodeVersionIdentifier());
    targetList.put(fieldPrefix + 'nodeVersionReferenceTarget',siblingNode.getNodeVersionReference());
    targetList.put(fieldPrefix + 'nodeNameTarget',siblingNode.getNodeName());
    targetList.put(fieldPrefix + 'nodeDescriptionTarget',siblingNode.getNodeDescription());
  }
}
main();