Skip to main content

Service Portal Widget: a 'my approvals' widget

 still sharpening up my angular/bootstrap skills but this does the trick functionally!

at the moment the sort by state is not working too great as it's held in different fields in the target tables (document_id > KB, CHG, RITM )


 


Contents

HTML. 1

CSS. 3

Client Script 8

Server Script 12

 


 


HTML

 

<!--

https://xd.adobe.com/view/0305f2c4-d644-4abb-8558-269aab4c658c-5656/screen/16db5429-79fc-4240-acbb-693a42cdf99d/specs/

https://xd.adobe.com/view/0305f2c4-d644-4abb-8558-269aab4c658c-5656/screen/1c1d968d-a267-4eca-868a-0a346f327916/specs/

https://xd.adobe.com/view/0305f2c4-d644-4abb-8558-269aab4c658c-5656/screen/21c0ca7f-8363-4625-bc93-7ba9f87a20c6/specs/

-->

<div class="ma-container">

    <div>

     <!-- your widget template -->

     <h1 class="mi1">My Approvals</h1>

     <!-- current user: {{data.sysUserID}}-->

    </div>

 

  <div class="ma-openclosed">

    <div class="ma-padded">

      <div class="ma-center">

        <button id="ma-button-open" autofocus class="btn btn-default" ng-click="ma_showOpen()">Open Approvals</button>&nbsp;

        <button id="ma-button-closed" class="btn btn-default" ng-click="ma_showClosed()">Closed Approvals</button>

      </div>

      <div class="typea">

         <widget id="typeahead-search" options=data.search_options></widget>

      </div>

    </div>

  </div>

 

  <div class="panel-heading break-word">

    <h2 id="ma-total-panel-title" class="h4 panel-title">{{c.resultsList}}

      <span class="issues-total">{{c.totalTickets}}</span>

     

      {{c.cellPadder}}<span class="sortby-small">Sorted by: {{c.sortedBy}}</span>

     

      <span class="issues-sort" >Sort by:

         <button id="ma-button-sort-1" class="btn btn-default" ng-click="ma_sort_last_updated()">Last updated</button>

         <button id="ma-button-sort-2" class="btn btn-default" ng-click="ma_sort_created_date()">Created date</button>

         <button id="ma-button-sort-3" class="btn btn-default" ng-click="ma_sort_status()">Status</button>

       </span>

    </h2>

  </div>

 

  <div id="ma-open-tickets" ng-model = "c.openList" ng-hide="{{c.data.showOpen}}">

    <div ng-repeat="openTicket in c.openTickets" >

     <div class="panel-body b-b result-item" style="background-color:#F2FBFE">

      

          <!--buttons-->

          <div style="float: right;">  

             <div class="divider"></div>

              <!--APPROVE:--><button class="btn btn-default" style="background-color:#145896;color:#F2FBFE;font-size:14px" ng-click="c.ma_approveRecord(${openTicket.approval_ticket}, ${openTicket.sys_id})">Approve</button>

              <!--REJECT:--><button class="btn btn-default" style="background-color:white;color:#145896;font-size:14px" ng-click="c.ma_rejectRecord(${openTicket.approval_ticket},${openTicket.sys_id})">Reject</button>

           </div>

      

          <a href="?id=ticket&sys_id={{openTicket.sys_id}}&table=sysapproval_approver"><span style="font-size:16px">Approval for: {{openTicket.approval_ticket}}, {{openTicket.short_description}}</span>

          </a><br/>

          <span id="ma-fi-updated-o-{{openTicket.number}}" style="font-weight:normal" class="ma-fi">Requestor: {{openTicket.requestor}}</span>

          <!--{{c.cellPadder}}<span id="ma-fi-updated-o-{{openTicket.number}}" style="font-weight:normal" class="ma-fi">Approver: {{openTicket.approver}}</span>-->

          {{c.cellPadder}}<span id="ma-priority-o-{{openTicket.number}}" style="font-weight:{{c.highlightColor5}}" class="ma-fi">Ticket state: {{openTicket.state}}</span>

          {{c.cellPadder}}<span id="ma-fi-created-o-{{openTicket.number}}" style="font-weight:{{c.highlightColor1}}" class="ma-fi">Created: {{openTicket.sys_created_on}}</span>

          {{c.cellPadder}}<span id="ma-fi-updated-o-{{openTicket.number}}" style="font-weight:{{c.highlightColor2}}" class="ma-fi">Updated: {{openTicket.sys_updated_on}}</span>

          

      </div>

     

      <div class="ma-spacer"></div>

   </div>

  </div>

 

   <div id="ma-closed-tickets" style="display:none" ng-model = "c.closedList">,

    <div ng-repeat="closedTicket in c.closedTickets" >

       <div class="panel-body b-b result-item" style="background-color:#F2FBFE">

        

         <!--buttons-->

          <div style="float: right;">

            <button class="btn btn-default" style="float: right;background-color:#145896;color:#F2FBFE;font-size:14px" ng-click="ma_viewRecord(${closedTicket.sys_id})">View Approval</button>

         </div>

        

          <a href="?id=ticket&sys_id={{closedTicket.sys_id}}&table=sysapproval_approver"><span class="ma-sd">Approval for: {{closedTicket.approval_ticket}}, {{closedTicket.short_description}}</span>

         </a><br/>

          <span id="ma-fi-updated-c-{{openTicket.number}}" style="font-weight:normal" class="ma-fi">Requestor: {{closedTicket.requestor}}</span>

          <!--{{c.cellPadder}}<span id="ma-fi-updated-o-{{openTicket.number}}" style="font-weight:normal" class="ma-fi">Approver: {{closedTicket.approver}}</span>-->

          {{c.cellPadder}}<span id="ma-priority-c-{{closedTicket.number}}" style="font-weight:{{c.highlightColor5}}" class="ma-fi">Ticket state: {{closedTicket.state}}</span>

          {{c.cellPadder}}<span id="ma-fi-created-c-{{closedTicket.number}}" style="font-weight:{{c.highlightColor3}}" class="ma-fi">Created: {{closedTicket.sys_created_on}}</span>

          {{c.cellPadder}}<span id="ma-fi-updated-c={{closedTicket.number}}" style="font-weight:{{c.highlightColor4}}" class="ma-fi">Updated: {{closedTicket.sys_updated_on}}</span>

         

         </div>

      </div>

      <div class="ma-spacer"></div>

   </div> 

   

