CNG - Scripting API

CNG - Scripting API

Scripting API

CNG uses Groovy for scripting. Groovy is build on top of Java and you can read more about it here.

Depending on the context different types of data will be available for manipulation. Below follows a listing of what properties are available in different contexts.

1. String based data type validation code

 

 

value

Value to validate. Value can currently be an instance of BigDecimal, BigInteger, String or an Object representing the date (see 1.1 section for more details).

2013/04/04
-----------------
supported.

1.1 properties available for date object

 

 

year

year as integer

month

month as integer (1-12)

day

day a integer (1-31)

asString

string representation. will take the form of "Y-MM-DD". e.g: 2013-04-01

format("Y/MM/DD")

formats the date according to the passed in format

The actual java type of the object used for representing date is 'no.more.cng.core.service.scripting.form.DateObjCtxObj.java'. However for groovy scripts this has been given the alias 'DateObj'.

So within scripts following is possible.

// assuming 'value' variable refers to a date object if (value instanceof DateObj) { /* if check would pass for a object representing date */ }

1.1.1 Clarification of date format

The date format uses the following special characters: Y, M and D to represent year, month and day. Repeating the same character more than once means that you set the minimum number of digits to use for those items. Stating YYY means you want year to be expressed as at least three characters, if it needs more then that then it will take that space. In the case of year, since DateObj can only express Gregorian dates (year 1582 October 15 - year 9999 December 31), that means a year will always take four characters no matter what. This is a difference to other date formats, but means there can be no ambiguity when expressing a date. Does 13 mean e.g. year 1913 or 2013 is thereby a situation that can never happen. Note also that using e.g. YMMDD is an ok date format even though there are no separators given that month and day never uses more than two characters.

2. Scripting within a form

You currently can use scripting within a form for the following:

  • Question input validation

  • Display condition on question

  • Display condition on question group

  • Display condition on page elements

  • Form answer population on Form Population Button

Some properties are available in all contexts, while other are available only in some. Below follows a list of examples for what properties are available.

2.1 General properties for all form contexts

 

 

Q42.answer

Get answer of Q42, where Q42 is a value input question. If there are multiple instances of Q42 you will get the closest match. If Q42 resides in a different question group you will get the version in the first instance of that question group, e.g. QG1[0].QG2[0].Q42. Note that both Q42 and answer can be null (might want to use the safe navigation operator like: Q42?.answer).

2013/01/09
----------------
supported.
If referenced from within a group will point to Q42 inside that group else will move up parent groups finding Q42.

QG1.Q42.answer

Gets closest instance of QG1 and the answer to Q42 for that question group, provided there is one. If QG1 resides inside another question group you will get the first occurrence of QG1 in same way as per questions.

2013/01/09
-----------------
syntax is supported.
However since there can be multiple instances of QG1, "QG1.Q42.answer" points to a list.
QG1.QG2.Q3.answer points to a list of lists.
Following groovy provided list methods can be used to work with this list
QG1.QG2.Q3.answer.flatten() - provides a list of answers inside all instances of QG2 insides all instances of QG1
QG1.QG2.Q3.answer.flatten()[0] - first Q3 answer
QG1[0].QG2[1].Q3 - Q3 inside second instance of QG2 which is inside first instance of QG1

formAnswer.Q42.answer

Gets answer to first occurrence of Q42 in form. Note difference to just using Q42 is that if e.g. Q42 resides inside question group QG1, and QG1 is a root question group, then with form.Q42 you will always get same as formAnswer.QG[0].Q42. If you however use Q42 only and you reference it from a question, or other item, within QG[2], then you will get form.QG[2].Q42.

2013/01/09
-----------------
supported
formAnswer.Q42.answer - first occurrence of Q42

formAnswer.questionAnswers

Returns a set of all question answers at root level of form. TODO: In addition to being able to iterate through question answers, it should ideally be possible to write formAnswer.questionAnswers.Q42, which should return Q42 only if it is present in the set.

