Skip to main content

Service Portal: a 'my orders' widget

 




Contents

HTML. 1

CSS. 1

Client 1

Server. 1


HTML

<div class="container-fluid">

    <h1>

    {{::c.options.title}}

  </h1>

  <div class="panel panel-success" ng-class="{'hide-border' : c.options.panel_border!='true'}">

    <div class="panel-heading">

      <div class="pull-left panel-button-row">

          <a class="btn btn-lg btn-primary" ng-class="{active : data.filter=='active'}" ng-href="?id={{data.currentPage}}&filter=active" role="button">{{::c.options.active_button_label}}</a>

          <a class="btn btn-lg btn-primary" ng-class="{active : data.filter!='active'}" ng-href="?id={{data.currentPage}}&filter=inactive" role="button">{{::c.options.inactive_button_label}}</a>

      </div>

      <div class="pull-right search-bar">

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

      </div>

      <div class="clearfix"></div>

    </div>

    <div class="panel-body">

      <div class="list-header">

        <div class="stats pull-left">

          <h4>{{::data.msgs.total_label}}

            <div ng-if="data.filter=='active'" class="button-group day-dropdown-container" uib-dropdown is-open="dayDropdownStatus.isopen">

              <button id="day-dropdown" type="button" class="btn btn-primary" uib-dropdown-toggle ng-disabled="disabled">

                {{c.dayRange}} <i class="fa fa-chevron-down"></i>

              </button>

              <ul class="dropdown-menu" uib-dropdown-menu role="menu" aria-labelledby="day-dropdown">

                <li role="menu-item"><a ng-hide="c.data.daysAgo == '7'" ng-href="?id={{data.currentPage}}&daysago=7{{c.getSortParam()}}{{c.getFilterParam()}}">Last 7 days</a></li>

                <li role="menu-item"><a ng-hide="c.data.daysAgo == '30'" ng-href="?id={{data.currentPage}}&daysago=30{{c.getSortParam()}}{{c.getFilterParam()}}">Last 30 days</a></li>

              </ul>

            </div>

          </h4>

        </div>

        <ul class="nav nav-pills pull-right">

          <li><h4>${Sort by}:</h4></li>

          <li role="presentation" ng-class="{'active': data.sort==='sys_updated_on'}"><a ng-href="?id={{data.currentPage}}&sort=sys_updated_on{{c.getFilterParam()}}{{c.getDaysAgoParam()}}">{{::data.msgs.last_updated}}</a></li>

          <li role="presentation" ng-class="{'active': data.sort==='sys_created_on'}"><a ng-href="?id={{data.currentPage}}&sort=sys_created_on{{c.getFilterParam()}}{{c.getDaysAgoParam()}}">{{::data.msgs.newest}}</a></li>

          <li role="presentation" ng-class="{'active': data.sort==='status'}"><a ng-href="?id={{data.currentPage}}&sort=status{{c.getFilterParam()}}{{c.getDaysAgoParam()}}">{{::data.msgs.status}}</a></li>

        </ul>

        <div class="clearfix"></div>

      </div>

     

     

      <div class="list-body">

 

        <uib-accordion close-others="true">

          <uib-accordion-group

                               ng-class="['accordion-header']"

                               template-url="group-template.html"

                               ng-repeat="record in c.data.recordList track by record.sys_id"

                               id="{{::record.sys_id}}"

                               is-open="record.isOpen">

            <uib-accordion-heading>

              <div class="row">

                <div class="flex-container">

                    <div aria-hidden="true" id="record_name_{{::record.sys_id}}" class="description-wrapper">

                    <img ng-if="record.icon"ng-src="{{::record.icon}}" class="item-icon pad-right"/>

                      <span aria-role="heading" class="description">{{::record.short_description}}</span>

                    </div>

                    <div aria-hidden="true" class="stage-container">

                      ${{{::data.msgs.current_state}}}: <span class="stage-value" ng-class="{'positive-stage': data.filter=='active'}">{{record.stage}}</span>

                    <span class="fa accordion-icon" ng-class="record.isOpen ? 'fa-chevron-up' : 'fa-chevron-down'" title="{{record.isOpen ?'${Collapse}':'${Expand}'}}" aria-hidden="true"></span>

                    </div>

                </div>

                <div ng-if="record.additionalFields.length > 0" class="sub-heading">

                  <span class="additional-field" ng-repeat="addition in record.additionalFields">

                    <span class="field-label">{{addition.field_label}}:</span>

                    <span class="field-value">{{addition.field_value}}</span>

                  </span>

                </div>

              </div>

            </uib-accordion-heading>

            <div id="record_details_{{::record.sys_id}}" role="region" aria-labelledby="{{::record.sys_id}}">

              <div class="wrapper record-details-wrapper">

                <div class="panel-body">

                  <span>${Reference}: {{record.number}}</span><br/>

                  <span>${{{::data.msgs.current_state}}}: {{record.stage}}</span><br/>

                  <span ng-if="record.item_name">${Item}: {{record.item_name}}</span>

                </div>

                <a class="btn btn-large btn-block btn-primary" role="button" ng-href="?id={{::c.data.targetPage}}&sys_id={{::record.sys_id}}&table={{::c.data.table}}">${{{c.options.view_record_label}}}</a>

              </div>

            </div>

          </uib-accordion-group>

        </uib-accordion>

       

        <div class="show-more" ng-if="data.recordCount > data.showingCount">

          <a ng-href="?id=list&t={{data.table}}&filter={{data.activeQuery}}&target_page_id={{data.targetPage}}&o={{(data.sort=='status') ? data.stateField : data.sort}}&d=desc">{{data.msgs.click_more_label}}<br/><i class="fa fa-chevron-down"></i></a>

        </div>

       

       

      </div> <!-- .list-body -->

     

    </div> <!-- .panel-body -->

  </div> <!-- .panel -->