</div>

 

 

 

 

 

CSS

 

/*the typeahead input within the embedded widget*/

input[name="q"] { 

  border-radius: 50px;

  height:100%;

}

button[name="search"] { 

  border-radius: 50px;

  height:100%;

}

button[class="input-group-btn"]{

  border-radius: 50px;

}

div.typea{

  width: 380px;

  float: right;

  padding: 15px;

}

/*end typeahead*/

 

h1.ma1{

  color:#003057;

  background-color:#F5F6F8;

  font-size:25px;

}

 

div.ma-openclosed{

  background-color:#145896;

  color:white;

  height: 70px;

}

 

div.ma-center{

  margin: 0;

  position: absolute;

  margin-top: 12px;

}

 

div.ma-padded{

  padding-left: 20px !important;

}

 

div.ma-spacer{

  height:10px;

  background-color:#F5F6F8;

}

 

div.ma-container{

/* min-height: 703px; */

}

 

div.divider{

    width:2px;

    height:auto;

    display:inline-block;

}

 

span.ma-fi{

  font-size:14px;

  color:#003057;

}

span.ma-sd{

  font-size:16px;

}

 

 

span.issues-total{

  color:#145896;

  font-size:16px;

}

span.issues-sort{

  color:#145896;

  font-size:16px;

  float: right;

}

span.sortby-small{

  color:#2A363B;

  font-size:14px;

}

 

span[id^='ma-fi-updated-o'] {    

  font-weight:bold;

}

span[id^='ma-fi-updated-c'] {    

  font-weight:bold;

}

/*span[id^='ma-priority']{

  font-weight:normal;

}*/

 

#ma-total-panel-title{

  color:#2A363B;

  font-size:16px;

}

 

#ma-button-open  {background-color:#F2FBFE;

    color:#145896;

    font-size:18px;

}

#ma-button-open:focus {    

    background-color:#145896;

    color:#F2FBFE;

    opacity: 1;

    font-size:18px;

}

#ma-button-closed  {

    /*background-color: white;*/

    background-color:#F2FBFE;

    color:#145896;

    /*opacity: 1;*/

    font-size:20px;

}

 

#ma-button-closed:focus {    

    background-color:#145896;

    /*color:white;*/ 

    color:#F2FBFE;

    opacity: 1;

    font-size:20px;

}

 

button[id^='ma-button-sort'] { 

    background-color:#F2FBFE;

    color:#145896;

}

button[id^='ma-button-sort']:focus {    

    background-color:#145896;

    color:#F2FBFE;

    opacity: 1;

}

 

 

 

Client Script

 

