Skip to content
Will Leingang edited this page May 5, 2017 · 21 revisions

Embedded Widgets and Directives

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.

Move the card template to a directive

We can separate the card from the card list by moving the card template to a directive.

  1. Open the widget Card List in the platform UI by going to [instance]/sp_widget_list.do
  2. Scroll down to the bottom of the widget find the related list Angular Providers
    3.Click New
  3. Use the following values for the new Angular Provider:
    Type: Directive
    Name: card
    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:

  • Keeps the template close to the directive code
  • Allows you to pass in a different template as a scope parameter
  • Can be reused without providing an angular template

Cons:

  • Template isn’t cached
  • Directive can get very large

This lab is ultimately about componentizing and so we want to isolate the rendering of the card from the card list widget.

Use in the widget template

  1. Open the Card List widget
  2. Set the HTML Template 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>
  1. Set the client script to:
function() {
	/* widget controller */
	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;
	}
	
}

Embed filter widget

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:

  1. Open the Card List widget
  2. Use the following code for the HTML Template:
<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>
  1. Add the following 2 functions anywhere inside your client controller:
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;
	}
  1. Your server script should look like this:
(function() {
	/* populate the 'data' object */
	/* e.g., data.table = $sp.getValue('table'); */

	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);
		}
	}
	
})();
  1. Save the widget
  2. 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

  1. Click on the filter icon. The filter builder from the platform UI List v3 will be loaded in a modal.
  2. 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.
Clone this wiki locally