-
Notifications
You must be signed in to change notification settings - Fork 74
Lab 3
As your widgets become more complex you will want to break them up into smaller manageable components. Those components could then be reused elsewhere or just help define the connections between parts of your application.
We can separate the card from the card list by moving the card template to a directive.
- Open the widget: Card List in the platform UI by going to [instance]/sp_widget_list.do
- Scroll down to the bottom of the widget find the related list Angular Providers
- Click New.
- Use the following values for the new Angular Provider:
Type: Directive
Name: workspaceCard
Client Script:
function() {
var template = "<div class='list-group'>";
template += "<a href='javascript:void(0)' class='list-group-item' ng-style='::ctrl.getPriority()'>";
template += "<div class='small'>{{::row[primaryField]}}</div>";
template += "<div class='h4 list-group-item-heading'>{{::row[displayField]}}</div>";
template += "<dl class='fields'>";
template += "<span ng-repeat='f in cardFields'><dt>{{fields[f].label}}</dt><dd>{{row[f]}}</dd></span>";
template += "</dl>";
template += "</a>";
template += "</div>";
return {
restrict: 'E',
scope: {
row: '=',
primaryField: '=',
displayField: '=',
priorityField: '=',
fields: '=',
cardFields: '='
},
template: template,
controllerAs: 'ctrl',
controller: function($scope) {
var ctrl = this;
var row = $scope.row;
ctrl.getPrimaryField = function getPrimaryField() {
return row[$scope.primaryField];
};
ctrl.getPriority = function() {
if (typeof row[$scope.priorityField] == "undefined") {
return;
}
var p = row[$scope.priorityField];
var color;
if (p.indexOf('4') > -1) {
color = 'green';
} else if (p.indexOf('3') > -1) {
color = 'yellow';
} else if (p.indexOf('2') > -1) {
color = 'orange';
} else if (p.indexOf('1') > -1) {
color = 'red';
}
if (color) {
return {'border-left': '3px solid ' + color};
}
};
}
};
}
Notice how the template is embedded right in the directive. Understand the pros and cons:
Pros:
- Allows you to create reusable components less widget-specific and share it between widgets.
- Allows you to separate DOM manipulation from the Controller.
Cons:
- Directives are available in the global scope, make sure that you provide a unique directive prefix.
- Directives can get very large and an unmaintainable.
This lab is ultimately about componentizing and so we want to isolate the rendering of the card from the card list widget.
- Open the Card List widget from STUDIO.
- Set the HTML Template code block to:
<div>
<div class="panel panel-default">
<div class="panel-heading">
<span class="h3 panel-title">{{::c.options.title}}</span>
</div>
<div class="panel-body">
<workspace-card ng-repeat="row in c.data.rows track by row.sys_id"
row="::row"
primary-field="::c.data.primaryField"
display-field="::c.options.display_field"
priority-field="::c.options.priority_field"
fields="::c.data.fields"
card-fields="::c.cardFields"></workspace-card>
</div>
</div>
</div>
- Set the Client Script code block to:
function() {
var c = this;
if (!isConfigured()) {
// Provide demo data if options.title is empty
c.options.title = "My active incidents";
c.options.priority_field = "priority";
c.options.display_field = "short_description";
c.data = {
"fields": {"category": {"label": "Category"}, "opened_by": {"label": "Opened By"}},
"primaryField": "number",
"cardFields": "category,opened_by",
"rows": [
{"sys_id":"1", "number":"INC0000002","short_description":"Network file shares access issue","category":"Network","priority":"1 - Critical","opened_by":"admin"},
{"sys_id":"2", "number":"INC0000003","short_description":"I need a mouse","category":"Hardware","priority":"4 - Low","opened_by":"Garfield"}
]
}
}
c.cardFields = getCardFields(c.data.cardFields, c.data.primaryField);
c.getPrimaryField = function getPrimaryField(row) {
return row[c.data.primaryField];
};
function getCardFields(allFields, primaryField) {
var cardFields = [];
allFields = allFields.split(",");
var exclude = [primaryField, c.options.display_field, c.options.priority_field];
for (var i = allFields.length-1; i>= 0; i--){
if (exclude.indexOf(allFields[i]) == -1) {
cardFields.push(allFields[i]);
}
}
return cardFields;
}
function isConfigured() {
if (!c.options) {
return false;
}
if (typeof c.options.title === "undefined")
return false;
return true;
}
}
You can embed a widget inside your HTML Template using the directive. To read more about embedded widgets go here: https://github.com/service-portal/documentation/blob/master/documentation/widget_embedded.md
What about embedding a widget inside another widget? The modal widget is nothing more than a modal dialog wrapper that loads another widget inside of it. The modal widget has a few options that help with loading and closing:
Options:
- embeddedWidgetId : string
- embeddedWidgetOptions : object
- afterOpen : callback
- afterClose: callback
- beforeRender: callback
Follow along to see how you can use the modal widget to embed the sn-desktop-filter widget in the Card List heading:
- Open the Card List widget from the STUDIO.
- Use the following code for the HTML Template code block:
<div>
<sp-widget widget="c.filterModal" ng-if="c.filterModal"></sp-widget>
<div class="panel panel-default">
<div class="panel-heading">
<a href="javascript:void(0)" ng-click="c.showFilter($event)" class="pull-right"><span class="glyphicon glyphicon-search"></span></a> <span class="h3 panel-title">{{::c.options.title}}</span>
</div>
<div class="panel-body">
<workspace-card ng-repeat="row in c.data.rows track by row.sys_id"
row="::row"
primary-field="::c.data.primaryField"
display-field="::c.options.display_field"
priority-field="::c.options.priority_field"
fields="::c.data.fields"
card-fields="::c.cardFields"></workspace-card>
</div>
</div>
</div>
- Use the following code for your Client Script code block:
function($scope) {
var c = this;
c.showFilter = function showFilter(event) {
var filterModalCtrl;
event.preventDefault();
event.stopPropagation();
var unregister = $scope.$on("snfilter:update_query", function(e, query) {
e.stopPropagation();
e.preventDefault();
console.info("new Query", massageEncodedQuery(query));
// Todo: call webservice for data
filterModalCtrl.close();
});
var filterModal = angular.copy(c.data.filterModal);
filterModal.options.afterOpen = function(ctrl){
filterModalCtrl = ctrl;
$scope.$broadcast("snfilter:initialize_query", massageEncodedQuery(c.options.filter));
};
filterModal.options.afterClose = function() {
unregister();
c.filterModal = null;
filterModalCtrl = null;
};
c.filterModal = filterModal;
}
function massageEncodedQuery(query) {
return (query) ? query.replace(/CONTAINS/g, "LIKE").replace(/DOES NOT CONTAIN/g, "NOT LIKE") : query;
}
if (!isConfigured()) {
// Provide demo data if options.title is empty
c.options.title = "My active incidents";
c.options.priority_field = "priority";
c.options.display_field = "short_description";
c.data = {
"fields": {"category": {"label": "Category"}, "opened_by": {"label": "Opened By"}},
"primaryField": "number",
"cardFields": "category,opened_by",
"rows": [
{"sys_id":"1", "number":"INC0000002","short_description":"Network file shares access issue","category":"Network","priority":"1 - Critical","opened_by":"admin"},
{"sys_id":"2", "number":"INC0000003","short_description":"I need a mouse","category":"Hardware","priority":"4 - Low","opened_by":"Garfield"}
]
}
}
c.cardFields = getCardFields(c.data.cardFields, c.data.primaryField);
c.getPrimaryField = function getPrimaryField(row) {
return row[c.data.primaryField];
};
function getCardFields(allFields, primaryField) {
var cardFields = [];
allFields = allFields.split(",");
var exclude = [primaryField, c.options.display_field, c.options.priority_field];
for (var i = allFields.length-1; i>= 0; i--){
if (exclude.indexOf(allFields[i]) == -1) {
cardFields.push(allFields[i]);
}
}
return cardFields;
}
function isConfigured() {
if (!c.options) {
return false;
}
if (typeof c.options.title === "undefined")
return false;
return true;
}
}
- Your Server Script code block should look like this:
(function() {
if (options.table) {
data.filterModal = $sp.getWidget('widget-modal', {'embeddedWidgetId': 'sn-desktop-filter', 'embeddedWidgetOptions': { table: options.table, initialQuery: options.filter }});
data.cardFields = $sp.getListColumns(options.table, options.view_dv);
data.rows = [];
var gr = new GlideRecord(options.table);
gr.addEncodedQuery(options.filter);
gr.setLimit(25);
gr.query();
if (!data.primaryField) {
data.primaryField = gr.getDisplayName();
}
var fields = "sys_id," + data.primaryField + "," + data.cardFields;
data.fields = $sp.getFieldsObject(gr, fields);
while(gr.next()) {
var row = {};
$sp.getRecordDisplayValues(row, gr, fields);
data.rows.push(row);
}
}
})();
- Save the widget
- Test the widget using the incident workspace page in a new tab. Go to: [instance]/$sp.do?id=iw
You should see a new glyph icon in the header of the card list
- Click on the filter icon. The filter builder from the platform UI List v3 will be loaded in a modal.
- Make changes to the filter and click Run. Nothing happens yet beyond a console log statement showing the updated query. In the next section, you will see how to fetch new data for the card list using a web service and the new query.