function($scope, spModal) {

    /* widget controller */

 

 

    var c = this;

    c.data.showOpen = false;

    c.data.showClosed = true;

    c.sortbystate=false;

 

    c.resultsList = 'Total open approvals: ';

    c.totalTickets = c.data.totalOpenIssues;

    c.openTickets = c.data.openIssuesList;

    c.closedTickets = c.data.closedIssuesList;

    c.sortedBy = c.data.sortedBy;

 

    document.getElementById("ma-button-sort-1").style.backgroundColor = "#145896";

    document.getElementById("ma-button-sort-1").style.color = "#F2FBFE";

 

    var sPad = '';

    for (ic = 0; ic < 3; ic++) {

        sPad += String.fromCharCode(160);

    }

    c.cellPadder = sPad;

 

    //--show open button clicked-------------------

    $scope.ma_showOpen = function() {

 

        document.getElementById("ma-button-sort-1").style.backgroundColor = "#145896";

        document.getElementById("ma-button-sort-1").style.color = "#F2FBFE";

 

        c.server.get({

            action: "sort_updated",

            msg: "Sorting issues by updated date..."

 

        }).then(function(r) {

            c.openTickets = r.data.openIssuesList;

            c.closedTickets = r.data.closedIssuesList;

            c.sortedBy = r.data.sortedBy;

        });

 

        c.data.showOpen = false;

        c.resultsList = 'Total open approvals: ';

        c.totalTickets = c.data.totalOpenIssues;

        document.getElementById("ma-closed-tickets").style.display = "none";

        document.getElementById("ma-open-tickets").style.display = "block";

 

        c.highlightColor1 = 'normal';

        c.highlightColor3 = 'normal';

 

        c.highlightColor2 = 'bold';

        c.highlightColor4 = 'bold';

 

        c.highlightColor5 = 'normal';

 

    }

 

    //--show open closed clicked-------------------

    $scope.ma_showClosed = function() {

 

        document.getElementById("ma-button-sort-1").style.backgroundColor = "#145896";

        document.getElementById("ma-button-sort-1").style.color = "#F2FBFE";

        c.server.get({

            action: "sort_updated",

            msg: "Sorting issues by updated date..."

 

        }).then(function(r) {

            c.openTickets = r.data.openIssuesList;

            c.closedTickets = r.data.closedIssuesList;

            c.sortedBy = r.data.sortedBy;

        });

 

        c.resultsList = 'Total closed approvals: ';

        c.totalTickets = c.data.totalClosedIssues;

        document.getElementById("ma-closed-tickets").style.display = "block";

        document.getElementById("ma-open-tickets").style.display = "none";

 

        c.highlightColor1 = 'normal';

        c.highlightColor3 = 'normal';

 

        c.highlightColor2 = 'bold';

        c.highlightColor4 = 'bold';

 

        c.highlightColor5 = 'normal';

 

    }

 

    //--approve approval button clicked-------------------

    c.ma_approveRecord = function(apprTicketNumber, apprTicketNumberSYSID) {

 

        spModal.confirm("Are you sure you wish to mark " + apprTicketNumber + " as approved?", c.name).then(function(confirmed) {

            c.confirmed = confirmed;

            if (c.confirmed) {

 

                c.server.get({

                    action: "approve_ticket",

                    msg: "Approving ticket...",

                    ticket_number: apprTicketNumber,

                    approval_sysid: apprTicketNumberSYSID

 

                }).then(function(r) {

                    location.reload(true);

                    window.location.href = window.location.href;

 

                });

            }

        });

    }

    //--reject approval button clicked-------------------

    c.ma_rejectRecord = function(apprTicketNumber, apprTicketNumberSYSID) {

        spModal.prompt("Why are you rejecting " + apprTicketNumber + "?", c.name).then(function(name) {

            c.name = name;

           

            c.server.get({

                action: "reject_ticket",

                msg: "Rejecting ticket...",

                ticket_number: apprTicketNumber,

                comments: c.name.toString(),

                approval_sysid: apprTicketNumberSYSID

 

            }).then(function(r) {

                location.reload(true);

                window.location.href = window.location.href;

            });

        });

    }

 

 

    //--view record button clicked-------------------

    $scope.ma_viewRecord = function(issueSYSID) {

        var sLink = "?id=ticket&sys_id=" + issueSYSID + "&table=sysapproval_approver";

        //alert(sLink);

        window.open(sLink, '_self');

    }

 

    //----------------sort by UPDATED button clicked----------------------

    $scope.ma_sort_last_updated = function() {

 

        document.getElementById("ma-button-sort-1").style.backgroundColor = "#145896";

        document.getElementById("ma-button-sort-1").style.color = "#F2FBFE";

       

        c.sortbystate=false;

 

        c.server.get({

            action: "sort_updated",

            msg: "Sorting issues by updated date..."

        }).then(function(up) {

            c.openTickets = up.data.openIssuesList;

            c.closedTickets = up.data.closedIssuesList;

            c.sortedBy = up.data.sortedBy;

        });

 

        c.highlightColor2 = 'bold';

        c.highlightColor4 = 'bold';

 

        c.highlightColor1 = 'normal';

        c.highlightColor3 = 'normal';

 

        c.highlightColor5 = 'normal';

    }

    //----------------sort by CREATED button clicked----------------------

    $scope.ma_sort_created_date = function() {

 

        document.getElementById("ma-button-sort-1").style.backgroundColor = "#F2FBFE";

        document.getElementById("ma-button-sort-1").style.color = "#145896";

       

        c.sortbystate=false;

 

        c.server.get({

            action: "sort_created",

            msg: "Sorting issues by created date..."

        }).then(function(cr) {

            c.openTickets = cr.data.openIssuesList;

            c.closedTickets = cr.data.closedIssuesList;

            c.sortedBy = cr.data.sortedBy;

        });

 

        c.highlightColor1 = 'bold';

        c.highlightColor3 = 'bold';

 

        c.highlightColor2 = 'normal';

        c.highlightColor4 = 'normal';

 

        c.highlightColor5 = 'normal';

    }

    //----------------sort by STATUS button clicked----------------------

    $scope.ma_sort_status = function() {

 

        document.getElementById("ma-button-sort-1").style.backgroundColor = "#F2FBFE";

        document.getElementById("ma-button-sort-1").style.color = "#145896";

       

        c.sortbystate=true;

 

        c.server.get({

            action: "sort_status",

            msg: "Sorting issues by status..."

        }).then(function(pr) {

            c.openTickets = pr.data.openIssuesList;

            c.closedTickets = pr.data.closedIssuesList;

            c.sortedBy = pr.data.sortedBy;

        });

 

        c.highlightColor1 = 'normal';

        c.highlightColor3 = 'normal';

 

        c.highlightColor2 = 'normal';

        c.highlightColor4 = 'normal';

 

        c.highlightColor5 = 'bold';

    }

 

}

