Novell Home

Identity Manager Form Script API

Novell Cool Solutions: AppNote
By Rudy Duym

Digg This - Slashdot This

Posted: 19 Jul 2007
 

Introduction
    New Features
    New Controls
    Known Issues
    Frequently Asked Questions
    Frequently Occurring Script Errors
Workflow Form Script
WFS API
    Field Methods
    Form Methods
    IDVault Methods
    Event Methods
Best Practices
Detailed Use Cases
Advanced Examples
    JUICE SDK
    Dojo

Introduction

This guide demonstrates some common use cases for the new features in workflow forms that will be available with the IDM 3.5 Provisioning Application.

New Features

  • Workflow Form Script (WFS) - allows you to add Javascript to your form and fields and to capture load and change events
  • Dynamic properties - instead of hard coding a property value, you can now use WFS to dynamically assign property values
  • DAL Global Queries - allows you to define DAL queries, such as getting a list of users in a department or a list of cities in a state, that you will use to set list items in fields or to drive the object selector

New Controls

  • DNQuery - a specialized version of DNLookup that allows you to use a DAL Global Query definition to drive the object selector
  • DNContainer - a specialized version of DNLookup that allows you to specify a root container for the container picker
  • New properties in MVEditor - it can now support global query and root-dn- driven object picking
  • CheckBoxPickList - like the PickList, but uses check boxes instead of a select box
  • HTML Control - add images, data and template based markup to your form
  • Tabs - you can now use tabs in a form

Assumptions

This document supposes that you are familiar with the DAL (Directory Abstraction Layer), the DALE (DAL editor), and the Designer provisioning plug-ins.

IDM 3.5 and Designer 2 are the supported products.

Known Issues (for IDM 3.5)

