
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
3 weeks ago - last edited 3 weeks ago
Hello all, I'll try and explain this best I can.
- I've created a portal widget, for security reasons I'm going to call the page "sp/exec".
- To make it easily accessible I have then created an IFrame widget in a dashboard to said widget.
This works all well and good without an issue, however... There's a catch.
- I have a UI action which runs on a "table" to the dashboard which looks like this...
action.setRedirectURL('now/nav/ui/classic/params/target/%24pa_dashboard.do%3Fsysparm_dashboard%3D29f056a11b9ba290408c4156b04bcb94');​
To loosely explain the widget it essentially takes 3 sets of criteria such as the current record id, its parent and the parent of the parent to display data.
All this information exists on the record on the UI action redirect so my question is... Is there a way to pass the current record data through to the widget on the dashboard?
Thanks in advance!
Andrew
Solved! Go to Solution.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
3 weeks ago
The client controller script and the server script that I provided are the whole scripts for each respective section. From your post it looks like you copied the whole script and just placed them within yours. That's not going to work. Just take the portions needed and place them appropriately within yours. For instance take the client controller script.
Look at the beginning of yours
function($scope, $sce) {
$scope.c = this;
...
And then the beginning of mine
api.controller=function($window) {
var c = this;
See the similarity? The only portion of the beginning of my script that is needed is the "$window".
For example to incorporate what I have into yours start off with:
function($scope, $sce, $window) {
$scope.c = this;
Then only take the following portion of my script
var locationPath = $window.parent.location.search;
var params = locationPath.split("&")
.filter(function(param){
return param.match(/child_id/);
});
if(params.length) {
var inputsObj = {};
inputsObj.action = "ADD_PARAMS";
params.forEach(function(param){
var keyValue = param.split("=");
inputsObj[keyValue[0]] = keyValue[1];
if(keyValue[0] == 'child_id')
c.param = keyValue[1];
});
c.server.get(inputsObj).then(function(resp){
c.data.child_number = resp.data.child_number;
c.data.parent_number = resp.data.parent_number;
c.data.grandparent_number = resp.data.grandparent_number;
});
}
And place it in the appropriately place in your script.
And then it's the same for the server script. The only portion that is needed for yours is this
if(input && input.action == "ADD_PARAMS") {
var taskGr = new GlideRecord('task');
if(taskGr.get(input.child_id)) {
data.child_number = taskGr.getValue('number');
data.parent_number = taskGr.parent.getDisplayValue('number');
data.grandparent_number = taskGr.parent.parent.getDisplayValue();
}
}
You'll have to figure out where the portions of the scripts appropriately fit into your script but the general idea is that the information needs to happen as early as possible since it's going to be needed either on load or once interaction starts.
Also, are you sure the server script is going to meet your needs. As my script was just a demonstration on how it's possible to use the data retrieved that was retrieved from the URL parameter.
Notes:
// within the client script
//gets all parameters from the top most frame of the web page
var locationPath = $window.parent.location.search;
// for an HTTP URL anything after a question mark "?" is considered a URL parameter. A parameter is constructed like a key/value pair and each parameter is separated by an ampersand "&"
//The following statement uses a split to create an array of the parameters
// Then it leverages the in-built array method .filter to grab only the parameter that is designated by the key "child_id"
var params = locationPath.split("&")
.filter(function(param){
return param.match(/child_id/);
});
//if the child_id parameter exists then run the code block
if(params.length) {
//Sending data back to the server takes an object and its properties are treated as the input object
// thus create an object placeholder to be filled in by iterating through the params array
var inputsObj = {};
// add an action property to use on the server side to identify it
inputsObj.action = "ADD_PARAMS";
//Use the in-built array method .forEach to iterate through the params array and set properties and values on the inputsObj
params.forEach(function(param){
//split the parameter between its key and value as the syntax as a URL parameter is "key=value"
var keyValue = param.split("=");
inputsObj[keyValue[0]] = keyValue[1];
//This part is not necessarily needed. I used this to display the value of the child_id parameter in my html markup
if(keyValue[0] == 'child_id')
c.param = keyValue[1];
});
//Send that data to the server side to process and wait for the promise (.then) and update any necessary variables to refresh the visible variables in the view (rendered html)
c.server.get(inputsObj).then(function(resp){
c.data.child_number = resp.data.child_number;
c.data.parent_number = resp.data.parent_number;
c.data.grandparent_number = resp.data.grandparent_number;
});
}
I didn't comment on the server script because it should be self explanatory as it's just a basic GlideRecord query script fetching a specific record based on a sys_id and then setting properties and values on the data object in the widget.
I hope this clears up some things.
Let me know.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
3 weeks ago
Hi @Community Alums ,
If I interpreted your explanation correctly I believe you can pass the values as url parameters by appending them onto your redirectURL. Adding URL parameters shouldn't cause any ill effects as long as they are unique enough. Let's say if you used sysparm_sys_id. That's could cause issues because that isn't unique enough for a ServiceNow instance where something else out-of-box could be looking for that same URL parameter. (it would be different story if you owned that whole dashboard page)
Anyway, with a unique URL parameter you could (you probably don't need all three as you can more than likely get the parent and grandparent ids from the child record) then grab the URL parameter from within your portal widget using the $window class from AngularJS and dot walking to "parent.location.search".
ie. $window.parent.location.search
The above will give you the list of URL parameters. Then you could parse the parameters to get to yours and send that to the server side to get the information from the record.
I built a very basic mock up
Widget HTML:
<div>
<p>
<strong>URL Parameter Child Id:</strong> <span class="text-danger">{{c.param}}</span>
</p>
<p>
<strong>Child Number:</strong> <span class="text-danger">{{c.data.child_number}}</span>
</p>
<p>
<strong>Parent Number: </strong> <span class="text-danger">{{c.data.parent_number}}</span>
</p>
<p>
<strong>Grandparent Number: </strong> <span class="text-danger">{{c.data.grandparent_number}}</span>
</p>
</div>
Widget Server Script:
(function() {
if(input && input.action == "ADD_PARAMS") {
var taskGr = new GlideRecord('task');
if(taskGr.get(input.child_id)) {
data.child_number = taskGr.getValue('number');
data.parent_number = taskGr.parent.getDisplayValue('number');
data.grandparent_number = taskGr.parent.parent.getDisplayValue();
}
}
})();
Widget Client Controller
api.controller=function($window) {
var c = this;
var locationPath = $window.parent.location.search;
var params = locationPath.split("&")
.filter(function(param){
return param.match(/child_id/);
});
if(params.length) {
var inputsObj = {};
inputsObj.action = "ADD_PARAMS";
params.forEach(function(param){
var keyValue = param.split("=");
inputsObj[keyValue[0]] = keyValue[1];
if(keyValue[0] == 'child_id')
c.param = keyValue[1];
});
c.server.get(inputsObj).then(function(resp){
c.data.child_number = resp.data.child_number;
c.data.parent_number = resp.data.parent_number;
c.data.grandparent_number = resp.data.grandparent_number;
});
}
};
UI Action:
action.setRedirectURL('/now/nav/ui/classic/params/target/%24pa_dashboard.do%3Fsysparm_dashboard%3Db5a9e955ffc07e10ca1dfd137c4fd93a%26child_id%3D' + current.getUniqueValue());
POC Demo:

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
3 weeks ago
Cheers, that’s the right interpretation. I’ll give it a whirl later and let you know…

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
3 weeks ago - last edited 3 weeks ago
Hi @ChrisBurks
I've tried the above however its not working like yours, high level its a status report widget which displays the filtered data based on program>project>date.
So essentially when the UI action buttons selected the date (as_on) Project (project) and Program(primary_program) should auto populate the widget.
Really appreciate the assist!
Fields on Widget
//UI Action
action.setRedirectURL('/now/nav/ui/classic/params/target/%24pa_dashboard.do%3Fsysparm_dashboard%3D29f056a11b9ba290408c4156b04bcb94%26child_id%3D' + current.getUniqueValue());
//HTML Template
<div class="project-status-widget">
<label for="programFilter">Select Program:</label>
<select id="programFilter"
ng-model="c.selectedProgram"
ng-options="prog for prog in data.programs"
ng-change="c.onProgramChange()">
<option value="">-- Select a Program --</option>
</select>
<div ng-if="c.selectedProgram">
<label for="projectFilter">Select Project:</label>
<select id="projectFilter"
ng-model="c.selectedProject"
ng-options="project for project in c.projectOptions"
ng-change="c.onProjectChange()">
<option value="">-- Select a Project --</option>
</select>
</div>
<div ng-if="c.dateOptions.length > 0">
<label for="dateFilter">Select Date:</label>
<select id="dateFilter"
ng-model="c.selectedDate"
ng-options="date as (date | date:'dd/MM/yyyy') for date in c.dateOptions"
ng-change="c.filterByProjectAndDate()">
<option value="">-- Select a Date --</option>
</select>
</div>
<div ng-if="c.filteredData && c.selectedProject && c.selectedDate">
<div ng-if="!c.pdfGenerated">
<button class="btn btn-primary" ng-click="c.generatePDFServer()">Generate PDF</button>
</div>
<div ng-if="c.pdfGenerated" class="alert alert-success" role="alert">
PDF has been added to your status report
</div>
<div id="pdfContent">
<div ng-repeat="(parentName, records) in c.filteredData">
<h3>{{ parentName }}</h3>
<div class="record-row" ng-repeat="rec in records">
<div class="main-table">
<table class="status-table">
<thead>
<tr>
<th>Status Date</th>
<th>Project Manager</th>
<th>Exec Sponsor</th>
<th>Current Phase</th>
<th>Financial</th>
<th>Schedule</th>
<th>Resources</th>
<th>Benefit</th>
<th>Scope</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{ rec.as_on | date:'dd-MM-yyyy' }}</td>
<td>{{ rec.project_manager }}</td>
<td>{{ rec.project_sponsor }}</td>
<td>{{ rec.phase }}</td>
<td ng-class="c.getRAGClass(rec.cost)">{{ rec.cost }}</td>
<td ng-class="c.getRAGClass(rec.schedule)">{{ rec.schedule }}</td>
<td ng-class="c.getRAGClass(rec.resource)">{{ rec.resource }}</td>
<td ng-class="c.getRAGClass(rec.benefit)">{{ rec.benefit }}</td>
<td ng-class="c.getRAGClass(rec.scope)">{{ rec.scope }}</td>
</tr>
</tbody>
</table>
</div>
<div class="status-box">
<div class="status-row">
<div class="label"><b>Previous Status</b></div>
<div class="value rag-box" ng-class="c.getRAGClass(rec.previous)">
{{ rec.previous }}
</div>
</div>
<div class="status-row">
<div class="label"><b>Current Status</b></div>
<div class="value rag-box" ng-class="c.getRAGClass(rec.overall_health)">
{{ rec.overall_health }}
</div>
</div>
<div class="status-row">
<div class="label"><b>Future Outlook</b></div>
<div class="value rag-box" ng-class="c.getRAGClass(rec.future)">
{{ rec.future + ' ' + c.getTrendEmoji(rec.overall_health, rec.future) }}
</div>
</div>
</div>
</div>
<div class="executive-summary">
<div class="exec-title">Executive Summary</div>
<!-- HTML preserved -->
<div class="exec-content" ng-bind-html="c.asHtml(records[0].exec)"></div>
</div>
<div class="progress-outlook">
<div class="column">
<div class="column-title">Progress</div>
<!-- HTML preserved -->
<div class="column-content" ng-bind-html="c.asHtml(records[0].progress)"></div>
</div>
<div class="column">
<div class="column-title">Outlook Next Period</div>
<!-- HTML preserved -->
<div class="column-content" ng-bind-html="c.asHtml(records[0].planned)"></div>
</div>
</div>
<div class="progress-outlook">
<div class="column">
<div class="column-content">
<table class="status-table risk-milestone-table">
<thead>
<tr>
<th>Key Risks and Issues</th>
<th>Due Date</th>
<th>Commentary</th>
<th>RAG</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in c.snapshots.risksAndIssues">
<td>{{ item.title }}</td>
<td>{{ item.date }}</td>
<!-- Allow HTML in commentary if present -->
<td ng-bind-html="c.asHtml(item.description)"></td>
<td ng-class="{
'rag-red-fill': item.rag == 'Red',
'rag-amber-fill': item.rag == 'Amber',
'rag-green-fill': item.rag == 'Green'
}" aria-label="{{ item.rag }}"></td>
</tr>
<tr ng-if="!c.snapshots.risksAndIssues.length">
<td colspan="4">No Risks or Issues available.</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="column">
<div class="column-content">
<table class="status-table risk-milestone-table">
<thead>
<tr>
<th>Milestone (L0/L1)</th>
<th>Due Date</th>
<th>Commentary</th>
<th>RAG</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="milestone in c.snapshots.milestones">
<td>{{ milestone.title }}</td>
<td>{{ milestone.date }}</td>
<!-- Allow HTML in commentary if present -->
<td ng-bind-html="c.asHtml(milestone.description)"></td>
<td ng-class="{
'rag-red-fill': milestone.rag == 'Red',
'rag-amber-fill': milestone.rag == 'Amber',
'rag-green-fill': milestone.rag == 'Green'
}" aria-label="{{ milestone.rag }}"></td>
</tr>
<tr ng-if="!c.snapshots.milestones.length">
<td colspan="4">No milestones available.</td>
</tr>
</tbody>
</table>
<div>
<p>
<strong>Select Date:</strong> <span class="text-danger">{{c.data.child_number}}</span>
</p>
<p>
<strong>Select Project:</strong> <span class="text-danger">{{c.data.parent_number}}</span>
</p>
<p>
<strong>Select Program:</strong> <span class="text-danger">{{c.data.grandparent_number}}</span>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
//Client Script
function($scope, $sce) {
$scope.c = this;
$scope.c.selectedProgram = '';
$scope.c.projectOptions = ($scope.data && $scope.data.projects) ? $scope.data.projects.slice() : [];
$scope.c.selectedProject = '';
$scope.c.selectedDate = '';
$scope.c.filteredData = null;
$scope.c.dateOptions = [];
$scope.c.generatedRecordId = null;
$scope.c.pdfGenerated = false;
$scope.c.snapshots = { risksAndIssues: [], milestones: [] };
$scope.c.asHtml = function (s) {
return $sce.trustAsHtml(s || '');
};
$scope.c.getTrendEmoji = function(previous, current) {
if (!previous || !current) return '→';
var valueMap = { 'Red': 1, 'Amber': 2, 'Green': 3 };
var prevVal = valueMap[previous] || 0;
var currVal = valueMap[current] || 0;
if (currVal === prevVal) return '→';
if (currVal > prevVal) return '↑';
return '↓';
};
$scope.c.getRAGClass = function(value) {
if (!value) return '';
var val = value.toUpperCase().charAt(0);
if (val === 'R') return 'rag-R';
if (val === 'A') return 'rag-A';
if (val === 'G') return 'rag-G';
return '';
};
this.onProgramChange = function() {
var prog = $scope.c.selectedProgram;
if (!prog) {
$scope.c.projectOptions = ($scope.data.projects || []).slice();
} else {
var byProg = ($scope.data.projectsByProgram && $scope.data.projectsByProgram[prog]) || [];
$scope.c.projectOptions = byProg.slice();
}
$scope.c.selectedProject = '';
$scope.c.selectedDate = '';
$scope.c.filteredData = null;
$scope.c.dateOptions = [];
$scope.c.pdfGenerated = false;
$scope.c.snapshots = { risksAndIssues: [], milestones: [] };
};
this.onProjectChange = function() {
$scope.c.selectedDate = '';
$scope.c.filteredData = null;
$scope.c.dateOptions = [];
$scope.c.pdfGenerated = false;
$scope.c.snapshots = { risksAndIssues: [], milestones: [] };
if (!$scope.c.selectedProject) return;
var records = $scope.data.groupedData[$scope.c.selectedProject];
if (records && records.length) {
var dates = [...new Set(records.map(function(r){ return r.as_on.split(' ')[0]; }))];
$scope.c.dateOptions = dates.sort().reverse();
}
};
this.filterByProjectAndDate = function() {
$scope.c.pdfGenerated = false;
var selectedProject = $scope.c.selectedProject;
var selectedDate = $scope.c.selectedDate;
if (!selectedProject || !selectedDate) {
$scope.c.filteredData = null;
$scope.c.snapshots = { risksAndIssues: [], milestones: [] };
return;
}
var records = $scope.data.groupedData[selectedProject] || [];
var filtered = records.filter(function(r){ return r.as_on.startsWith(selectedDate); });
if (filtered.length) {
$scope.c.filteredData = {};
$scope.c.filteredData[selectedProject] = filtered;
} else {
$scope.c.filteredData = null;
}
$scope.c.fetchSnapshots();
};
this.fetchSnapshots = function() {
var projectId = ($scope.data.projectIdByName || {})[$scope.c.selectedProject] || '';
var dateOnly = $scope.c.selectedDate || ''; // YYYY-MM-DD
$scope.c.snapshots = { risksAndIssues: [], milestones: [] };
if (!projectId || !dateOnly) return;
var ga = new GlideAjax('StatusSnapshotAPI');
ga.addParam('sysparm_name', 'getSnapshots');
ga.addParam('sysparm_project_id', projectId);
ga.addParam('sysparm_date', dateOnly);
ga.getXMLAnswer(function(answer) {
var payload = { risksAndIssues: [], milestones: [] };
try { payload = JSON.parse(answer || '{}'); } catch (e) {}
$scope.$apply(function() { $scope.c.snapshots = payload; });
});
};
this.generatePDFServer = function () {
const content = document.getElementById("pdfContent");
if (!content) return;
const html = `
<style>
/* Ensure colors render in PDF */
* { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
.project-status-widget { padding: 10px; }
.status-table { border-collapse: collapse; width: 100%; }
.status-table td, .status-table th {
border: 1px solid #ccc; padding: 4px; text-align: center;
font-family: Arial, sans-serif; font-size: 10px;
}
.status-table th { background-color: #f8f8f8; font-weight: bold; }
.rag-R { background-color: #FF0000; color: white; font-weight: bold; }
.rag-A { background-color: #FFBF00; color: black; font-weight: bold; }
.rag-G { background-color: #009E60; color: white; font-weight: bold; }
.record-row {
clear: both;
overflow: hidden; /* contain floats */
margin-bottom: 8px;
}
.main-table {
float: left;
width: calc(100% - 190px); /* leave room for right box + gap */
box-sizing: border-box;
}
.status-box {
float: right;
width: 170px; /* fixed width */
margin-left: 0px; /* gap from left panel */
border: 1px solid #ccc;
font-family: Arial, sans-serif;
font-size: 9px;
box-sizing: border-box;
display: table;
border-collapse: collapse;
vertical-align: top;
}
.status-row { display: table-row; }
.label, .value, .rag-box {
display: table-cell;
border-bottom: 1px solid #ccc;
vertical-align: middle;
}
.status-row:last-child .label,
.status-row:last-child .value,
.status-row:last-child .rag-box { border-bottom: none; }
.label {
width: 100px;
padding: 6px;
font-weight: bold;
background-color: #f8f8f8;
border-right: 1px solid #ccc;
color: black;
font-size: 9px;
}
.value, .rag-box {
width: 60px;
text-align: center;
font-weight: bold;
padding: 6px;
box-sizing: border-box;
font-size: 9px;
}
.rag-box.rag-R { background-color: #FF0000; color: white; }
.rag-box.rag-A { background-color: #FFBF00; color: black; }
.rag-box.rag-G { background-color: #009E60; color: white; }
.trend-arrow { display: inline-block; margin-left: 2px; font-size: 10px; }
.executive-summary {
margin-top: 12px; border: 1px solid #ccc;
font-family: Arial, sans-serif; font-size: 9px;
}
.exec-title {
background-color: #f8f8f8; padding: 6px; font-weight: bold; border-bottom: 1px solid #ccc;
}
.exec-content { padding: 6px; white-space: normal; background-color: white; }
/* Normalize paragraphs/lists from HTML fields in PDF */
.exec-content p,
.column-content p { margin: 0 0 6px 0; }
.exec-content ul,
.column-content ul { margin: 0 0 6px 18px; }
/* Preserve HTML in columns */
.progress-outlook {
display: table; width: 100%; margin-top: 8px;
border: 1px solid #ccc; border-collapse: collapse;
font-family: Arial, sans-serif; font-size: 9px;
}
.progress-outlook .column {
display: table-cell; width: 50%; border-right: 1px solid #ccc; vertical-align: top;
}
.progress-outlook .column:last-child { border-right: none; }
.column-title {
background-color: #f8f8f8; padding: 6px; font-weight: bold; border-bottom: 1px solid #ccc;
}
.column-content { padding: 6px; white-space: normal; background-color: white; }
.risk-milestone-table td { font-size: 9px; padding: 3px; }
.rag-status { font-weight: bold; font-size: 9px; }
.rag-red { color: #FF0000; }
.rag-amber { color: #FFBF00; }
.rag-green { color: #009E60; }
}
.rag-red-fill,
.rag-amber-fill,
.rag-green-fill {
color: transparent;
text-shadow: none;
font-size: 0;
background-color: transparent;
border: 1px solid #ccc;
}
.rag-red-fill {
background-color: #FF0000;
}
.rag-amber-fill {
background-color: #FFBF00;
}
.rag-green-fill {
background-color: #009E60;
}
.rag-status {
font-weight: bold;
}
.rag-red {
color: #FF0000;
}
.rag-amber {
color: #FFBF00;
}
.rag-green {
color: #009E60;
}
</style>
` + content.innerHTML;
const ga = new GlideAjax("PDFExportHelper");
ga.addParam("sysparm_name", "generatePDF");
ga.addParam("sysparm_html", html);
ga.addParam("sysparm_project", $scope.c.selectedProject);
ga.addParam("sysparm_date", $scope.c.selectedDate);
const selectedRecord = $scope.c.filteredData?.[$scope.c.selectedProject]?.[0];
if (!selectedRecord || !selectedRecord.sys_id) return;
ga.addParam("sysparm_record_id", selectedRecord.sys_id);
ga.getXMLAnswer(function (recordSysId) {
if (recordSysId) {
$scope.$apply(function(){ $scope.c.pdfGenerated = true; });
}
});
};
this.openInClassic = function () {
const sysId = $scope.c.generatedRecordId;
if (!sysId) return;
const classicURL = `/project_status.do?sys_id=${sysId}`;
window.open(classicURL, '_blank');
};
api.controller=function($window) {
var c = this;
var locationPath = $window.parent.location.search;
var params = locationPath.split("&")
.filter(function(param){
return param.match(/child_id/);
});
if(params.length) {
var inputsObj = {};
inputsObj.action = "ADD_PARAMS";
params.forEach(function(param){
var keyValue = param.split("=");
inputsObj[keyValue[0]] = keyValue[1];
if(keyValue[0] == 'child_id')
c.param = keyValue[1];
});
c.server.get(inputsObj).then(function(resp){
c.data.child_number = resp.data.child_number;
c.data.parent_number = resp.data.parent_number;
c.data.grandparent_number = resp.data.grandparent_number;
});
}
};
}
//Serverside
(function() {
var groupedData = {};
var projects = [];
var projectIdByName = {};
var programs = [];
var projectsByProgram = {}; // { "Program A": ["Proj 1","Proj 2"] }
var programIdByName = {}; // optional: if you need IDs later
var gr = new GlideRecord('project_status');
gr.orderBy('as_on');
var sixmonthsago = new GlideDateTime();
sixmonthsago.addMonthsUTC(-6);
gr.addQuery('as_on', '>=', sixmonthsago);
gr.query();
while (gr.next()) {
var projName = gr.getDisplayValue('project') || 'No Projects';
var projId = gr.getValue('project');
var progName = gr.project.primary_program.getDisplayValue() || 'No Program';
var progId = gr.project.primary_program.getValue();
if (programs.indexOf(progName) === -1) {
programs.push(progName);
if (progName && progId) programIdByName[progName] = progId;
}
if (!projectsByProgram[progName]) projectsByProgram[progName] = [];
if (projName && projectsByProgram[progName].indexOf(projName) === -1) {
projectsByProgram[progName].push(projName);
}
if (!groupedData[projName]) {
groupedData[projName] = [];
if (projects.indexOf(projName) === -1) projects.push(projName);
}
if (projName && projId && !projectIdByName[projName]) {
projectIdByName[projName] = projId;
}
groupedData[projName].push({
sys_id: gr.getUniqueValue(),
exec: gr.getValue('executive_summary'),
comments: gr.getValue('comments'),
achievements: gr.getValue('achievements_last_week'),
keyachievements: gr.getValue('key_activities_next_week'),
sched_comms: gr.getValue('schedule_comments'),
cost_comms: gr.getValue('cost_comments'),
resource_comms: gr.getValue('resource_comments'),
scope_comms: gr.getValue('scope_comments'),
progress: gr.getValue('comments'),
planned: gr.getValue('key_activities_next_week'),
as_on: gr.getValue('as_on'),
updated_by: gr.getDisplayValue('sys_updated_by'),
project_manager: gr.project.project_manager.getDisplayValue(),
project_sponsor: gr.project.primary_program.program_manager.getDisplayValue(),
phase: gr.phase.getDisplayValue(),
overall_health: gr.getDisplayValue('overall_health'),
schedule: gr.getDisplayValue('schedule'),
cost: gr.getDisplayValue('cost'),
resource: gr.getDisplayValue('resources'),
scope: gr.getDisplayValue('scope'),
future: gr.getDisplayValue('u_future_outlook'),
benefit: gr.getDisplayValue('u_benefit'),
previous: gr.getDisplayValue('u_previous_status')
});
}
projects.sort();
programs.sort();
Object.keys(projectsByProgram).forEach(function(p){
projectsByProgram[p].sort();
});
data.groupedData = groupedData;
data.projects = projects;
data.projectIdByName = projectIdByName;
data.programs = programs;
data.projectsByProgram = projectsByProgram;
data.programIdByName = programIdByName;
(function() {
if(input && input.action == "ADD_PARAMS") {
var taskGr = new GlideRecord('project_status');
if(taskGr.get(input.child_id)) {
data.child_number = taskGr.getValue('as_on');
data.parent_number = taskGr.project.getDisplayValue('short_description');
data.grandparent_number = taskGr.project.primary_project.getDisplayValue('short_description');
}
}
})();
})();
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
3 weeks ago
The client controller script and the server script that I provided are the whole scripts for each respective section. From your post it looks like you copied the whole script and just placed them within yours. That's not going to work. Just take the portions needed and place them appropriately within yours. For instance take the client controller script.
Look at the beginning of yours
function($scope, $sce) {
$scope.c = this;
...
And then the beginning of mine
api.controller=function($window) {
var c = this;
See the similarity? The only portion of the beginning of my script that is needed is the "$window".
For example to incorporate what I have into yours start off with:
function($scope, $sce, $window) {
$scope.c = this;
Then only take the following portion of my script
var locationPath = $window.parent.location.search;
var params = locationPath.split("&")
.filter(function(param){
return param.match(/child_id/);
});
if(params.length) {
var inputsObj = {};
inputsObj.action = "ADD_PARAMS";
params.forEach(function(param){
var keyValue = param.split("=");
inputsObj[keyValue[0]] = keyValue[1];
if(keyValue[0] == 'child_id')
c.param = keyValue[1];
});
c.server.get(inputsObj).then(function(resp){
c.data.child_number = resp.data.child_number;
c.data.parent_number = resp.data.parent_number;
c.data.grandparent_number = resp.data.grandparent_number;
});
}
And place it in the appropriately place in your script.
And then it's the same for the server script. The only portion that is needed for yours is this
if(input && input.action == "ADD_PARAMS") {
var taskGr = new GlideRecord('task');
if(taskGr.get(input.child_id)) {
data.child_number = taskGr.getValue('number');
data.parent_number = taskGr.parent.getDisplayValue('number');
data.grandparent_number = taskGr.parent.parent.getDisplayValue();
}
}
You'll have to figure out where the portions of the scripts appropriately fit into your script but the general idea is that the information needs to happen as early as possible since it's going to be needed either on load or once interaction starts.
Also, are you sure the server script is going to meet your needs. As my script was just a demonstration on how it's possible to use the data retrieved that was retrieved from the URL parameter.
Notes:
// within the client script
//gets all parameters from the top most frame of the web page
var locationPath = $window.parent.location.search;
// for an HTTP URL anything after a question mark "?" is considered a URL parameter. A parameter is constructed like a key/value pair and each parameter is separated by an ampersand "&"
//The following statement uses a split to create an array of the parameters
// Then it leverages the in-built array method .filter to grab only the parameter that is designated by the key "child_id"
var params = locationPath.split("&")
.filter(function(param){
return param.match(/child_id/);
});
//if the child_id parameter exists then run the code block
if(params.length) {
//Sending data back to the server takes an object and its properties are treated as the input object
// thus create an object placeholder to be filled in by iterating through the params array
var inputsObj = {};
// add an action property to use on the server side to identify it
inputsObj.action = "ADD_PARAMS";
//Use the in-built array method .forEach to iterate through the params array and set properties and values on the inputsObj
params.forEach(function(param){
//split the parameter between its key and value as the syntax as a URL parameter is "key=value"
var keyValue = param.split("=");
inputsObj[keyValue[0]] = keyValue[1];
//This part is not necessarily needed. I used this to display the value of the child_id parameter in my html markup
if(keyValue[0] == 'child_id')
c.param = keyValue[1];
});
//Send that data to the server side to process and wait for the promise (.then) and update any necessary variables to refresh the visible variables in the view (rendered html)
c.server.get(inputsObj).then(function(resp){
c.data.child_number = resp.data.child_number;
c.data.parent_number = resp.data.parent_number;
c.data.grandparent_number = resp.data.grandparent_number;
});
}
I didn't comment on the server script because it should be self explanatory as it's just a basic GlideRecord query script fetching a specific record based on a sys_id and then setting properties and values on the data object in the widget.
I hope this clears up some things.
Let me know.