Server Script

 

(function() {

    /* populate the 'data' object */

    /* e.g., data.table = $sp.getValue('table'); */

    var sMsgPadder = "*********************************** MSG: ";

 

    data.sysUserID = $sp.getParameter("sys_id");

    if (!data.sysUserID) {

        data.sysUserID = gs.getUser().getID();

    }

    data.search_options = '{"contextual_search_sources": { "value": "51cb15cadb8d6c10f04aad05059619bc", "displayValue": "My Approvals" }}';

 

    if (input && input.action === "reject_ticket") { //--REJECT BUTTON CLICKED

        gs.addInfoMessage(sMsgPadder + 'rejecting: ' + input.ticket_number);

        var rejSYSID = input.approval_sysid;

        var rejectionComments = input.comments;

        processApproval('rejected', rejSYSID, rejectionComments);

 

        return;

    }

    if (input && input.action === "approve_ticket") { //--APPROVE BUTTON CLICKED

        gs.addInfoMessage(sMsgPadder + 'approving: ' + input.ticket_number);

        var apprSYSID = input.approval_sysid;

        processApproval('approved', apprSYSID, '');

 

        return;

    }

    //gs.addInfoMessage(sMsgPadder+'test');

 

    var sOrderByField = 'sys_updated_on';

    data.sortedBy = 'approval updated';

    var sOB_descAsc = 'desc';

 

    //--order by status

    if (input && input.action === "sort_status") {

        sOrderByField = 'document_id.state';

        data.sortedBy = 'ticket state';

        sOB_descAsc = 'asc';

    }

    //--order by created

    if (input && input.action === "sort_created") {

        sOrderByField = 'sys_created_on';

        data.sortedBy = 'approval created';

        sOB_descAsc = 'desc';

    }

    //default--order by updated

 

    var sQuery = 'state=requested^approver=' + data.sysUserID;

    var iLimit = 5;

 

    var bSortByState = (data.sortedBy == 'ticket state');

 

    data.openIssuesList = [];

    var grApprOpen = buildApprovalsGR(sOB_descAsc, sOrderByField, true, sQuery, 0);

    iLimit = grApprOpen.getRowCount(); //--uncomment if want to limit to initial iLimit value

    data.totalOpenIssues = iLimit;

    while (grApprOpen.next()) {

        var object_o = buildUpApprArr(grApprOpen, bSortByState);

        data.openIssuesList.push(object_o);

    }

 

    sQuery = 'approver=' + data.sysUserID;

    data.closedIssuesList = [];

    var grApprClosed = buildApprovalsGR(sOB_descAsc, sOrderByField, false, sQuery, 0);

    iLimit = grApprClosed.getRowCount(); //--uncomment if want to limit to initial iLimit value

    data.totalClosedIssues = iLimit;

    while (grApprClosed.next()) {

        var object_c = buildUpApprArr(grApprClosed, bSortByState);

        data.closedIssuesList.push(object_c);

    }

 

})();

 