2013/01/09
----------------
supported. However for iteration have to use "formAnswer.questionAnswers.values()"
formAnswer.questionAnswers.Q42 - points to Q42 at root level

formAnswer.questionGroupAnswers

Returns a set of lists of all question group answers at root level of form. TODO: In addition to being able to iterate through question group answers, it should ideally be possible to write formAnswer.questionGroupAnswers.Q42 or formAnswer.questionGroupAnswers.QG1, which should return Q42 or QG1 respectively, only if they are present in the set, or any question group answer below this question group answer set. Note that if e.g. Q42 exists only at root level it should not be found via formAnswer.questionGroupAnswers.Q42.

2013/01/09
----------------
supported. However for iteration have to use "formAnswer.questionGroupAnswers.values()"

form

This will return an object representing details of a particular form version. Please refer section 2.3 for what properties/methods can be invoked on this.

  • There will be more properties available later, like e.g. being able to iterate through form views.

2.2 Additional properties for questions

 

 

question.id

Id of question, e.g. "Q12". *

2012/11/14
------------------
supported

Q42.answer

Get answer of Q42, where Q42 is a value input question. If there are multiple instances of Q42 you will get the closest match. If Q42 resides in a different question group you will get the version in the first instance of that question group, e.g. QG1[0].QG2[0].Q42. Note that both Q42 and answer can be null (might want to use the safe navigation operator like: Q42?.answer).

2013/01/09
----------------
supported

QG1.Q42.answer

Gets first instance of QG1. If QG1 resides inside another question group you will get the first occurrence of QG1 in same way as per questions.

2013/01/09
-----------------
syntax supported. However QG1.Q42.answer gives a list of answers.

Q12.question.label.text

This gives the localized question label for the language used by the respondent. i.e: if the workflow version has the language set to 'nob' this will return the question text for 'nob' language.
See section (2.2.4) to see what properties of the returned object can be accessed further

2013/04/08
-----------------
supported

Q12.question.label.getText()

Same result as invoking "Q12.question.text". See section (2.2.4) to see what properties of the returned object can be accessed further

2013/04/08
-----------------
supported

Q12.question.label.getText("eng")

This gives the localized question label for the language code passed in. If null is passed default value will be returned. See section (2.2.4) to see what properties of the returned object can be accessed further.

2013/04/08
----------------
supported

  • Questions will later expose more values than just the id.

2.2.1 Additional properties for questions of type value input

 

 

answer

Answer value that may be an instance of BigDecimal, BigInteger or String. Note that answer value will be null, from the scripts point of view, if empty or if a value that does not match the data type is filled out.

2012/11/14
---------------
supported

2.2.2 Additional properties for questions of type single select

 

 

answer?.value

Answer value that may be an instance of BigDecimal, BigInteger or String. Note that use of ? might be advisable on this in many cases, since if you use this on questions an answer might not yet have been selected. *

2012/11/14
----------------
supported

answer?.label.text

gets the label value of the option localized according to the respondent users language. Returned object will be a String

2013/04/08
----------------
supported

answer?.label.getText()

gets the label value of the option localized according to the respondent users language. Returned object will be a String

2013/04/08
----------------
supported

answer?.label.getText("eng")

gets the label value of the option localized according to the passed in language code. Returned object will be a String

2013/04/08
----------------
supported

  • The syntax difference from value input questions is because it is intended that the option label later also will be made available.

  • "?" is groovy null safe operator. Use of it prevents null pointers from happening when answer is null (i.e: when no answer has been selected)

2.2.3 Additional properties for questions of type multiple select

 

 

answers

Answer values, which is a list of objects having similar properties to that of single value questions (refer section 2.2.2). To get answer value with index one you could e.g. do: answers[0].value.

2012/11/14
------------------
supported. as per normal groovy list behavior following syntax yield stated output
answers.value - list of answer values
answers.value[0] - first answer value
answers[0] - first answer
answers[0].value - first answer value

2.2.4 Available properties for formatted text

 

 

plainText

returns the text without markup information.
e.g: Q13.question.text.plainText

