“You’ve got to think about big things while you’re doing small things, so that all the small things go in the right direction.”
— Alvin Toffler
Recently I was playing around with the Content Selector Configuration Editor to create a dashboard for my Service Account Management app, which is a Scoped Application, and realized that the last fix that I put in to make things work with a Scoped Application did not quite go far enough. Looking things over it is quite clear that the original design had only considered support for global scripts, and my first attempt to rectify that oversight did not resolve all of the issues for configuration scripts that were not in the global scope. Today it is time to finish that up and correct all of the other shortcomings in that tool when working outside of the global scope.
For starters, the pick list for available scripts in the current version includes all of the configuration scripts for all scopes. What we really need is to limit that selection list to just those scripts in the current scope. Otherwise, you could potentially be editing a script in one scope while you are working in another scope, which will not end well if it works at all. To limit the list to just the scripts in the current scope, we need to add something like this to the filter:
^sys_scope=javascript:gs.getCurrentApplicationId()
That will turn this attribute in the record picker:
default-query="'active=true^scriptLIKEObject.extendsObject(ContentSelectorConfig^ORscriptLIKEObject.extendsObject(global.ContentSelectorConfig'"
… to this:
default-query="'active=true^sys_scope=javascript:gs.getCurrentApplicationId()^scriptLIKEObject.extendsObject(ContentSelectorConfig^ORscriptLIKEObject.extendsObject(global.ContentSelectorConfig'"
It would also be good to add a little more information to the help text for that field, so the entire snh-form-field tag now looks like this:
<snh-form-field
snh-label="Content Selector Configuration"
snh-model="c.data.script"
snh-name="script"
snh-type="reference"
snh-help="Select the Content Selector Configuration from the current Scope that you would like to edit."
snh-change="scriptSelected();"
placeholder="Choose a Content Selector Configuration"
table="'sys_script_include'"
default-query="'active=true^sys_scope=javascript:gs.getCurrentApplicationId()^scriptLIKEObject.extendsObject(ContentSelectorConfig^ORscriptLIKEObject.extendsObject(global.ContentSelectorConfig'"
display-field="'name'"
search-fields="'name'"
value-field="'api_name'"/>
That solves one problem, but there are others. When building the new script from the user’s input in the save() function of the widget’s server script, this conditional only reduces the API Name to the root name for global scripts:
if (data.scriptInclude.startsWith('global.')) {
data.scriptInclude = data.scriptInclude.split('.')[1];
}
This needs to be done for scripts in any scope, so the entire conditional should just go away and simply be reduced to this:
data.scriptInclude = data.scriptInclude.split('.')[1];
Further down in that same function, this line again assumes that you are working in the global scope:
scriptGR.api_name = 'global.' + name;
The API Name is actually set for you whenever you save a new script, so this line can actually just be removed entirely and things will work just fine.
With all of these changes, the new save() function now looks like this:
function save() {
if (input.script && input.script.value) {
data.scriptInclude = input.script.value;
data.scriptInclude = data.scriptInclude.split('.')[1];
}
var name = data.scriptInclude;
if (input.newRecord) {
name = input.newScriptName;
}
var script = "var ";
script += name;
script += " = Class.create();\n";
script += name;
script += ".prototype = Object.extendsObject(";
if (gs.getCurrentApplicationScope() != 'global') {
script += "global.";
}
script += "ContentSelectorConfig, {\n";
script += " initialize: function() {\n";
script += " },\n";
script += "\n";
script += " perspective: [";
var separator = '';
for (var p=0; p<input.config.perspective.length; p++) {
var thisPerspective = input.config.perspective[p];
script += separator;
script += "{\n name: '";
script += thisPerspective.name;
script += "',\n label: '";
script += thisPerspective.label;
script += "',\n roles: '";
script += thisPerspective.roles || '';
script += "'\n }";
separator = ",";
}
script += "],\n\n";
script += " state: [";
separator = '';
for (var s=0; s<input.config.state.length; s++) {
var thisState = input.config.state[s];
script += separator;
script += "{\n name: '";
script += thisState.name;
script += "',\n label: '";
script += thisState.label;
script += "'\n }";
separator = ",";
}
script += "],\n\n";
script += " table: {";
separator = '';
for (var tp=0; tp<input.config.perspective.length; tp++) {
var tablePerspective = input.config.perspective[tp];
script += separator;
script += "\n ";
script += tablePerspective.name;
script += ": [";
var tableSeparator = '';
for (var tt=0; tt<input.config.table[tablePerspective.name].length; tt++) {
var tableTable = input.config.table[tablePerspective.name][tt];
script += tableSeparator;
script += "{\n name: '";
script += tableTable.name;
script += "',\n displayName: '";
script += tableTable.displayName;
script += "'";
for (var ts=0; ts<input.config.state.length; ts++) {
var tableState = input.config.state[ts];
script += ",\n ";
script += tableState.name;
script += ": {\n filter: '";
script += tableTable[tableState.name].filter;
script += "',\n fields: '";
script += tableTable[tableState.name].fields;
script += "',\n svcarray: [";
var lastSeparator = '';
for (var v=0; v<tableTable[tableState.name].svcarray.length; v++) {
var thisScriptedValue = tableTable[tableState.name].svcarray[v];
script += lastSeparator;
script += "{\n name: '";
script += thisScriptedValue.name;
script += "',\n label: '";
script += thisScriptedValue.label;
script += "',\n heading: '";
script += thisScriptedValue.heading;
script += "',\n script: '";
script += thisScriptedValue.script;
script += "'\n }";
lastSeparator = ",";
}
script += "]";
script += ",\n aggarray: [";
lastSeparator = '';
for (var g=0; g<tableTable[tableState.name].aggarray.length; g++) {
var thisAggregate = tableTable[tableState.name].aggarray[g];
script += lastSeparator;
script += "{\n name: '";
script += thisAggregate.name;
script += "',\n label: '";
script += thisAggregate.label;
script += "',\n heading: '";
script += thisAggregate.heading;
script += "',\n table: '";
script += thisAggregate.table;
script += "',\n field: '";
script += thisAggregate.field;
script += "',\n filter: '";
script += thisAggregate.filter;
script += "',\n source: '";
script += thisAggregate.source;
script += "',\n hint: '";
script += thisAggregate.hint;
script += "',\n page_id: '";
script += thisAggregate.page_id;
script += "'\n }";
lastSeparator = ",";
}
script += "]";
script += ",\n btnarray: [";
lastSeparator = '';
for (var b=0; b<tableTable[tableState.name].btnarray.length; b++) {
var thisButton = tableTable[tableState.name].btnarray[b];
script += lastSeparator;
script += "{\n name: '";
script += thisButton.name;
script += "',\n label: '";
script += thisButton.label;
script += "',\n heading: '";
script += thisButton.heading;
script += "',\n icon: '";
script += thisButton.icon;
script += "',\n color: '";
script += thisButton.color;
script += "',\n hint: '";
script += thisButton.hint;
script += "',\n page_id: '";
script += thisButton.page_id;
script += "',\n condition: '";
if (thisButton.condition) {
var condition = thisButton.condition;
if (condition.indexOf("'") != -1) {
condition = condition.replace(/'/g, "\\'");
}
script += condition;
}
script += "'\n }";
lastSeparator = ",";
}
script += "]";
script += ",\n refmap: {";
lastSeparator = '';
var indent = '';
for (var key in tableTable[tableState.name].refmap) {
script += lastSeparator;
script += "\n ";
script += key;
script += ": '";
script += tableTable[tableState.name].refmap[key];
script += "'";
lastSeparator = ",";
indent = '\n ';
}
script += indent + "}";
script += ",\n actarray: [";
lastSeparator = '';
for (var a=0; a<tableTable[tableState.name].actarray.length; a++) {
var thisAction = tableTable[tableState.name].actarray[a];
script += lastSeparator;
script += "{\n name: '";
script += thisAction.name;
script += "',\n label: '";
script += thisAction.label;
script += "'\n }";
lastSeparator = ",";
}
script += "]";
script += "\n }";
}
script += "\n }";
tableSeparator = ",";
}
script += "]";
separator = ",";
}
script += "\n },\n\n";
script += " type: '";
script += name;
script += "'\n});";
var scriptGR = new GlideRecord('sys_script_include');
if (input.newRecord) {
scriptGR.initialize();
scriptGR.name = name;
scriptGR.description = name;
scriptGR.access = 'public';
scriptGR.insert();
} else {
scriptGR.get('name', name);
}
scriptGR.setValue('script', script);
scriptGR.update();
data.sys_id = scriptGR.getUniqueValue();
}
All in all, not a huge number of changes, but just enough to make things work. I bundled all of the relevant parts into another Update Set that includes these various changes, which you can find here. This component is also a part of the larger SNH Data Table Widget collection, so eventually I will need to publish a new version of that collection out on Share as well.