- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎08-28-2020 10:20 AM
Hi,
On the Change request we see Add Button which triggers a ui page which is not visible for the users.
Is there anyway we can create a similar functionality on custom table ?
Solved! Go to Solution.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎08-30-2020 08:01 PM
From your use case, my understanding is the the KPI will be linked to the Performance Management record using a many to many relationship. Would a simple many to many definition (sys_m2m) do the job? It would add an edit button on that created relatinship (related list) to allow to link one or many KPI to be linked to the Performance Mangement record.
If you really wish to have a modal window to complete this, I have done something like that in the past. It is a custom UI page that is loading a list based on a table query and allows to select rows to then apply an action to these rows. So it is similar to the Add Selected option of the Impacted CIs. However it does not allow to search or select a table, therefore it only fits a use case with a small amount of options.
Here is the info of that custom page. I did modify the script to post it, so there may be bugs since it is untested.
Name: select_list
Description:
Parameters:
sysparm_description_text: Description, the text is passed into gs.getMessage for translation purpose
sysparm_table: table to query
sysparm_query: encoded query
sysparm_fields: comma seperated list of fields to be included
sysparm_selected_query: encoded query of record to be pre-selected
HTML:
<?xml version="1.0" encoding="utf-8" ?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
<g:evaluate object="true">
var gr = new GlideRecordSecure(RP.getParameterValue('sysparm_table'));
gr.addEncodedQuery(RP.getParameterValue('sysparm_query'));
gr.query();
</g:evaluate>
<g:evaluate>
var selectedQuery = RP.getParameterValue('sysparm_selected_query') || '';
selectedQuery = GlideStringUtil.unEscapeHTML(selectedQuery);
</g:evaluate>
<j:set var="jvar_fields" value="${RP.getParameterValue('sysparm_fields').split(',')}" />
<g:evaluate object="true" jelly="true" var="jvar_field_labels">
//Set field labels
var fieldLabels = jelly.jvar_fields.map(function(field){
return new DotWalkingHelper(gr, field).getLabel();
});
fieldLabels;
</g:evaluate>
<j:set var="jvar_fields" value="${RP.getParameterValue('sysparm_fields').split(',')}" />
<style>
/* dialog styles */
.dialog_buttons {
text-align: right;
vertical-align:bottom;
white-space: nowrap;
padding-right: 15px;
}
tr.dialog_spacer {
height: 8px;
}
#content_row .reference-label {
padding-right: 15px;
}
.row{
padding-left: 15px;
}
</style>
<!-- Set up form fields and labels -->
<table width="100%" id="select_list">
<tr class="dialog_spacer"></tr>
<tr>
<p>${gs.getMessage(RP.getParameterValue('sysparm_description_text'))}</p>
</tr>
<tr id="content_row" valign="top">
<td>
<table class="table table-hover list_table">
<thead>
<tr>
<th></th>
<j:forEach items="${jvar_field_labels}" var="jvar_field_label">
<th>${jvar_field_label}</th>
</j:forEach>
</tr>
</thead>
<tbody>
<j:while test="${gr.next()}">
<g:evaluate>
var rowClass = gr.getLocation() % 2 == 0 ? 'list_odd' : 'list_even';
</g:evaluate>
<g:evaluate>
var checked = false;
if(selectedQuery){
var gf = new GlideFilter(selectedQuery, '');
gf.setCaseSensitive(false);
checked = gf.match(gr, true);
}
</g:evaluate>
<tr class="list_row ${rowClass}" id="select_list_${gr.getUniqueValue()}">
<td class="list_decoration_cell col-control col-small col-center" style="white-space:nowrap;">
<span class="input-group-checkbox">
<j:choose>
<j:when test="${checked}">
<input type="checkbox" id="select_list_check_${gr.getUniqueValue()}" class="checkbox" checked=""/>
</j:when>
<j:otherwise>
<input type="checkbox" id="select_list_check_${gr.getUniqueValue()}" class="checkbox" />
</j:otherwise>
</j:choose>
<label for="select_list_check_${gr.getUniqueValue()}" class="checkbox-label"></label>
</span>
</td>
<j:forEach items="${jvar_fields}" var="jvar_field">
<g:evaluate jelly="true">
var displayValue = new DotWalkingHelper(gr, jelly.jvar_field).getDisplayValue();
</g:evaluate>
<td class="vt">${displayValue}</td>
</j:forEach>
</tr>
</j:while>
</tbody>
</table>
</td>
</tr>
<tr class="dialog_spacer"></tr>
<tr>
<td class="dialog_buttons" colspan="2">
<g:dialog_buttons_ok_cancel ok="dialogSelectList.completed()"
ok_text="${gs.getMessage('Save')}"
cancel="dialogSelectList.actionCancel();" cancel_type="button"/>
</td>
</tr>
</table>
</j:jelly>
Client Script:
//Create object for page to avoid scope issue when loaded in modal window
var dialogSelectList = {
actionCancel: function(){
var gdw = GlideDialogWindow.get();
var f = gdw.getPreference('onPromptCancel');
if (typeof(f) == 'function') {
try {
f.call(gdw, gdw.getPreference('oldValue'));
} catch(e) {
}
}
else
gdw.destroy();
},
completed: function(){
var gdw = GlideDialogWindow.get();
var f = gdw.getPreference('onPromptComplete');
if (typeof(f) == 'function') {
try {
f.call(gdw, this.getCheckedSysIds(), this.getNotCheckedSysIds());
} catch(e) {
console.log(e);
}
}
else
gdw.destroy();
},
getCheckedSysIds: function(){
var checked = $j('#select_list input:checked');
var ids = checked.toArray().map(function(check){
var checkSplit = check.id.split('_');
return checkSplit[checkSplit.length -1];
});
return ids;
},
getNotCheckedSysIds: function(){
var notChecked = $j('#select_list input:not(:checked)');
var ids = notChecked.toArray().map(function(check){
var checkSplit = check.id.split('_');
return checkSplit[checkSplit.length -1];
});
return ids;
}
};
This UI page as a dependency on a custom script include to work with DotWalking.
Name: DotWalkingHelper
Script:
var DotWalkingHelper = Class.create();
DotWalkingHelper.prototype = {
initialize: function(gr, dotWalkinkString) {
this.element = this._getElement(gr, dotWalkinkString);
},
getLabel: function(){
if(typeof this.element == 'undefined'){
return '';
}
else{
return this.element.getLabel();
}
},
getValue: function(){
if(typeof this.element == 'undefined'){
return '';
}
else{
return this.element.toString();
}
},
getDisplayValue: function(){
if(typeof this.element == 'undefined'){
return '';
}
else{
return this.element.getDisplayValue();
}
},
getType: function(){
if(typeof this.element == 'undefined'){
return '';
}
else{
return this.element.getED().getInternalType();
}
},
_getElement: function(gr, dotWalkinkString){
var fieldSplit = j2js(dotWalkinkString).split('.');
var field = fieldSplit[0];
if(!gr.isValidField(field)){
return;
}
var element = gr[field];
for(var x=1; x<fieldSplit.length; x++){
if(typeof element[fieldSplit[x]] == 'undefined'){
//Support to dot walk for system user field (sys_created_by, sys_updated_by)
if(element.getName() == 'sys_created_by' || element.getName() == 'sys_updated_by'){
var user = new GlideRecord('sys_user');
if(user.get('user_name', element.toString())){
if(typeof user[fieldSplit[x]] == 'undefined')
return;
element = user[fieldSplit[x]];
}
else
return;
}
else
return;
}
else
element = element[fieldSplit[x]];
}
return element;
},
type: 'DotWalkingHelper'
};
Then the UI page is called by a Client side UI Action. You can pass a callback function to the UI page and the function will be called with the checked and unchecked elements of the list. From there you can use a GlideAjax call to make the required inserts/updates. Here is a snippet of the usage of the UI page.
var dialogClass = window.GlideModal ? GlideModal : GlideDialogWindow;
var gDialog = new dialogClass("select_list");
gDialog.setTitle(getMessage('Select device'));
gDialog.setPreference('sysparm_description_text', 'message.text');
gDialog.setPreference('sysparm_table', 'cmdb_ci_computer');
gDialog.setPreference('sysparm_query', 'assigned_to=' + g_form.getUniqueValue());
gDialog.setPreference('sysparm_fields', 'name,model_id,model_id.type');
gDialog.setPreference('sysparm_selected_query', 'assigned_to=' + g_form.getUniqueValue());
gDialog.setPreference('onPromptComplete', function(checked, unchecked){
var ga = new GlideAjax('CMDBGroup');
ga.addParam('sysparm_name', 'addAndRemoveCIsFromGroup');
ga.addParam('sysparm_group_id', WORKSTATION_PILOT_GROUP);
ga.addParam('sysparm_added_cis', checked.join(','));
ga.addParam('sysparm_removed_cis', unchecked.join(','));
ga.getXMLAnswer(function(answer){
if(g_form.getActionName() == 'sysverb_update'){
g_form.submit();
}
else{
g_form.save();
}
});
});
gDialog.render();
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎08-28-2020 02:20 PM
What do you mean by similar functionality? Do you mean adding an impacted CI on a task record? If so it is possible by simply adding the Impacted CIs related list and adding your custom table to the Global property "com.snc.task.associate_ci".
If you are restricted to your scope, maybe you can copy the "Add" UI action that is on the task_cmdb_ci_service table and add the required condition for the UI action to show without the system property. However I did not check if there are any scope accessiblity issue of doing this.
If your use case is not for CIs and Tasks, could you give more details on what you are trying to do?
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎08-28-2020 05:43 PM
Thanks for the reply.
My use case does not relate to CI's unfortunately.
Let me explain my requirement, you can tell me if there is any other way to achieve this.
We have 2 custom table which are created under Scoped application,
Table 1: Individual KPI
Table 2: Performance Management
So, users will be raising Individual KPI's (it can be 1 or 10) on a monthly basis which all will be falling under Monthly Performance management record. So every month i will have 1 Performance management record that contains multiple Individual KPI
There will be situation users may Add or Copy the existing Individual KPI to the next month if they not able to accomplish the KPI within a given month.
So, as a user when he/she open's Performance Management record then in the related list they should be able to Add the existing Individual KPI to the next Performance record.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎08-30-2020 08:01 PM
From your use case, my understanding is the the KPI will be linked to the Performance Management record using a many to many relationship. Would a simple many to many definition (sys_m2m) do the job? It would add an edit button on that created relatinship (related list) to allow to link one or many KPI to be linked to the Performance Mangement record.
If you really wish to have a modal window to complete this, I have done something like that in the past. It is a custom UI page that is loading a list based on a table query and allows to select rows to then apply an action to these rows. So it is similar to the Add Selected option of the Impacted CIs. However it does not allow to search or select a table, therefore it only fits a use case with a small amount of options.
Here is the info of that custom page. I did modify the script to post it, so there may be bugs since it is untested.
Name: select_list
Description:
Parameters:
sysparm_description_text: Description, the text is passed into gs.getMessage for translation purpose
sysparm_table: table to query
sysparm_query: encoded query
sysparm_fields: comma seperated list of fields to be included
sysparm_selected_query: encoded query of record to be pre-selected
HTML:
<?xml version="1.0" encoding="utf-8" ?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
<g:evaluate object="true">
var gr = new GlideRecordSecure(RP.getParameterValue('sysparm_table'));
gr.addEncodedQuery(RP.getParameterValue('sysparm_query'));
gr.query();
</g:evaluate>
<g:evaluate>
var selectedQuery = RP.getParameterValue('sysparm_selected_query') || '';
selectedQuery = GlideStringUtil.unEscapeHTML(selectedQuery);
</g:evaluate>
<j:set var="jvar_fields" value="${RP.getParameterValue('sysparm_fields').split(',')}" />
<g:evaluate object="true" jelly="true" var="jvar_field_labels">
//Set field labels
var fieldLabels = jelly.jvar_fields.map(function(field){
return new DotWalkingHelper(gr, field).getLabel();
});
fieldLabels;
</g:evaluate>
<j:set var="jvar_fields" value="${RP.getParameterValue('sysparm_fields').split(',')}" />
<style>
/* dialog styles */
.dialog_buttons {
text-align: right;
vertical-align:bottom;
white-space: nowrap;
padding-right: 15px;
}
tr.dialog_spacer {
height: 8px;
}
#content_row .reference-label {
padding-right: 15px;
}
.row{
padding-left: 15px;
}
</style>
<!-- Set up form fields and labels -->
<table width="100%" id="select_list">
<tr class="dialog_spacer"></tr>
<tr>
<p>${gs.getMessage(RP.getParameterValue('sysparm_description_text'))}</p>
</tr>
<tr id="content_row" valign="top">
<td>
<table class="table table-hover list_table">
<thead>
<tr>
<th></th>
<j:forEach items="${jvar_field_labels}" var="jvar_field_label">
<th>${jvar_field_label}</th>
</j:forEach>
</tr>
</thead>
<tbody>
<j:while test="${gr.next()}">
<g:evaluate>
var rowClass = gr.getLocation() % 2 == 0 ? 'list_odd' : 'list_even';
</g:evaluate>
<g:evaluate>
var checked = false;
if(selectedQuery){
var gf = new GlideFilter(selectedQuery, '');
gf.setCaseSensitive(false);
checked = gf.match(gr, true);
}
</g:evaluate>
<tr class="list_row ${rowClass}" id="select_list_${gr.getUniqueValue()}">
<td class="list_decoration_cell col-control col-small col-center" style="white-space:nowrap;">
<span class="input-group-checkbox">
<j:choose>
<j:when test="${checked}">
<input type="checkbox" id="select_list_check_${gr.getUniqueValue()}" class="checkbox" checked=""/>
</j:when>
<j:otherwise>
<input type="checkbox" id="select_list_check_${gr.getUniqueValue()}" class="checkbox" />
</j:otherwise>
</j:choose>
<label for="select_list_check_${gr.getUniqueValue()}" class="checkbox-label"></label>
</span>
</td>
<j:forEach items="${jvar_fields}" var="jvar_field">
<g:evaluate jelly="true">
var displayValue = new DotWalkingHelper(gr, jelly.jvar_field).getDisplayValue();
</g:evaluate>
<td class="vt">${displayValue}</td>
</j:forEach>
</tr>
</j:while>
</tbody>
</table>
</td>
</tr>
<tr class="dialog_spacer"></tr>
<tr>
<td class="dialog_buttons" colspan="2">
<g:dialog_buttons_ok_cancel ok="dialogSelectList.completed()"
ok_text="${gs.getMessage('Save')}"
cancel="dialogSelectList.actionCancel();" cancel_type="button"/>
</td>
</tr>
</table>
</j:jelly>
Client Script:
//Create object for page to avoid scope issue when loaded in modal window
var dialogSelectList = {
actionCancel: function(){
var gdw = GlideDialogWindow.get();
var f = gdw.getPreference('onPromptCancel');
if (typeof(f) == 'function') {
try {
f.call(gdw, gdw.getPreference('oldValue'));
} catch(e) {
}
}
else
gdw.destroy();
},
completed: function(){
var gdw = GlideDialogWindow.get();
var f = gdw.getPreference('onPromptComplete');
if (typeof(f) == 'function') {
try {
f.call(gdw, this.getCheckedSysIds(), this.getNotCheckedSysIds());
} catch(e) {
console.log(e);
}
}
else
gdw.destroy();
},
getCheckedSysIds: function(){
var checked = $j('#select_list input:checked');
var ids = checked.toArray().map(function(check){
var checkSplit = check.id.split('_');
return checkSplit[checkSplit.length -1];
});
return ids;
},
getNotCheckedSysIds: function(){
var notChecked = $j('#select_list input:not(:checked)');
var ids = notChecked.toArray().map(function(check){
var checkSplit = check.id.split('_');
return checkSplit[checkSplit.length -1];
});
return ids;
}
};
This UI page as a dependency on a custom script include to work with DotWalking.
Name: DotWalkingHelper
Script:
var DotWalkingHelper = Class.create();
DotWalkingHelper.prototype = {
initialize: function(gr, dotWalkinkString) {
this.element = this._getElement(gr, dotWalkinkString);
},
getLabel: function(){
if(typeof this.element == 'undefined'){
return '';
}
else{
return this.element.getLabel();
}
},
getValue: function(){
if(typeof this.element == 'undefined'){
return '';
}
else{
return this.element.toString();
}
},
getDisplayValue: function(){
if(typeof this.element == 'undefined'){
return '';
}
else{
return this.element.getDisplayValue();
}
},
getType: function(){
if(typeof this.element == 'undefined'){
return '';
}
else{
return this.element.getED().getInternalType();
}
},
_getElement: function(gr, dotWalkinkString){
var fieldSplit = j2js(dotWalkinkString).split('.');
var field = fieldSplit[0];
if(!gr.isValidField(field)){
return;
}
var element = gr[field];
for(var x=1; x<fieldSplit.length; x++){
if(typeof element[fieldSplit[x]] == 'undefined'){
//Support to dot walk for system user field (sys_created_by, sys_updated_by)
if(element.getName() == 'sys_created_by' || element.getName() == 'sys_updated_by'){
var user = new GlideRecord('sys_user');
if(user.get('user_name', element.toString())){
if(typeof user[fieldSplit[x]] == 'undefined')
return;
element = user[fieldSplit[x]];
}
else
return;
}
else
return;
}
else
element = element[fieldSplit[x]];
}
return element;
},
type: 'DotWalkingHelper'
};
Then the UI page is called by a Client side UI Action. You can pass a callback function to the UI page and the function will be called with the checked and unchecked elements of the list. From there you can use a GlideAjax call to make the required inserts/updates. Here is a snippet of the usage of the UI page.
var dialogClass = window.GlideModal ? GlideModal : GlideDialogWindow;
var gDialog = new dialogClass("select_list");
gDialog.setTitle(getMessage('Select device'));
gDialog.setPreference('sysparm_description_text', 'message.text');
gDialog.setPreference('sysparm_table', 'cmdb_ci_computer');
gDialog.setPreference('sysparm_query', 'assigned_to=' + g_form.getUniqueValue());
gDialog.setPreference('sysparm_fields', 'name,model_id,model_id.type');
gDialog.setPreference('sysparm_selected_query', 'assigned_to=' + g_form.getUniqueValue());
gDialog.setPreference('onPromptComplete', function(checked, unchecked){
var ga = new GlideAjax('CMDBGroup');
ga.addParam('sysparm_name', 'addAndRemoveCIsFromGroup');
ga.addParam('sysparm_group_id', WORKSTATION_PILOT_GROUP);
ga.addParam('sysparm_added_cis', checked.join(','));
ga.addParam('sysparm_removed_cis', unchecked.join(','));
ga.getXMLAnswer(function(answer){
if(g_form.getActionName() == 'sysverb_update'){
g_form.submit();
}
else{
g_form.save();
}
});
});
gDialog.render();
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎08-28-2020 10:00 PM
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎08-28-2020 10:40 PM
Hi
for creating the Button you have to make use of UI Action.
Application Navigator>UI Action.(For Native UI)
Application Navigator>UI Action(for Mobile View)
Depends on your Requirement select wisely.
Now for creating button select the option I have provided to you.
Form Button=It will Create Button On Banner of your Content Frame.
List Banner Button=It Will Create button at besides the selection option.i.t select action small square.
List Banner Button=Beside New Button.
Now as UI Action is Client side,you need to call Script Include here in UI Action.Write Script in Script Section
Create a Script Include for your Functionality what you want to do when your User click on this "Add" Button.
https://www.youtube.com/watch?v=PE86Z7NF47k&t=287s
check video to call script include in UI Action.
Please Mark Correct and Helpful
Thanks and Regards
Gaurav Shirsat