</div> <!-- .container-fluid -->

 

<script type="text/ng-template" id="group-template.html">

   <div class="accordian-panel">

    <div class="panel-heading">

      <div class="panel-title">

        <div tabindex="0" class="accordion-toggle" ng-click="toggleOpen()" uib-accordion-transclude="heading" role="none">

          <span uib-accordion-header>

          </span>

        </div>

        </div>

    </div>

    <div class="panel-collapse collapse" uib-collapse="!isOpen">

      <div ng-transclude></div>

    </div>

  </div>

  </script>

 

CSS

/* Use with ng-class to selectively use in a theme */

.hide-border {

  border: none;

}

 

/* Blending in the buttons with the panel-header */

.panel-button-row a.btn-primary.active {

  background-color: #fff;

  color: $brand-success;

}

 

/* Style the search bar nicely */

.search-bar {

  width: 380px;

 

  input[name="q"] {

    border-top-left-radius:$search-border-radius;

    border-bottom-left-radius:$search-border-radius;

    color: $input-color !important;

  }

 

  span.input-group-btn {

    button.btn-default {

      border-top-right-radius:$search-border-radius;

      border-bottom-right-radius:$search-border-radius;

      border-left: none;

      color: $brand-success;

    }

  }

 

  input[name="q"] {

    &::placeholder {

      color: $input-color;

    }

    &::-ms-input-placeholder {

      color: $input-color;

    }

    &:-ms-input-placeholder {

      color: $input-color;

    }

    &::-webkit-input-placeholder {

      color: $input-color;

    }

    &:-moz-placeholder {

      color: $input-color;

    }

    &::-moz-placeholder {

      color: $input-color;

    }

  }

}

 

/* Format the tabs into pills and other header stuff */

.list-header {

  padding-left: 1em;

  padding-right: 1em;

  margin-bottom: 1em;

 

  &>.nav-pills > li + li {

    margin-left:1em;

  }

 

  &>.nav-pills > li {

    font-size: 12px;

  }

 

  &>.nav-pills > li > h4 {

    padding: 5px;

  }

 

  &>.nav-pills > li > a {

    border-radius: $search-border-radius;

    background-color: $search-border-color;

    color: #000;

    padding: 7px 12px;

    top: 10px;

    &:hover {

      background-color: darken($search-border-color, 50%); /* Calculate a darker colour */

      color: #fff;

    }

  }

 

  &>.stats {

    margin-top: 10px;

    margin-bottom: 10px;

    padding: 5px;

    h4 {

      color: #000;

      display: inline;

     

      .day-dropdown-container {

        display:inline-block;

       

        button {

          margin-left: 5px;

          background: $search-border-color;

          height: 42px;

          border:none;

          color: #000;

          .fa {

            margin-left: 5px;

          }

        }

       

        .dropdown-menu > li > a {

          color: #000;

        }

      }

    }

  }

 

  &>.nav-pills > li.active > a {

    background-color: $brand-success;

    color: #fff;

  }

}

 

