Reject button in custom Service Portal widget not recognizing textarea input

Puneet Hegde1
Tera Guru

Hi everyone,

I'm building a custom Service Portal widget that allows users to either accept or reject a proposed solution on an incident record when it's in the Resolved state.

The "Accept Solution" button works perfectly and updates the incident state to Closed.
However, when clicking the "Reject Solution" button, a modal pops up with a textarea for the user to enter a rejection reason. The form has a submit button which should call submitRejection().

Here’s the issue:

Even after entering text into the textarea, the script always thinks the comment is empty. It keeps showing the alert:

"Please enter a reason for rejection."

 

Please find the blow client script in widget is where i'm having problem:

function($scope) {
$scope.showModal = false;
$scope.rejectionReason = '';

// Accept Solution (unchanged)
$scope.acceptSolution = function() {
var ga = new GlideAjax('SolutionFeedbackHandler');
ga.addParam('sysparm_name', 'processFeedback');
ga.addParam('sysparm_incident_id', $scope.data.sys_id);
ga.addParam('sysparm_action', 'accept');
ga.addParam('sysparm_comment', ''); // Empty for accept

ga.getXMLAnswer(function(response) {
if (response === 'success') {
$scope.$apply(function() {
$scope.data.message = "Thank you! The incident is now closed.";
$scope.data.actionTaken = true;
});
} else {
alert("Failed: " + response);
}
});
};

// Open Reject Modal
$scope.showRejectModal = function() {
$scope.rejectionReason = '';
$scope.showModal = true;
};

// Close Reject Modal
$scope.closeModal = function() {
$scope.rejectionReason = '';
$scope.showModal = false;
};

// Force model sync on change
$scope.updateRejectionReason = function() {
if (!$scope.$$phase) {
$scope.$apply();
}
};

// Submit Rejection
$scope.submitRejection = function() {
var comment = ($scope.rejectionReason || '').trim();

console.log("DEBUG: rejectionReason = [" + comment + "]");

if (!comment) {
alert("Please enter a reason for rejection.");
return;
}

var ga = new GlideAjax('SolutionFeedbackHandler');
ga.addParam('sysparm_name', 'processFeedback');
ga.addParam('sysparm_incident_id', $scope.data.sys_id);
ga.addParam('sysparm_action', 'reject');
ga.addParam('sysparm_comment', comment);

ga.getXMLAnswer(function(response) {
if (response === 'success') {
$scope.$apply(function() {
$scope.data.message = "Thanks! The incident has been reopened.";
$scope.data.actionTaken = true;
$scope.showModal = false;
});
} else {
alert("Failed: " + response);
}
});
};
}



 

1 ACCEPTED SOLUTION

Puneet Hegde1
Tera Guru

Hi everyone,
i was able to figure out how to build a custom Service Portal widget that allows end users to accept or reject the resolution of an Incident.

When an incident is moved to the Resolved state, the widget appears to the user.
They can either:

  • Accept the solution, which automatically closes the incident, or

  • Reject the solution, by providing a reason — which then reopens the incident.


HTML:

<div ng-if="data.showWidget && !data.actionTaken" class="panel panel-default p-3">
<h4 class="widget-heading text-primary">Was the solution helpful?</h4>

<div class="d-flex flex-column align-items-start">
<button class="btn btn-success mb-2 widget-button" ng-click="acceptSolution()">
Accept Solution
</button>

<button class="btn btn-danger widget-button" ng-click="showRejectModal()">
Reject Solution
</button>
</div>
</div>

<div ng-if="data.actionTaken">
<p>{{ data.message }}</p>
</div>

<div class="modal-backdrop" ng-if="showModal">
<div class="modal-content-box p-3" style="background: white; border-radius: 6px; max-width: 500px; margin: 100px auto;">
<form name="rejectForm" novalidate ng-submit="submitRejection()">
<h5 class="mb-2">Please tell us why you're rejecting the solution</h5>

<textarea ng-model="data.rejectionReason"
class="form-control"
rows="4"
required
placeholder="Enter your reason..."></textarea>