IssueWorkaround
Clearing a field with setValues(??) throws an error: An error ''Expected string or array value for required parameter ''data values'' of function ''field.setValues()'''' was encountered while executing the script ''field.setValues("");' Bug 265203.Use form.setValues("name", [""]) or field.setValues([""])
Clearing a DNLookup control with a field.setValues(??) generates an invalid parameter error. Bug 265203.fire the clearDN() method on the control instance. Use this code:
JUICE.UICtrlUtil.getControl(field.getName()).clearDN();
Bug 251611 - Javascript added to the Submit Action is called twice in IE, once in FF. A patch is available from Support.This only occurs when the action buttons are displayed at the bottom of the form.
Workaround: show buttons at the top of the form or both at the top and at the bottom of the form.
Inclusion of an external script generates an empty tag. A patch is available from support.Install the patch or include the script yourself with the following script (in a script of the internal type):
document.writeln("<script type=\"text\/javascript\"
src=\"url-to-your-library-here\"><\/script>");
Setting a field to hidden from an action script hides the control but does not hide the associated line breaks. This results in some funny- looking pages.No workaround for now. You could write a function that would check whether all controls on the current table row are hidden, and in that case you could hide the table row as well. This method should be called each time you execute a hide/show on a field.
Newly created radio buttons or check boxes are not triggering onchange eventsThe workaround is to setup the onchange event capture after you have set the values for the field:
var ctrl = JUICE.UICtrlUtil.getControl("nameofcontroltobeupdated");
JUICE.WFASUtil.setupEventHandler(ctrl,
JUICE.WFASEvent.EVENT.ONCHANGE);

Frequently Asked Questions

1. Can I filter a field depending on the value of another field?
This is very easy; it's the best approach is to publish a custom event on the field where you wish to capture the change event. Next, on each field that you want to change the content of, you add a script for that custom event. In that script you can set the values for that field by using WFS.

2. Can I access other fields from within the onload event of a field?
Yes, you can. The onload event is executed only when ALL fields have been created.

3. Can I add a custom validation or treatment to an action button?
While you cannot directly add form script to an action button, you can "attach" a Javascript function to an action button using the form.interceptAction(). You can also hook your custom validation code into a field with form.addCustomValidation()

4. What events are exposed in WFS?
To keep things simple the only event that is exposed in the controls is the onchange event. In order to have a common behavior between IE and FF this is triggered when the users tabs away from the control for text entry based controls like Text, TextArea, DatePicker or when the the user selects a different entry for choice based controls like PickList, MVCheckbox, StaticList etc.

There is also an onload event that is available for each control. It is triggered at startup and can be used to set initial values, preselect entries.

5. When exactly is the onchange event fired?
The onchange event is fired once by the control as soon as all controls have been instantiated AND the onload script attached to the control have been executed.

All methods that changes the value(s) of a field, or that change the available list items, will trigger the onchange as well: field.setValues, field.select().

Changes that the user makes in the control can also fire events. For text entry controls like Text, TextArea and DatePicker the event is fired after you made a change and the focus leaves the field. For list based controls like PickList, StaticList, MVEditor, MVCheckbox the onchange event is triggered as soon as you change a selection, radio button or check box.

6. I notice that an onchange event is fired automatically when a control is instantiated.
This is done by design. This way we are sure that any onchange script attached to the control is executed. Imagine another control that shows content dependent of the first control. If we do not fire the onchange for the first control, the content of the second control will not be synchronized with the first one unless the user makes a change in the first control. In a general way, by doing this we ensure that all controls in the form will be synchronized once the form is loaded.

7. I notice that onload events are fired in an order that does not correspond to the order of the fields on the form.
This is due to the multi-threaded nature of the JUICE engine that controls the instantiation of the controls. No guarantee is given as to the order of instantiation of controls and the execution of events.

8. Can I hide/disable a field?
Yes, the WFS API is designed to make these things very easy for you. There is one thing to understand about hidden or disabled fields: they will still send their data back to the workflow engine, so make sure these hidden or disabled fields contain valid and consistent data!

9. Can I dynamically add fields to a form?
This does not really make sense, because the only field values that will be persisted by the workflow engine are those that you have defined in the form editor and mapped to the workflow data.

That said, you have access to the DOM and can therefore create any HTML node that you want. You can also use the JUICE SDK and the Dojo library that is used by JUICE. Remember that the data from these nodes will not be available in downstream workflow activities, but you can use them to store temporary data for use in your scripts.

10. Can I use my own Javascript libraries to add custom data validation?
Yes you can add script to the form definition either to include libraries or to define globally accessible variables or functions.

11. Can I make my own Ajax calls?
The IDVault exposes a function called execService. This allows you to make calls to Ajax services that must be registered with the UI Control Registry. There is more information about this in the "Using and Developing UI Controls" guide.

Frequently Occurring Script Errors

1. I get an error: "The script 'x' for dynamic property 'x' does not return a value"

The script for a dynamic property should always return a value for the property, such as in: <(function(){ return ?red?; })()>. This is unlike event scripts that execute a treatment and do not need to return a value.

2. I get a "Field xx not found or not instantiated yet" error in one of my scripts.

Make sure you specified the correct name. If the name is correct, it is possible that you are trying to access the field before it is instantiated and available to the scripts.

A possible reason for this: you cannot access the fields directly from within a script that is attached to the form unless you do this from within a function that will be called by one of the field scripts. That is because these scripts are executed before the fields are created.

Workflow Form Script

Prior to IDM 3.5, fields in workflow forms could be only populated with static information. Customers have asked for the ability to filter selection lists based on values in other form fields, or to be able to dynamically obtain picking lists based on context and previous selections.

IDM 3.5 allows customers to customize the workflow forms using a simple API. Supported operations are:

  • Query the directory by executing predefined queries
  • Obtain container enumerations and global list definitions
  • Hide/show/disable/enable/validate and change the content for form fields
  • Add your own javascript libraries to customize the data validation process
The idea is to allow the user to add actions to a form field; this will be done using the Workflow Forms designer. The general form of an action is:
on <event> do {actions}

The actions script is written in Javascript, allowing flexibility to add conditions, loop instructions, and user-defined functions.

To express dependencies between fields, this design proposes to use a named event model instead of using direct coupling between fields. So instead of saying:

for field1 on event "change" execute query/refresh list on field2

we would add the following actions:

field 1, onchange event fire event "x ", 
field 2, on event "x " query/refresh list

The advantage of this approach is that the treatment for a given field is coded in an event on that same field, thus allowing for cleaner usage. A script attached to a field x can, of course, read the value(s) of another field y. The event will be used to trigger scripts that listen to that event.

WFS API

The API exposes 4 objects:

  • Field - The current field, allows operations on the current field
  • Form - The workflow form, exposes form operations and gives access to the other fields in the form
  • IDVault - Gives access to the directory through the DAL. You can get the value of an attribute for a given entity, execute global queries, obtain container lists and execute Ajax services
  • Event - Used to pass information to a script that gets executed when a predefined event or custom event occurs

Legend

Methods marked "*" are part of a gray API and are not exposed in the Eclipse expression editor, only because they did not pass Quality Assurance, mostly because of time constraints. This does not mean that they do not work, but that there will no official support for these. Use them at your own risk.

  • /*array*/ means that the type of the parameter or result must/will be array().
  • /*string*/ means that the type must be string, etc.
  • /*string?*/ means that the parameter is optional. If the "?" is not present, the parameter is required.
  • /*string || array*/ means that both string and array types are supported

Field Methods

/*string*/ getName()

The name given to the field in the forms designer.


/*string*/ getLabel()

The label associated with the field. If no label is found, the method returns the name of the field.


fireEvent(/*string*/ name, /*any?*/ customdata)

Fires a custom event and passes along the name of the field. If you specify a value for customdata, it will be stored in the event. In the scripts that are listening to the triggered event, you can get the data with event.getCustomDate()


/*string*/ getValue()

Returns the (first) value. The type returned is always string; this is independent of the data type of the field.

If the field does not have a value, the method returns an empty string if text can be entered into the field (such as Text, TextArea, DatePicker, DNLookup etc). Or, it will be "undefined" if the control is choice-based (StaticList, radio buttons, check boxes etc). For dn type controls, this method always return the dn and never the display expression.


/*array*/ getValues()

Returns an array of type string containing the values. If no values are found, the array is empty (size = 0). For dn type controls, this method always returns the dn and never the display expression.


setValues(/*string || array*/ data-values, /*string? || Array?*/ display-values, /*boolean?*/keep-old-values)

Sets a value - multi-values are supported. This allows changing the available entries for list-based controls like StaticList, MVCheckbox, PickList, etc. By default, existing entries will be deleted unless "keep-old-values=true". For non-list-based controls, the display values parameter is ignored.

If you want to set or change the initial value of a field, you should do so in "onload" event. Note: This will trigger the onchange event for the current field.

Examples:

field.setValues("cn=jwest,ou=users,ou=idmsample-rudy,o=novell"); (// for a DNLookup
field.setValues(["tarzan@novell.com", "test@novell.com"]) // for an MVEditor
field.setValues(["W","B"],["White","Black"],true); // for a StaticList

enable()

Enable this field.


disable()

Disable this field. Note: A disabled field still sends its data back to workflow engine. Therefore, its content will be validated when submitting the form or when calling field.validate() or form.validate().


focus(/*string? || Array?*/ values)

If no values parameter is passed in, this method sets the focus to the underlying text field. For list- or choice-based controls, this sets the focus to the selected choice; or, when no selection was done, to the first choice. If a values parameter is passed and if the field is list or choice based, the focus is set on the choice(s) corresponding to the values parameter.

Note: If the values parameter is an array, only the first value is used to determine on which check box or radio button to set focus. Also, this method is without effect on disabled or invisible fields.


select(/*string? || Array?*/ values)

Highlights the entered text if the control is a Text, DNMaker, DatePicker or TextArea control; or, for list- or choice-based controls, the method selects the choice(s) corresponding to the values parameter. If no values are passed in, the first field option is selected.

Note: When an array of values is passed in only the first value will be considered. This method is without effect on disabled or invisible fields, and it will trigger the onchange event for the current field.

Examples:

field.select("a"); // select the option "a" in a StaticList
field.select(["choice1", "choice2"]); // preselects 2 choices in a PickList of MVCheckbox

activate(/*string? || Array?*/ values)

Combination of focus() and select(). Note: This method triggers the onchange event for the current field.


setRequired(/*boolean*/ isRequired)

Sets the field to required if isRequired is true or optional otherwise. Note: A required field that is empty will block the form submission.


hide()

Hide this field. Note: A hidden field still sends its data back to workflow engine. Therefore, its content will be validated when submitting the form or when calling field.validate() or form.validate().


show()

Show this field.


/*boolean*/  validate()

Trigger in-browser validation for the field. If you want to validate the data entered in a field as soon as the user navigates to another field call this method in the "onchange" event. This method returns "true" if validation errors were detected, or "false" otherwise.

Form Methods

Alert(/*string*/ msg)

Show a message in an alert box.


showMsg(/*string*/ msg, /*string? || Array?*/ par, /*string?*/ bId), 

showWarning(/*string*/ msg, /*string? || Array?*/ par, /*string?*/ bId), 

showError(/*string*/ msg, /*string? || Array?*/ param, /*string?*/ bId)

showFatal(/*string*/ msg, /*string? || Array?*/ param, /*string?*/ bId)

Add a message,warning, error or fatal error to the JUICE status DIV. Both normal and fatal errors will block form submission. The distinction between error and fatal error is that normal errors get resets just before form validation occurs, due to a form submit or a scripted form.validate(), whereas fatal errors are remembered and will therefore block the form submission unless you start all over again. So, a normal error will only block submission if it is generated during the validation phase. If you add normal errors during onload or custom events, these will be lost when the form is submitted.

Note: An error for a given control gets displayed only once - duplicate error messages are eliminated.

The "msg" parameter can either contain the text of the message itself or a key pointing to an entry in the resource bundle bId. These methods always try to find an entry with key msg. in the resource bundle with id bId. Or, it may be the default one that is used by all standard controls - com.novell.srvprv.impl.uictrl.UIControlJSRsrc - which contains the messages for the out-of-the-box controls. The resource bundle java class should extend java.util.ListResourceBundle.

The "param" parameter can be used to pass in replacements for stakeholders ({0}, {1}, etc) in message "msg". Note: If you want to add debugging messages to your script, it is better practice to use form.showDebugMsg() for that purpose.

Examples:

form.showWarning("my warning {0},{1}", ["value0","value1"]);
form.showError("my error", null, "com.acme.MyResourceBundle");

clearMessages()

Clears all messages produced with form.showXX(), from simple messages up to errors. Fatal errors can not be cleared!


enable(/*string*/ field)

See field.enable()


disable(/*string*/ field)

See field.disable()


/*string*/ getValue(/*string*/ field)

See field.getValue()


/*array*/ getValues(/*string*/ field)

See field.getValues()


setValues(/*string*/ field, , /*string || array*/ data-values, /*string? || Array?*/ display-values, /*boolean?*/keep)

See field.setValues(). Note: This will trigger the onchange event for the field.


/*boolean*/ validate(/*string?*/ fieldname)

Trigger the data validation either for the specified field or for the entire form if no field name was specified. This method returns "true" if validation errors were detected, or "false" otherwise.


submit(/*string*/ action)

Submit a form by simulating a click on the "action" button. The choices for a request form are SubmitAction and CancelAction. The choices for an approval form are ApprovalAction, RefusalAction, DenyAction, UpdateAction, CancelAction and CommentAction.


hide(/*string*/ field)

See field.hide()


show(/*string*/ field)

See field.show()


focus(/*string*/ field, /*string? || Array?*/ values)

See field.focus()


select(/*string*/ field, /*string? || Array?*/ values)

See field.select(). Note: This will trigger the onchange event for the field.


activate(/*string*/ field, /*string? || Array?*/ values)

See field.activate(). Note: This will trigger the onchange event for the field.


setRequired(/*string*/ field, /*boolean*/ isRequired)

See field.setRequired()


interceptAction(/*string*/ actionname, /*string*/ advice, /*function*/ advfunc, /*object?*/ advObj)

Allows the interception of the script that is attached to an action button. The function you pass in will be executed, based on the "advice" parameter. If the function is an instance method, you will have to pass the object in question in advObj. The choices for actionname for a request form are SubmitAction and CancelAction. The choices for actionname for an approval form are ApprovalAction, RefusalAction, DenyAction, UpdateAction, CancelAction and CommentAction.

The advice choices are:

  • Before - Your function will be called before the script attached to the button is executed.
  • After - Your function will be called after the script attached to the button is executed.
  • Around - Your function will be passed a parameter that allows you to decide whether to execute the script attached to the button. You can use anonymous or in-line functions for this.

In the example below, the submit action is intercepted, and the form is submitted only if the user replies "yes":

  form.interceptAction("SubmitAction", "around",
    function (invocation) {
      if (confirm( "Are you sure you want to submit ?")) {
         var result = invocation.proceed(); return result;
      }
    }
  );

Here is an example of using an instance method:

   var p = new PrintForm();
   form.interceptAction("SubmitAction", "around",
                                    p.printFormInterceptor, p
addCustomValidation=function(/*string*/ fieldname, /*string*/ advice, /*function*/ func)

Your custom validation function will be called before or after the build-in doValidate method of the control. This way, you can impose extra validation of the control's data.

Example:

window.myValid=function() { var val=field.getValue(); if (val && val=="1") form.showError("1 not valid");  };
form.addCustomValidation("assetProp",  "after", window.myValid);

/*string*/getLocale=function()

Returns the current locale. This can be used as input for all methods that support a locale parameter.


/*string*/getRBMessage=function(/*string*/ key, /*string? || Array?*/ param, /*string*/ bId)

This method tries to find an entry with a key of "key." in the resource bundle with id bId. The resource bundle java class should extend java.util.ListResourceBundle. The "param" parameter can be used to pass in replacements for stakeholders ({0}, {1}, etc) in the "msg" message.

Example:

var msg = form.getRBMessage ("mykey",  ["value0", "value1"], "mybundle");

/* Date */ stringToDate=function(/*string*/ sdate, /*boolean?*/ includeTime) 

Converts a date string to a Date. The format must correspond to the date form for the current locale, as used in the DatePicker. The value of a DatePicker control can be converted with this method.

Example:

form.showMsg("Date="+form.stringToDate(d,true));

/* string */ dateToString=function(/*date*/ date, /*boolean?*/ includeTime)

Converts a date to a string that can be stored in the DatePicker.

Example:

var d = form.dateToString(new Date(), true); form.setValues("hireddate", d);

/* boolean */ isValidDate=function(/*string*/ sdate, /*boolean?*/ includeTime)

Use this to validate the correct format for a date string.


showDebugMsg(msg)

Displays a message in the trace area towards the bottom of the form. This can be useful for debugging scripts.

IDVault Methods

/*array*/ globalQuery(/*string?*/ fieldname, /*string*/ key, /*object?*/ param)

Executes the predefined DAL query "key". If the field name is specified, the result of the query will be used to refresh the content of the field. If you specify a null value for the field name, it will be up to the form developer to use the result of the query.

The "param" is used as input to the query. It has the form {parname1:value,parname2:value}, where "value" can be a single value or an array. The first column of the result list (always a DN) is used for the data value; the second one is for the display label.

Examples:

IDVault.globalQuery("canchangepwd", "getsites");  // query without a parameter
IDVault.globalQuery("building", "getbuildings", {site:form.getValue("site")}); // query with one parameter
IDVault.globalQuery("room", "getrooms", {site:form.getValue("site"), building:form.getValue("building")}); // 2 parameters

/*array*/ containers(/*string?*/ fieldname, /*string?*/ rootdn, /*string?*/ scope, /*boolean?*/ showDN)

Gets a list of containers, scope subtree or same level. If the field name is specified the result of the query will be used to refresh the content of the field. If you specify a null value for the field name, it will be up to the form developer to use the result of the query.

If the rootdn parameter is empty, the root container for the default entity is used. If the scope parameter is empty, one level is used. Valid choices for scope are "o"(nelevel) and "s"(ubtree). If no rootdn is specified, the root container for the default entity is used (see the DAL editor). If the parameter showDN is true, the full DN is used for the display label; otherwise, the naming part (ou, dc, etc.) is displayed. The method returns an array with 2 entries: the first is an array with the resulting DN's, and the second entry is an array with the display labels.

Example:

IDVault.containers("assetProp2", null, "o", true);  
// set the entries in a StaticList to all containers directly under the root dn 
// of the default entity

/*array*/ globalList(/*string?*/fieldname, /*string*/ key, /*string?*/ locale )

Retrieves a global list identified by it's key from the DAL. Locale is optional; if not specified, the locale in the Http request will be used.

Example:

IDVault.globalList("dallist", "fr-departements", "en");

/*array*/ get(/*string?*/ fieldname, /*string*/ dn, /*string*/ entity_key, /*string || array*/ attribute_key)

This corresponds to the IDVault.get() function of the workflow script engine. It retrieves the value(s) of the attribute for the given entity. The result is an array of values.

If the field parameter is specified the result of the query will be used to refresh the content of the field. If you specify a null value for the fieldname it will be up to the form developer to use the result of the query.

Example:

IDVault.get("assetProp", dn, "user", "LastName");

/*array*/ execService(/*string*/ service, /*object?*/ param, /*string?*/ locale)
Executes an Ajax service; the result is used to refresh the content of the field. The service must be registered in the UI control registry. The first column of the result list is used for the display value and the second one for the data value.

Example:

var r=IDVault.execService("dnlookup2",params);
var res=r?r["_data"]["raw"][dn]["value"]:"error encountered";
field.setValues("IDVault.execService(\"dnlookup2\") :"+res);

Event Methods

/* string */ getOrigin()

Returns the name of the field from which the event was fired.


/* string */ getName()

The name of the event.


/* any */ getCustomData()

A form event can now transport user data: when firing a custom event, you can use this to send custom data along. You set the data with an extra parameter on the field.fireEvent() method. In the triggered script, use event.getCustomData() to access the data.

Best Practices

For a discussion of Best Practices, including data validation, script organization, and internationalization and localization, see the author's article:

http://www.novell.com/coolsolutions/feature/19298.html

Detailed Use Cases

Case 1: Filtered Form Fields (In-line)

Imagine 3 fields on a form:

  • Occupation - a dropdown list of occupations to choose from
  • Organizations - a dropdown list of organizations to choose from, depending on occupation
  • Sponsor - a drop down list of sponsors to choose from, depending on organization

The dependencies between the fields are as follows:

  • The organizations list needs to be filtered to show only organizations valid for the chosen occupation.
  • The sponsors list needs to be filtered to show only sponsors valid for the chosen organization.

For each field, a Global Query will be created in the DALE. These queries will be used to set up the list of items available in each field. The queries will be executed in the onload event for the first field and in the custom event for the second and third fields.

- Field occupation: On onload, load the items and fire a custom event on selection changed:

          <prop name="onload" type="event">
            <value><![CDATA[IDVault.globalQuery("occupation", "getoccupations");]]></value>
          </prop>
            <prop name="onchange"  type="event">
              <value><![CDATA[field.fireEvent("occup_sc")]]></value>
            </prop>

- Field organization: On custom event 'occup', refresh the list with an Ajax query and fire a custom event 'organ' on selection changed:

            <prop name="occup_sc"  type="event">
              <value><![CDATA[IDVault.query(?organization?, ?getorganizations?, 
			{occupation: form.getValue(?occupation?)});]]></value>
            </prop>
            <prop name="onchange"  type="event">
              <value><![CDATA[field.fireEvent("organ_sc")</value>
            </prop>

- Field sponsor: On custom event 'occup', refresh the list with an Ajax query:

            <prop name="organ_sc"  type="event">
              <value><![CDATA[IDVault.query(?sponsor?, ?getsponsors?', 
			{ organization: form.getValue(?organization?))]]></value>
      </prop>

Case 2: Filtered Object Picker (DN Selector)

The new control DNQuery, which is an extension of the DNLookup control, allows just that. The pick list will be the result of a Global Query execution. The query key and the parameters are stored in two extra properties:

    <field name="buildingquery">
      <control control-type="DNQuery" required="true">
        <props>
          <prop name="selector-querydef">
            <value>getbuildings</value>
          </prop>
          <prop name="selector-queryparam">
            <value>(function(){ return {site:form.getValue("site")}; })()</value>
          </prop>
          <prop name="display-entitydef">
            <value>WFASBullding</value>
          </prop>
          <prop name="display-exp">
            <value>Description</value>
          </prop>
        </props>
      </control>
      <display-label>DNQuery, dependent on site</display-label>
    </field>

Figure 1 - Filtered Object Picker example

Case 3: Filtered Object Picker (Container Selector)

In our tree, the companies are represented by OUs as well as the departments. Suppose we want to offer in a workflow a selection of all sub containers beyond the company-container, which is a DN in an attribute defined within an auxiliary class. Now we want to offer a Container-selection with a search base that points to the company.

A new container lookup control has been created for this purpose: DNContainer. This control takes its root dn either from the entity key or from the root dn specified in its properties.

Example: We want to use the DNContainer with a dynamic value for the root dn. This value is taken from another field called "rootdn".

    <field name="container">
      <control control-type="DNContainer" required="true">
        <props>
          <prop name="selector-entitydef">
            <value/>
          </prop>
          <prop name="selector-root">
            <value>(function(){ return IDVault.get(null, ?cn=xx,o=xx?, ?user?, ?company?); })()</value>
          </prop>
        </props>
      </control>
      <display-label>DNContainer</display-label>
    </field>

Case 4: State-Province with Static/Global DAL Lists

1. We have a single territory form field (that binds to a single attribute) and listens to changes to the country form field. The territory form field gets hidden in the onload event:

-If the country is U.S., then we populate the form field with the U.S. States choice list from the DAL i.e. IDVault.globalList(?territory?, "us-states");, and make the field visible;
- Else, If country is Canada, then we populate the form field with Canadian provinces from the DAL (i.e. IDVault.globalList(?territory?, "ca-provinces");) and make field visible;
- Else clear and hide the field.

2. We have 2 different form fields, one for U.S. States and the other for Canadian province. Both are initially hidden by a script in the onload event. - If the country is U.S. we get the states choice list from the DAL i.e. IDVault.globalList(?states?, "us-states");, and make the field visible. - Else If country is Canada, we get the Canadian provinces from the DAL (i.e. IDVault.globalList(?province?, "ca-provinces");) and make field visible. - Else clear and hide both fields

Case 5: Setting Initial Field Values, Preselecting Items

While you still have to bind your form fields to the data flow, you can now very easily initialize your fields within the form.

Example 1 - Setting the current date in a DatePicker control:

form.setValues("mydatefield", form.dateToString(new Date(), true));

Example 2 - Replacing the entries in a PickList with new values:

field.setValues(["cn=xx,ou=idmsample,o=novell","cn=yy,ou=idmsample,o=novell"]);

Example 3 - Preselecting (highlighting) a given item in a choice-based control such as the PickList:

field.select("cn=Sales,ou=groups,ou=idmsample,o=novell");]]

Case 6: Adding Treatment to Submit, Approve and Other Action Buttons

One way of doing this without breaking the underlying action is by using the form.interceptAction method:

			  form.interceptAction("SubmitAction", "around", 
			    function (invocation) { 
				if (confirm( "Are you sure you want to submit ?")) { 
                           var result = invocation.proceed(); return result; 
                         }
                       }
                     );

In this example, we intercept the standard submit action in a request form to ask for confirmation. If it is confirmed, we indicate that treatment should proceed by calling the proceed() method on the invocation argument.

Case 7: Closing a Request Form based on a Directory Query

Note: See also the Cool Solutions article on this topic.

Suppose you want to close a request form upon a certain condition. The following example gets the value of an attribute for a user, depending on the condition (a global query, a value of a control present on the form, the result of an Ajax service call, etc.).

Add the following in line script to your form:

      function checkExists(form,IDVault) {
        var v = IDVault.get(null, "cn=admin,ou=idmsample-rudy,o=novell","user","FirstName");
        if (v) {
          alert("user already exists, closing request form");
          form.submit("CancelAction");
          return;
        }
        form.showMsg("Validation ok, please proceed");
      }

Call the script from one of the onload events; any control will do (except Title, it does not have a browser-side object representation):

checkExists(form,IDVault);

Case 8: Checking Uniqueness of Attributes while Creating Objects

You can easily check the uniqueness of a UID or CN by making IDVault.get() calls or by executing your own predefined Global Query.

Below is an example of a script that is called when the submit button is clicked. It calculates a common name, based on the first x characters of the first name along with the last name. It then queries the directory until it finds a free CN:

		window.computeCN=function (invocation) { 
	var fn = form.getValue("fn");
	var ln = form.getValue("ln");
	var dn,cn;
	for (var i=0;i<fn.length;i++) {
	  cn =(fn.substring(0,i+1)+ln).toLowerCase();
	  form.setValues("cn", cn);
	  dn="cn=" + cn + ",ou=users,ou=idmsample,o=novell";
	  form.showMsg("checking existence of dn " + dn);
	  var v = IDVault.get(null, dn,"user","FirstName");
	  if (v && v!= null) {
	    form.showMsg("dn " + dn + "already exists:"+v);
	  } else {
 	    invocation.proceed();
	    form.alert("Found a free DN: " + dn);
	    break;
	  }
	}
    };
    form.interceptAction("SubmitAction", "around", window.computeCN);

Figure 2 - UI Control Preview

Case 9: Custom Validation

The best way of doing custom validation is to "hook" your custom code into the doValidate function of a control. By doing this your custom code will be called whenever validation is needed, either upon form submission or by a scripted call to form.validate() or field.validate().

Example: for a Text field called ?assetProp? I want to exclude the value "1".

     form.addCustomValidation("assetProp", "after", 
         function() { var val=field.getValue(); if (val && val=="1") form.showError("1 not valid");  }
     );

Case 10: Using the HTML Control

You can include static HTML, like images and text or tabular data:

Figure 3 - HTML Request form

You can also use Javascript Templates to generate sophisticated HTML, based on data that you retrieved either through a web service or with an Ajax call:

Figure 4 - HTML control for Container List

The script behind this is:

    (function(){
      var data={};  // container for all data that will be used in the template
      // Get the data
      var params={ent:"user"};
      data.rs=IDVault.execService("getcontainers", params); // get a list of containers
      // Optional, add other data for use in template
      // data.xx = xx
      // Get the template, URL is relative to the Dojo location, which is in <WAR name>/javascript/dojo
      // The SDK uses a local cache to store these templates !  
	  // The templates used by the UI controls and by this example are in 
      // <WAR name>/javascript/JUICE/controls/templates
      var temp = JUICE.UICtrlUtil.getTemplate (dojo.uri.dojoUri("../JUICE/controls/templates/Container_l.html"), "cont_tmpl1");
      // Generate html
      var html = JUICE.UICtrlUtil.processTemplate(data, temp);
      return html;
    })()

The template is based on Javascript templates (http://trimpath.com/project/wiki/JavaScriptTemplates). Here is the source:

<div class="nv-fontSmall">
    Container List
</div>
<table border="2" cellspacing="2">
  <tr>
    <th class="nv-fontExtraSmall">Short Name</th>
    <th class="nv-fontExtraSmall">DN</th>
  </tr>
    {var i=0}
    {for p in rs._data.raw.map}
      {if p.map}
        <tr class="{if i%2==1}nv-table-row-odd{else}nv-table-row-even{/if}">
            <td class="nv-fontExtraSmall">
              <span style="padding-left:10px;text-decoration:blink;cursor:pointer">
                    ${p.map.disp}
              </span>
            </td>
            <td class="nv-fontExtraSmall">
              <span style="padding-left:10px;text-decoration:blink;cursor:pointer">
                    ${p.map.full}
              </span>
            </td>
        </tr>
      {/if}
      ${i++|eat}
    {forelse}
      <tr>
          <td>
              No containers were found
          </td>
      </tr>
    {/for}

</table>

Your templates must be stored in a WAR on the JBoss server were the User application is running, it can be in a different WAR than the one of the UA of course, always use a relative URL starting from the dojo location.

Case 11: Adding Tabs to your Form

You can include one set of tabs in your form; this can only be realized by a form script. The following script:

           var labels=[["Tab1", "Tab 1"], ["Tab2", "Tab 2"]];
            window.mytabs = new JUICE.TabsController(labels);
            window.mytabs.setHeight("300");
            window.mytabs.setWidth("500");
            window.mytabs.attachControl("Tab1", "title");
            window.mytabs.addLineBreak("Tab1");
            window.mytabs.attachControl("Tab1", "fn");
            window.mytabs.addLineBreak("Tab1");
            window.mytabs.attachControl("Tab1", "ln");
            window.mytabs.attachControl("Tab2", "cn");
            window.mytabs.addLineBreak("Tab2");

will produce something like this:

Figure 5 - Added tabs on a form

Case 12: Hiding a Field until a Task is Claimed

Note: See also the corresponding Cool Solutions article.

You can do this with a couple of lines of Javascript. The trick is that until a task is claimed the action buttons like approve, deny, etc. are not present.

So you can test for the presence of the approve buttons (top, bottom or both) and if not present execute some form script, such as hiding a field.

When the form is loaded the field will show up for a second or so but will then be hidden once the script is executed.

Add this to an onload event of one of the fields:

if (!dojo.byId("ApprovalAction") && !dojo.byId("ApprovalAction1") {
? // task is not claimed
? form.hide("afieldname");
}

Advanced Examples

Your scripts are, of course, not limited to the WFS API; you also have access to the DOM, the JUICE SDK, and Dojo. You can also include your own libraries and include calls to these in your scripts.

JUICE SDK

Accessing the Properties of a Control

First get a handle on the control:

var ctrl = JUICE.UICtrlUtil.getControl(field.getName());

Then access a property with this:

var value=JUICE.UICtrlUtil.getProperty(ctrl, ?myproperty?);

Adding a text button to a field

Use JUICE.UICtrlUtil.addButton(/*JUICE.BaseControl*/ ctrl, name, txt, alt, onClickScript, w, h).

Example of in-line Javascript:

var btn = JUICE.UICtrlUtil.addButton(ctrl, "mybtn", "A", "tooltip", "javascript:alert(\"clicked A\");");

Example of an in-line anonymous function:

var btn1 = JUICE.UICtrlUtil.addButton(ctrl, "mybtn1", "B", "tooltip",
	    function(){ alert("clicked B"); }
  	  );

Example of calling a global function:

              function fctC()
              {
                alert("clicked C");
              }
	var btn2 = JUICE.UICtrlUtil.addButton(ctrl, "mybtn2", "C", "tooltip", fctC);

Figure 6 - Example of an extra image and text button

Adding an Image Button to a Field

Use JUICE.UICtrlUtil.addImageLink (/*JUICE.BaseControl*/ ctrl, icon, alt, onClickScript, linkId) for this. It returns the element that has been added. The icons/images by default are situated in the WAR in the subdirectory resource/images.

Example of adding an image button next to a control that will call the global function MyFunc():

	var ctrl = JUICE.UICtrlUtil.getControl(field.getName());
              var ele = JUICE.UICtrlUtil.addImageLink (ctrl, "small-add.gif", "tooltip here", "javascript:MyFunc()", "mybuttonid");

Adding Print Form Functionality to your Request or Approval Forms

The library is called PrintForm.js, it is included in the UA. Here are the steps to add a preview to a form:

1. Include the library in the form, add external script with URL

./javascript/JUICE/form/PrintForm.js

2. To open the print form when doing a submit (after data is validated), add the following to the form onload event or one of the field's onload events:

    var pf = new PrintForm();
    form.interceptAction("SubmitAction", "around", pf.printFormInterceptor, pf);

3. To add a print button next to one of the fields, add this to the field's onload event:

    var ctrl = JUICE.UICtrlUtil.getControl(field.getName());
    var btn = JUICE.UICtrlUtil.addButton(ctrl, "printid", "Print", "Print",
                          "javascript:var p = new PrintForm(); p.printFormAfterValidation(\"printid\");");

You can also use the HTML control to generate the Print button. The Print popup window looks like this:

Figure 7 - Print Form detail

Note: There is a PrintForm library available for IDM 3.5; send the author an e-mail if you want this. The script to use this is also slightly different from what is documented here.

Dojo

Adding Custom Data Validation

The package "dojo.regexp" contains a number of regular expressions that can be of interest for data validation, beyond what is provided out-of-the-box with IDM 3.5.


Novell Cool Solutions (corporate web communities) are produced by WebWise Solutions. www.webwiseone.com

© 2014 Novell