.item-icon {

  max-width:30px;

  height:auto;

}

 

.positive-stage {

  color: $brand-info;

}

 

 

.accordian-panel {

  background-color: #F2FBFE; /* TODO: Make into a CSS variable in the Theme */

    border: none;

  margin-bottom: 15px;

  padding: 10px;

  border-radius: $border-radius-base;

 

  /* Using flex to distribute icons in accordian-header */

  .flex-container {

    display: flex;

    flex-direction: row;

    flex-wrap: nowrap;

    justify-content: space-between;

    height:unset;

    width: unset;

   

    .description-wrapper {

      min-width: 250px;

    }

    span.description {

      font-weight: 600;

      color: $brand-primary;

      text-decoration: underline;

    }

   

    .stage-container .fa {

      margin-left: 10px;

      color: $search-border-color;

    }

   

    .stage-container .stage-value {

      font-weight: 600;

    }

  }

 

    /* These are the contents of each panel (stage and button to view details) */

  .record-details-wrapper {

    background-color: #fff;

    position: relative;

    border-radius: $border-radius-large;

    margin-top: 10px;

  }

 

  .sub-heading {

    font-size: smaller;

   

    .additional-field {

      margin-right:5px;

     

      .field-label {

        font-weight: bold;

      }

    }

  }

}

 

.show-more {

  text-align: center;

  a {

    display:block;

  }

}

 

.accordion-toggle {

  padding: 17px;

  &:focus {

    outline-offset: 0;

  }

}

 

 

Client

 

function($scope, spUtil, $timeout) {

  /* widget controller */

  var c = this;

   

    $timeout(function() {

        spUtil.setBreadCrumb($scope, [

            {label: c.data.myOrdersBreadcrumb, url : '#'}

        ]);

    });

   

    c.getFilterParam = function() {

        return (c.data.filter) ? "&filter=" + c.data.filter : "";

    }

   

    c.getSortParam = function() {

        return (c.data.sort) ? "&sort=" + c.data.sort : "";

    }

   

    c.getDaysAgoParam = function() {

        return (c.data.daysAgo) ? "&daysago=" + c.data.daysAgo : "";

    }

   

    // Dynamic binding to show/hide the day options

    c.dayDropdownStatus = {

        isopen: false

    }

   

    // Dynamic binding used for the day dropdown label

    c.dayRange = "Last " + c.data.daysAgo + " days";

   

}

 

Server