<div class="modal-buttons mt-3 d-flex justify-content-end">
<button type="button" class="btn btn-secondary mr-2" ng-click="closeModal()">Cancel</button>
<button type="submit" class="btn btn-primary widget-button" ng-disabled="rejectForm.$invalid">Submit</button>
</div>
</form>
</div>
</div>


CSS:

/* === Panel/Card === */
.panel {
border: none;
border-radius: 10px;
background-color: #ffffff;
padding: 20px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
}

/* === Heading === */
.widget-heading {
font-weight: 600;
margin-bottom: 15px;
color: var(--brand-primary, #007bff); /* Use portal theme color or fallback to Bootstrap blue */
}

/* === Base Button === */
.btn {
width: 100%;
max-width: 300px;
font-weight: 600;
padding: 10px;
font-size: 14px;
border-radius: 8px;
}

.mb-2 {
margin-bottom: 10px;
}

/* === Custom Widget Button Enhancements === */
.widget-button {
width: 100%;
max-width: 300px;
font-weight: 600;
padding: 10px 20px;
font-size: 15px;
border-radius: 8px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
transition: all 0.2s ease-in-out;
}

.widget-button:hover {
transform: translateY(-1px);
}

/* === Force Button Colors (Overrides Theme) === */
.btn-success.widget-button {
background-color: #28a745 !important;
border-color: #28a745 !important;
color: #fff !important;
}

.btn-danger.widget-button {
background-color: #dc3545 !important;
border-color: #dc3545 !important;
color: #fff !important;
}

.btn-success.widget-button:hover {
background-color: #218838 !important;
}

.btn-danger.widget-button:hover {
background-color: #c82333 !important;
}

/* === Modal Overlay === */
.modal-backdrop {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.6);
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
}

/* === Modal Content Box === */
.modal-content-box {
background-color: #fff;
padding: 25px;
border-radius: 10px;
width: 500px;
max-width: 90%;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
}

/* === Modal Buttons Layout === */
.modal-buttons {
display: flex;
justify-content: flex-end;
}

/* === Textarea === */
textarea.form-control {
width: 100%;
resize: vertical;
}