html4

returns the text with html markup information.
e.g: Q13.question.text.html4

markup

return the original formatted text with markup information
e.g: Q13.question.text.markup

xml

return the formatted text without any markup information which can be used in a xml. i.e: xml sensitive characters would be escaped
e.g: Q13.question.text.xml

Formatted text is represented by no.more.cng.core.service.scripting.form.FormattedText.java. This is given the alias 'FormattedText' for groovy. So the following is valid.

if (Q13.question.label.text instanceof FormattedText) { // this check would pass because question label is in formatted text }

2.3 properties available to a Form

 

 

id

gives the id of the Form as a String. i.e: the repository item id

version

gives the version of the Form as a String.

Form is represented by no.more.cng.core.service.scripting.form.FormVersionCtxObj. This is given the alias 'Form' for groovy. So the following is valid

if (form instanceof Form) { // above check would pass }

2.4 Form answer population script on a Form Population Button

A script can be specified for populating form answers on a 'Form Population Button'. Inside this script following syntax could be used for manipulating form answers.(Although the syntax is valid in other places, modified answers would not get reflected on the respondent GUI)

Value Input Questions
  1. For Questions with base data type as 'string' following statements are supported.

 

 

Q1.answer = "populated input value"

sets the answer to the given value

Q1.answer = 101

sets the answer converting the value to a string

Q1.answer = 201.13

sets the answer converting the value to a string

Q1.answer will always return either a string or null.

  1. For Questions with base data type as 'integer' following statements are supported

 

 

Q2.answer = 101

sets the answer to the given value

Q2.answer = "101"

sets the answer converting the value to BigInteger. Exception if conversion fails

'Q2.answer' will always return either a BigInteger or null

  1. For Questions with base data type as 'decimal'

 

 

Q3.answer = 201.34

sets the answer to the given value

Q3.answer = "201.34"

sets the answer converting the value to BigDecimal. Exception if conversion fails

'Q3.answer' will always return either a BigDecimal or null

  1. For Questions with base data type as 'date'

 

 

Q4.answer = "2014-11-23"

sets the answer to the given value. converts the string value to Date. Exception if conversion fails. Format should be Y-MM-DD

Q4.answer = Util.currentDate()

sets the answer to the current date

Q4.answer = Util.newDate('2014-11-23', 'Y-MM-DD')

sets the answer to the given value. format can be specified as the 2nd arg. Exception if conversion fails.

Q4.answer = Util.newDate('2014-11-23')

sets the answer to the given value. uses Y-MM-DD format.

'Q4.answer' will always return either null or object representing the date.

Common for all value input questions

Instead of the static values shown above variables could also be assigned.

They will be converted to the correct data type and stored as the answer. Exceptions thrown if conversion fails (script will stop execution)

e.g: Q1.answer = Q4.answer

Single Select Questions

 

 

Q5.setAsAnswerValue('internal-option-value')

sets the answer to the given 'internal' option value. 

Q5.answer = Q6.answer

valid only if both Q5/Q6 are single select questions

In all cases passed in value will be converted to the correct data type. Exception if conversion fails.

Multi Select Questions

 

 

Q6.setAsAnswerValues('internal-option-value1','internal-option-value2')

sets the answers to the passed in internal option values

Q6.setAsAnswerValues(internalAnswerValueList)

'internalAnswerValueList' is a variable of type 'java.util.List'. sets the answers to these values

Q6.answers = Q7.answers

valid only if both Q6/Q7 are multi select questions

In all cases passed in value will be converted to the correct data type. Exception if conversion fails.

Removing question group instances

 

 

QG1[0].markAsRemoved()

marks the first instance of QG1 as to be removed. 'markAsRemoved()' method available for any question group.

Adding question group instances

 

 

QG1[0].addGroupInstance('QG2')

adds an instance of 'QG2' into QG1[0]. 'addGroupInstance' method is available for any question group

addGroupInstance('QG1')

adds an instance of 'QG1'. i.e: QG1 is a root question group (it is not inside another question group)

3. Transformation scripting on input variables

 

 

value

Value to transform. Value can currently be an instance of BigDecimal, BigInteger or String.

2012/11/14
----------------
supported

  • It is likely that we will allow other types of inputs for the transformations later. What is stored regarding the inputs to these therefore needs to reflect where the inputs comes from like e.g. acticity_1.Q15.answer or activity_1.Q16.answer.value. We e.g. currently do not support multi-select questions.

4. Text generation scripting

Here the user is in control over what names input variables become available under. For examples below input has been used, but this could be anything as long as it does not brake naming constraints. Please also note that for text generation Groovy scriptlets are used, i.e. you need to use  <% and %> for code blocks. You can read more about text generation here.

4.1 Utility methods

 

 

Util.escapeXml(value)

Escapes characters so that value can be used safely in an XML-tag.

Util.escapeHtml4(value)

Escapes characters so that value can be used safely in an HTML4 document.

Util.currentTime()

Gets current system time as a String in standard format: "yyyy-MM-dd HH:mm:ss.SSS". Output could be e.g. "2012-11-14 15:47:54.365"

Util.currentTime(format)

Gets current system time as as String in given format. Input argument follows Javas SimpleDateFormat, with locale set to US.

Warning: Don't use this, this will be removed in future versions of CNG, or at least changed to use a different date formatting format!

4.2 String based data type input variables

 

 

input

Variable of type BigDecimal, BigInteger,String or object holding date information(see 1.1) matching the data type of the input variable. Variable may also be null.

2012/11/14
----------------
supported

4.3. Form answer input variables

This should work for the most part in exactly the same as when scripting in forms. Basically only difference is that you are not working from a form item perspective. It is therefore e.g. not possible to get the current question or answer.

 

 

input.Q42.answer

Get answer of Q42, where Q42 is a value input question. In case of multiple answers the first occurrence will be fetched.

input.QG1.Q42.answer

Gets first instance of QG1, i.e. the one with index zero, and the answer to Q42 provided it resides in that question group. Note that if Q42 resides in a different question group this will return null regardlessly.

input.QG1[2].Q42.answer

Gets the QG1 instance with index 2 and answer to Q42 for this question group, provided it is available.

input.questionAnswers

Returns a set of all question answers at root level of form in same way as for form scripting.

input.questionGroupAnswers

Returns a set of lists of all question group answers at root level of form.

input.form.id

Gives the form id (repository item id) as a String

input.form.version

Gives the form version as a String

2012/11/14

-----------------

supported functionality is similar to that in the 2.1 section since the same objects are being used.

Please refer FormAnswerVariable for more details on how to deal with variables of type FormAnswer.

4.4 Key-value source input variables

TODO

4.5 Session iterator input variables

TODO

5. File based data type validation function syntax

 

 

value

Value to validate. Value can only be a sub instance of FileAnswerCtxObj class.
General File Answer Context Attributes
--------------------------------------------------------
value.fileName
value.contentType
value.content.size()

TextBased File Answer Context Attributes
-----------------------------------------------------------
value.encoding
value.textReader
value.getTextReader("ISO-8859-1")

XMLBased File Answer Context Attributes
------------------------------------------------------------
value.encoding
value.textReader
value.getTextReader("ISO-8859-1")
value.XMLElements
value.XMLStreamReader
value.getXMLStreamReader("UTF-8")

PDFBased Answer Context Attributes
------------------------------------------------------
value.pdfVersion

Sample_1

// Text file specific operations reader = value.getTextReader("ISO-8859-1"); def val = reader.readLine() (val.contains("application.base.url") && value.encoding.equals("ISO-8859-1")) // PDF file specific operations (value.content.size() > 70000 && value.pdfVersion.equals("1.3") && value.fileName.equals("test.pdf") && value.contentType.equals("application/pdf"))

Sample_2

def line; def reader = value.textReader; while ((line = reader.readLine()) != null) { if(line.contains("Test Value")) { return true; } } return false;

Sample_3