

Last date modified: April 15 2025
Relativity Forms is a layout-rendering option, providing robust JavaScript APIs and a granular front-end page life cycle for easier, more controlled Relativity object customization.
In this lesson, you will learn how to complete these tasks:
Estimated completion time - 2 hours
Use the following steps to prepare your Relativity application for updates:
Use the following steps to set up the root folder, WikipediaForms, and create a PageInteractionEventHandler for this lesson:
Click Next to display the Configure your new project dialog.
Click Create.
Verify that the solution contains the PageInteractionEventHandlers project.
In the PageInteractionEventHandlers project, rename class file from Class1.cs to ArticleCategoryPageInteractionEventHandler.cs and rename the class within the file, if Visual Studio doesn't prompt to rename it for you.
Note: Relativity Forms ignores the PopulateScriptBlocks() method, so you need only return an empty kCura.EventHandler.Response.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using System;
using kCura.EventHandler;
namespace HelloWikipedia.PageInteractionEventHandlers
{
[kCura.EventHandler.CustomAttributes.Description("HelloWikipedia ArticleCategory Page Interaction EventHandler")]
[System.Runtime.InteropServices.Guid("13a13a13-a13a-1313-a241-a241a241a241")]
public class ArticleCategoryPageInteractionEventHandler : kCura.EventHandler.PageInteractionEventHandler
{
public override Response PopulateScriptBlocks()
{
return new kCura.EventHandler.Response();
}
public override string[] ScriptFileNames => new string[] { "articleCategory.js" };
}
}
Use the following steps to create a file for your event handler:
1
2
3
4
5
6
7
8
9
10
11
(function (eventNames, convenienceApi, privilegedEnvelope) {
var vars = privilegedEnvelope || {};
var eventHandlers = {};
console.log("Hello From Article Category.js");
return eventHandlers;
}(eventNames, convenienceApi, privilegedEnvelope));
// eof
Use the following steps to associate an event handler with an object type:
Note: Because you upload these files separately, they don't need to be embedded in your .NET project. See Add event handlers to applications.
In your workspace, associate the newly uploaded Page Interaction event handler with the Article Category object types as in Lesson 3 - Validate object changes.
.
Use the following steps to add functionality to the event handler:
Note: To view the GUIDs, navigate to the details view of the Hello Wikipedia application and click Show Component GUIDs in the Application console.
1
2
3
4
5
vars.GUIDS = {
SAMPLE_LAYOUT: "9f195c94-30aa-4e9c-9939-e4c5c05f6193",
FIELD_AUTOMATIC_UPDATES: "f365de2e-a641-428f-9188-a3970a7c308f",
FIELD_OVERWRITE_TEXT: "042e0329-1467-4993-8188-66615e103de3"
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
eventHandlers[eventNames.TRANSFORM_LAYOUT] = function (layoutData) {
// 1. Ensure we are only transforming the correct layout
var layoutGuids = this.layoutMetadataCopy.Guids;
if(layoutGuids.indexOf(vars.GUIDS.SAMPLE_LAYOUT.toLowerCase()) >= 0) {
// 2. Get and iterate the Fields (for the layout)
var fields = convenienceApi.layout.getFields(layoutData);
fields.forEach(function (field) {
// 3. Is the Field one of the Fields for which we're looking?
if (field.Guids.indexOf(vars.GUIDS.FIELD_AUTOMATIC_UPDATES.toLowerCase()) >= 0 ||
field.Guids.indexOf(vars.GUIDS.FIELD_OVERWRITE_TEXT.toLowerCase()) >= 0) {
// 4. If so, change the DisplayType to "Toggle"
field.DisplayType = "Toggle";
}
});
}
};
Final code for articleCategory.js file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
(function (eventNames, convenienceApi, privilegedEnvelope) {
var vars = privilegedEnvelope || {};
var eventHandlers = {};
console.log("Hello From Article Category.js");
vars.GUIDS = {
SAMPLE_LAYOUT: "ACF6E4CA-3074-4261-AD4B-1362B4D970AD",
FIELD_AUTOMATIC_UPDATES: "448DB41B-7A15-4428-A3F6-3E83E9DAA96F",
FIELD_OVERWRITE_TEXT: "A1D203B4-2A52-46B9-9077-F2EA8DB6C0FC"
};
eventHandlers[eventNames.TRANSFORM_LAYOUT] = function (layoutData) {
// 1. Ensure we are only transforming the correct layout
var layoutGuids = this.layoutMetadataCopy.Guids;
if(layoutGuids.indexOf(vars.GUIDS.SAMPLE_LAYOUT.toLowerCase()) >= 0) {
// 2. Get and iterate the Fields (for the layout)
var fields = convenienceApi.layout.getFields(layoutData);
fields.forEach(function (field) {
// 3. Is the Field one of the Fields for which we're looking?
if (field.Guids.indexOf(vars.GUIDS.FIELD_AUTOMATIC_UPDATES.toLowerCase()) >= 0 ||
field.Guids.indexOf(vars.GUIDS.FIELD_OVERWRITE_TEXT.toLowerCase()) >= 0) {
// 4. If so, change the DisplayType to "Toggle"
field.DisplayType = "Toggle";
}
});
}
};
return eventHandlers;
}(eventNames, convenienceApi, privilegedEnvelope));
// eof
If the layout didn't update, the browser may be caching the page. To correct this issue, use the development tools in the browser to clear the cache. In Chrome, use the following steps:
This step contains the following subsections describing how to set up your testing environment and run unit tests:
To run the JavaScript tests, download and install Node.js from nodejs.org. This lesson uses version 8 or higher.
Use the following steps to configure your project for unit testing:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// Create the package.json file in your project
npm init -y
// install karma test runner and plugins, including the testing framework, Jasmine
npm install karma karma-jasmine karma-chrome-launcher jasmine-core --save-dev
// Create karma.conf.js
node ./node_modules/karma/bin/karma init
// Provide the following answers. "> " means to leave it blank, simply pressing enter to skip the question.
Which testing framework do you want to use ?
Press tab to list possible options. Enter to move to the next question.
> jasmine
Do you want to use Require.js ?
This will add Require.js plugin.
Press tab to list possible options. Enter to move to the next question.
> no
Do you want to capture any browsers automatically ?
Press tab to list possible options. Enter empty string to move to the next question.
> ChromeHeadless
>
What is the location of your source and test files ?
You can use glob patterns, eg. "js/*.js" or "test/**/*Spec.js".
Enter empty string to move to the next question.
>
Should any of the files included by the previous patterns be excluded ?
You can use glob patterns, eg. "**/*.swp".
Enter empty string to move to the next question.
>
Do you want Karma to watch all the files and run the tests on change ?
Press tab to list possible options.
> no
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"name": "hellowikipedia-forms",
"version": "1.0.0",
"description": "",
"private": true,
"scripts": {
"test": "karma start --experimental-modules"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"jasmine-core": "^3.6.0",
"karma": "^5.2.0",
"karma-chrome-launcher": "^3.1.0",
"karma-jasmine": "^4.0.1"
}
}
Note: With this update, Karma serves all your JavaScript files and immediately includes any test files as ES6 modules. The event handlers must be written in ES5.1 and earlier code, but the tests are in ES6 to eliminate dependency management in testing.
1
2
3
4
5
6
7
8
9
10
// change this:
files: [
],
// to this:
files: [
{ pattern: 'JavaScript/**/!(*.spec).js', watched: true, included: false, served: true }, // non-test regular js files
{ pattern: 'JavaScript/**/*.spec.js', watched: true, included: true, served: true , type: "module" } // test files
],
Use the following steps to add files for unit testing:
testHelper.js code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
export const EVENT_NAMES = {
EVENT_HANDLERS_REGISTERED: "eventHandlersRegistered",
VALIDATION: "validation",
TRANSFORM_LAYOUT: "transformLayout",
HYDRATE_LAYOUT: "hydrateLayout",
HYDRATE_LAYOUT_COMPLETE: "hydrateLayoutComplete",
REPLACE_OBTAIN_ADDITIONAL_DATA: "replaceObtainAdditionalData",
POST_OBTAIN_ADDITIONAL_DATA: "postObtainAdditionalData",
PAGE_LOAD_COMPLETE: "pageLoadComplete",
PAGE_UNLOAD: "pageUnload",
CREATE_ACTION_BAR: "createActionBar",
CREATE_CONSOLE: "createConsole",
OVERRIDE_PICKER_DATASOURCE: "overridePickerDataSource",
PAGE_INTERACTION: "pageInteraction",
PRE_SAVE: "preSave",
POST_SAVE: "postSave",
REPLACE_READ: "replaceRead",
REPLACE_SAVE: "replaceSave",
REPLACE_GET_NEW_OBJECT_INSTANCE: "replaceGetNewObjectInstance",
UPDATE_ACTION_BAR: "updateActionBar",
UPDATE_CONSOLE: "updateConsole",
VALIDATE_SAVE: "validateSave",
ITEM_LIST_RELOADED: "itemListReloaded",
PRE_DELETE: "preDelete",
REPLACE_DELETE: "replaceDelete",
POST_DELETE: "postDelete",
ITEM_LIST_MODIFY_COLUMNS: "itemListModifyColumns",
ITEM_LIST_MODIFY_ACTIONS: "itemListModifyActions",
REPLACE_READ_DELETE_DEPENDENCY_LIST: "replaceReadDeleteDependencyList",
REPLACE_FILE_ACTIONS: "replaceFileActions"
};
export function getFileTextContent(fileUrl) {
if (!fileUrl) { throw new Error("fileUrl must not be empty"); }
const fileTextContentPromise = fetch(`base/JavaScript/${fileUrl}`, { method: "GET" }).then((response) => {
if (!response.ok) {
throw new Error(`request for ${fileUrl} failed. Reason: ${response.statusText} (${response.status})`);
}
const fileTextContent = response.text();
return fileTextContent;
});
return fileTextContentPromise;
};
export function createEventHandlersFromTextFileContent(fileTextContent, settings = {}) {
const { convenienceApi, privilegedEnvelope } = settings;
const eventHandlers = new Function(
"eventNames",
"convenienceApi",
"privilegedEnvelope",
`"use strict"; return ${fileTextContent}`
)(EVENT_NAMES, convenienceApi, privilegedEnvelope);
return eventHandlers;
}
export function getEventHandlers(sourceFileUrl, settings) {
const contentPromise = getFileTextContent(sourceFileUrl);
const eventHandlersPromise = contentPromise.then(
(fileTextContent) => {
return createEventHandlersFromTextFileContent(
fileTextContent,
settings);
}
);
return eventHandlersPromise;
}
export default getEventHandlers;
// eof
The getEventHandlers() function contains the following functionality:
Parameter | Description | |||||||||
---|---|---|---|---|---|---|---|---|---|---|
sourceFileUrl: String | The path and file name of your file. In this lesson, the path is relative to the JavaScript folder. | |||||||||
settings: Object | The following the properties on the settings object provide these advantages:
Use these properties on the settings object during the tests:
|
In this step, you use a JavaScript testing framework called Jasmine. Review the Getting Started page on the Jasmine website for more information.
The test itself iterates over the names of each expected event handler function in lines 4-6, asserting that the eventHandlers object has such a property, defined as a function. After all expected functions are checked, the properties on the eventHandlers object are checked for existence in the expected handlers array. This step ensures that only the required handlers are included.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import getEventHandlers from "./testHelper.js";
describe("articleCategory.js event handlers", () => {
const EXPECTED_SURFACE_AREA = [
"transformLayout"
];
const FILE_NAME = "articleCategory.js";
it("has the expected surface area", (done) => {
getEventHandlers(FILE_NAME).then((eventHandlers) => {
expect(typeof eventHandlers).toBe("object", "Event handlers object not returned.\nIs this file a well-formed immediately invoked function expression (iife)?\nBe sure the file's first line is the beginning of the iife, and not a comment or empty line.");
EXPECTED_SURFACE_AREA.forEach((eventName) => {
expect(typeof eventHandlers[eventName]).toBe("function",`Expected event handler function '${eventName} expected' is missing.`);
});
for(let memberName in eventHandlers) {
expect(EXPECTED_SURFACE_AREA.indexOf(memberName)).toBeGreaterThanOrEqual(0, `Unexpected ${typeof eventHandlers[memberName]} property on event handlers: '${memberName}'`);
}
done();
}).catch(done.fail);
});
});
In this section, you run a command that starts Karma and executes all tests from each file with the .spec.js extension in the JavaScript folder.
Use the following steps to run the code:
1
npm test
1
2
3
4
5
6
7
8
9
10
11
12
PS S:\SourceCode\platform\ads\Source\WikipediaForms> npm test
> hellowikipedia-forms@1.0.0 test S:\SourceCode\platform\ads\Source\WikipediaForms
> karma start --experimental-modules
03 09 2020 14:19:14.872:INFO [karma-server]: Karma v5.2.0 server started at http://localhost:9876/
03 09 2020 14:19:14.879:INFO [launcher]: Launching browsers ChromeHeadless with concurrency unlimited
03 09 2020 14:19:14.884:INFO [launcher]: Starting browser ChromeHeadless
03 09 2020 14:19:18.029:INFO [Chrome Headless 84.0.4147.135 (Windows 10)]: Connected on socket UW5f9zaRceX8BSwEAAAA with id 53968789
Chrome Headless 84.0.4147.135 (Windows 10): Executed 1 of 1 SUCCESS (0.026 secs / 0.016 secs)
TOTAL: 1 SUCCESS
PS S:\SourceCode\platform\ads\Source\WikipediaForms>
On this page
Why was this not helpful?
Check one that applies.
Thank you for your feedback.
Want to tell us more?
Great!
Additional Resources |
|||
DevHelp Community | GitHub | Release Notes | NuGet |