(function() {

  // Prepare messages for template

    data.msgs = {};

    data.msgs.last_updated = gs.getMessage("Last updated");

    data.msgs.newest = gs.getMessage("Created");

    data.msgs.status = gs.getMessage("Status");

    data.msgs.current_state = options.current_state_label || "Current State";

    data.msgs.click_more_label = gs.getMessage(options.click_more_label);

   

   

    data.myOrdersBreadcrumb = gs.getMessage(options.title);

    data.search_options = "{title:'Search open orders', size: 'md', color: 'default', contextual_search_sources: '3d9d9d0edb8d6c10f04aad0505961925'}";

   

    // Ensure to redirect to current or specified page when sorting

    data.currentPage = $sp.getParameter("id");

    data.stateField = options.query_state_field || 'state';

    var sortTypes = ['sys_updated_on', 'sys_created_on', 'status'];

    data.sort = (input && input.sort) || $sp.getParameter("sort") || "sys_updated_on";

    if (!contains(sortTypes, data.sort))

        data.sort = "sys_updated_on";

   

    var filterTypes = ['active', 'inactive'];

    data.filter = (input && input.filter) || $sp.getParameter("filter") || "active";

    if (!contains(filterTypes, data.filter)) {

        data.filter = "active";

    }

   

    var daysAgoOptions = ['7', '30'];

    data.daysAgo = (input && input.daysAgo) || $sp.getParameter('daysago') || "7";

    if (!contains(daysAgoOptions, data.daysAgo)) {

        data.daysAgo = "7";

    }

   

    var recordList = [];

    data.table = options.query_table || "incident";

    data.queryLimit = options.query_limit || 5;

    data.targetPage = options.target_page || "ticket";

    data.activeQuery = options.active_query || "active=true";

    data.inActiveQuery = options.inactive_query || "active=false";

   

    // Prep date range query string

    var sDaysAgo = "", nDaysAgo = parseInt(data.daysAgo);

    switch (nDaysAgo) {

        case 7 :

            sDaysAgo = "^sys_created_onONLast 7 days@javascript:gs.beginningOfLast7Days()@javascript:gs.endOfLast7Days()";

            break;

        case 30 :

            sDaysAgo = "^sys_created_onONLast 30 days@javascript:gs.beginningOfLast30Days()@javascript:gs.endOfLast30Days()";

            break;

        default:

            // TBD

    }

   

    var grRecord = new GlideRecord(data.table);

   

    if (data.filter=='active') {

        grRecord.addEncodedQuery(data.activeQuery + sDaysAgo);

    }

    else {

        grRecord.addEncodedQuery(data.inActiveQuery);

    }

   

    grRecord.setLimit(data.queryLimit);

   

    if (data.sort=='status') {

        grRecord.orderByDesc(data.stateField);

    }

    else {

        grRecord.orderByDesc(data.sort);

    }

   

    grRecord.query();

   

    while (grRecord.next()) {

        var targetRecord = {

            number: grRecord.getDisplayValue(),

            sys_id: grRecord.getUniqueValue(),

            short_description: grRecord.getValue('short_description')

        };

       

        targetRecord.stage = grRecord.getDisplayValue(data.stateField);

       

        /* Catalogue specific attempt at getting an image for the list */

        if (grRecord.cat_item && grRecord.cat_item.icon.toString() != '') {

            targetRecord.icon = grRecord.cat_item.icon.toString() + ".iix"

        }

        if (grRecord.cat_item && grRecord.cat_item.icon.toString() == '' && grRecord.cat_item.picture.toString() != '') {

            targetRecord.icon = grRecord.cat_item.picture.toString() + ".iix"

        }

       

        // Check if there are additional fields specified in the options and show them in the sub-header

        targetRecord.additionalFields = [];

        if (options.additional_fields != '') {

            var additionalFields = options.additional_fields.split(',');

                       

            for (var a = 0; a < additionalFields.length; a++) {

                var sField = additionalFields[a];

                var oField = {

                    'field_label' : grRecord[sField].getLabel(),

                    'field_value' : grRecord.getDisplayValue(sField)

                }

                targetRecord.additionalFields.push(oField);

            }

        }

        recordList.push(targetRecord);

    }

   

    data.recordList = recordList;

    data.showingCount = recordList.length;

    data.recordCount = getCount(data.table, (data.filter=='active') ? data.activeQuery+sDaysAgo : data.inActiveQuery);

   

    if (data.filter=='active') {

        data.msgs.total_label = gs.getMessage("{0} orders placed in", [data.recordCount])

    }

    else {

        data.msgs.total_label = (options.total_inactive_label || "Total closed records") + ": " + data.recordCount;

    }

})();

 

function contains(arr, str) {

    for (var i = 0; i < arr.length; i++) {

        if (arr[i].equals(str))

            return true;

    }

 

    return false;

}

 

function getCount(table, query) {

    var count = 0,

            ga = new GlideAggregate(table);

    ga.addAggregate('COUNT');

    ga.addEncodedQuery(query);

    ga.query();

    if (ga.next()) {

        count = ga.getAggregate('COUNT');

    }

    return count;

}

 

 

 

 

 

 

Comments

Popular posts from this blog

URL link in addInfoMessage

var ga=new GlideAjax('gld_HR_ajax'); ga.addParam('sysparm_name', 'checkEmployeeNumber_hrProfile'); ga.addParam('sysparm_hrprofilenumber', g_form.getValue('number')); ga.addParam('sysparm_employeenumber', newValue); ga.getXMLAnswer(function(answer) { if (answer!='undefined' && answer!=''){ var navURL="<a style='text-decoration:underline;color:blue' href=hr_profile.do?sysparm_query=number=" + answer + ">" + answer + "</a><img width='3' src='images/s.gif'/>"; var sMsg='The employee number entered already exists on another HR Profile ' + navURL; //alert(sMsg); g_form.showErrorBox('employee_number', 'error - please check'); g_form.addInfoMessage(sMsg); } });

GlideRecord setValue

setValue(String name, Object value) Sets the specified field to the specified value. Normally a script would do a direct assignment, for example,  gr.category = value . However, if in a script the element name is a variable, then  gr.setValue(elementName, value)  can be used. When setting a value, ensure the data type of the field matches the data type of the value you enter. This method cannot be used on journal fields. If the value parameter is null, the record is not updated, and an error is not thrown https://developer.servicenow.com/app.do#!/api_doc?v=madrid&id=r_GlideRecord-setValue_String_Object