<h2>Tutorial: Build a custom widget</h2><br/><div style="overflow-x:auto"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"><head><meta content="text/html; charset=UTF-8" /><meta name="copyright" content="(C) Copyright 2025" /><meta name="DC.rights.owner" content="(C) Copyright 2025" /><meta name="generator" content="DITA-OT" /><meta name="DC.type" content="concept" /><meta name="DC.title" content="Tutorial: Build a custom widget" /><meta name="abstract" content="Follow this tutorial to build a custom widget that displays Service Catalog items. Use this tutorial as a model to help you understand the advanced scripting power of the Service Portal." /><meta name="description" content="Follow this tutorial to build a custom widget that displays Service Catalog items. Use this tutorial as a model to help you understand the advanced scripting power of the Service Portal." /><meta name="DC.subject" content="Create a widget, Widget tutorial, Service Portal widgets, widget client script, widget server script, widget html template, widget empty state, embedding widgets, Angular providers, Angular directives" /><meta name="keywords" content="Create a widget, Widget tutorial, Service Portal widgets, widget client script, widget server script, widget html template, widget empty state, embedding widgets, Angular providers, Angular directives" /><meta name="DC.relation" scheme="URI" content="../../../build/service-portal/concept/widget-dev-guide.html" /><meta name="DC.relation" scheme="URI" content="../../../administer/navigation-and-ui/concept/c_NavigationAndTheUserInterface.html" /><meta name="DC.relation" scheme="URI" content="../../../administer/general/concept/configure-uis-and-portals.html" /><meta name="DC.relation" scheme="URI" content="../../../build/service-portal/concept/c_ServicePortal.html" /><meta name="DC.creator" content="ServiceNow" /><meta name="DC.date.created" content="2023-08-03" /><meta name="DC.date.modified" content="2023-08-03" /><meta name="DC.format" content="XHTML" /><meta name="DC.identifier" content="adv-widget-tutorial" /><link rel="stylesheet" type="text/css" href="../../../CSS/commonltr.css" /><title>Tutorial: Build a custom widget</title></head><body> <div class="nested0" id="adv-widget-tutorial"> <h1 class="title topictitle1" id="ariaid-title1">Tutorial: Build a custom widget</h1> <div class="body conbody"><p class="shortdesc">Follow this tutorial to build a custom widget that displays <span class="ph">Service Catalog</span> items. Use this tutorial as a model to help you understand the advanced scripting power of the <span class="ph">Service Portal</span>.</p> <div class="p">In this tutorial, you will create the Quick Order widget. This widget: <ul class="ul" id="adv-widget-tutorial__ul_n2x_lkh_xz"><li class="li">Displays popular items to the user prior to any search.</li><li class="li">Queries the <span class="ph">Service Catalog</span> and displays available options to the user.</li><li class="li">Includes an embedded SC Catalog Item widget, allowing the user to view and order items within the Quick Order widget.</li><li class="li">Uses an Angular Provider to display a category icon beside each queried item.</li></ul> </div> </div> <div class="related-links"> <div class="familylinks"> <div class="parentlink"><strong>Parent Topic:</strong> <a class="link" href="../../../build/service-portal/concept/widget-dev-guide.html" title="Develop custom widgets for portals using AngularJS, Bootstrap, and the ServiceNow API.">Developing custom widgets</a></div> </div> </div><div class="topic task nested1" id="create-widget"> <h2 class="title topictitle2" id="ariaid-title2">Create a widget and set up a template</h2> <div class="body taskbody"><p class="shortdesc">Create the Quick Order widget to query items in the <span class="ph">Service Catalog</span>.</p> <div class="section prereq p" id="create-widget__prereq_cpj_hwv_s1b"> <p class="p">Role required: admin or sp_admin</p> </div> <ol class="ol steps"><li class="li step stepexpand"> <span class="ph cmd">Navigate to <span class="ph menucascade"><span class="ph uicontrol">All</span> > <span class="ph uicontrol">Service Portal</span> > <span class="ph uicontrol">Service Portal Configuration</span></span> and click <span class="ph uicontrol">Widget Editor</span>.</span> </li><li class="li step stepexpand"> <span class="ph cmd">Click <span class="ph uicontrol">Create a new widget</span>.</span> </li><li class="li step stepexpand"> <span class="ph cmd">Define the following values.</span> <div class="itemgroup info"> <div class="p"><ul class="ul" id="create-widget__ul_tf5_rjh_xz"><li class="li"><span class="ph uicontrol">Widget Name</span>: Quick Order</li><li class="li"><span class="ph uicontrol">Widget ID</span>: quick_order</li><li class="li"><span class="ph uicontrol">Create a test page</span>: Active</li><li class="li"><span class="ph uicontrol">Page ID</span>: quick_order</li></ul> </div> </div> <div class="itemgroup stepresult"> <p class="p">Adding a widget to a test page creates a record in each of the following tables:</p> <div class="p"><ul class="ul" id="create-widget__ul_ag2_wqh_xz"><li class="li">sp_page</li><li class="li">sp_container</li><li class="li">sp_row</li><li class="li">sp_column</li><li class="li">sp_instance</li><li class="li">sp_widget</li></ul> </div> <div class="p"><div class="note"><span class="notetitle">Note:</span> You can use the Page Editor in Service Portal Configuration to view the hierarchy of elements on your test page.</div> </div> </div> </li><li class="li step stepexpand"> <span class="ph cmd">Click <span class="ph uicontrol">Submit</span>.</span> </li><li class="li step stepexpand"> <span class="ph cmd">From the <span class="ph uicontrol">Widget Editor</span>, open the Quick Order widget.</span> </li><li class="li step stepexpand"> <span class="ph cmd">Add the following simple template to the HTML field.</span> <div class="itemgroup info"> <pre class="pre codeblock"><code><div class="panel panel-primary"> <div class="panel-heading">Request an item from the catalog</div> <div class="panel-body"> My catalog results </div> </div> </code></pre> </div> </li><li class="li step stepexpand"> <span class="ph cmd">Click <span class="ph uicontrol">Save</span>.</span> </li><li class="li step stepexpand"> <span class="ph cmd">Preview your test page in a new tab using the following URL: <code class="ph codeph"><yourInstanceUrl>/sp?id=quick_order</code>.</span> <div class="itemgroup stepresult"> <p class="p">Your widget template displays on the test page.</p> <p class="p"><img class="image" id="create-widget__image_q2t_zfw_xz" src="../image/quick-order-test.png" alt="Widget rendering on a page" /></p> </div> </li></ol> </div> </div> <div class="topic task nested1" id="add-scripts"> <h2 class="title topictitle2" id="ariaid-title3">Add a server script to query an instance table</h2> <div class="body taskbody"><p class="shortdesc">After adding your widget and creating a basic template, you can define advanced client and server scripts that enable users to query data from an instance table. You can pass the data model between the client and server by querying data from the database, displaying it to the user, and sending any updates back to the server.</p> <div class="section prereq p" id="add-scripts__prereq_sfv_hwv_s1b"> <p class="p">Role required: admin or sp_admin</p> </div> <ol class="ol steps"><li class="li step stepexpand"> <span class="ph cmd">From the <span class="ph uicontrol">Widget Editor</span>, open the Quick Order widget.</span> </li><li class="li step stepexpand"> <span class="ph cmd">Select <span class="ph uicontrol">Server Script</span> to open the server script field.</span> </li><li class="li step stepexpand"> <span class="ph cmd">Replace the default server script with the following custom script.</span> <div class="itemgroup info"> <div class="p"><pre class="pre codeblock language-javascript">(<strong class="hl-keyword">function</strong>() { <strong class="hl-keyword">if</strong> (input.keywords != null && input.keywords != <span class="hl-string">''</span>) data.items = getCatalogItems(input.keywords); <strong class="hl-keyword">function</strong> getCatalogItems(keywords) { <strong class="hl-keyword">var</strong> sc = <strong class="hl-keyword">new</strong> GlideRecord(<span class="hl-string">'sc_cat_item'</span>); sc.addActiveQuery(); sc.addQuery(<span class="hl-string">'123TEXTQUERY321'</span>, keywords); sc.addQuery(<span class="hl-string">'sys_class_name'</span>, <span class="hl-string">'NOT IN'</span>, <span class="hl-string">'sc_cat_item_wizard,sc_cat_item_content'</span>); sc.addQuery(<span class="hl-string">'sc_catalogs'</span>, <span class="hl-string">'e0d08b13c3330100c8b837659bba8fb4'</span>); sc.setLimit(<span class="hl-number">100</span>); sc.orderByDesc(<span class="hl-string">"ir_query_score"</span>); sc.query(); <strong class="hl-keyword">var</strong> results = []; <strong class="hl-keyword">while</strong> (sc.next()) { <strong class="hl-keyword">if</strong> (!$sp.canReadRecord(sc)) <strong class="hl-keyword">continue</strong>; <strong class="hl-keyword">var</strong> item = {}; $sp.getRecordDisplayValues(item, sc, <span class="hl-string">'name,price,sys_id'</span>); item.category = sc.getValue(<span class="hl-string">'category'</span>); results.push(item); } <strong class="hl-keyword">return</strong> results; } })(); </pre></div> <p class="p">This script performs a keyword search on the sc_cat_item table using the 123TEXTQUERY321 query method.</p> </div> </li><li class="li step stepexpand"> <span class="ph cmd">Replace the HTML template with the following script:</span> <div class="itemgroup info"> <div class="p"><pre class="pre codeblock"><code><div class="panel panel-primary"> <div class="panel-heading">Request an item from the catalog</div> <div class="panel-body"> <input class="form-control" type="search" placeholder="Start typing here to search the list of catalog items" ng-model="c.data.keywords" ng-change="c.server.update()" ng-model-options="{debounce: 250}" /> <ul class="list-group result-container"> <li class="list-group-item" ng-repeat="item in c.data.items"> <a href>{<!-- -->{item.name}}</a><span class="pull-right">{<!-- -->{item.price}}</span> </li> </ul> </div> </div> </code></pre></div> <p class="p">This template adds a search field and displays the results of the query performed in the server script using the following Angular directives. To learn more about these directives, review the <a class="xref" href="https://docs.angularjs.org/api/ng/directive" target="_blank" rel="noopener noreferrer">Angular API Reference</a>.</p> <div class="p"> <div class="tablenoborder"><table cellpadding="4" cellspacing="0" summary="" id="add-scripts__table_q2x_tzw_xz" class="table" frame="border" border="1" rules="all"><caption><span class="tablecap"><span class="table--title-label">Table 1. </span>Angular directives used in the template</span></caption><colgroup><col style="width:26.385224274406333%" /><col style="width:73.61477572559367%" /></colgroup><thead class="thead" style="text-align:left;"><tr class="row"><th class="entry cellrowborder" style="vertical-align:top;" id="d37309e475">Angular directive</th><th class="entry cellrowborder" style="vertical-align:top;" id="d37309e478">Description</th></tr></thead><tbody class="tbody"><tr class="row"><td class="entry cellrowborder" style="vertical-align:top;" headers="d37309e475 ">ng-model</td><td class="entry cellrowborder" style="vertical-align:top;" headers="d37309e478 ">Automatically reads and writes value changes to the model variable <code class="ph codeph">c.data.keywords</code>.</td></tr><tr class="row"><td class="entry cellrowborder" style="vertical-align:top;" headers="d37309e475 ">ng-model-options</td><td class="entry cellrowborder" style="vertical-align:top;" headers="d37309e478 ">Configures ng-model behavior. In this template, ng-model updates the model after a user has stopped typing for 250 milliseconds.</td></tr><tr class="row"><td class="entry cellrowborder" style="vertical-align:top;" headers="d37309e475 ">ng-change</td><td class="entry cellrowborder" style="vertical-align:top;" headers="d37309e478 ">Executes <code class="ph codeph">c.server.update()</code> after the model value changes. This function posts the data object to the server script. After the script is executed, the data object is automatically updated with the new values from the server generated data object.</td></tr><tr class="row"><td class="entry cellrowborder" style="vertical-align:top;" headers="d37309e475 ">ng-repeat</td><td class="entry cellrowborder" style="vertical-align:top;" headers="d37309e478 ">Creates a template from the parent element and child elements. For each <code class="ph codeph">item in c.data.items</code>, an instance of the template is created and the expressions <code class="ph codeph">{<!-- -->{item.name}}</code> and <code class="ph codeph">{<!-- -->{item.price}}</code> are replaced with the values from each item.</td></tr></tbody></table> </div> </div> </div> </li><li class="li step stepexpand"> <span class="ph cmd">Add the following script to the <span class="ph uicontrol">CSS - SCSS</span> field:</span> <div class="itemgroup info"> <div class="p"><pre class="pre codeblock language-css">.result-container { <strong class="hl-keyword">margin-top</strong>: <span class="hl-number">10</span>px; } </pre></div> </div> </li><li class="li step stepexpand"> <span class="ph cmd">Refresh your test page preview to view the changes.</span> <div class="itemgroup stepresult"> <p class="p">As you type in the search box, matching catalog items appear. Try searching for <span class="ph uicontrol">ipad</span>.</p> <p class="p"><img class="image" id="add-scripts__image_ys4_czw_xz" src="../image/widget-tutorial-search.png" alt="Search displays results for iPad." /></p> </div> </li></ol> </div> </div> <div class="topic task nested1" id="empty-state"> <h2 class="title topictitle2" id="ariaid-title4">Manage the empty state of a widget</h2> <div class="body taskbody"><p class="shortdesc">Display a list of popular items to the user before any search terms are entered.</p> <div class="section prereq p" id="empty-state__prereq_mkp_rwv_s1b"> <p class="p">Role required: admin or sp_admin</p> </div> <div class="section context" id="empty-state__context_cwr_sxb_yz"> <p class="p">Because no search has been executed when the widget initializes, the server <span class="ph uicontrol">input</span> variable is undefined. This empty state may cause confusion when a user first interacts with the widget. To solve this issue, give your widget something to display when the <span class="ph uicontrol">input</span> variable is empty. This initial data can guide your users when initially interacting with your widget.</p> </div> <ol class="ol steps"><li class="li step stepexpand"> <span class="ph cmd">From the <span class="ph uicontrol">Widget Editor</span>, open the Quick Order widget.</span> </li><li class="li step stepexpand"> <span class="ph cmd">Replace the existing server script with the following script:</span> <div class="itemgroup info"> <div class="p"><pre class="pre codeblock language-javascript">(<strong class="hl-keyword">function</strong>() { <strong class="hl-keyword">if</strong> (input.keywords != null && input.keywords != <span class="hl-string">''</span>) data.items = getCatalogItems(input.keywords); <strong class="hl-keyword">else</strong> data.items = getPopularItems(); <strong class="hl-keyword">function</strong> getCatalogItems(keywords) { <strong class="hl-keyword">var</strong> sc = <strong class="hl-keyword">new</strong> GlideRecord(<span class="hl-string">'sc_cat_item'</span>); sc.addActiveQuery(); sc.addQuery(<span class="hl-string">'123TEXTQUERY321'</span>, keywords); sc.addQuery(<span class="hl-string">'sys_class_name'</span>, <span class="hl-string">'NOT IN'</span>, <span class="hl-string">'sc_cat_item_wizard,sc_cat_item_content'</span>); sc.addQuery(<span class="hl-string">'sc_catalogs'</span>, <span class="hl-string">'e0d08b13c3330100c8b837659bba8fb4'</span>); sc.setLimit(<span class="hl-number">100</span>); sc.orderByDesc(<span class="hl-string">"ir_query_score"</span>); sc.query(); <strong class="hl-keyword">var</strong> results = []; <strong class="hl-keyword">while</strong> (sc.next()) { <strong class="hl-keyword">if</strong> (!$sp.canReadRecord(sc)) <strong class="hl-keyword">continue</strong>; <strong class="hl-keyword">var</strong> item = {}; $sp.getRecordDisplayValues(item, sc, <span class="hl-string">'name,price,sys_id'</span>); item.category = sc.getValue(<span class="hl-string">'category'</span>); results.push(item); } <strong class="hl-keyword">return</strong> results; } <strong class="hl-keyword">function</strong> getPopularItems() { <strong class="hl-keyword">var</strong> items = []; <strong class="hl-keyword">var</strong> count = <strong class="hl-keyword">new</strong> GlideAggregate(<span class="hl-string">'sc_req_item'</span>); count.addAggregate(<span class="hl-string">'COUNT'</span>, <span class="hl-string">'cat_item'</span>); count.groupBy(<span class="hl-string">'cat_item'</span>); count.addQuery(<span class="hl-string">'cat_item.sys_class_name'</span>, <span class="hl-string">'NOT IN'</span>, <span class="hl-string">'sc_cat_item_guide,sc_cat_item_wizard,sc_cat_item_content'</span>); count.addQuery(<span class="hl-string">'cat_item.sc_catalogs'</span>, <span class="hl-string">'e0d08b13c3330100c8b837659bba8fb4'</span>); count.orderByAggregate(<span class="hl-string">'COUNT'</span>, <span class="hl-string">'cat_item'</span>); count.query(); <strong class="hl-keyword">while</strong> (count.next() && items.length < <span class="hl-number">9</span>) { <strong class="hl-keyword">if</strong> (!$sp.canReadRecord(<span class="hl-string">"sc_cat_item"</span>, count.cat_item.sys_id.getDisplayValue())) <strong class="hl-keyword">continue</strong>; <em class="hl-comment">// user does not have permission to see this item</em> <strong class="hl-keyword">var</strong> item = {}; item.name = count.cat_item.name.getDisplayValue(); item.category = count.cat_item.category.toString(); item.price = count.cat_item.price.getDisplayValue(); item.sys_id = count.cat_item.sys_id.getDisplayValue(); items.push(item); } <strong class="hl-keyword">return</strong> items; } })();</pre></div> <p class="p">This script introduces a new function <code class="ph codeph">getPopularItems()</code> to query the database and return popular items when the <span class="ph uicontrol">input</span> variable is empty.</p> </div> </li><li class="li step stepexpand"> <span class="ph cmd">Replace the HTML template with the following script:</span> <div class="itemgroup info"> <div class="p"><pre class="pre codeblock"><code><div class="panel panel-primary"> <div class="panel-heading">Request an item from the catalog</div> <div class="panel-body"> <input class="form-control" type="search" placeholder="Start typing here to search the list of catalog items" ng-model="c.data.keywords" ng-change="c.server.update()" ng-model-options="{debounce: 250}" /> <h5 ng-if="!c.data.keywords">Showing the most popular items</h5> <ul class="list-group result-container"> <li class="list-group-item" ng-repeat="item in c.data.items"> <a href>{<!-- -->{item.name}}</a><span class="pull-right">{<!-- -->{item.price}}</span> </li> </ul> </div> <div class="panel-footer" ng-if="c.data.keywords"> <ng-pluralize count="c.data.items.length" when="{'0': 'No items found for ', '1': 'One item matching ', 'other': 'Found {} items matching '}"> </ng-pluralize> {<!-- -->{c.data.keywords}} </div> </div> </code></pre></div> <p class="p">This script provides a template to display the popular items returned from the server script.</p> </div> </li><li class="li step stepexpand"> <span class="ph cmd">Refresh your test page preview to view the changes.</span> <div class="itemgroup stepresult"> <p class="p">The widget displays popular items to the user prior to any search input.</p> <p class="p"><img class="image" id="empty-state__image_gwb_whc_yz" src="../image/empty-state.png" alt="A search box with the text "Start typing here to search the list of catalog items"." /></p> </div> </li></ol> </div> </div> <div class="topic task nested1" id="embed-widget"> <h2 class="title topictitle2" id="ariaid-title5">Embed an existing widget</h2> <div class="body taskbody"><p class="shortdesc">Enable the user to view and purchase <span class="ph">Service Catalog</span> items in the Quick Order widget by embedding the SC Catalog Item widget.</p> <div class="section prereq p" id="embed-widget__prereq_anx_twv_s1b"> <p class="p">Role required: admin or sp_admin</p> </div> <div class="section context" id="embed-widget__context_lxh_tjc_yz"> <p class="p">Instead of duplicating code, you can embed widgets to leverage pre-existing functionality. The SC Catalog Item widget is a base system widget that enables the user to view and purchase <span class="ph">Service Catalog</span> items.</p> </div> <ol class="ol steps"><li class="li step stepexpand"> <span class="ph cmd">Inspect the SC Catalog Item widget.</span> <div class="itemgroup info">Before embedding the SC Catalog Item widget, inspect the widget to understand what data it needs access to. You may need to update your Quick Order widget client or server script to make sure that the correct data is passed to the embedded widget. </div> <ol type="a" class="ol substeps" id="embed-widget__substeps_x34_wkc_yz"><li class="li substep substepexpand"> <span class="ph cmd">Navigate to <code class="ph codeph"><yourInstanceURL>/sp_config?id=widget_editor</code>.</span> </li><li class="li substep substepexpand"> <span class="ph cmd">Open the SC Catalog Item widget.</span> </li><li class="li substep substepexpand"> <span class="ph cmd">Note that the widget ID is <code class="ph codeph">widget-sc-cat-item</code>.</span> <div class="itemgroup info">You will use this ID to embed the widget model in the client script.</div> </li><li class="li substep substepexpand"> <span class="ph cmd">Examine the server script.</span> <div class="itemgroup info"> <p class="p">Notice that the <span class="ph uicontrol">data</span> object includes a sys_id property populated by either the <span class="ph uicontrol">input</span> or <span class="ph uicontrol">options</span> objects. If neither <span class="ph uicontrol">input</span> nor <span class="ph uicontrol">options</span> include a sys_id, the <a class="xref" href="../app-store/dev_portal/API_reference/GlideSPScriptableScoped/concept/c_GlideSPScriptableScopedAPI.dita/c_GlideSPScriptableScopedAPI.html" target="_blank" rel="noopener noreferrer">$sp.getParameter()</a> method retrieves the sys_id from the request query string.</p> <p class="p"><img class="image" id="embed-widget__image_xjd_cnc_yz" src="../image/embedded-widget.png" alt="Sample server script using an If Else statement to retrieve the sys_id." /></p> <p class="p">To populate the <span class="ph uicontrol">input</span> object, you can pass a catalog item sys_id from the Quick Order widget client script.</p> </div> </li></ol> </li><li class="li step stepexpand"> <span class="ph cmd">From the <span class="ph uicontrol">Widget Editor</span>, open the Quick Order widget.</span> </li><li class="li step stepexpand"> <span class="ph cmd">Replace the Quick Order widget client script with the following script.</span> <div class="itemgroup info"> <div class="p"><pre class="pre codeblock language-javascript"><strong class="hl-keyword">function</strong>($location, spUtil) { <strong class="hl-keyword">var</strong> c = <strong class="hl-keyword">this</strong>; c.select = <strong class="hl-keyword">function</strong>(item_id) { <strong class="hl-keyword">if</strong> (c.openItem == item_id) { c.openItem = null; <strong class="hl-keyword">return</strong>; } renderCatalogItemWidget(item_id); } <strong class="hl-keyword">function</strong> renderCatalogItemWidget(item_id) { c.catalogItemWidget = null; spUtil.get(<span class="hl-string">"widget-sc-cat-item"</span>, { sys_id: item_id }).then(<strong class="hl-keyword">function</strong>(response) { c.catalogItemWidget = response; c.openItem = item_id; }); } }</pre></div> <p class="p">This script uses <code class="ph codeph">spUtil.get()</code> to retrieve the widget model by ID (<code class="ph codeph">widget-sc-cat-item</code>) and define the <code class="ph codeph">{sys_id: item_id}</code> object. This object posts to the server script as <span class="ph uicontrol">input</span>.</p> </div> </li><li class="li step stepexpand"> <span class="ph cmd">Replace the HTML template with the following script:</span> <div class="itemgroup info"> <div class="p"><pre class="pre codeblock"><code><div class="panel panel-primary"> <div class="panel-heading">Request an item from the catalog</div> <div class="panel-body"> <input class="form-control" type="search" placeholder="Start typing here to search the list of catalog items" ng-model="c.data.keywords" ng-change="c.server.update()" ng-model-options="{debounce: 250}" /> <h5 ng-if="!c.data.keywords">Showing the most popular items</h5> <ul class="list-group result-container"> <li class="list-group-item" ng-repeat="item in c.data.items"> <a href ng-click="c.select(item.sys_id)">{<!-- -->{item.name}}</a><span class="pull-right">{<!-- -->{item.price}}</span> <div class="catalog-item" ng-if="item.sys_id == c.openItem"> <sp-widget ng-if="c.catalogItemWidget" widget="c.catalogItemWidget" /> </div> </li> </ul> </div> <div class="panel-footer" ng-if="c.data.keywords"> <ng-pluralize count="c.data.items.length" when="{'0': 'No items found for ', '1': 'One item matching ', 'other': 'Found {} items matching '}"> </ng-pluralize> {<!-- -->{c.data.keywords}} </div> </div></code></pre></div> <p class="p">This template:</p> <div class="p"><ul class="ul" id="embed-widget__ul_ygs_bsg_zz"><li class="li">Adds on-click behavior using the ng-click directive.</li><li class="li">Displays the embedded SC Catalog Item widget using the sp-widget directive.</li></ul> </div> </div> </li><li class="li step stepexpand"> <span class="ph cmd">Replace the CSS with the following script:</span> <div class="itemgroup info"> <div class="p"><pre class="pre codeblock language-css">.result-container { <strong class="hl-keyword">margin-top</strong>: <span class="hl-number">10</span>px; } .catalog-item { <strong class="hl-keyword">background-color</strong>: #f5f5f5; <strong class="hl-keyword">padding</strong>: <span class="hl-number">10</span>px; @include border-top-radius($panel-border-radius); @include border-bottom-radius($panel-border-radius); } </pre></div> </div> </li><li class="li step stepexpand"> <span class="ph cmd">Refresh your test page preview to view the changes.</span> <div class="itemgroup stepresult"> <p class="p">When you select a search result, the item opens in the embedded SC Catalog Item widget.</p> <p class="p"><img class="image" id="embed-widget__image_abs_4zc_yz" src="../image/embed-widget-result.png" alt="Test page showing a selected item displaying in an embedded widget." /></p> </div> </li></ol> </div> </div> <div class="topic task nested1" id="reusable-directive"> <h2 class="title topictitle2" id="ariaid-title6">Create a reusable directive and add it to a widget</h2> <div class="body taskbody"><p class="shortdesc">Angular Providers are reusable components that can be added to multiple widgets. Using the Widget Angular Providers table, create a directive that shows a category icon next to each result in the Quick Order widget.</p> <div class="section prereq p" id="reusable-directive__prereq_x4q_vwv_s1b"> <p class="p">Role required: admin or sp_admin</p> </div> <div class="section context" id="reusable-directive__context_fvk_x1d_yz"> <p class="p">Angular Providers let you build angular directives and services that can be injected into your client script controller. The code in a Provider differs from a typical Angular directive or service because it must be anonymous, without being appended to a specific module.</p> </div> <ol class="ol steps"><li class="li step stepexpand"> <span class="ph cmd">Navigate to <span class="ph menucascade"><span class="ph uicontrol">All</span> > <span class="ph uicontrol">Service Portal</span> > <span class="ph uicontrol">Service Portal Configuration</span> > <span class="ph uicontrol">Portal Tables</span> > <span class="ph uicontrol">Widget Angular Provider</span></span>.</span> <div class="itemgroup stepresult">The Widget Angular Providers table opens.</div> </li><li class="li step stepexpand"> <span class="ph cmd">Select <span class="ph uicontrol">New</span> to create a new record.</span> </li><li class="li step stepexpand"> <span class="ph cmd">Fill out the form.</span> <ol type="a" class="ol substeps" id="reusable-directive__substeps_crw_pcd_yz"><li class="li substep substepexpand"> <span class="ph cmd">Add the type and name.</span> <div class="itemgroup info"> <div class="p"><ul class="ul" id="reusable-directive__ul_tf5_rjh_xz"><li class="li"><span class="ph uicontrol">Type</span>: Directive</li><li class="li"><span class="ph uicontrol">Name</span>: categoryIcon</li></ul> </div> </div> </li><li class="li substep substepexpand"> <span class="ph cmd">Add the client script.</span> <div class="itemgroup info"> <div class="p"><pre class="pre codeblock language-javascript"><strong class="hl-keyword">function</strong>() { <strong class="hl-keyword">return</strong> { template: <span class="hl-string">'<span class="fa fa-stack fa-lg"><i class="fa fa-circle fa-stack-2x"></i><i class="fa fa-{<!-- -->{::icon}} fa-stack-1x fa-inverse"></i></span>'</span>, restrict: <span class="hl-string">'E'</span>, replace: true, scope: { category: <span class="hl-string">'='</span> }, link: <strong class="hl-keyword">function</strong>(scope, element) { <strong class="hl-keyword">var</strong> _iconMap = { <span class="hl-string">"b06546f23731300054b6a3549dbe5dd8"</span>: <span class="hl-string">"tablet"</span>, <em class="hl-comment">/* Tablets */</em> <span class="hl-string">"15706fc0a0a0aa7007fc21e1ab70c2f"</span>: <span class="hl-string">"question"</span>, <em class="hl-comment">/* Can we help you? */</em> <span class="hl-string">"d68eb4d637b1300054b6a3549dbe5db2"</span>: <span class="hl-string">"mobile-phone"</span>, <em class="hl-comment">/* Mobiles */</em> <span class="hl-string">"109cdff8c6112276003b17991a09ad65"</span>: <span class="hl-string">"print"</span>, <em class="hl-comment">/* Office and Print */</em> <span class="hl-string">"5d643c6a3771300054b6a3549dbe5db0"</span>: <span class="hl-string">"print"</span>, <em class="hl-comment">/* Printers */</em> <span class="hl-string">"2c0b59874f7b4200086eeed18110c71f"</span>: <span class="hl-string">"plug"</span>, <em class="hl-comment">/* Peripherals */</em> <span class="hl-string">"2809952237b1300054b6a3549dbe5dd4"</span>: <span class="hl-string">"desktop"</span>, <em class="hl-comment">/* Software */</em> }; scope.icon = _iconMap[scope.category] || <span class="hl-string">"shopping-cart"</span>; } } }</pre></div> <p class="p">This script associates the sys_id of the Category record with the <span class="ph">Service Catalog</span> item. The icon that displays is the icon defined in the Category record in the <span class="ph">Service Catalog</span>.</p> </div> </li><li class="li substep substepexpand"> <span class="ph cmd">Select <span class="ph uicontrol">Save</span>.</span> </li></ol> </li><li class="li step stepexpand"> <span class="ph cmd">Associate the new Angular directive with the Quick Order Widget.</span> <ol type="a" class="ol substeps" id="reusable-directive__substeps_uqt_y3d_yz"><li class="li substep"> <span class="ph cmd">Navigate to <span class="ph menucascade"><span class="ph uicontrol">All</span> > <span class="ph uicontrol">Service Portal</span> > <span class="ph uicontrol">Widgets</span></span>.</span> </li><li class="li substep"> <span class="ph cmd">Open the Quick Order widget.</span> </li><li class="li substep"> <span class="ph cmd">Under <span class="ph uicontrol">Related Lists</span>, select <span class="ph uicontrol">Angular Providers</span>.</span> </li><li class="li substep"> <span class="ph cmd">In the Angular Providers list, select <span class="ph uicontrol">Edit</span> and associate the categoryIcon Angular Provider with the Quick Order widget.</span> </li><li class="li substep"> <span class="ph cmd">Select <span class="ph uicontrol">Save</span>.</span> </li></ol> </li><li class="li step stepexpand"> <span class="ph cmd">Add the categoryIcon directive to your Quick Order HTML template.</span> <ol type="a" class="ol substeps" id="reusable-directive__substeps_pww_rkd_yz"><li class="li substep substepexpand"> <span class="ph cmd">From the <span class="ph uicontrol">Widget Editor</span>, open the Quick Order widget.</span> </li><li class="li substep substepexpand"> <span class="ph cmd">Replace the HTML template with the following script.</span> <div class="itemgroup info"> <div class="p"><pre class="pre codeblock"><code><div class="panel panel-primary"> <div class="panel-heading">Request an item from the catalog</div> <div class="panel-body"> <input class="form-control" type="search" placeholder="Start typing here to search the list of catalog items" ng-model="c.data.keywords" ng-change="c.server.update()" ng-model-options="{debounce: 250}" /> <h5 ng-if="!c.data.keywords">Showing the most popular items</h5> <ul class="list-group result-container"> <li class="list-group-item" ng-repeat="item in c.data.items"> <a href ng-click="c.select(item.sys_id)"><category-icon category="item.category" style="margin-right: 10px"></category-icon>{<!-- -->{item.name}}</a><span class="pull-right">{<!-- -->{item.price}}</span> <div class="catalog-item" ng-if="item.sys_id == c.openItem"> <sp-widget ng-if="c.catalogItemWidget" widget="c.catalogItemWidget" /> </div> </li> </ul> </div> <div class="panel-footer" ng-if="c.data.keywords"> <ng-pluralize count="c.data.items.length" when="{'0': 'No items found for ', '1': 'One item matching ', 'other': 'Found {} items matching '}"> </ng-pluralize> {<!-- -->{c.data.keywords}} </div> </div> </code></pre></div> </div> </li></ol> </li><li class="li step stepexpand"> <span class="ph cmd">Refresh your test page preview to view the changes.</span> <div class="itemgroup stepresult"> <p class="p">A category icon displays beside each result.</p> <p class="p"><img class="image" id="reusable-directive__image_rlh_lld_yz" src="../image/category-icon.png" alt="" /></p> </div> </li></ol> </div> </div> </div> </body></html></div>