

Last date modified: June 17 2025
Custom pages are used to tailor the look and feel of applications that you develop on the Relativity platform. You can develop your own cascading style sheets (CSS), JavaScript, HTML, and images for your custom pages.
In this lesson, you will learn how to complete these tasks:
You can also use Visual Studio templates to create custom pages. For an example of that approach, see the tutorial Build your first custom page. While that tutorial does not utilize the application that you create in this series of lessons, you may want utilize the Visual Studio templates to build custom pages at some point.
Estimated completion time - 3 hours
You start creating a simple custom page by adding HTML to an index.html file. As you work through this lesson, you continue building the page by adding JavaScript, CSS, and more HTML. Use the code editor of your choice for working with these technologies in your custom page. This lesson uses Visual Studio Code.
Use the following steps to create a simple custom page:
1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html>
<head>
<title>HelloWikipedia Categories</title>
</head>
<body>
<div id="hw-container">Hello World!</div>
</body>
</html>
This section illustrates how to deploy a custom page in Relativity and associate it with a tab. For more information, see
Use the following steps to deploy a custom page in Relativity:
Set the name of the .zip file to HelloWikipedia.CustomPage.zip.
In this example, you only have a single file, which is index.html. For more complex pages, you may have additional files containing JavaScript or other code. Make sure that you select and include all these files and any directories in the .zip file.
Record the value displayed in this field because you need to enter it when creating a tab for your custom page.
Your Hello Wikipedia application is now updated with the new custom page. In the following steps, you push your updated application to the Application Library, so that it is available throughout your Relativity instance.
This workflow updates your application in the application library. Your application must be installed in a workspace. The Push to Library option performs the updates to the application in the library.
1
%applicationPath%/CustomPages/e57fa0fe-59fd-49eb-92ed-895f3e592cd1/index.html
You can set this field to any value. It controls the order that the tab is displayed in the drop-down menu of the parent tab.
After you push the application to the Application Library, it may take several minutes for your page to reload. You may also need to clear your browse cache by performing a hard reload (CTRL + F5).
Use the following steps to add client-side JavaScript to a custom page:
1
2
3
4
5
6
7
function startApplication() {
console.info("HelloWikipedia Categories application started");
const label = document.getElementById("hw-container");
label.innerText = "Hello from JavaScript!";
}
startApplication();
1
<script src="./scripts/main.js"></script>
Make sure that you add the src directory with the index.html file and scripts sub-directory.
After you push the application to the Application Library, it may take several minutes for your page to reload. You may also need to clear your browse cache by performing a hard reload (CTRL + F5).
After confirming that your custom page is working properly in Relativity, you can begin debugging your JavaScript in the browser. This lesson uses Google Chrome, but you could use other tools for this purpose.
Use the following steps to debug JavaScript in the browser:
The Console tab in the bottom pane displays the message: HelloWikipedia Categories application started. The JavaScript for your page logged this message.
In this step, you add functionality to your custom page, which provides users with the ability to search for the categories in Wikipedia using an existing Kepler service. Users should also be able to add categories as RDOs through the Object Manager service. For more information, see Kepler framework and Object Manager Fundamentals.
Your completed custom page should look like the following screen shot after you update the code and deploy it in Relativity. This screen shot illustrates a search on the word science.
Use the following steps to add new functionality to your custom page:
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
<!DOCTYPE html>
<html>
<head>
<title>HelloWikipedia Categories</title>
<link href="./styles/style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="hw-container">
<div id="hw-search-container">
<span id="hw-search-label">Search Wikipedia for article categories</span>
<div id="hw-search">
<input id="hw-search-input" placeholder="Search for category" />
<button id="hw-category-search-button" class="hw-button" type="button">Search</button>
</div>
</div>
<div id="hw-results-container"></div>
</div>
<script type="module" src="./scripts/main.js"></script>
</body>
</html>
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
#hw-container {
margin: 0;
padding: 20px;
}
#hw-search-container {
margin-left: 8px;
}
#hw-results-container {
display: table;
width: auto;
max-width: 800px;
margin-top: 20px;
}
.hw-results-header {
color: #0670c1;
}
.hw-category-row {
display: table-row;
}
.hw-category-cell {
display: table-cell;
padding: 8px;
border-bottom: 1px solid #e2ebf3;
text-align: left;
}
.hw-button-cell {
display: table-cell;
padding-left: 12px;
}
#hw-search {
margin-top: 10px;
}
#hw-search-input {
width: 300px;
margin-right: 5px;
padding: 5px;
border-radius: 3px;
border: .0625rem solid #acbfd6;
line-height: 1.4rem;
}
#hw-search-label {
color: #0670c1;
}
.hw-button {
background-color: #0075e0;
border: none;
border-radius: 3px;
display: inline-block;
cursor: pointer;
color: #fff;
padding: 9px 23px;
text-decoration: none;
}
.hw-button:hover {
background-color: #0670c1;
}
The ApiFetchClient class contains the following functionality:
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
export class ApiFetchClient {
constructor(globalObjectService) {
this.globalObjectService = globalObjectService;
}
async get(apiEndpoint) {
const response = await this.globalObjectService.getWindow().fetch(this._getFullApiPath(apiEndpoint));
this._validateResponse(response);
return await response.json();
}
async post(apiEndpoint, body) {
const response = await this.globalObjectService.getWindow().fetch(this._getFullApiPath(apiEndpoint), this._getPostRequestInit(body));
this._validateResponse(response);
return await response.json();
}
_getPostRequestInit(payload) {
return {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Header': '-'
},
body: JSON.stringify(payload)
};
}
_validateResponse(response) {
if (!response.ok) {
throw new Error(response.statusText);
}
}
_getFullApiPath(apiEndpoint) {
return this.globalObjectService.getTopWindow().GetKeplerApplicationPath() + apiEndpoint;
}
}
Because the ApiFetchClient class simplifies calling Kepler services via HTTPS, you can now create the WikiCategorySearchService class, which searches for categories.
This class contains the following functionality:
1
2
3
4
5
6
7
8
9
10
export class WikiCategorySearchService {
constructor(fetchClient) {
this.fetchClient = fetchClient;
}
async search(categoryName) {
return await this.fetchClient.get(`wikipedia-management/v1/wikipedia-service/categories?prefix=${categoryName}`);
}
}
The CategoryService class contains the following functionality:
Completed these steps to add the code for the CategoryService class:
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
71
72
73
74
75
76
77
78
79
80
81
82
export class CategoryService {
constructor(fetchClient, globalObjectService) {
this.fetchClient = fetchClient;
this.globalObjectService = globalObjectService;
this.appConstants = {
articleCategoryObjectTypeGuid: '6B20F149-1B17-4E9C-8403-439E98E8BFD2',
articleCategoryNameFieldGuid: '16D8A362-2923-45B7-8444-7339C57B3AF0',
overwriteArticleTextFieldGuid: '042E0329-1467-4993-8188-66615E103DE3',
automaticUpdatesEnabledFieldGuid: 'F365DE2E-A641-428F-9188-A3970A7C308F'
};
}
create(categoryName) {
const request = {
'request': {
'ObjectType': {
'Guid': this.appConstants.articleCategoryObjectTypeGuid
},
'FieldValues': [
{
'Field': {
'Guid': this.appConstants.articleCategoryNameFieldGuid
},
'Value': categoryName
},
{
'Field': {
'Guid': this.appConstants.overwriteArticleTextFieldGuid
},
'Value': false
},
{
'Field': {
'Guid': this.appConstants.automaticUpdatesEnabledFieldGuid
},
'Value': true
}
]
}
};
return this.fetchClient.post(this._getObjectManagerMethodPath('create'), request);
}
async getCategories() {
const request = {
'request': {
'ObjectType': {
'Guid': this.appConstants.articleCategoryObjectTypeGuid
},
'Condition': '',
'Fields': [
{
'Guid': this.appConstants.articleCategoryNameFieldGuid
}
]
},
'start': 1,
'length': 99999
};
const result = await this.fetchClient.post(this._getObjectManagerMethodPath('queryslim'), request);
const mappedCategories = result.Objects.map(object => {
return object.Values[0];
});
return mappedCategories;
}
_getWorkspaceId() {
const windowObject = this.globalObjectService.getTopWindow();
const url = new URL(windowObject.location.href);
return url.searchParams.get('AppID');
}
_getObjectManagerMethodPath(methodName) {
const workspaceId = this._getWorkspaceId();
return `Relativity.Objects/workspace/${workspaceId}/object/${methodName}`;
}
}
Add the following code to the globalObjectService.js file:
1
2
3
4
5
6
7
8
9
10
11
12
13
export class GlobalObjectService {
getTopWindow() {
return window.top;
}
getWindow() {
return window;
}
getDocument() {
return document;
}
}
The ElementFactory class facilitates the creation of HTML elements, such as <div>, <span>, and <button> elements.
Add the following code to the elementFactory.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
export class ElementFactory {
constructor(globalObjectService) {
this.globalObjectService = globalObjectService;
}
createDiv(className) {
const div = this._createElement('div');
div.className = className;
return div;
}
createSpan(className) {
const span = this._createElement('span');
span.className = className;
return span;
}
createButton(buttonText, className) {
const button = this._createElement('button');
button.setAttribute('type', 'button');
button.innerText = buttonText;
button.className = className;
return button;
}
_createElement(tagName) {
const documentObject = this.globalObjectService.getDocument();
return documentObject.createElement(tagName);
}
}
The CategoryService class contains the following functionality:
Adds a Create button for a category only when it doesn't already exist. When you click this button, an event is triggered that removes the button and prevents it from firing a second time. It makes a call to the createCategory() function, which is provided as dependency in the constructor.
Add the following code to the categoryResultElementFactory.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
35
export class CategoryResultElementFactory {
constructor(elementFactory, createCategory) {
this.elementFactory = elementFactory;
this.createCategory = createCategory;
}
createHtmlElement(category) {
const row = this.elementFactory.createDiv('hw-category-row');
const categoryName = this.elementFactory.createSpan('hw-category-cell');
categoryName.innerText = category.name;
row.appendChild(categoryName);
if (!category.exists) {
const buttonDiv = this.elementFactory.createDiv('hw-button-cell');
const button = this._createButton('Create', category.name);
buttonDiv.appendChild(button);
row.appendChild(buttonDiv);
}
return row;
}
_createButton(buttonText, categoryName) {
const button = this.elementFactory.createButton(buttonText, 'hw-button');
button.addEventListener('click',
() => {
button.parentNode.removeChild(button);
this.createCategory(categoryName);
});
return button;
}
}
The SearchResultsPresenter class contains the following functionality:
Add the following code to the searchResultsPresenter.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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
export class SearchResultsPresenter {
constructor(
categoryService,
elementFactory,
categoryResultElementFactory,
globalObjectService) {
this.categoryService = categoryService;
this.elementFactory = elementFactory;
this.categoryResultElementFactory = categoryResultElementFactory;
this.globalObjectService = globalObjectService;
}
async showSearchResults(categories) {
const existingCategories = await this.categoryService.getCategories();
const categoriesToRender = categories.map(category => {
const exists = existingCategories.includes(category.Title);
return {
name: category.Title,
exists: exists
};
});
this._renderCategories(categoriesToRender);
}
_renderCategories(categories) {
const documentObject = this.globalObjectService.getDocument();
const resultsDiv = documentObject.getElementById('hw-results-container');
resultsDiv.innerHTML = '';
this._addResultsTitle(resultsDiv);
categories.forEach(category => {
const resultElement = this.categoryResultElementFactory.createHtmlElement(category);
resultsDiv.appendChild(resultElement);
});
}
_addResultsTitle(resultsDiv) {
const resultsHeader = this.elementFactory.createDiv('hw-results-header hw-category-row');
const headerText = this.elementFactory.createSpan('hw-category-cell');
headerText.innerText = 'Categories Found';
resultsHeader.appendChild(headerText);
resultsDiv.appendChild(resultsHeader);
}
}
The SearchHandler class executes a query by calling the CategorySearchService and then passes the results to the SearchResultsPresenter class for display.
Add the following code to the searchHandler.js file:
1
2
3
4
5
6
7
8
9
10
11
12
export class SearchHandler {
constructor(categorySearchService, resultsPresenter) {
this.categorySearchService = categorySearchService;
this.resultsPresenter = resultsPresenter;
}
async executeSearch(categoryPrefix) {
const result = await this.categorySearchService.search(categoryPrefix);
await this.resultsPresenter.showSearchResults(result);
}
}
The main.js file contains the following functionality:
Add the following code to the main.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
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
import { SearchHandler } from './searchHandler.js';
import { WikiCategorySearchService } from './services/wikiCategorySearchService.js';
import { SearchResultsPresenter } from './searchResultsPresenter.js';
import { CategoryService } from './services/categoryService.js';
import { ApiFetchClient } from './services/apiFetchClient.js';
import { CategoryResultElementFactory } from './categoryResultElementFactory.js';
import { ElementFactory } from './elementFactory.js';
import { GlobalObjectService } from './services/globalObjectService.js';
let _searchHandler;
function startApplication() {
console.info('HelloWikipedia Categories application started');
const searchButton = _getSearchButton();
searchButton.addEventListener('click', _onSearchButtonClicked);
searchButton.setAttribute('disabled', '');
_getSearchInput().addEventListener('input', _onSearchTextChanged);
}
function _getSearchHandler() {
if (_searchHandler) {
return _searchHandler;
}
const globalObjectService = new GlobalObjectService();
const fetchClient = new ApiFetchClient(globalObjectService);
const categoryService = new CategoryService(fetchClient, globalObjectService);
const elementFactory = new ElementFactory(globalObjectService);
const categoryResultElementFactory = new CategoryResultElementFactory(elementFactory, categoryName => categoryService.create(categoryName));
const resultsPresenter = new SearchResultsPresenter(categoryService, elementFactory, categoryResultElementFactory, globalObjectService);
const categorySearchService = new WikiCategorySearchService(fetchClient);
_searchHandler = new SearchHandler(categorySearchService, resultsPresenter);
return _searchHandler;
}
function _onSearchButtonClicked() {
const searchHandler = _getSearchHandler();
searchHandler.executeSearch(_getSearchInput().value);
}
function _onSearchTextChanged() {
const searchButton = _getSearchButton();
if (_getSearchInput().value) {
searchButton.removeAttribute('disabled');
} else {
searchButton.setAttribute('disabled', '');
}
}
function _getSearchButton() {
return document.getElementById('hw-category-search-button');
}
function _getSearchInput() {
return document.getElementById('hw-search-input');
}
startApplication();
After deploying your updated custom page, you can begin writing tests for it. In this step, you use a JavaScript testing framework called Jasmine. Review the Getting Started page on the Jasmine website for more information.
Use the following steps to add tests to the project:
1
2
3
4
5
6
7
8
// Run following command to initialize npm. This command creates package.json file in your project.
npm init -y
// Then run following command to install jasmine package. This command adds jasmine dependency to package.json.
npm install jasmine --save-dev
// Then run following command to initialize jasmine. This command adds jasmine configuration file.
npx jasmine init
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"name": "hellowikipedia-custompage",
"version": "1.0.0",
"description": "",
"private": true,
"scripts": {
"test": "jasmine"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"jasmine": "^3.6.1"
}
}
1
2
3
4
5
6
7
8
9
10
11
{
"spec_dir": "src/tests",
"spec_files": [
"**/*[sS]pec.js"
],
"helpers": [
"helpers/**/*.js"
],
"stopSpecOnExpectationFailure": false,
"random": true
}
The following code illustrates how you can implement tests for your classes. You can optionally write your own tests for the code. For information about writing tests, see introduction.js on the Jasmine website.
In the following code sample, update the GUIDs for the appConstants to match the GUIDs that you defined in categoryService.js. The GUIDs in categoryService.js are defined by your application hosted on the Relativity instance.
Add the following code to the categoryService.spec.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
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import { CategoryService } from '../scripts/services/categoryService.js';
import { createWindowMock, createGlobalObjectServiceMock, createApiFetchClientMock } from './utils/mockFactory';
describe('CategoryService', () => {
const workspaceId = 12345;
const topWindowObject = createWindowMock({ href: `https://localhost/endpoint?AppID=${workspaceId}` });
const globalObjectService = createGlobalObjectServiceMock({ topWindowObject });
const appConstants = {
articleCategoryObjectTypeGuid: '6B20F149-1B17-4E9C-8403-439E98E8BFD2',
articleCategoryNameFieldGuid: '16D8A362-2923-45B7-8444-7339C57B3AF0',
overwriteArticleTextFieldGuid: '042E0329-1467-4993-8188-66615E103DE3',
automaticUpdatesEnabledFieldGuid: 'F365DE2E-A641-428F-9188-A3970A7C308F'
};
describe('getCategories', () => {
it('should return array of categories for workspace', async () => {
// Arrange
const fetchClient = createApiFetchClientMock({
postResult: {
'Objects': [
{
'Values': [
'Movie'
]
},
{
'Values': [
'Sport'
]
}
]
}
});
const sut = new CategoryService(fetchClient, globalObjectService);
// Act
const categories = await sut.getCategories();
// Assert
expect(categories).toEqual(['Movie', 'Sport']);
expect(fetchClient.post).toHaveBeenCalledWith('Relativity.Objects/workspace/12345/object/queryslim', {
request: {
ObjectType: {
Guid: appConstants.articleCategoryObjectTypeGuid
},
Condition: '',
Fields: [
{
Guid: appConstants.articleCategoryNameFieldGuid,
},
],
},
start: 1,
length: 99999,
});
});
});
describe('create', () => {
it('should create article category', async () => {
// Arrange
const categoryName = 'sport';
const fetchClient = createApiFetchClientMock({});
const sut = new CategoryService(fetchClient, globalObjectService);
// Act
await sut.create(categoryName);
// Assert
expect(fetchClient.post).toHaveBeenCalledWith('Relativity.Objects/workspace/12345/object/create', {
request: {
ObjectType: {
Guid: appConstants.articleCategoryObjectTypeGuid
},
FieldValues: [
{
Field: {
Guid: appConstants.articleCategoryNameFieldGuid
},
Value: categoryName
},
{
Field: {
Guid: appConstants.overwriteArticleTextFieldGuid
},
Value: false
},
{
Field: {
Guid: appConstants.automaticUpdatesEnabledFieldGuid
},
Value: true
}
]
}
});
});
});
});
Code for the documentFake.js class:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { HtmlElementFake } from './htmlElementFake';
export class DocumentFake {
constructor() {
this.children = [];
}
createElement(tagName) {
return new HtmlElementFake(tagName);
}
appendChild(newChild) {
newChild.parentNode = this;
this.children.push(newChild);
}
getElementById(id) {
return this.children.find(x => x.id === id);
}
}
Code for the htmlElementFake.js class:
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
export class HtmlElementFake {
constructor(tagName) {
this.tagName = tagName;
this.id = (void 0);
this.className = (void 0);
this.type = (void 0);
this.innerText = (void 0);
this.parentNode = (void 0);
this.children = [];
this.eventActions = {};
}
appendChild(newChild) {
newChild.parentNode = this;
this.children.push(newChild);
}
removeChild(child) {
const index = this.children.indexOf(child);
if (index > -1) {
this.children.splice(index, 1);
child.parentNode = null;
}
}
setAttribute(name, value) {
this[name] = value;
}
addEventListener(eventName, eventAction) {
this.eventActions[eventName] = eventAction;
}
raiseEvent(eventName) {
this.eventActions[eventName]();
}
}
Code for the mockFactory.js class:
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import { HtmlElementFake } from '../fakes/htmlElementFake';
import { DocumentFake } from '../fakes/documentFake';
export function createGlobalObjectServiceMock(mockParams) {
return {
getTopWindow: function () {
return mockParams.topWindowObject;
},
getWindow: function () {
return mockParams.windowObject;
},
getDocument: function () {
return mockParams.documentObject;
}
};
}
export function createWindowMock(mockParams) {
return {
location: {
href: mockParams.href
},
fetch: mockParams.fetch,
GetKeplerApplicationPath: jasmine.createSpy().and.returnValue(mockParams.keplerApplicationPath)
};
}
export function createDocumentMock() {
return new DocumentFake();
}
export function createFetchMock(fetchResponses) {
return jasmine.createSpy().and.callFake(function (input) {
const response = fetchResponses[input];
return Promise.resolve({
ok: response.ok,
json: function () {
return Promise.resolve(response.data);
},
statusText: response.statusText
});
});
}
export function createApiFetchClientMock(mockParams) {
return {
get: jasmine.createSpy().and.returnValue(Promise.resolve(mockParams.getResult)),
post: jasmine.createSpy().and.returnValue(Promise.resolve(mockParams.postResult))
};
}
export function createCategorySearchServiceMock(mockParams) {
return {
search: jasmine.createSpy().and.returnValue(Promise.resolve(mockParams.searchResult))
};
}
export function createSearchResultsPresenterMock() {
return {
showSearchResults: jasmine.createSpy()
};
}
export function createElementFactoryMock() {
return {
createDiv: function (className) {
const div = new HtmlElementFake('div');
div.className = className;
return div;
},
createSpan: function (className) {
const span = new HtmlElementFake('span');
span.className = className;
return span;
},
createButton: function (buttonText, className) {
const button = new HtmlElementFake('button');
button.className = className;
button.innerText = buttonText;
button.type = 'button';
return button;
}
};
}
1
npm install @babel/core @babel/register @babel/preset-env --save-dev
For more information, see What is Babel? on the Babel website.
1
2
3
4
5
6
7
...
"helpers": [
"../../node_modules/regenerator-runtime/runtime.js",
"../../node_modules/@babel/register/lib/node.js",
...
],
...
1
2
3
4
5
{
"presets": [
"@babel/preset-env"
]
}
1
npm run test
This step illustrates how to make your custom page ready for deployment in a production environment. Use these guidelines when publishing a custom page:
Use the following steps to prepare the page for a production environment:
1
npm install webpack webpack-cli --save-dev
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
<!DOCTYPE html>
<html>
<head>
<title>HelloWikipedia Categories</title>
</head>
<body>
<div id="hw-container">
<div id="hw-search-container">
<span id="hw-search-label">Search Wikipedia for article categories</span>
<div id="hw-search">
<input id="hw-search-input" placeholder="Search for category" />
<button id="hw-category-search-button" class="hw-button" type="button">Search</button>
</div>
</div>
<div id="hw-results-container"></div>
</div>
<script src="bundle.js"></script>
</body>
</html>
1
npm install style-loader css-loader --save-dev
1
2
...
import '../styles/style.css';
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const path = require('path');
module.exports = {
entry: [
path.resolve(__dirname, "src/scripts/main.js"),
],
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
],
}
],
},
};
1
2
3
4
"scripts": {
"test": "jasmine",
"build": "webpack"
}
1
npm run build
1
npm install babel-loader core-js whatwg-fetch --save-dev
1
2
3
4
5
6
7
8
9
10
11
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "entry",
"corejs": 3
}
]
]
}
Add the following code to the webpack.config.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
const path = require('path');
module.exports = {
entry: [
"core-js/stable",
"regenerator-runtime/runtime",
"whatwg-fetch",
path.resolve(__dirname, "src/scripts/main.js"),
],
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
],
},
{
test: /\.m?js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
}
],
},
};
When deploying your bundled custom page, you only need to add the contents in the dist directory to the HelloWikipedia.CustomPage.zip file. No other files or directories are required.
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 |