.btn-primary.widget-button {
background-color: var(--brand-primary, #007bff) !important;
border-color: var(--brand-primary, #007bff) !important;
color: #fff !important;
}

.btn-primary.widget-button:hover {
background-color: #0069d9 !important;
}


Client script:

function($scope) {
$scope.showModal = false;
$scope.rejectionReason = '';

$scope.acceptSolution = function() {
var ga = new GlideAjax('SolutionFeedbackHandler');
ga.addParam('sysparm_name', 'processFeedback');
ga.addParam('sysparm_incident_id', $scope.data.sys_id);
ga.addParam('sysparm_action', 'accept');
ga.addParam('sysparm_comment', '');

ga.getXMLAnswer(function(response) {
if (response === 'success') {
$scope.$apply(function() {
$scope.data.message = "Thank you! The incident is now closed.";
$scope.data.actionTaken = true;
});
} else {
alert("Failed: " + response);
}
});
};

$scope.showRejectModal = function() {
$scope.rejectionReason = '';
$scope.showModal = true;
};

$scope.closeModal = function() {
$scope.rejectionReason = '';
$scope.showModal = false;
};

$scope.data.rejectionReason = '';

$scope.submitRejection = function() {
var comment = ($scope.data.rejectionReason || "").trim();
console.log("DEBUG: comment =", comment);

if (comment === "") {
alert("Please enter a reason for rejection.");
return;
}

var ga = new GlideAjax('SolutionFeedbackHandler');
ga.addParam('sysparm_name', 'processFeedback');
ga.addParam('sysparm_incident_id', $scope.data.sys_id);
ga.addParam('sysparm_action', 'reject');
ga.addParam('sysparm_comment', comment);

ga.getXMLAnswer(function(response) {
if (response === 'success') {
$scope.$apply(function() {
$scope.data.message = "Thanks! The incident has been reopened.";
$scope.data.actionTaken = true;
$scope.showModal = false;
});
} else {
alert("Failed: " + response);
}
});
};
}

Server script:
(function() {
data.sys_id = $sp.getParameter("sys_id");
data.showWidget = false;

var gr = new GlideRecord('incident');
if (gr.get(data.sys_id) && gr.state == 6) { // 6 = Resolved
data.showWidget = true;
}
})();

Screenshots of the output of the widget are below

Feel free to use the widget as needed or suggest the best modification which makes functionality more dynamic for other tables.

Please click helpful if you use it 

PuneetHegde1_0-1752761204764.pngPuneetHegde1_1-1752761233625.png

 


Thank you,
Puneet Hegde


View solution in original post

4 REPLIES 4

Bhimashankar H
Mega Sage

Hi @Puneet Hegde1 ,

 

In submit rejection function replace the line 'var comment = ($scope.rejectionReason || '').trim();' with

var comment =($scope.rejectiontReason || "").trim();

 

Try it.

 

Did you check after logging the value of $scope.rejectionReason  or comment is it printing the values as entered in pop up. It should log the value entered in pop up.

 

One more thing, replace if(!comment) with if(comment  == "")  =>if comment is blank then it will give pop up with "Please enter the reason for rejection".

 

Thanks,

Bhimashankar

 

------------------------------------------------------------------------------------------------------------------------------------
If my response points you in the right directions, please consider marking it as 'Helpful'. Thanks!

 

 

 

 

Dear Bhimashankar,
Yes, I have tried logging the value, but that also returns blank, as any input provided is not at all considered

Thank you,
Puneet

Dear Bhimashankar,
I was able to identify the issue. The main problem was with the scope, where I forgot to define the scope in the client script. Once I defined the scope, it worked as expected, and I also polished some CSS to make the widget look good.

Thank you,
Puneet

Puneet Hegde1
Tera Guru

Hi everyone,
i was able to figure out how to build a custom Service Portal widget that allows end users to accept or reject the resolution of an Incident.

When an incident is moved to the Resolved state, the widget appears to the user.
They can either:

  • Accept the solution, which automatically closes the incident, or

  • Reject the solution, by providing a reason — which then reopens the incident.


HTML:

<div ng-if="data.showWidget && !data.actionTaken" class="panel panel-default p-3">
<h4 class="widget-heading text-primary">Was the solution helpful?</h4>

<div class="d-flex flex-column align-items-start">
<button class="btn btn-success mb-2 widget-button" ng-click="acceptSolution()">
Accept Solution
</button>

<button class="btn btn-danger widget-button" ng-click="showRejectModal()">
Reject Solution
</button>
</div>
</div>

<div ng-if="data.actionTaken">
<p>{{ data.message }}</p>
</div>

<div class="modal-backdrop" ng-if="showModal">
<div class="modal-content-box p-3" style="background: white; border-radius: 6px; max-width: 500px; margin: 100px auto;">
<form name="rejectForm" novalidate ng-submit="submitRejection()">
<h5 class="mb-2">Please tell us why you're rejecting the solution</h5>

<textarea ng-model="data.rejectionReason"
class="form-control"
rows="4"
required
placeholder="Enter your reason..."></textarea>


<div class="modal-buttons mt-3 d-flex justify-content-end">
<button type="button" class="btn btn-secondary mr-2" ng-click="closeModal()">Cancel</button>
<button type="submit" class="btn btn-primary widget-button" ng-disabled="rejectForm.$invalid">Submit</button>
</div>
</form>
</div>
</div>


CSS:

/* === Panel/Card === */
.panel {
border: none;
border-radius: 10px;
background-color: #ffffff;
padding: 20px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
}

/* === Heading === */
.widget-heading {
font-weight: 600;
margin-bottom: 15px;
color: var(--brand-primary, #007bff); /* Use portal theme color or fallback to Bootstrap blue */
}

/* === Base Button === */
.btn {
width: 100%;
max-width: 300px;
font-weight: 600;
padding: 10px;
font-size: 14px;
border-radius: 8px;
}

.mb-2 {
margin-bottom: 10px;
}

/* === Custom Widget Button Enhancements === */
.widget-button {
width: 100%;
max-width: 300px;
font-weight: 600;
padding: 10px 20px;
font-size: 15px;
border-radius: 8px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
transition: all 0.2s ease-in-out;
}

.widget-button:hover {
transform: translateY(-1px);
}

/* === Force Button Colors (Overrides Theme) === */
.btn-success.widget-button {
background-color: #28a745 !important;
border-color: #28a745 !important;
color: #fff !important;
}

.btn-danger.widget-button {
background-color: #dc3545 !important;
border-color: #dc3545 !important;
color: #fff !important;
}

.btn-success.widget-button:hover {
background-color: #218838 !important;
}

.btn-danger.widget-button:hover {
background-color: #c82333 !important;
}

/* === Modal Overlay === */
.modal-backdrop {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.6);
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
}

/* === Modal Content Box === */
.modal-content-box {
background-color: #fff;
padding: 25px;
border-radius: 10px;
width: 500px;
max-width: 90%;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
}

/* === Modal Buttons Layout === */
.modal-buttons {
display: flex;
justify-content: flex-end;
}

/* === Textarea === */
textarea.form-control {
width: 100%;
resize: vertical;
}

.btn-primary.widget-button {
background-color: var(--brand-primary, #007bff) !important;
border-color: var(--brand-primary, #007bff) !important;
color: #fff !important;
}

.btn-primary.widget-button:hover {
background-color: #0069d9 !important;
}


Client script:

function($scope) {
$scope.showModal = false;
$scope.rejectionReason = '';

$scope.acceptSolution = function() {
var ga = new GlideAjax('SolutionFeedbackHandler');
ga.addParam('sysparm_name', 'processFeedback');
ga.addParam('sysparm_incident_id', $scope.data.sys_id);
ga.addParam('sysparm_action', 'accept');
ga.addParam('sysparm_comment', '');

ga.getXMLAnswer(function(response) {
if (response === 'success') {
$scope.$apply(function() {
$scope.data.message = "Thank you! The incident is now closed.";
$scope.data.actionTaken = true;
});
} else {
alert("Failed: " + response);
}
});
};

$scope.showRejectModal = function() {
$scope.rejectionReason = '';
$scope.showModal = true;
};

$scope.closeModal = function() {
$scope.rejectionReason = '';
$scope.showModal = false;
};

$scope.data.rejectionReason = '';

$scope.submitRejection = function() {
var comment = ($scope.data.rejectionReason || "").trim();
console.log("DEBUG: comment =", comment);

if (comment === "") {
alert("Please enter a reason for rejection.");
return;
}

var ga = new GlideAjax('SolutionFeedbackHandler');
ga.addParam('sysparm_name', 'processFeedback');
ga.addParam('sysparm_incident_id', $scope.data.sys_id);
ga.addParam('sysparm_action', 'reject');
ga.addParam('sysparm_comment', comment);

ga.getXMLAnswer(function(response) {
if (response === 'success') {
$scope.$apply(function() {
$scope.data.message = "Thanks! The incident has been reopened.";
$scope.data.actionTaken = true;
$scope.showModal = false;
});
} else {
alert("Failed: " + response);
}
});
};
}

Server script:
(function() {
data.sys_id = $sp.getParameter("sys_id");
data.showWidget = false;

var gr = new GlideRecord('incident');
if (gr.get(data.sys_id) && gr.state == 6) { // 6 = Resolved
data.showWidget = true;
}
})();

Screenshots of the output of the widget are below

Feel free to use the widget as needed or suggest the best modification which makes functionality more dynamic for other tables.

Please click helpful if you use it 

PuneetHegde1_0-1752761204764.pngPuneetHegde1_1-1752761233625.png

 


Thank you,
Puneet Hegde