function buildUpApprArr(grAppr, bSortByState) {

    var objArr = {};

    objArr.sys_id = grAppr.getValue("sys_id");

    var sTicketNumber = grAppr.getDisplayValue('document_id') + "";

    objArr.approval_ticket = sTicketNumber;

    objArr.short_description = grAppr.document_id.short_description + "";

    //--created on:

    var sCrOn = grAppr.getDisplayValue('sys_created_on');

    var sCrOn_Arr = sCrOn.split(' ');

    objArr.sys_created_on = sCrOn_Arr[0] + "";

    //--updated on:

    var sUpdOn = grAppr.getDisplayValue('sys_updated_on');

    var sUpdOn_Arr = sUpdOn.split(' ');

    objArr.sys_updated_on = sUpdOn_Arr[0] + "";

    objArr.approver = grAppr.getDisplayValue('approver') + "";

    var sCreatedByName = "";

    var sCrBy = grAppr.getValue("sys_created_by");

    var grUser = new GlideRecord('sys_user');

    if (grUser.get('user_name', sCrBy)) {

        sCreatedByName = grUser.getValue("name");

    }

    //--state

    objArr.requestor = sCreatedByName;

    if (sTicketNumber.indexOf('KB') > -1) {

        objArr.state = grAppr.getDisplayValue('document_id.workflow_state') + ""; //--KA

    } else {

        objArr.state = grAppr.getDisplayValue('document_id.state') + ""; //--RITM / CHG

    }

 

    return objArr;

}

 

function buildApprovalsGR(orderByAscOrDesc, orderByField, activeQuery, query, iLimit) {

    var grAppr = new GlideRecord('sysapproval_approver');

    if (activeQuery) {

        grAppr.addActiveQuery();

    }

    if (!activeQuery) {

        grAppr.addInactiveQuery();

    }

    if (iLimit > 0) {

        grAppr.setLimit(iLimit);

    }

    if (orderByField != '') {

        if (orderByAscOrDesc == "desc") {

            grAppr.orderByDesc(orderByField);

        } else {

            grAppr.orderBy(orderByField);

        }

        if (orderByField == 'document_id.state') {

            //--add an additional order by for KB

            grAppr.orderBy('document_id.workflow_state');

        }

    }

    grAppr.addQuery(query);

    grAppr.query();

    return grAppr;

}

 

function processApproval(approveReject, aqpproval_SYSID, comments) {

    var grApproval = new GlideRecord('sysapproval_approver');

    if (grApproval.get('sys_id', aqpproval_SYSID)) {

        grApproval.state = approveReject;

        if (!gs.nil(comments)) {

            grApproval.comments = comments;

        }

        grApproval.update();

    }

}

 

Comments

Popular posts from this blog

ServiceNow check for null or nil or empty (or not)

Haven't tested these all recently within global/local scopes, so feel free to have a play! option 1 use an encoded query embedded in the GlideRecord , e.g.  var grProf = new GlideRecord ( 'x_cls_clear_skye_i_profile' ); grProf . addQuery ( 'status=1^ owner=NULL ' ); grProf . query (); even better use the glideRecord  addNotNullQuery or addNullQuery option 2 JSUtil.nil / notNil (this might be the most powerful. See this link ) example: if ( current . operation () == 'insert' && JSUtil . notNil ( current . parent ) && ! current . work_effort . nil ())  option 3 there might be times when you need to get inside the GlideRecord and perform the check there, for example if the code goes down 2 optional routes depending on null / not null can use gs.nil : var grAppr = new GlideRecord ( 'sysapproval_approver' ); var grUser = new GlideRecord ( 'sys_user' ); if ( grUser . get ( 'sys_id' , current . approver )){

Get URL Parameter - server side script (portal or classic UI)

Classic UI : var sURL_editparam = gs . action . getGlideURI (). getMap (). get ( ' sysparm_aparameter ' ); if ( sURL_editparam == 'true' ) { gs . addInfoMessage ( 'parameter passed ); } Portal : var sURL_editparam = $sp . getParameter ( " sysparm_aparameter " ); if ( sURL_editparam == 'true' ) { gs . addInfoMessage ( 'parameter passed ); }