- Subscribe to RSS Feed
- Mark as New
- Mark as Read
- Bookmark
- Subscribe
- Printer Friendly Page
- Report Inappropriate Content
The list controller got a lot of attention in the Yokohama release. Probably everyone who has already dealt with the Yokohama release has noticed that we can now directly filter on the list view. But that's just the tip of the iceberg. If we dive under the surface, we can see that there is now a lot more that we can do with the list view. With the new changes we are now able to implement a lot of the requirements that almost every customer has, and which were not possible before. Like changing the column width, changing the label of a column, showing an SLA timer, creating clickable links, extending the context menu, and so much more.
In this series of articles, I will document what I have explored under the surface.
Exploring the Yokohama list controller – Part 2 – Context Row Actions
Disclaimer: The articles are the result of me exploring and playing around. I was not able to find comprehensive documentation for this feature, therefore I cannot guarantee that the information provided is complete or that it shows the best way to do it. It only represents my current state of knowledge about this topic and therefore will most probably change in the future.
Let’s start.
We will use the Service Operations Workspace (SOW) to explore the new features of the list controller. First, we need to create our own list variant in the SOW because the ootb variants are read-only and we need to make some small changes.
Duplicate the “List Bundle SNC” variant and make sure to set an order below 90.
When we open the list controller of our newly created variant and scroll down to the bottom of the “Configure” tab we see a new option “Customizations to run on data fetch”
“You can customize how data appears in this list when it fetches data. For example, you can change column header labels to better fit your needs. This will not impact the actual data. Ensure to select only the transform script; otherwise, the list will not function as expected.“
There is already a script include configure, “SOW List page transform” but because this script include is ready-only based on its protection policy we create a copy of it and make our customizations in our own version. This script include is also the place where the ootb “Copy URL” and “Copy sys_id” Context menu row actions are implemented.
How does this work?
The defined transform function will be executed on every data fetch. The first parameter of the transform function “transformBuilder“ is an object defined in the script include “ListTransformScriptBuilder” [a816a0930535a510f8777eba8d465d65]. This script include defines all the available methods to change the appearance of the list. All we need to do is gather the data we want to display in the list, bring it in the right format and pass it into the correct "transformBuilder" function.
For the SLA time we need a new column on the list, therefore we use the function
addColumns(newColumns)
to add the column definition for the SLA timer. Because this script will be executed for every list, no matter which table we are on, we need to first check whether the script is executed for a list of incidents.
Looking into this Script Include "ListTransformScriptBuilder" we can see that there is a property "tableName" that we can use to check whether we are on the incident table or not.
We start by defining an array for our SLA Timer column definition and then check whether the table name is incident.
var newCols = [];
if(transformBuilder.tableName == "incident"){
// …
}
Now the question is how to define the new column definition? The good thing is that the script include comes with a lot of comments explaining how to use the API.
The documentation of the function "addColums" provides the following information
/**
* Adds new columns and their corresponding body data at a provided index.
*
* {Object[]} newColumns Array representing all the columns to be added, index and their respective column data
* {Object} newColumns[].column An object containing the definiton of a single column. Full column docs - <public url>/now-list/typeSchemas/columnDefinitions/columnsDocs.md
* {number} newColumns[].index The 0-based index at which the column needs to be added. If empty or falsy, the columns is added at the end of the table.
* {Object[]} newColumns[].columnData Array representing all of the columns' cell data to be added and their respective row keys
* {string} newColumns[].columnData.rowKey The key of a row to update
* {Object} newColumns[].columnData.cellValue An object containing the data for a single cell. Full cells docs - <public url>/now-list/typeSchemas/rowDefinitions/cellsDocs.md
* TODO: add public urls for docs for columnsDocs and cellDocs
*/
.addColumns(newCols)
We want the SLA time at the fourth column (index = 3), and we want to use the "now-sla-timer" component. So, we create our object like the following:
newCols.push( {
index: 3,
column: {
key: "sla",
type: "custom",
label: "SLA",
componentTag: "now-sla-timer",
},
columnData: [],
});
The only thing left is to fill the columnData array with the SLA timer information for each row. To access the row data we use "transformBuilder.rowDefinitions.rows".
We need to fill the "columnData" array of the first element of the array "newCols" that we created. For "cellValue" we provide an object "componentsProps". The properties for "componentProps" can be looked up on the developer site (https://developer.servicenow.com/dev.do#!/reference/next-experience/yokohama/now-components/now-sla-...) or on the Horizon Design System (https://horizon.servicenow.com/workspace/components/now-sla-timer). For the SLA timer we set the task and we want to show the clock icon.
transformBuilder.rowDefinitions.rows.forEach(function(row, index) {
newCols[0].columnData.push({
rowKey: row.key,
cellValue: {
componentProps: {
task:row.key,
showIcon: true
},
},
});
});
If everything is saved and we open an incident list in the SOW we see a running SAL Timer, if not already breached.
Complete Code:
(function transform(transformBuilder, transformScriptArgs) {
try {
if (!transformBuilder) return;
// Build rowActions object that is passed down as param in editRowActions function
var rowActionsObj = {
add: []
};
var actionIndex = 1;
if (gs.hasRole("sn_sow.sow_list") || gs.hasRole("itil")) {
rowActionsObj['add'].push({
id: 'copy_url',
icon: 'square-share-outline',
label: gs.getMessage('Copy URL'),
action: 'LIST#COPY_URL',
index: actionIndex
});
actionIndex++;
}
if (gs.hasRole("admin")) {
rowActionsObj['add'].push({
id: 'copy_sysId',
icon: 'documents-outline',
label: gs.getMessage('Copy sys_id'),
action: 'LIST#COPY_SYSID',
index: actionIndex
});
actionIndex++;
}
var newCols = [];
if (transformBuilder.tableName == "incident") {
newCols.push({
index: 3,
column: {
key: "sla",
type: "custom",
label: "SLA",
componentTag: "now-sla-timer",
},
columnData: [],
});
transformBuilder.rowDefinitions.rows.forEach(function(row, index) {
newCols[0].columnData.push({
rowKey: row.key,
cellValue: {
componentProps: {
task:row.key,
showIcon: true
},
},
});
});
}
var listModel = transformBuilder
/* INSTRUCTIONS */
// The below commented out calls are customizations that can be applied to the list.
// Uncomment any customizations that you would like to use, and provide your own arguments.
// We recommend that if possible, you maintain the order the functions are written in, and only use each function once. This prevents accidentally overwriting a customization.
// Please note note that any operations involving removal or disabling will be executed before operations involving addition or enabling.
// Keep in mind that any customizations are only for rendering and will not be persisted in any capacity.
// This script will run on every data fetch, including initial fetch, filtering, sorting, grouping, etc.
// This API is defined in sys_script_include_a816a0930535a510f8777eba8d465d65, see that file for source code and documentation.
/* DATA MUTATORS */
/**
* Adds new columns and their corresponding body data at a provided index.
*
* {Object[]} newColumns Array representing all the columns to be added, index and their respective column data
* {Object} newColumns[].column An object containing the definiton of a single column. Full column docs - <public url>/now-list/typeSchemas/columnDefinitions/columnsDocs.md
* {number} newColumns[].index The 0-based index at which the column needs to be added. If empty or falsy, the columns is added at the end of the table.
* {Object[]} newColumns[].columnData Array representing all of the columns' cell data to be added and their respective row keys
* {string} newColumns[].columnData.rowKey The key of a row to update
* {Object} newColumns[].columnData.cellValue An object containing the data for a single cell. Full cells docs - <public url>/now-list/typeSchemas/rowDefinitions/cellsDocs.md
* TODO: add public urls for docs for columnsDocs and cellDocs
*/
.addColumns(newCols)
/* COLUMN HEADER MUTATORS */
/**
* Changes the label (text) for one or many column header cells
*
* {Object[]} newLabels Array representing all the columns to be edited and their respective new labels
* {string} newLabels[].columnKey The key of a column to update
* {string} newLabels[].newLabel The new label to display
*/
//.editColumnLabels(newLabels)
/**
* Toggles the enablement of the column header label click based on the provided edit type for one, many, or all columns.
* This function can be used to undo the default behavior of sorting a column on header text click.
* It can also be used to add clickability to a custom added column, for example for a custom sort.
*
* {Object} editColumnsObj Object containing 'enable' and 'disable' properties.
* {string[]} editColumnsObj.enable Array of column keys for which to enable column header click.
* {string[]} editColumnsObj.disable Array of column keys for which to disable column header click.
*/
//.toggleColumnHeaderClickEnablement(editColumnsObj)
/**
* Sets the column header text click action text for one, many, or all columns.
*
* {string} text The new text to display in a tooltip on every column that conveys what action clicking triggers. If empty or falsy, all columns will be disabled and will not have a tooltip.
*/
//.setColumnHeaderClickActionText(newText)
/**
* Updates the column header icons based on the provided edit type.
*
* Added icons are purely decorative and not clickable.
* {Object} editIconsObj Object containing 'add' and 'remove' properties.
* {Object[]} editIconsObj.add Array representing all the columns to be edited and their respective inputs for adding icons.
* - {string} editIconsObj.add[].columnKey The key of a column where column header icons should be added.
* If null, icons will be added to every column.
* - {'start'|'end'} editIconsObj.add[].position The position to add these icons, relative to the text label.
* Either 'start' or 'end'.
* - {Object[]} editIconsObj.add[].iconArr Array of objects representing the icons to be added, from start to end.
* - {string} editIconsObj.add[].iconArr[].value Name of the icon to render from the icon library.
* - {string} editIconsObj.add[].iconArr[].color Color variant for the icon, e.g., 'warning' or 'moderate'.
* - {string} editIconsObj.add[].iconArr[].label Aria-label to be used for accessibility.
* {Object[]} editIconsObj.remove Array representing all the icons to be removed and from which columns.
* - {string} editIconsObj.remove[].columnKey The key of a column where column header icons should be removed.
* If null, icons will be removed from the given position across the table. If position is also null, icons for both positions will be removed.
* - {'start'|'end'} editIconsObj.remove[].position The existing position of the icon(s) to be removed, relative to the text label.
* Either 'start' or 'end'. If null, icons for the given column will be removed from both positions.
* - {number[]} editIconsObj.remove[].iconIndexArr Array of 0-based indices of objects representing the icons to be removed.
* If null, all icons will be removed from the given position. If position is also null, all icons for both positions will be removed.
*/
//.editColumnHeaderIcons(editIconsObj)
/**
* Sets a column's aria-sort value. This does not actually sort the data, just provides context to screen reader users if the data has already been sorted.
*
* {string} columnKey The key of the column that will receive a new sort value. If empty or falsy, all columns will be set to "none" regardless of the value of the direction parameter.
* {'ascending' | 'descending' | 'none'} direction The direction of the sort.
*/
//.setColumnSortDirection(columnKey, direction) {
/**
* Toggles the ability to click on the text in a given column of type URL, link, or person.
* If you want to toggle this for all columns across the list, we recommend using the boolean property on the controller.
*
* {Object} editColumnObj Object containing 'enable' and 'disable' properties.
* {string[]} editColumnObj.enable An array of column keys where clickable text should be enabled.
* If empty or falsy, clickable text will be enabled in all columns.
* {string[]} editColumnObj.disable An array of column keys where clickable text should be disabled.
* If empty or falsy, clickable text will be disabled in all columns.
*/
//.toggleColumnEnablement(editColumnObj)
/**
* Toggles the ability to click on the text in a given cell of type URL, link, or person.
* The value of disabled at the cell level overrides the value of disabled at the column level.
*
* {Object} editCellsObj Object containing 'enable' and 'disable' properties.
* {Object[]} editCellsObj.enable Array of objects representing cells where clickable text should be enabled.
* - {string} editCellsObj.enable[].rowKey The key of the row containing the cell.
* - {string} editCellsObj.enable[].cellKey The key of the cell.
* {Object[]} editCellsObj.disable Array of objects representing cells where clickable text should be disabled.
* - {string} editCellsObj.disable[].rowKey The key of the row containing the cell.
* - {string} editCellsObj.disable[].cellKey The key of the cell.
*/
//.toggleCellsEnablement(editCellsObj)
/**
* Changes the column width for one or many columns
* This new width will remain in effect until the user manually resizes any column. If the user resizes a column, the manually adjusted width will take precedence.
* Users can be prevented from resizing a column by setting `columnResizingEnabled` to false. Full doc: <public url>/now-list#public-props
*
* {Object[] | string} newWidths Either an array representing all the columns to be edited and their respective new widths, or a string representing the width in px to be set for every column, eg '40px'.
* {string} newLabels[].columnKey The key of a column to update
* {string} newLabels[].newWidth The new width in px
*/
//.setColumnWidths(newWidths)
/**
* Changes the maximum number of characters allowed to be displayed per cell before truncation for one or many columns
*
* {Object[]} newMaxChars Either an array representing all the columns to be edited and their respective new maximum.
* {string} newMaxChars[].columnKey The key of a column to update
* {string} newMaxChars[].newMaxChars The new maximum characters in the column
*/
//.setColumnMaxCharacters(newMaxChars)
/**
* Sets highlighted values for cells
*
* {Object[]} newHighlightedValues An array representing the highlighted values and the cell they should appear in.
* {string} newHighlightedValues[].rowKey The key of a row containing the cell
* {string} newHighlightedValues[].cellKey The key of a cell to set highlighted value
* {Object} newHighlightedValues[].highlightedValueObj The object containing the highlighted value. Full highlighted value doc - <public url>/now-list/typeSchemas/rowDefinitions/cellsDocs.md#highlighted-value
* TODO: add public urls for docs for cellDocs
*/
//.setHighlightedValues(newHighlightedValues)
/**
* Sets the display type for a person column: either avatar, name, or both. This function is not applicable to columns that are not the person type.
*
* {Object[]} newDisplays An array representing the columns to update and their new display setting.
* {string} newDisplays[].columnKey The key of a column to update.
* {'avatar'|'name'|'both'} newDisplays[].newDisplay Sets the appearance of body cells in this column. Can be 'avatar', 'name', or 'both'.
*/
//.changeDisplayForPersonColumn(newDisplays)
/**
* Sets the numActionIcons property to determine the number of actions to render as icons.
* The first n items in the actionItems array will be rendered as icon buttons, and the rest will be hidden under a dropdown.
*
* {number} numActionIcons Number of actions to render as icons
*/
.setRowActionCountOverflow(1)
/**
* Updates the row actions for all rows based on the provided edit type.
*
* {Object} editActionsObj Object containing 'add' and 'remove' properties.
* {Object[]} editActionsObj.add Array of objects representing the row actions to be added.
* - {string} editActionsObj.add[].id A unique string identifier for this action
* - {string} editActionsObj.add[].icon A now-icon name to represent this action
* - {string} editActionsObj.add[].label The label to be displayed in tooltips and dropdowns
* - {string} editActionsObj.add[].action The name of a Seismic action to dispatch
* - {number} editActionsObj.add[].index The index at which to insert the action. If null or falsy, the action will be added to the end of the array.
* {string[]} editActionsObj.remove An array of ID strings representing all the row actions to be removed.
* If empty or falsy, all the row actions will be removed
*/
.editRowActions(rowActionsObj)
/**
* Toggles enablement/disablement one or many row actions on a row by row basis. Any disabled actions will be visible but not interactable.
*
* {Object} editActionsObj An array of objects representing the row actions to be toggled.
* {Object[]} editActionsObj[].disable An array of objects representing the row actions to be disabled. The specified action will be visible but not interactable.
* - {string} editActionsObj[].disable.rowKey The key of the row to update
* - {string[]} editActionsObj[].disable.actionIds An array of action keys that represent actions to disable.
*/
//.toggleRowActionsEnablement(editActionsObj)
/**
* Updates the column header actions based on the provided edit type.
* If all actions are removed through this function, the default column filtering popover will display on the list.
*
* {Object} editColumnActionsObj Object containing 'add' and 'remove' properties.
* {Object[]} editColumnActionsObj.add Array of objects representing the column and its respective column header actions to be added.
* - {string} editColumnActionsObj.add[].columnKey The key of the column to update
* - {Object[]} editColumnActionsObj.add[].actionObjArr The action object to be added
* - {string} editColumnActionsObj.add[].actionObjArr[].id The id of the action to be added
* - {string} editColumnActionsObj.add[].actionObjArr[].label The label of the action to be added
* - {string} editColumnActionsObj.add[].actionObjArr[].action The name of a Seismic action to dispatch on click
* - {number} editColumnActionsObj.add[].actionObjArr[].index The index at which to insert the action. If null or falsy, the action will be added to the end of the array.
* {Object[]} editColumnActionsObj.remove Array of objects representing the column and its respective column header actions to be removed.
* - {string} editColumnActionsObj.remove[].columnKey The key of the column to update
* - {string[]} editColumnActionsObj.remove[].actionIdArr The IDs of actions to be removed
*/
//.editColumnHeaderActions(editActionsObj)
/**
* Updates the cell actions across a whole column based on the provided edit type.
* This can be used to remove the default 'Show matching' and 'Filter out' actions, but we recommend using the controller prop if you want to remove both.
*
* {Object} editActionsObj Object containing 'add' and 'remove' properties.
* {Object[]} editActionsObj.add Array of objects representing the column and its respective cell actions to be added.
* - {string} editActionsObj.add[].columnKey The key of the column to update
* - {Object[]} editActionsObj.add[].actionObjArr The action object to be added
* - {string} editActionsObj.add[].actionObjArr[].id The id of the action to be added
* - {string} editActionsObj.add[].actionObjArr[].label The label of the action to be added
* - {string} editActionsObj.add[].actionObjArr[].action The name of a Seismic action to dispatch on click
* - {number} editActionsObj.add[].actionObjArr[].index The index at which to insert the action. If null or falsy, the action will be added to the end of the array.
* {Object[]} editActionsObj.remove Array of objects representing the column and its respective cell actions to be removed.
* - {string} editActionsObj.remove[].columnKey The key of the column to update. If empty or falsy, removes all cell actions across the list.
* - {string[]} editActionsObj.remove[].actionIdArr The IDs of actions to be removed. If empty or falsy, removes all the cell actions on the column.
*/
//.editCellActionsForColumn(editActionsObj)
/**
* Updates the override cell actions based on the provided edit type.
* If there are no actions present in this override, the cell actions defined at the column level will be used instead.
*
* {Object} editActionsObj Object containing 'add' and 'remove' properties.
* {Object[]} editActionsObj.add Array of objects representing the cell and its respective cell actions to be added.
* - {string} editActionsObj.add[].rowKey The key of the row to update
* - {string} editActionsObj.add[].cellKey The key of the cell to update
* - {Object[]} editActionsObj.add[].actionObjArr The action object to be added
* - {string} editActionsObj.add[].actionObjArr[].id The id of the action to be added
* - {string} editActionsObj.add[].actionObjArr[].label The label of the action to be added
* - {string} editActionsObj.add[].actionObjArr[].action The seismic action to dispatch from the new action
* - {number} editActionsObj.add[].actionObjArr[].index The index at which to insert the action. If null or falsy, the action will be added to the end of the array.
* {Object[]} editActionsObj.remove Array of objects representing the cell and its respective cell actions to be removed.
* - {string} editActionsObj.remove[].rowKey The key of the row to update
* - {string} editActionsObj.remove[].cellKey The key of the cell to update
* - {string[]} editActionsObj.remove[].actionIdArr The action ids to be removed
*/
//.editOverrideCellActions(editActionsObj)
/**
* Sets the expanded state of one or more groups
*
* {Object[]} groupObjectArr An array of objects representing the group and its respective expanded state
* {string} groupObjectArr[].groupKey The key of the group to update
* {boolean} groupObjectArr[].isExpanded The expanded state of the group
*/
//.setGroupsExpandedState(groupObjectArr)
/**
* Updates the group actions based on the provided edit type.
*
* {Object} editActionsObj Object containing 'add' and 'remove' properties.
* {Object[]} editActionsObj.add Array of objects representing the group and its respective actions to be added.
* - {string} editActionsObj.add[].groupKey The key of the group to update
* - {Object[]} editActionsObj.add[].actionObjArr The action object to be added
* - {string} editActionsObj.add[].actionObjArr[].id The id of the action to be added
* - {string} editActionsObj.add[].actionObjArr[].label The label of the action to be added
* - {string} editActionsObj.add[].actionObjArr[].action The name of a Seismic action to dispatch on click
* - {number} editActionsObj.add[].actionObjArr[].index The index at which to insert the action. If null or falsy, the action will be added to the end of the array.
* {Object[]} editActionsObj.remove Array of objects representing the group and its respective actions to be removed.
* - {string} editActionsObj.remove[].groupKey The key of the group to update. If null or falsy, the actions will be removed from all groups.
* - {string[]} editActionsObj.remove[].actionIdArr The IDs of actions to be removed. If null or falsy, all the actions will be removed from the given group.
*/
//.editGroupActions(editActionsObj)
/**
* Hides declarative actions that require record selection
*/
//.hideDAsRequiringSelection()
// do not remove
.transform();
// You can make further changes to columnDefinitions and rowDefinitions objects here
// to customize the experience of the list
// TODO: add public urls for docs for rowDefinitions and columnDefinitions
// Full rowDefinitions docs - <public url>/now-list/typeSchemas/rowDefinitions/rowDefinitionsDocs.md
// Full columnDefinitions docs - <public url>/now-list/typeSchemas/columnDefinitions/columnDefinitionsDocs.md
// eg. loop thru columns and set column properties
// listModel.columnDefinitions.columns.forEach(function(column) {
// // add end icon to short description column
// if(column.key === 'short_description') {
// column.icons = {
// endIcons: [{ value: 'camera-fill', color: 'positive', label: 'aria label' }]
// };
// }
// // and starting icon to state column
// if(column.key === 'state') {
// column.icons = {
// startIcons: [{ value: 'eye-outline', color: 'critical', label: 'aria label' }]
// };
// }
// // add custom cell action in priority column
// if(column.key === 'priority') {
// if(!column.cellActions) column.cellActions = [];
// column.cellActions.push({
// id: 'custom_action',
// label: 'Do Cell Action',
// action: 'CUSTOM_COMPONENT#CUSTOM_CELL_CLICK',
// });
// }
// });
// eg. loop thru rows and make changes to row based properties
// listModel.rowDefinitions.rows.forEach(function(row, index) {
// // disable row selection on every other row
// if(index % 2 === 0) {
// row.selectionDisabled = true;
// }
// });
return listModel;
}
// do not remove
catch (error) {
return {
failed: true,
error: error.message
};
}
})(transformBuilder, transformScriptArgs);
- 1,119 Views
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.