Functions
Flex Operations supports a single functions.js file; the functions of which can be referenced from the configuration JSON files where specifying functions is supported.
When overriding product configuration, functions in the base configuration can be overridden by specifying the string name of the function in the functions.js file. When the custom function is invoked this can be used to refer to the configuration object, so it has access to the other configuration, just like the base product implementation does. It also has access to the base product implementation of the function it has replaced via this['super.'<original function name>], so the custom function can work like a class override and retain the original implementation with some tweaks, if desired.
These functions can be referenced in layout configuration where methods are specified, for example:
{
"type": "button",
"actionFunction": "$custom.myCustomButtonClickHandler”
They can also be referenced in expressions such as enabled/disabled checks or custom column definitions:
"expression": "$custom.myCustomFormatter(field1, field2)"
Calling functions.js functions from within functions.js:
When a function in functions.js calls another function from within functions.js, you must be aware of a quirk of how javascript works.
For example:
self.exampleFunction1 = function () {
// outputs value of eventIdx observable
console.log(this.eventIdx());
self.exampleFunction2();
};
 
self.exampleFunction2 = function () {
// throws exception - eventIdx not defined
console.log(this.eventIdx());
};
 
The issue is that the value of 'this' for exampleFunction2 is not the view model of the tool, it is now the functions.js module (points to the same thing as 'self'). In order for 'this' for exampleFunction2 to be the same as exampleFunction1, you have to call it in a different way.
self.exampleFunction2.apply(this);
If exampleFunction2 had parameters, you would do it like this:
self.exampleFunction2.apply(this, param1, param2);
so the first parameter passed to 'apply' becomes 'this' in the function you are calling.
Knockout Observables
The system handles knockout observables (the technology that Flex Operations uses to dynamically tie the HTML to the view model) in the configuration by ensuring that, if an observable or observableArray is overridden, it is updated with the new values from the overrides, not replaced.
Find and Update or Replace Configuration Items
Sometimes configuration can be nested very deep within a structure and identifying and updating it with the above method is not practical. For UI elements, there is the override mechanism described further down, but for general configuration you can use syntax like this to find something in the configuration and replace it:
 
{
"findAndOverride": [
{
"match": {
"comment": "---- Glows ----"
},
"type": "replace",
"values": {
"comment": "---- Glows ----",
"stopAtFirstMatch": true,
... other values
}
}
 
The above example comes from the map configuration. You can see the full example in examples/map_config_examples/multi_level_glows.jsonc. It is finding a configuration item that has a field "comment" with value "---- Glows ----" and then replacing it with the content of "values". Valid values for "type" are "replace", "remove" or "update". "update" will merge the specified values with the existing configuration instead of replacing them. "remove" will remove the entire item from the configuration (Note: you do not need to specify "values" for a "remove" item).
In addition to the "match" method above which supports simple attribute comparisons, you can also use javascript like you can in Flex expressions (documented in a later section), for example the above could also be done like this:
 
{
"findAndOverride": [
{
"match": "comment === '---- Glows ----'",
"type": "replace",
"values": {
"comment": "---- Glows ----",
"stopAtFirstMatch": true,
... other values
}
}
 
Basic Configuration Overriding
The basic method of overriding configuration is to establish the "path" (hierarchy of items above it) in order to define some JSON that will identify the item and update or replace it. For simple values this is a simple replacement, for example, here we are overriding the default value of one of the map options (this is placed in the file $NMS_HOME/flex/config/client/tools/networkViewer.jsonc)
{
// Disable device phase annotation by default
"defaultSettings": {
"map": {
"options": {
"PHASE_ANNOTATION": false
}
}
}
}
 
Note the comment in the above example - comments are allowed and encouraged to help document the changes that are being made to the base product configuration. You can identify this path by looking at the original product configuration in config/client/tools/networkViewer.js:
...
class NetworkViewerConfig extends layoutToolConfig_1.LayoutToolConfig {
constructor() {
super('tools/networkViewer', nls_1.Nls.load('client/tools/networkViewer', _nlsNetworkViewer), [
commonCrewsConfig_1.CommonCrewsConfig.instance,
commonControlConfig_1.CommonControlConfig.instance,
commonClassInfoDataConfig_1.CommonClassDataInfoConfig.instance,
]);
this.defaultSettings = {
map: {
// Update this to force users settings to be discarded and
// reverted to these default settings
version: 8,
background: 'OSM',
glowOpacity: 0.8,
highlightOpacity: 0.25,
outlineOpacity: 1,
mapOpacity: 1,
glowScale: 1,
outlineScale: 1,
lineScale: 1,
symbolScale: 1,
textScale: 1,
conditionDistanceScale: 1,
feederColorMode: 'feeder_color',
showNominalNetworkState: false,
options: {
DECLUTTER: true,
DARK_THEME: 'AUTO',
DASH_MODE: 'UNDERGROUND',
AUTO_OPEN_SELECTION_PANEL: true,
PANELS_GO_OVER_MAP: false,
DS_ROTATION: true,
UG_DASHING: true,
BIG_SYMBOLS: true,
BIG_SYMBOLS_SCALE: 0.28,
PHASE_DASHING: false,
PHASE_PIES: true,
DEVICE_LABELS: true,
PHASE_ANNOTATION: true,
 
If the item being overridden is an array then there are special methods available for working with arrays documented below. If the item being overridden is an object then you can either merge the changes with the existing object (as the example above is doing) or you can overwrite the entire object using the syntax below:
{
"replaceConfigValues": true,
"value1": "example1",
"value2": "example2
}
 
If the above was applied to this configuration:
 
{
value1: "example1",
value2: "example2",
value3: "example3",
}
 
Then the resulting configuration would be:
{
value1: "example1",
value2: "example2",
}
 
Note that value3 has been removed since it did not exist in the substituted configuration. This can be useful in some instances.
Merging or Replacing Array/Object Content
If an array or object within a configuration class is overridden, it can be fully replaced or it can be merged with the array/object in the base product configuration.
Arrays
For arrays, if you provide an array, it will overwrite the array in the configuration, but you can also define an object with a key attribute (which defaults to 'id') specifying the key field to match against, and up to five other arrays (add, replace, update, remove, and reorder) containing details of array elements to add, update, replace, remove, or reorder.
For example:
"columns": {
// field is the unique key for matching columns on
// NOTE: tchnically field combined with ID is the unique
// key but we do not currently have any tables where the
// same field is included twice, so field suffices
"key": "field",
"update": [
{
"field": "priw",
// Change the label of this column
"label": "# Hazards"
}
],
// Add some new columns that don't exist in base configuration
"add": [
{
"field": "#fe_dmg_asmt",
"label": "FE Dmg Asmt",
"expression": "generic_field_values[2]"
},
{
"field": "#generic_field_value",
"label": "Locked By",
"expression": "generic_field_values[1]"
}
],
// Fully replace some columns with our own versions
"replace": [
{
"field": "#emergency",
"expression": "$getCritCustCount('E') > 0 ? '999' :
$subst()",
"label": "999"
}
],
// Remove some columns we don't want
"remove": [
{
"field": "#dms_status"
}
],
// Reorder some columns. Columns at the start do not have
// an explicit order specified and will appear in the
// order specified here, before any other columns. Note
// also these just specify the key (field) value for
// convenience
"reorder": [
"notify_count",
"status",
"event_id_text",
"ctrl_zone_name_2",
"ctrl_zone_name_3",
"ctrl_zone_name_4",
{
"field": "est_rest_time",
// This will be moved to after the 10th column in
// the base product configuration
"order": 10.5
}
]
}
Note: If you specify an element to add, but it is already there, it will be replaced and a warning will be issued in the console log. Likewise, if you specify an element to replace, and it is not found, it will be added and a warning will be issued in the console log. If you specify an element for removal, but it is not found, a warning will be issued in the console log.
Updating Arrays
For manipulating arrays of arrays, numeric indexes must be used as keys instead of field names, but all of the above instructions then work in the same way, with the exception of 'update', which allows specific indexes of the array to be updated without touching the other entries.
For example:
"lineGlowTypes": {
"key": 1,
"update": [
{
"1": "confirmed_degraded",
"2": "$color.RED.color"
},
{
"1": "confirmed_deenergized",
"2": "$color.YELLOW.color"
}
]
},
Objects
By default, configuration specified in overrides is merged with the product configuration, but if you specify the field replaceConfigValues as true in an object, the entire object will be replaced instead.
For example:
"mapSymbolRotation": {
"replaceConfigValues": true,
"Transformer": 180,
"TransformerPM": 180,
"Capacitor": 180,
"Switch_Ground": 180,
"Breaker": 90,
"PowerTransformer_Substation Grounding Bank": 180
},
Any additional fields in the product configuration that are not specified here will be removed when using this method.
Custom Syntax
Multi-line Values
Normal JSON does not allow values to be spread accross multiple lines, but Flex configuration files allow for this by specifying "$lines" as the value, then placing the multi-line value in a comment block in the following lines. This maintains the validity of the JSON while also allowing a more readable expression. This is very useful for large or complex expressions where the structure and indentation afforded by spreading over multiple lines can help clarify what it is doing. For example, the selectionDescriptionExpression from networkViewer.jsonc generates HTML based on the specific type of item that has been selected on the map, and would be very hard to follow if it was all on a single line. You could override it as shown below:
Note: The following code sample has lines that are wrapping to the next line, but every line begins with a forward slash (//).
"selectionDescriptionExpression": "$lines",
// '<span class="nms-item-headline">' + (ppJumperSelecting() ? nls.pp_jumper_label + '<br><br>' : '') + label + '</span>' +
// '<span class="nms-item-description">' +
// (
// $matchesType('truck') ?
// ('Crew Type: ' + item?.CrewType) :
// ($matchesType('da') && item?.feature?.attributes?.STATUS !== 568) ?
// nls.main_damage_asset :
// isInstruct ?
// instructAction +
// (
// ' ' + deviceAlias || ''
// ) :
// (isTag || isGround) ?
// (deviceAlias || '') :
// isElectric ?
// deviceAlias +
// (
// feeder ?
// '<br>' + nls.feeder_label + feeder : '' +
// nominalStatusDescription ?
// '<br>' + nls.nominal_status_label + nominalStatusDescription : '' +
// currentStatusDescription ?
// '<br>' + nls.current_status_label + currentStatusDescription : ''
// ) : ''
// ) +
// '</span>'
Reference Standard NMS Color Values
Where colors are configurable, standard NMS colors can be used by using the $color syntax in values. To reference the index of a color by name:
$color.<name>.index
For example:
"secondaryColor": "$color.snow1.index",
Or to reference a color's hex value:
$color.<name>.color
For example:
"lineGlowTypes": {
"key": 1,
"update": [
{
"1": "confirmed_degraded",
"2": "$color.RED.color"
},
{
"1": "confirmed_deenergized",
"2": "$color.YELLOW.color"
}
]
},
Reference Other Values in the Configuration
Other objects or values in the configuration file can be referenced by their path, for example to allow reference to an existing item in the configuration or to reference an item without duplicating it:
{
"id": "SELECTED_ITEM_CHOOSER_DESCRIPTION",
"type": "html",
"expression": "$ref.selectionDescriptionExpression"
}