Service Portal
Okay, here are detailed notes summarizing the ServiceNow Service Portal training based on the provided transcript.
Service Portal Complete Training - Detailed Notes
Overall Goal: To provide a comprehensive understanding of ServiceNow Service Portal development, from basic concepts to advanced features, enabling users to build, customize, and maintain portal experiences.
Day 1: Introduction to Service Portal
-
What is Service Portal?
- A framework for building mobile-friendly, self-service experiences for users within the ServiceNow platform.
- Interacts with the Now Platform, allowing users to access specific features (e.g., catalog, KB, requests).
- Replaced the older Content Management System (CMS).
-
Why Service Portal over CMS?
- CMS was built on Jelly script, which is harder to work with.
- CMS was not inherently mobile-friendly.
- CMS lacked native support for modern frameworks like AngularJS.
-
Key Features of Service Portal:
- Mobile-Friendly: Designed for responsive experiences across devices (desktop, tablet, mobile).
- Bootstrap Support: Built-in support for the Bootstrap framework for layout and styling.
- AngularJS: Uses AngularJS (specifically version 1.x, not newer Angular versions 2+). This is crucial for understanding the client-side framework.
-
Roles:
-
Developers: Need
adminorsp_adminroles. Even those with basic knowledge can reuse components. - Users: Intended for everyone - end users, service desk agents, internal/external users, even public (unauthenticated) users if configured.
-
Developers: Need
-
Required Skills/Languages:
- Frontend: HTML, CSS, Bootstrap, AngularJS (1.x).
-
Backend/ServiceNow Specific: ServiceNow Server-Side Scripting (GlideRecord, GlideSystem -
gs, etc.), JavaScript. - Integration (Optional): HTTP Services (for interacting with external APIs).
-
Default Portal Demo (
/sp)- Accessed by typing
/spafter the instance URL. -
Elements Highlighted:
- Header Menu (can be static or scrollable).
- Announcements (dismissible banner at the top).
- Search Bar (Typeahead functionality).
- Icon Links (e.g., Request Something, Knowledge Base, Get Help).
- Knowledge Base Section.
- Request Something Section (Catalog Items).
- My Approvals / My Tickets / Top Rated Articles sections.
- Chat icon (integrates with Virtual Agent/Live Agent).
- Shopping Cart icon.
- Accessed by typing
-
Developer Tools (In Portal View):
-
Access: Hold
Ctrl+Alt+ Right-click. -
Options:
-
Show Widget Customization: Outlines widgets on the page. Color-coded:- Green: Out-of-Box (OOB).
- Red: Custom built from scratch.
- Yellow: Cloned/modified OOB.
-
Instance Options: Configure parameters specific to this instance of the widget. -
Widget Info: Shows widget name, ID, template used, Angular providers. - Performance metrics (widget load times in milliseconds).
- Logging options.
-
-
Access: Hold
Day 2: Backend Structure & Portal Creation
-
Backend Structure Hierarchy:
-
Portal (
sp_portalrecord): Defines the overall portal (URL suffix, theme, menu, homepage). -
Page (
sp_pagerecord): A specific view within a portal (e.g., homepage, category page, form page). Identified byidin the URL. - Container/Row/Column: Bootstrap layout elements defined within the page structure.
-
Instance (
sp_instanceor related tables): A specific placement of a widget on a page. Holds configuration options for that widget placement. -
Widget (
sp_widgetrecord): The reusable component containing HTML, CSS, Client Script, and Server Script logic.
-
Portal (
-
URL Structure:
-
your_instance.service-now.com/[portal_suffix]?id=[page_id] - Example:
instance.service-now.com/sp?id=index(Default portal, index/homepage). - If
?id=...is omitted, the portal's defined 'Homepage' is loaded (oftenindex).
-
-
Widget Structure (
sp_widgetrecord):- Contains fields for: Body HTML, CSS, Server Script, Client Controller (Client Script), Link Function (advanced JS).
- OOB widgets are read-only; modifications require cloning.
-
Portal Configuration UI (Service Portal -> Configuration):
- Visual interface for managing portals.
- Branding Editor: Modify logos, colors, fonts for a theme/portal.
- Widget Editor: Create/edit widgets with live preview.
- New Portal/Page/Widget: Buttons to create new components.
-
Portal Record Details (
sp_portaltable):-
Key Fields:
-
Title: Browser tab title. -
URL suffix: The/suffixused to access the portal. -
Homepage:sp_pagerecord used as the entry point. -
KB home page: Default page for Knowledge Base articles. -
Login page: Page shown for unauthenticated users. -
Logo,Icon: Images for branding. -
404 page: Page shown if requested page ID doesn't exist. -
Main menu:sp_rectangle_menurecord defining the header navigation. -
Theme:sp_themerecord controlling overall styling and header/footer structure. Crucial: Theme is required for the header menu to appear. -
CSS Variables: Store theme/portal specific style variables.
-
- Related Lists: Search Sources, Catalogs, Knowledge Bases (define which ones are accessible within this portal).
-
Key Fields:
-
Steps to Create a New Portal:
-
1. Create/Reuse Widget: Define the content component. (Demo: Created
our_first_casewidget via Widget Editor). - 2. Create/Reuse Page: Define the layout and place widgets. (Demo: Created test page via "Create test page" option in Widget Editor).
-
3. Create Portal Record: Navigate to
Service Portal -> Portals -> New.- Define
Title(e.g., Portal Training). - Define
URL suffix(e.g.,pt).
- Define
-
4. Configure Portal Record:
- Set
Homepage(select the page created in step 2). - Set
Main menu(select ansp_rectangle_menu, e.g., OOB 'SP Header Menu'). - Set
Theme(select ansp_theme, e.g., OOB 'La Jolla'). Must have a theme for header.
- Set
-
5. Test: Access the portal via its URL suffix (e.g.,
your_instance.service-now.com/pt).
-
1. Create/Reuse Widget: Define the content component. (Demo: Created
Day 3: Menus & Server Data Retrieval
Day 4: HTML Basics & Displaying Lists
-
HTML Basics (Review):
- Tags:
<tag>content</tag>. - Table Structure:
<table>,<thead>,<tbody>,<tr>(row),<th>(header cell),<td>(data cell). - Common Tags:
<h1>-<h6>,<p>,<a>,<img>,<button>,<div>,<span>.
- Tags:
-
Creating Widgets (Alternative):
- Navigate:
Service Portal -> Widgets. Click 'New'. DefineNameandID. Can edit code directly here or use 'Open in Widget Editor'.
- Navigate:
-
Page Designer:
- Access:
Service Portal -> Pages-> Open Page record -> ClickOpen DesignerUI Action. - Visual layout tool. Drag & drop widgets.
- Layout: Uses Bootstrap grid (12 columns). Add containers/rows/columns (+ sign).
- Widget Configuration: Drag widget onto page, may need initial configuration (pencil icon or Ctrl+Right Click -> Instance Options).
- Demo: Created
my_taskpage. Added 6/6 column layout. Draggedincident_detailswidget.
- Access:
-
Fetching Multiple Records (Server Script Pattern):
- Initialize an empty array on the data object:
data.records = []; - Query the table (e.g.,
incidentwherecaller_id = gs.getUserID()). - Use a
while (gr.next())loop. -
Inside the loop:
- Create a temporary JavaScript object:
var item = {}; - Populate the object with needed fields:
item.number = gr.getValue('number'); item.short_desc = gr.getValue('short_description'); item.sys_id = gr.getValue('sys_id'); item.group = gr.getDisplayValue('assignment_group');(UsegetValuefor raw values,getDisplayValuefor user-friendly ones). - Push the temporary object into the
dataarray:data.records.push(item);
- Create a temporary JavaScript object:
- The
data.recordsarray (containing multipleitemobjects) is passed to the client.
- Initialize an empty array on the data object:
-
Displaying Lists in HTML (
ng-repeat):- Use the
ng-repeatdirective on the HTML element you want to repeat for each record (usually the<tr>in a table). - Syntax:
ng-repeat="tempVarName in data.recordsArray"-
tempVarName: A variable representing the current item in the loop (e.g.,incident,item,record). -
data.recordsArray: The array populated in the server script (e.g.,data.records).
-
- Inside the repeated element, access data using the temporary variable and dot notation:
-
<td>{{incident.number}}</td> -
<td>{{incident.short_desc}}</td> -
<td>{{incident.group}}</td>
-
- Demo: Displayed list of user's incidents in a table using this pattern.
- Use the
-
Debugging Server Data:
- Temporarily print the
dataobject in HTML:<pre>{{data | json}}</pre>. Use thejsonfilter for readability. - Use online JSON Viewers to analyze the structure.
- Temporarily print the
Day 5: Client Script Interaction (HTML to Client)
Day 6: Client to Server Interaction
-
Challenge: Cannot directly call widget server script functions from the client script after the initial page load.
-
Method:
c.server.update()- This is the primary way to trigger server-side logic from the client after load.
- It re-runs the entire Server Script of the widget.
- It sends the current state of the client-side
c.dataobject to the server.
-
inputvsdataObjects:-
data(Server -> Client): Populated by the Server Script on load (and afterc.server.update()). This is what the HTML/Client sees. -
input(Client -> Server): Represents thec.dataobject as it was whenc.server.update()was called. Available only within the Server Script execution triggered byc.server.update(). On initial page load,inputis typically null or empty.
-
-
Workflow for Client-Triggered Server Action:
-
1. Client (Action Trigger): User clicks a button (e.g., 'Close Incident').
ng-clickcalls a client functionc.closeIncident(sysId). -
2. Client (Prepare Data): Inside
c.closeIncident(sysId):- Set properties on
c.datato tell the server what to do and with what data. -
c.data.action = 'closeIncident'; -
c.data.recordSysId = sysId;
- Set properties on
-
3. Client (Trigger Server): Call
c.server.update();. - 4. Server (Script Re-runs): The widget's Server Script executes again.
-
5. Server (Check Input): Check if
inputexists and contains the expected action/data:-
if (input && input.action === 'closeIncident') { ... }
-
-
6. Server (Perform Action): Execute the server-side logic using data from
input:-
var gr = new GlideRecord('incident'); -
if (gr.get(input.recordSysId)) { gr.state = 6; gr.update(); }
-
-
7. Server (Refresh Data): After performing the action, re-run the necessary queries to fetch the updated data and populate the
dataobject for the refreshed view.-
// ... (re-query incidents) ... -
data.records = // ... updated list ...;
-
-
1. Client (Action Trigger): User clicks a button (e.g., 'Close Incident').
-
Importance of Server Script Order: For immediate UI updates reflecting the action, the server logic acting on
input(Step 6) should generally come before the logic fetching data for display (Step 7). -
Using
.then()withc.server.update():-
c.server.update().then(function() { /* client code */ }); - Allows executing client-side code after the server script finishes and the UI potentially re-renders.
- Useful for: Showing success/failure messages (using
spUtil), clearing flags set inc.data, etc.
-
Day 7: Widget Reusability (Instance Options)
-
Problem: Widgets with hardcoded values (table names, filters, titles) are not reusable.
-
Solution: Use Instance Options to pass configuration parameters to specific widget placements (instances) on a page.
-
Enabling Instance Options:
-
1. Open Widget Record (Platform View): Navigate to
sp_widget.do. -
2. Select
Data table: Choose an appropriate instance table from the dropdown (e.g.,sp_instance_tablewhich providestable,filter,titlefields). The choice depends on which configuration fields you need. -
3. Select
Fields: From the available fields in the chosenData table, select the ones you want to expose as configurable options (e.g., check 'table', 'filter', 'title'). - 4. Save Widget Record.
-
5. Update Page: In the Page Designer, remove the existing instance of the widget and re-add it. This creates a new instance record based on the updated
Data tabledefinition.
-
1. Open Widget Record (Platform View): Navigate to
-
Accessing Options (Widget Server Script):
- Use the
$spAPI object (available in Server Script). -
var tableName = $sp.getValue('option_field_name'); - Example:
data.table = $sp.getValue('table'); data.filter = $sp.getValue('filter'); data.title = $sp.getValue('title');
- Use the
-
Configuring Options (Portal UI):
- Go to the portal page where the widget instance is placed.
-
Ctrl+Alt+ Right-click on the widget instance. - Select
Instance Options. - A pop-up appears with the fields you exposed in Step 3.
- Enter the desired configuration values for that specific instance.
- Save.
-
Demo Summary:
- Refactored the
incident_detailswidget into a genericrecord_detailswidget. - Enabled Instance Options (
table,filter,title,short_description). - Used
$sp.getValue()in the server script to fetch these options. - Placed two instances of
record_detailson themy_taskpage. - Configured the left instance via Instance Options for 'Incident' table,
active=truefilter. - Configured the right instance for 'Change Request' table, different filter.
- Result: The same widget code displayed different data based on instance configuration.
- Refactored the
Day 8: Widget Options Schema
-
Problem: Need configuration options beyond those provided by the standard
sp_instance...tables (e.g., custom color choices, boolean flags for specific behaviors). -
Solution: Define a Widget Option Schema directly on the widget record.
-
Creating Option Schema (Steps):
- 1. Open Widget Record (Platform View).
-
2. Access Schema Editor: Hamburger Menu ->
Edit Option Schema. - 3. Add Option (+): Click the plus icon.
-
4. Define Option Properties:
-
Label: User-friendly name shown in Instance Options pop-up. -
Name: Internal variable name (auto-generated, camelCase). Used to access the option in scripts. -
Type: Data type (String, Boolean, Integer, Choice, Reference, etc.). -
Hint: Help text for configuration. -
Default value: Pre-filled value. -
Section: Organizes options in the pop-up (Presentation, Behavior, Data, Other).
-
- 5. Save Schema & Widget Record.
-
Underlying Mechanism: Saving the schema populates the
Option schemafield (JSON format) on thesp_widgetrecord. -
Accessing Schema Options:
-
Server Script: Use the global
optionsobject.-
var color = options.optionName; - Example:
data.titleColor = options.title_color;
-
-
Client Script / HTML: Options are also available client-side via the
optionsobject (passed implicitly).- Example (HTML):
<h1 style="color: {{options.title_color}}">...</h1> - Example (Client):
if (options.showDetails) { ... } - Can also pass from server via
dataobject if preferred:data.titleColor = options.title_color;then use{{data.titleColor}}.
- Example (HTML):
-
Server Script: Use the global
-
Configuring Schema Options (UI):
-
Ctrl+Alt+ Right-click ->Instance Options. - The custom options defined in the schema appear alongside any fields exposed via the
Data tablesetting.
-
-
Demo Summary:
- Added
title_color(String) andsd_color(String) options via the Option Schema to therecord_detailswidget. - Configured different colors for these options in the Incident and Change Request instances.
- Accessed the colors via
options.optionNamein the Server Script (assigning todata) and directly viaoptions.optionNamein the HTMLstyleattribute.
- Added
Day 9: Out-of-Box (OOB) Widgets & Pages
-
Why Use OOB Components?
- Maintained and updated by ServiceNow.
- Reduce custom development effort.
- Cover common portal functionalities.
- Ensure consistency with platform upgrades.
-
Useful OOB Widgets:
-
Icon Link: Simple tile with icon, title, description, linking elsewhere. Configured via Instance Options. -
Simple List: Basic list view of records from a table. Configurable fields, filter, link target. -
Data Table from Instance Definition / Data Table from URL Definition: More advanced list widgets, often used as basis for custom lists. -
Homepage Search: The main portal search bar with typeahead. Requires Search Source configuration on the Portal record. -
Form: Renders a standard platform form view of a record. Core of theformpage. -
Ticket Conversations: Renders a simplified, activity-stream-focused view of a task record for end users. Core of theticketpage. -
SC Catalog Item: Renders a service catalog item. Core of thesc_cat_itempage. -
SC Order Guide: Renders a service catalog order guide. Core of thesc_order_guidepage. -
KB Article Content / KB Article Comments / KB Article Attachments: Widgets used on knowledge article pages. -
Approvals: Displays list of user's pending approvals. -
Announcements: Displays messages defined in thesp_announcementtable.
-
-
Useful OOB Pages (Identified by
idparameter):-
index: Default homepage for many portals. -
form: Displays a single record using theFormwidget (?id=form&table=<table>&sys_id=<sys_id>). -
ticket: Displays a single record using theTicket Conversationswidget (?id=ticket&table=<table>&sys_id=<sys_id>). Often preferred for end-user task view. -
sc_cat_item: Displays a catalog item (?id=sc_cat_item&sys_id=<item_sys_id>). -
sc_category: Displays items in a catalog category. -
sc_order_guide: Displays an order guide. -
kb_article: Displays a knowledge article bysys_id(?id=kb_article&sys_id=<kb_sys_id>). -
kb_article_view: Displays a knowledge article bynumber(?id=kb_article_view&sysparm_article=<KBNumber>). Often more stable link than sys_id. -
list: Displays a standard list view of records (?id=list&table=<table>&filter=<encoded_query>). -
lf: List & Form page. Shows list on left, selected record form on right (?id=lf&table=<table>). -
search: Default search results page.
-
-
Demo: Added OOB
Icon LinkandSimple Listwidgets to the custom portal's homepage, configuring them via Instance Options. Discussed the purpose ofform,ticket,sc_cat_item,list,lfpages.
Day 10: Typeahead Search
-
Widget: Typically the
Homepage Searchwidget (or similar custom implementations). -
Functionality: As the user types in the search bar, suggestions appear based on configured data sources. Clicking a suggestion navigates the user.
-
Configuration Location: Primarily configured on the Portal (
sp_portal) record. -
Search Sources (
sp_search_source):- Define what data should be searchable.
- Create new records via
Service Portal -> Search Sources -> New. -
Configuration:
-
Name: Identifier for the source. -
ID: Unique system ID. -
Data Sourcesection:-
Type: Table, Scripted, Attachment. (Usually 'Table'). -
Table: Select the ServiceNow table to search (e.g.,kb_knowledge,sc_cat_item,incident). -
Condition: Filter records within the table (e.g.,active=true,workflow=published).
-
-
Typeaheadsection:-
Primary field: Field whose value is shown prominently in suggestions (e.g., 'short_description' for KB, 'name' for Catalog Item). -
Display fields: Comma-separated list of additional fields to show below the primary (e.g., 'number, category'). -
Page: Thesp_pagerecord to redirect to when a suggestion is clicked (e.g.,kb_article_viewfor KB,sc_cat_itemfor Catalog). -
Template: Optional Angular template for custom suggestion rendering.
-
- Performance Note: Ensure the searched table and primary/display fields are indexed for efficient searching.
-
-
Linking Sources to Portal:
- Open the specific Portal record (
sp_portal). - Navigate to the
Search Sourcesrelated list. - Click 'Edit...' and add the desired
sp_search_sourcerecords. - Save the Portal record.
- Open the specific Portal record (
-
How it Works: When the user types, the portal framework queries only the Search Sources linked to the current Portal record.
-
Recent & Popular Searches: The
Homepage Searchwidget often includes functionality to show recent user searches or popular searches (may require additional configuration or OOB setup). -
Demo: Added the
Homepage Searchwidget to the custom portal homepage. Created a new Search Source for theincidenttable (searching active incidents, displaying number/short description, linking to theformpage). Added this Search Source to the customPortal Trainingportal record. Demonstrated typing incident numbers or keywords and getting suggestions.
Day 11: SP Modal (Part 1 - Alert, Confirm, Prompt)
-
spModalService:- An Angular provider service specifically for creating modals in Service Portal.
- Inject into Client Controller:
function($scope, spModal) { ... }.
-
Comparison with Standard JavaScript
alert(),confirm(),prompt():-
Styling:
spModalmodals match the portal's theme; JS pop-ups use browser defaults. -
Blocking Behavior: JS pop-ups block script execution until dismissed.
spModalmethods are non-blocking; execution continues immediately. Use the.then()promise pattern to handle actions after the modal is closed. -
Customization:
spModaloffers much more control over content and buttons.
-
Styling:
-
spModal.alert('message'):- Displays
messagewith a single "OK" button. - Non-blocking.
- Syntax:
spModal.alert('Operation Complete!'); - To wait for dismissal:
spModal.alert('Wait for this!').then(function() { console.log('Alert OK clicked.'); });
- Displays
-
spModal.confirm('message'):- Displays
messagewith "Cancel" and "OK" buttons. - Non-blocking.
- Requires
.then()to determine the outcome. - Syntax:
spModal.confirm('Are you sure?').then(function(confirmed) { if (confirmed) { // User clicked OK } else { // User clicked Cancel } });- The
confirmedargument passed to the.then()function istruefor OK,falsefor Cancel.
- The
- Displays
-
spModal.open({ ... })for Prompt Behavior:- Use the generic
.open()method for input prompts. -
Key Options:
-
title: String for the modal title. -
message: String (can include basic HTML) for the prompt message. -
input: Set totrueto display an input field. -
value: (Optional) Pre-fill the input field with a value from$scope(e.g.,value: c.data.userName).
-
- Requires
.then()to get the entered value. - Syntax:
spModal.open({ title: 'Enter Name', message: 'Please provide your name:', input: true, value: '' // Initial value }).then(function(nameEntered) { if (nameEntered) { // Check if user entered something (didn't just cancel) // Use the nameEntered value } });- The
nameEnteredargument contains the string entered by the user.
- The
- Use the generic
-
Demo: Showcased each
spModalmethod (alert,confirm,openfor prompt) alongside its standard JS equivalent, explicitly demonstrating the visual differences and the blocking vs..then()based non-blocking nature.
Day 12: SP Modal (Part 2 - Opening Widgets)
-
spModal.open({ ... })for Embedding Widgets:- Extends the
.open()method to load an entiresp_widgetinside the modal.
- Extends the
-
Key Options for Widget Embedding:
-
title: String, the title for the modal window. -
widget: String, the ID of the widget to embed (e.g.,'widget-form','widget-simple-list','my-custom-widget-id'). Find the ID on thesp_widgetrecord. -
widgetInput: Object, contains data passed from the calling widget's client script to the embedded widget's server script. The embedded widget accesses this data via itsinputobject.- Example:
widgetInput: { recordSysId: incident.sys_id, tableName: 'incident' }
- Example:
-
buttons: (Covered Day 19) Array for custom buttons, overrides default OK/Cancel if provided.
-
-
Workflow:
-
1. Calling Widget (Client):
ng-clicktriggers a function. -
2. Calling Widget (Client): Function prepares
widgetInputobject with necessary data (e.g.,sys_id,table). -
3. Calling Widget (Client): Calls
spModal.open({ title: '...', widget: 'widget-id', widgetInput: { ... } });. - 4. Modal Framework: Creates the modal shell.
-
5. Embedded Widget (Server): The server script of the widget specified by
widget-idexecutes. It can access the data passed in step 3 via its owninputobject (e.g.,var sysId = input.recordSysId;). It populates itsdataobject as usual. -
6. Embedded Widget (Client/HTML): The embedded widget's HTML, CSS, and client script load and render inside the modal, using the
dataobject populated in step 5. - 7. Modal Closure: User closes modal (e.g., clicks default 'Close', 'X', or a custom button).
-
8. Calling Widget (Client): The
.then()function (if provided) attached tospModal.openexecutes.
-
1. Calling Widget (Client):
-
Demo: Modified the custom list widget's 'Open Record' action.
- Instead of
window.location.href, usedspModal.open. - Set
widgetto'widget-form'(the OOB Form widget). - Passed
widgetInput: { sys_id: recordSysId, table: c.data.table }. - Result: Clicking 'Open Record' opened the standard form view of the selected incident/change inside a modal pop-up, without navigating away from the list page.
- Instead of
Day 13: SP Util (Messages & Intro to Record Watch)
-
spUtilService:- Another core Service Portal Angular provider service.
- Provides client-side utility functions.
- Inject into Client Controller:
function($scope, spUtil) { ... }.
-
Comparison with
gs.addInfoMessage()etc.:-
gsmethods are server-side only. -
spUtilmethods are used in the Client Controller script.
-
-
Client-Side Message Functions:
-
spUtil.addInfoMessage('Your message here'):- Displays a blue informational banner at the top of the portal page content area.
- Remains visible until the user manually dismisses it (clicks the 'x').
-
spUtil.addErrorMessage('Your error message'):- Displays a red error banner.
- Also remains visible until manually dismissed.
-
spUtil.addTrivialMessage('Quick confirmation'):- Displays a message (often green/success styling by default).
- Automatically fades out after a few seconds (typically ~5 seconds).
- Ideal for non-critical confirmations that don't require user interaction (e.g., "Record saved successfully").
-
-
spUtil.recordWatch()(Introduction):- A powerful function for real-time monitoring of table data.
- Syntax:
spUtil.recordWatch($scope, tableName, filterString, callbackFunction) - Allows the widget to react instantly when data matching the
filterStringon thetableNameis created, updated, or deleted, without requiring a page refresh. (Detailed in Day 14).
-
Demo: Created a basic 'My Approvals' widget listing requested approvals.
- Added 'Approve' and 'Reject' buttons (without server interaction yet).
- On 'Approve' click: Called
spUtil.addTrivialMessage('Request approved'). - On 'Reject' click: Called
spUtil.addErrorMessage('Request rejected'). - Showcased how the info/error messages appeared and persisted, while the trivial message appeared and then automatically disappeared.
Day 14: SP Util (Record Watch)
-
spUtil.recordWatch(scope, table, filter, callback)Syntax:-
scope: The widget's$scopeobject. Essential for Angular to manage and clean up the watcher when the widget is destroyed. -
table: String, the name of the table to monitor (e.g.,'sysapproval_approver'). -
filter: String, an encoded query defining the specific records to watch on that table. Can include dynamic elements like the current user ID. Example:'approver=javascript:gs.getUserID()^state=requested'. -
callback: A JavaScript function that gets executed automatically whenever an event (insert, update, delete) occurs on a record matching thetableandfilter.
-
-
Callback Function Signature:
-
function(eventData) { ... } - The
eventDataobject contains details about the change:-
eventData.data.action: String indicating the type of change ('insert', 'update', 'delete'). -
eventData.data.sys_id: Thesys_idof the record affected. -
eventData.data.record: Often contains the data of the affected record (structure might vary slightly based on event type). - Other properties related to the specific event.
-
-
-
Use Case & Goal: Keep a displayed list (e.g., 'My Pending Approvals') synchronized with the database in real-time, without forcing the user to manually refresh the page.
-
Implementation Steps:
-
1. Inject
spUtilinto the Client Controller. -
2. Define Watch: Typically within the main client controller function (so it runs on load) or within a function called after initial data load. Call
spUtil.recordWatch(...)with the correct table, filter, and a callback function. -
3. Implement Callback: Inside the callback function:
- (Optional) Log the
eventDatato understand the structure:console.log("Record Watch Event:", eventData);. -
Trigger Data Refresh: The simplest way to update the widget's display is to call
c.server.update();. This forces the widget's server script to re-run, re-query the database (using the original query that populatesdata.records), and send the fresh data back to the client, causing theng-repeatlist to re-render.
- (Optional) Log the
-
1. Inject
-
Demo: Enhanced the 'My Approvals' widget from Day 13.
- Added
spUtil.recordWatch($scope, 'sysapproval_approver', 'approver=javascript:gs.getUserID()^state=requested', function(eventData) { c.server.update(); });to the client script. - Demonstrated:
- Going elsewhere in the platform and creating a new approval record assigned to the current user -> The 'My Approvals' widget automatically updated to show the new approval without a page refresh.
- Approving/Rejecting one of the listed approvals elsewhere -> The widget automatically updated to remove that approval from the list (as its state no longer matched 'requested').
- Added
Day 15: Scope vs. Root Scope (Broadcasting)
-
Scope (
$scope):- An object specific to each widget instance (and other Angular components like controllers).
- Holds data (
$scope.variable) and functions ($scope.myFunc) for that specific widget. - Scopes can be nested, inheriting from parent scopes (though less common directly in simple widget structures).
- Variables on one widget's
$scopeare not directly accessible by another widget's$scope.
-
Root Scope (
$rootScope):- The top-level scope for the entire Angular application on the current page.
- All other
$scopeobjects are descendants of$rootScope. - Acts as a global event bus for communication across unrelated components (like different widgets on the same page).
- Inject into Client Controller:
function($scope, $rootScope) { ... }.
-
Problem: How can Widget A notify Widget B about an event or send data to it?
-
Mechanism: Event Broadcasting/Emitting & Listening:
-
$rootScope.$broadcast(eventName, data):- Sends an
eventName(a string you define) and optionaldata(any JS variable/object) downwards through the scope hierarchy, starting from$rootScope. All scopes that are listening foreventNamewill receive it. - Most common method for widget-to-widget communication.
- Sends an
-
$scope.$emit(eventName, data):- Sends an event upwards from the current
$scopetowards$rootScope. Only ancestor scopes (and$rootScope) listening foreventNamewill receive it. Less common for unrelated widget communication.
- Sends an event upwards from the current
-
$scope.$on(eventName, function(event, data) { ... }):- Listens for a specific
eventNameon the current$scope. - When an event with
eventNameis broadcast (or emitted and reaches this scope), the callback function is executed. -
event: The event object itself. -
data: The data payload that was sent with$broadcastor$emit.
- Listens for a specific
-
-
Implementation (Widget A -> Widget B):
-
Widget A (Sender):
- Inject
$rootScope. - When action occurs (e.g., button click):
var state = 'inProgress'; $rootScope.$broadcast('stateChange', { newState: state });.
- Inject
-
Widget B (Receiver):
- Inject
$scope. - Set up listener:
$scope.$on('stateChange', function(event, receivedPayload) { $scope.currentState = receivedPayload.newState; });.
- Inject
-
Widget A (Sender):
-
Demo: Created two widgets side-by-side ('broadcast1', 'broadcast2').
- Widget 1 had 'In Progress' and 'Closed' buttons. Clicking a button triggered
$rootScope.$broadcast('broadcast1', stateValue);. - Widget 2 displayed text bound to
$scope.state. It had$scope.$on('broadcast1', function(event, data) { $scope.state = data; });. - Result: Clicking buttons in Widget 1 updated the text displayed in Widget 2.
- Widget 1 had 'In Progress' and 'Closed' buttons. Clicking a button triggered
Day 16: Widget Embedding
-
Purpose: Include the functionality and UI of one widget directly within the HTML template of another widget. Promotes reusability and modular design.
-
Methods for Embedding:
-
1. Direct HTML Tag (Element):
- Syntax:
<widget id="widget-id-to-embed"></widget> - Simplest method. Embeds the specified widget using its default options.
- Example:
<widget id="widget-cool-clock"></widget>
- Syntax:
-
2. HTML Tag with Static Options:
- Syntax:
<widget id="widget-id" options='{ "json": "options", "here": true }'></widget> - Passes a static JSON string as the
optionsobject to the embedded widget's server script. The embedded widget accesses these via theoptionsglobal object. - Note the single quotes inside the double quotes for the JSON string.
- Syntax:
-
3. Server-Side Embedding (
$sp.getWidget):-
Calling Widget (Server Script):
-
var widgetOptions = { table: 'incident', filter: 'active=true', ... }; -
data.embeddedWidget = $sp.getWidget('widget-id-to-embed', widgetOptions); - Fetches the widget model, processing it with the provided
widgetOptions.widgetOptionsbecomes theoptionsobject inside the embedded widget's server script.
-
-
Calling Widget (HTML):
-
<sp-widget widget="data.embeddedWidget"></sp-widget> - Renders the widget model fetched on the server. Options can be determined dynamically based on server logic.
-
-
Calling Widget (Server Script):
-
4. Client-Side Embedding (
spUtil.get- Asynchronous):-
Calling Widget (Client Script):
- Inject
spUtil. -
var widgetOptions = { table: 'incident', filter: 'state=2' }; -
spUtil.get('widget-id-to-embed', widgetOptions).then(function(response) { c.data.embeddedWidget = response; }); - Fetches the widget model asynchronously. The
responsein.then()is the widget model. Assign it to ac.datavariable.
- Inject
-
Calling Widget (HTML):
-
<sp-widget widget="c.data.embeddedWidget"></sp-widget> - Renders the widget model after the
spUtil.getpromise resolves. Useng-if="c.data.embeddedWidget"to avoid errors before it loads. - Useful when the embedded widget or its options depend on client-side interactions or data fetched asynchronously.
-
-
Calling Widget (Client Script):
-
1. Direct HTML Tag (Element):
-
Use Case Example (Dynamic List Update):
- Widget A (Controls): Has buttons/dropdowns to select a status. Broadcasts the selected status using
$rootScope.$broadcast. - Widget B (Display): Listens for the status change event using
$scope.$on. Inside the listener, callsspUtil.get('widget-data-table', { filter: 'state=' + receivedStatus })to fetch the data table widget configured with the new filter. Assigns the response toc.data.listWidget. The HTML uses<sp-widget widget="c.data.listWidget"></sp-widget>to display the dynamically updated list.
- Widget A (Controls): Has buttons/dropdowns to select a status. Broadcasts the selected status using
-
Demo: Implemented the Use Case Example above. Widget 1 broadcast state changes ('New', 'In Progress', 'Closed'). Widget 2 listened and used
spUtil.get('widget-data-table', ...)inside the listener to dynamically reload the embedded data table with the correct filter, displaying the filtered incident list. Showed both initial load via server ($sp.getWidget) and dynamic updates via client (spUtil.get).
Day 17: GlideSP Scriptable API ($sp)
-
$spObject:- A server-side API object available only within the Widget Server Script.
- Provides utility methods specifically useful in the Service Portal context.
-
Common
$spMethods:-
$sp.getValue(optionName): Retrieves the value of an instance option (from Data Table fields or Option Schema). Returns the raw value.- Example:
var tableName = $sp.getValue('table');
- Example:
-
$sp.getParameter(parameterName): Retrieves a value from the URL query string. Returns a string.- Example:
var recordId = $sp.getParameter('sys_id');
- Example:
-
$sp.getWidget(widgetId, optionsObject): Fetches another widget's model, pre-processed with the providedoptionsObject. Returns a widget model suitable for server-side embedding (<sp-widget>).- Example:
data.list = $sp.getWidget('widget-simple-list', { table: 'sys_user' });
- Example:
-
$sp.getRecord(): If the current page context is a specific record (e.g., on/sp?id=form&table=incident&sys_id=...), this returns aGlideRecordobject for that record, pre-loaded. Returnsnullotherwise. -
$sp.getRecordVariables(): Retrieves catalog item variables if the current context is an RITM or SC Task form. -
$sp.getRecordDisplayValue(fieldName)/$sp.getRecordValues(commaSeparatedFields): Get display/raw values from the context record retrieved by$sp.getRecord(). -
$sp.getCatalogItem(sysId, isOrderGuide): Retrieves details (fields, variables) of a Service Catalog Item or Order Guide. Useful for building custom catalog experiences. -
$sp.canReadRecord(glideRecordOrTable, sysId): Checks if the current logged-in user has permission to read the specified record. Returnstrueorfalse.
-
-
Demo:
-
Fixing List Click: Addressed issue where clicking rows in the embedded data table didn't work. Listened for the specific event emitted by
widget-data-table:$scope.$on('data_table.click', function(evt, DtClickData) { ... });. Extracted table (DtClickData.table) and sys_id (DtClickData.sys_id) from the payload. Redirected usingwindow.location.href. -
Custom Form Widget:
- Created
form_customwidget and page. - Modified the list redirect to point to
?id=form_custom&table=...&sys_id=.... - In
form_customServer Script:- Used
var table = $sp.getParameter('table'); - Used
var sysId = $sp.getParameter('sys_id'); - Queried the record:
var gr = new GlideRecord(table); if (gr.get(sysId)) { data.number = gr.getValue('number'); }. - Displayed
{{data.number}}in HTML.
- Used
- Demonstrated
$sp.getValue()by adding atitleinstance option to theform_customwidget instance and fetching/displaying it usingdata.title = $sp.getValue('title');.
- Created
-
Fixing List Click: Addressed issue where clicking rows in the embedded data table didn't work. Listened for the specific event emitted by
Day 18: Angular Templates (sp_ng_template)
-
Problem: Widget HTML (
sp_widgetBody HTML field) can become cluttered and hard to manage, especially with complexng-iforng-switchlogic for displaying different views based on conditions. -
Solution: Use Angular Templates (
sp_ng_template) to store reusable blocks of HTML associated with a widget. -
Creating Angular Templates:
- 1. Open Widget Record (Platform View).
- 2. Navigate: Go to the 'Angular ng-templates' related list.
- 3. New: Click 'New'.
-
4. Define Template:
-
ID: A unique identifier for the template within the widget, typically ending in.html(e.g.,incident_view.html,change_view.html). This ID is used to include the template. -
Widget: Automatically linked to the current widget. -
Template: Paste the specific HTML block for this template here. Do not include the outer conditional (ng-if) logic here.
-
- 5. Submit. Create multiple templates as needed.
-
Including Templates in Widget HTML:
- Use the
ng-includedirective within the main widget's Body HTML. -
Syntax:
-
<div ng-include="templateUrlExpression"></div> - The
templateUrlExpressionmust evaluate to the ID of the template you want to include (the string defined in Step 4a). -
Crucially, the ID string needs to be wrapped in single quotes within the expression:
ng-include="'incident_view.html'"
-
-
Other Syntax Forms:
- Element:
<ng-include src="'change_view.html'"></ng-include> - Attribute:
<div ng-include src="'common_header.html'"></div>(less common for main include)
- Element:
- Use the
-
Conditional Inclusion: Combine
ng-if(orng-switch) withng-includein the main widget HTML to load the appropriate template based on data or scope variables.<div ng-if="data.table === 'incident'" ng-include="'incident_view.html'"></div> <div ng-if="data.table === 'change_request'" ng-include="'change_view.html'"></div> -
Benefits:
- Keeps the main widget HTML clean and focused on structure/conditions.
- Improves organization and readability.
- Makes maintaining complex conditional views much easier.
-
Demo: Modified the
form_customwidget.- Created two
sp_ng_templaterecords:incident.htmlandchange.html, linked to theform_customwidget. - Moved the incident-specific display markup into
incident.html. - Moved the change-specific display markup into
change.html. - Updated the
form_customwidget's Body HTML to use the conditionalng-includestructure shown in point 5, loading the correct template based ondata.table.
- Created two
Day 19: SP Modal Customization (Buttons & HTML)
-
Revisiting
spModal.open({ ... }): Beyond embedding widgets, this method can display custom messages with highly customized buttons. -
Key Options for Custom Modals:
-
title: String, modal title. -
message: String, the main content of the modal. Can contain HTML tags (e.g.,<p>,<b>,<i>,<ul>) which will be rendered. -
buttons: Array of button definition objects. If provided, overrides default OK/Cancel.- Each button object in the array defines one button:
-
label: String, text on the button (e.g., 'Confirm Close', 'Cancel Action'). -
value: Any JavaScript value (String, Number, Boolean, Object) that will be passed to the.then()promise if this specific button is clicked. -
primary: Boolean (true/false). Settruefor exactly one button to give it the primary visual styling (often a solid background color). -
cancel: Boolean (true/false). (Optional) Iftrue, clicking this button may trigger specific cancel behavior (like rejecting the promise, checkspModaldocs for specifics). Often used for buttons that should simply close the modal without a "successful" outcome. -
class: String. (Optional) Add custom CSS class name(s) to the button element (<button class="... your-class ...">).
-
- Each button object in the array defines one button:
-
-
Handling Button Clicks (
.then()):-
spModal.open({ ... }).then(function(result) { ... }); - The
resultargument passed to.then()is thevalueproperty of the button object that was clicked. - Check the
resultto determine which button was pressed and act accordingly. - Example:
if (result === 'closeConfirmed') { c.server.update(); } else if (result === 'cancel') { /* do nothing */ }.
-
-
Custom Button CSS:
- If you add a custom class using the
classproperty in thebuttonsarray (e.g.,class: 'my-danger-button'). - Define the CSS rules for that class (
.my-danger-button { background-color: red; color: white; }). -
Important: Add this custom CSS to the Page's CSS (
sp_pagerecord -> CSS field) or a Theme CSS Include, not usually the widget's CSS, because the modal dialog exists at the page/theme level, outside the widget's direct scope.
- If you add a custom class using the
-
Demo: Enhanced the 'Close Incident' confirmation modal.
- Replaced
spModal.confirmwithspModal.open. - Used the
messageproperty with HTML (<b>Please confirm</b> you want to...). - Defined a
buttonsarray:-
{ label: 'Close Ticket', value: 'close', primary: true } -
{ label: 'Keep Open', value: 'cancel', class: 'keep-open-button' }
-
- In the
.then(function(result) { ... }), addedif (result === 'close') { c.server.update(); }. - Added CSS
.keep-open-button { color: green; }to the Page's CSS field. - Showed the customized modal with HTML message, styled buttons, and correct logic execution based on which button was clicked.
- Replaced
Day 20: Angular Services ($timeout, $location)
-
Angular Services (Built-in):
- Core AngularJS services providing common functionalities.
- Typically start with
$. - Need to be injected into the Client Controller where used.
-
$timeoutService:-
Purpose: Executes a function after a specified delay, integrating with Angular's digest cycle (ensuring UI updates). Angular's safe wrapper for
window.setTimeout. -
Inject:
function($scope, $timeout) { ... } -
Syntax:
-
$timeout(function() { /* Code to execute later */ }, delayMilliseconds); -
$timeout(myFunctionReference, delayMilliseconds);(Pass function reference without()).
-
- Use Cases: Adding artificial delays, waiting for DOM rendering, debouncing frequent events (like key presses).
-
Demo: Wrapped an
alert('Delayed message!');inside$timeout(..., 5000);. Showed the alert appearing 5 seconds after a button click.
-
Purpose: Executes a function after a specified delay, integrating with Angular's digest cycle (ensuring UI updates). Angular's safe wrapper for
-
$locationService:- Purpose: Interact with the browser's URL (the part after the domain). Allows reading URL components or navigating programmatically.
-
Inject:
function($scope, $location) { ... } -
Key Methods:
-
$location.url(): Gets/sets the path and query string (e.g.,/sp?id=form&table=x). -
$location.absUrl(): Gets the full URL including domain (e.g.,https://instance.service-now.com/sp?id=...). -
$location.path(): Gets/sets only the path part (e.g.,/sp). -
$location.search():- Gets URL query parameters as an object (e.g.,
{ id: 'form', table: 'x' }). - Sets multiple parameters replacing existing ones:
$location.search({ id: 'list', table: 'incident' });-> changes URL to...?id=list&table=incident. - Sets a single parameter:
$location.search('sys_id', '12345');-> adds or updatessys_id=12345.
- Gets URL query parameters as an object (e.g.,
-
$location.hash(): Gets/sets the fragment identifier (#hashValue).
-
-
Use Cases: Reading URL parameters (
sys_id,table) to drive widget behavior, programmatic navigation between portal pages. -
Demo:
- Alerted
$location.url()and$location.absUrl(). - Alerted
JSON.stringify($location.search())to show the parameter object. - Used
$location.search({ id: 'list' });inside the$timeoutfunction to redirect the user to the list page after the delay.
- Alerted
Day 21: Angular Services ($uibModal, $watch)
-
$uibModalService:- Source: Provided by the UI Bootstrap library (integrated into Service Portal).
- Purpose: An alternative way to create modals, often favored for more complex modals requiring separate templates and controllers.
-
Inject:
function($scope, $uibModal) { ... } -
Basic Syntax:
var modalInstance = $uibModal.open({ templateUrl: 'myCustomTemplate.html', // ID of an sp_ng_template controller: 'MyModalController', // (Optional) Name of a separate controller scope: $scope, // Pass current scope or create new scope // resolve: { // Pass data to modal controller } // size: 'lg', // sm, lg // backdrop: 'static', // Prevent closing on click outside // keyboard: false // Prevent closing with ESC key }); modalInstance.result.then(function (resultFromModal) { // Modal closed successfully (e.g., OK button) }, function (reason) { // Modal dismissed (e.g., Cancel button, backdrop click, ESC) }); -
Comparison with
spModal:-
spModalis often simpler for basic alerts, confirms, prompts, or embedding existing widgets. -
$uibModalencourages separation usingtemplateUrland dedicated controllers, better for complex, self-contained modal logic. Often feels more "Angular-native".
-
-
Demo: Created a basic
test.htmlAngular Template. Used$uibModal.open({ templateUrl: 'test.html', scope: $scope });to show the template content within a modal.
-
$scope.$watch():-
Purpose: Observe a variable on the
$scopefor changes and execute a callback function whenever its value is modified. -
Inject: Requires
$scope. -
Syntax:
$scope.$watch('scopeVariableName', function(newValue, oldValue) { ... });-
'scopeVariableName': String name of the$scopeproperty to watch (e.g.,'data.name','userInput'). -
callbackFunction: Executes when the value changes. Receives thenewValueandoldValue.
-
- Execution: The callback runs once initially when the watch is registered, and then every time the watched value changes during an Angular digest cycle.
-
Use Cases: Reacting to
ng-modelchanges in real-time, triggering dependent calculations, validation. -
Preventing Initial Run: Check
if (newValue !== oldValue)inside the callback if you only want to react to actual changes, not the initial setup. -
Demo: Created
$scope.name = 'Rohit'. Added a button setting$scope.name = 'Rohit Kumar'. Implemented$scope.$watch('name', function(nv, ov) { if (nv !== ov) { alert('Name changed!'); } });. Showed the alert only firing after the button click changed the value.
-
Purpose: Observe a variable on the
Day 22: Angular Service ($http)
-
$httpService:- Purpose: Angular's primary service for making asynchronous HTTP requests to servers (both internal ServiceNow APIs and external third-party APIs).
-
Inject:
function($scope, $http) { ... }
-
Making Requests (Promise-based
.then()syntax):-
General Structure:
$http({ method: 'GET', // GET, POST, PUT, DELETE, etc. url: '/api/some/endpoint' // Internal or external URL // headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, // params: { key: 'value' }, // For GET request query params // data: { bodyKey: 'bodyValue' } // For POST/PUT request body }).then(function successCallback(response) { // SUCCESS! Process the response console.log('Status:', response.status); console.log('Data:', response.data); // Usually the main payload $scope.responseData = response.data; }, function errorCallback(response) { // ERROR! Handle the failure console.error('Error Status:', response.status); console.error('Error Data:', response.data); }); -
Shorthand Methods: More concise for common verbs:
-
$http.get(url, { params: {...}, headers: {...} }) -
$http.post(url, data, { headers: {...} }) -
$http.put(url, data, { headers: {...} }) -
$http.delete(url, { headers: {...} }) - These still return promises, so chain
.then(successCallback, errorCallback).
-
-
General Structure:
-
Response Object: The object passed to the success/error callbacks contains:
-
data: The body of the response, parsed if JSON. -
status: The HTTP status code (e.g., 200, 401, 404, 500). -
headers: A function to get response headers. -
config: The original request configuration object. -
statusText: Text description of the status (e.g., "OK", "Not Found").
-
-
Asynchronous:
$httprequests do not block execution. The code after the$http(...).then(...)call runs immediately. The callbacks inside.then()run later, only when the server responds. -
Demo:
- Used
https://jsonplaceholder.typicode.com/todos/as a public demo API. - First, fetched a single todo:
$http.get('.../todos/1').then(...). Displayed the rawresponse.datausing thejsonfilter. - Showed error handling by providing an invalid URL.
- Enhanced demo: Added an input box (
ng-model="todoId"). Modified the URL to be dynamic ('.../todos/' + $scope.todoId). On a button click, triggered the$http.get. Displayed specific fields (userId,id,title,completed) from the fetchedresponse.datain an HTML table.
- Used
Day 23: Angular Directives Review & Details
-
Directives: Markers on DOM elements (element names, attributes, classes, or comments) that tell Angular's compiler to attach specific behavior or transform the element.
-
Common Built-in Directives:
-
ng-repeat="item in collection": Iterates overcollection, creating a new instance of the element for eachitem.-
Extras (available inside
ng-repeat):$index,$first,$last,$middle,$even,$odd. Used for conditional logic or display based on position/parity. Example:ng-if="!$last". -
track by expression: Helps Angular identify items, improving performance especially if items can be duplicated or reordered. Common:track by item.sys_id,track by $index.
-
Extras (available inside
-
ng-if="expression": Removes the element from the DOM ifexpressionis falsey; recreates it if truthy. Creates a child scope. -
ng-show="expression"/ng-hide="expression": Adds/removes the.ng-hideCSS class (display: none). Element stays in the DOM. Use for simple visibility toggles. -
ng-click="expression": Executesexpression(usually a function call) on click. -
ng-src="expression": Use instead ofsrcfor<img>tags when the URL is dynamic ({{...}}). Prevents broken image requests. -
ng-model="scopeVariable": Creates two-way data binding between a form element (input,select,textarea) and a$scopevariable. -
ng-bind="expression": One-way binding. Sets the element'stextContentto the result ofexpression. Alternative to{{expression}}. -
ng-bind-html="expression": One-way binding. Sets the element'sinnerHTMLto the result ofexpression, rendering HTML content. Requires sanitization (ngSanitizemodule) for security. Use only with trusted HTML sources. -
ng-init="expression": Executesexpressiononce during compilation. Mainly for trivial initializations directly in HTML; complex logic belongs in the controller.
-
-
Demo: Revisited the array display example. Demonstrated usage of
$index,$first,$last,$even,$oddwithinng-if. Comparedng-if(element disappeared from DOM Inspector) vs.ng-show(element hidden via CSS). Showedng-bind-htmlrendering bold tags correctly whileng-bindshowed the raw<b>tag.
Day 24: Angular Filters
-
Filters: Format data for display within HTML bindings (
{{ }}orng-bind). Applied using the pipe|character. -
Syntax:
{{ expression | filterName : argument1 : argument2 ... }} -
Common Built-in Filters:
-
limitTo : limit : begin: Truncates arrays or strings.limitis the max number of items/chars.begin(optional, 0-based index) is the starting position. Example:item in items | limitTo:10:currentPage*10. -
uppercase/lowercase: Changes string case. Example:{{ name | uppercase }}. -
orderBy : expression : reverse: Sorts an array.expressionis the property to sort by (string) or a function.reverse(boolean, optional) sorts descending iftrue. Example:item in items | orderBy:'lastName':true. -
filter : expression: Selects a subset of an array.- String search:
item in items | filter : searchText(searches all properties). - Object search:
item in items | filter : { status: 'active' }. - Function predicate:
item in items | filter : myFilterFunction.
- String search:
-
currency : symbol : fractionSize: Formats number as currency.symbol(optional string).fractionSize(optional number). Example:{{ amount | currency : '£' : 2 }}. -
date : format: Formats Date object or date string/number.format(optional string, e.g.,'yyyy-MM-dd','mediumDate'). Example:{{ createdDate | date : 'short' }}. -
number : fractionSize: Formats number.fractionSize(optional number) sets decimal places. Example:{{ value | number : 2 }}. -
json: Converts JS object/array to a pretty-printed JSON string. Useful for debugging. Example:<pre>{{ data | json }}</pre>.
-
-
Chaining: Filters can be chained:
{{ name | lowercase | limitTo: 5 }}. -
Demo: Continued with array display.
- Implemented
limitTo:3and then dynamic pagination usinglimitTo:itemsPerPage:startIndex. - Applied
uppercase/lowercase. - Demonstrated
orderBy:'x'andorderBy:'x':true. - Implemented live search using text input
ng-model="searchText"and thefilter:searchText.
- Implemented
Day 25: Building a Custom List Widget (Consolidation)
This day integrated many previous concepts into a practical, reusable widget.
-
Goal: Create a configurable widget (
custom-list) to display records from any table, with dynamic columns, sorting, filtering, and pagination. -
Key Implementation Steps:
-
Widget & Instance Options: Created
custom-listwidget. UsedInstance with Tabledata table, exposingtableandfilteroptions. Added custom Option Schema forfield_list(comma-separated field names) andheader_list(comma-separated display labels). -
Server Script:
- Fetched options using
$sp.getValue()andoptions.fieldName. - Split
field_listandheader_liststrings into arrays (data.fields,data.headers). - Queried the specified
tablewith thefilter. - Looped (
while gr.next()), createditemobject for each record. -
Dynamically populated
itemby looping throughdata.fields:item[data.fields[i]] = gr.getDisplayValue(data.fields[i]);. Addedsys_id. - Pushed
itemintodata.records = [].
- Fetched options using
-
HTML Structure:
-
<table><thead><tr>. Usedng-repeat="header in data.headers"for<th>. -
<tbody>. Usedng-repeat="record in data.records | ...filters..."for<tr>. - Inside
<tr>, used nestedng-repeat="field in data.fields"for<td>. - Displayed cell data using
{{ record[field] }}(accessing object property using the field name string).
-
- CSS: Applied styling for borders, padding, header background, row hover, even/odd row striping.
-
Sorting (Client & HTML):
- Client:
$scope.sortKey,$scope.reverse,$scope.sort(key)function. - HTML: Added
ng-click="sort(data.fields[$index])"to<th>. AddedorderBy:sortKey:reversefilter to bodyng-repeat. Added conditional icons (ng-show) in<th>based onsortKeyandreverse.
- Client:
-
Filtering (Client & HTML):
- HTML: Added
<input ng-model="searchText">. - HTML: Added
filter:searchTextfilter to bodyng-repeat(chained afterorderBy).
- HTML: Added
-
Pagination (Client & HTML):
- Client: Calculated
totalRecords,itemsPerPage,totalPages.$scope.currentPage. HelpergetNumber(num)function forng-repeat.setPage(num)function to updatecurrentPage. - HTML: Generated page links (
<ul><li><a>) usingng-repeatongetNumber(totalPages). Usedng-click="setPage($index)". Appliedng-class="{ 'active': isPageActive($index) }"for styling. - HTML: Added
limitTo:itemsPerPage:currentPage*itemsPerPagefilter to bodyng-repeat(chained last).
- Client: Calculated
-
(Optional) Breadcrumbs: Embedded
widget-filter-breadcrumbsvia$sp.getWidget, passing table/query. (Handling breadcrumb events to update filter was left as an exercise).
-
Widget & Instance Options: Created
-
Result: A flexible list widget driven by instance/schema options.
Day 26: Localization (i18n)
-
Purpose: Adapt widget/portal text to the user's preferred language set in their profile.
-
Prerequisites: ServiceNow Internationalization plugins enabled. User language preferences set.
-
Translation Storage:
sys_ui_messagetable. Contains records mapping an EnglishKeyto a translatedMessagefor a specificLanguage. -
Translation Methods:
-
Method 1: HTML (
$\{...}syntax):- Wrap static text directly in HTML:
<span>$\{Submit}</span>. - ServiceNow attempts to find a
sys_ui_messagerecord whereKeyis 'Submit' andLanguagematches the user's language. - If found, displays the translated
Message; otherwise, displays the originalKey. - Note: This relies on ServiceNow pre-processing. May not work reliably for dynamic content added purely via Angular after load.
- Wrap static text directly in HTML:
-
Method 2: Server Script (
gs.getMessage):- Use
gs.getMessage('Your Key Text')in the Server Script. - Assign the result to the
dataobject:data.buttonLabel = gs.getMessage('Submit Request');. - Bind in HTML:
{{data.buttonLabel}}. - Reliable for translating text determined on the server.
- Use
-
Method 3: Client Script (
i18nservice / filter):-
a)
i18nService (Asynchronous):- Inject
i18n. -
i18n.getMessage('Your Key Text', function(translatedMsg) { $scope.translatedText = translatedMsg; }); - Requires a callback because the lookup might be asynchronous. UI might show original text briefly before updating.
- Inject
-
b)
i18nFilter (Synchronous - Preferred for display):- Apply directly in HTML bindings:
{{ 'Your Key Text' | i18n }}. - Looks up the key synchronously (or uses cached translations). Simpler for direct display of translated static keys.
- Apply directly in HTML bindings:
-
a)
-
Method 1: HTML (
-
Adding Missing Translations: Manually create records in
sys_ui_messagefor any custom text keys that need translation for specific languages. Use Google Translate or professional translators. -
Demo: Showed text translation failing initially when user language changed. Demonstrated successful translation using:
-
$\{...}in HTML (after adding missing key tosys_ui_message). -
gs.getMessage(...)in Server Script. -
i18n.getMessage(...)in Client Script (showing async nature). Mentioned thei18nfilter as a simpler client-side alternative for display.
-
Day 27: Custom Components (Reference & Date Pickers)
-
Problem: Need platform-like reference field lookups and date/time pickers within portal widgets, which standard HTML lacks.
-
Solution: ServiceNow provides specific Angular directives:
sn-record-pickerandsp-date-picker. -
Reference Field (
sn-record-picker):-
Directive:
<sn-record-picker field="boundFieldObject" table="'table_name'" ... ></sn-record-picker> -
Key Attributes:
-
field: Binds to an object on$scope. This object holds the picker's state (value, displayValue, name). Initialize in controller:$scope.myRefField = { name: 'uniquePickerName', value: '', displayValue: '' };. -
table: String literal (note inner quotes) or scope variable referencing the target table name. -
display-field: String literal/variable for the field to display in the input/dropdown. -
value-field: String literal/variable for the field whose value should be stored (usually'sys_id'). -
search-fields: String literal/variable, comma-separated fields to search against. -
page-size: Number, results per page/scroll. -
multiple: Boolean (true/false) for multi-selection. -
default-query: String literal/variable, encoded query for filtering lookup results.
-
-
Getting Selected Value: Access via the bound field object:
$scope.myRefField.value(sys_id),$scope.myRefField.displayValue. -
Listening for Changes:
$scope.$on('field.change', function(evt, params) { if (params.field.name === 'uniquePickerName') { var newValue = params.newValue; ... } });.
-
Directive:
-
Date/Time Picker (
sp-date-picker):-
Directive:
<sp-date-picker field="boundFieldObject" sn-change="onChangeFunction()" ... ></sp-date-picker> -
Key Attributes:
-
field: Binds to an object on$scope. Holds date value and potentially other state. Initialize:$scope.myDateField = { name: 'uniqueDateName', value: '' };. -
sn-change: Calls the specified$scopefunction when the date value changes. -
ng-model: Can optionally bind the date string value directly to another scope variable.
-
-
Getting Selected Value: Access via the bound field object:
$scope.myDateField.value. Format is typically 'YYYY-MM-DD HH:MM:SS' (ServiceNow internal). -
Listening for Changes: Use the function specified in
sn-change. Access the new value via the boundfieldobject within that function.
-
Directive:
-
Demo: Implemented both pickers.
-
sn-record-pickerfor Group table, configured attributes. Listened viafield.changeevent. -
sp-date-picker, boundfield, usedsn-changeto trigger an alert showing the selected date value from the bound field object.
-
Day 28: Themes & Branding
-
Theme (
sp_themerecord):- Defines the overall visual container: colors, fonts, and crucially, the Header and Footer structure.
- Linked from the Portal (
sp_portal) record.
-
Header & Footer (
sp_header_footerrecord):- A Theme record references one Header record and one optional Footer record.
- These Header/Footer records simply point to the actual Widgets (
sp_widget) that render the header/footer content (e.g., OOB 'Stock Header' widget contains the logo, menus, profile).
-
Theme Requirement: A Theme must be assigned to a Portal for the configured Header (and its menu) to display. The theme provides the container.
-
Fixed Header/Footer: Checkboxes on the Theme record control if they remain visible during scroll.
-
Branding Editor (UI Tool):
- Quick way to adjust common theme elements (navbar color, text color, background) for a specific portal.
-
Mechanism: Saves overrides as CSS Variables on the Portal (
sp_portal) record, not the Theme record itself. Portal CSS variables take precedence over Theme CSS variables.
-
CSS Variables (Custom Properties):
-
Definition: In Theme or Portal 'CSS Variables' field:
--my-brand-color: #AABBCC; --text-font-size: 14px;. -
Usage: In any CSS (Widget, Page, Theme Include):
color: var(--my-brand-color); font-size: var(--text-font-size);. - Benefit: Centralized control over styles. Change the variable definition once, it updates everywhere it's referenced.
-
Definition: In Theme or Portal 'CSS Variables' field:
-
Theme CSS/JS Includes:
- Organize custom CSS/JS into reusable files applied across portals using the same theme.
-
CSS: Create Style Sheet (
sp_css) -> Create CSS Include (sp_css_include) linking to Style Sheet -> Add CSS Include to Theme's 'CSS Includes' related list. -
JS: Create UI Script (
sys_ui_script) -> Create JS Include (sp_js_include) linking to UI Script -> Add JS Include to Theme's 'JS Includes' related list.
-
Customizing Header/Footer: For significant changes: Clone the OOB header/footer widget -> Modify Clone -> Create new
sp_header_footerrecord pointing to Clone -> Update Theme record to use the new Header/Footer record. -
Demo: Created custom theme, assigned OOB header. Used Branding Editor, showed Portal CSS variables updated. Defined custom CSS variable in Theme, used it in widget CSS. Created external stylesheet, included it via Theme, showed styles applied.
Day 29: Angular Providers (Directive)
-
Angular Providers (
sp_angular_provider): Mechanism to create reusable, custom Angular components (Directives, Services, Factories) within Service Portal. -
Custom Directive:
- Purpose: Encapsulate HTML structure, CSS, and client-side logic into a custom HTML tag or attribute, making complex UI elements reusable.
-
Provider Type: Set 'Type' to 'Directive' on the
sp_angular_providerrecord. -
Naming: Provider Name (camelCase, e.g.,
myCustomButton). HTML Usage (kebab-case, e.g.,<my-custom-button>ormy-custom-buttonattribute). -
Structure (Provider's 'Client Script' field): A function returning an object defining the directive:
function() { return { restrict: 'EA', // How it can be used: Element, Attribute template: '<button>...</button>', // Inline HTML // or templateUrl: 'my_template.html', // Link to sp_ng_template ID scope: { // Defines attributes & binding types label: '@', // Pass string value pageId: '@', configObject: '=', // Two-way bind object action: '&' // Pass parent scope function }, controller: function($scope, /* inject services */) { // Directive's logic }, // link: function(scope, element, attrs) { /* DOM manipulation */ } }; } -
scope: {}defines isolated scope: Protects directive's internal scope. Binding types (@,=,&) define how attributes passed in HTML link to this isolated scope.
-
Using the Custom Directive:
-
1. Link Provider: Add the
sp_angular_providerrecord to the Widget's 'Angular Providers' related list. -
2. Use in HTML: Add the custom tag/attribute to the Widget's HTML, passing required attributes. Attribute names in HTML are kebab-case (e.g.,
page-id).-
<my-custom-button label="'Go Home'" page-id="'index'"></my-custom-button> -
<div my-custom-button label="..." page-id="..."></div>
-
-
1. Link Provider: Add the
-
Demo: Created
spClickButtondirective.- Defined template for a button.
- Defined controller with logic to redirect using
$location.search. - Initially hardcoded redirect target.
- Added
scope: { customText: '@', customPageId: '@' }. - Made button label (
{{customText}}) and redirect target ($scope.customPageId) dynamic based on scope properties. - Used the directive in widget HTML, passing different
custom-textandcustom-page-idattributes to demonstrate dynamic behavior. Showed both element and attribute usage (restrict: 'EA').
Day 30: Angular Providers (Service)
-
Custom Service:
- Purpose: Create reusable, singleton (single instance per application load) objects to share common client-side logic or data across multiple widgets or controllers.
-
Provider Type: Set 'Type' to 'Service' on the
sp_angular_providerrecord. - vs. Script Include: Services are client-side (run in browser); Script Includes are server-side. Use services for client utility functions, managing shared client state. Use Script Includes for server logic, DB operations.
-
Creating a Service:
-
Structure (Provider's 'Client Script' field): A constructor function. Use
this.to expose public methods/properties.function() { // Private variables/functions (optional) var privateCounter = 0; // Public method this.sortArray = function(arr, sortProperty) { if (!arr) return []; arr.sort(function(a, b) { if (a[sortProperty] < b[sortProperty]) return -1; if (a[sortProperty] > b[sortProperty]) return 1; return 0; }); return arr; }; // Public property this.serviceName = 'My Utility Service'; }
-
Structure (Provider's 'Client Script' field): A constructor function. Use
-
Using the Custom Service:
-
1. Link Provider: Add the
sp_angular_providerrecord to the Widget's 'Angular Providers' related list. -
2. Inject Service: Inject the service into the Widget's Client Controller using its provider Name (e.g.,
jsonUtil).-
function($scope, jsonUtil) { ... }
-
-
3. Call Methods: Access public methods/properties via the injected service variable.
-
var sorted = jsonUtil.sortArray($scope.myData, 'order'); -
console.log(jsonUtil.serviceName);
-
-
1. Link Provider: Add the
-
Demo: Created
jsonUtilservice.- Added a
jsonSortmethod that sorted an array of objects based on anorderproperty. - Injected
jsonUtilinto a widget's client controller. - Called
jsonUtil.jsonSort($scope.myUnsortedArray)to get the sorted version. - Displayed both original and sorted arrays to show the service worked. Demonstrated reusability potential.
- Added a
Day 31: Additional Features
-
Announcements (
sp_announcement): Reviewed. Banners/widgets for portal-wide messages. Configurable visibility, style, dates, dismiss options, target portals. -
Page Route Map (
sp_route_map): Reviewed. Silently redirect from one page ID to another within a portal. Useful for swapping underlying pages (e.g.,form->ticket) without changing links. URL shows source ID, content is target ID. -
Log Entries (
sp_log): Reviewed. Automatic logging of page/widget loads. Useful for performance analysis and usage tracking via reporting. -
Guided Tours (
sys_guided_tour): Reviewed. Create step-by-step interactive guides for portal users. Configured via Tour Builder UI, linked to specific portals/pages/roles. -
Widget Related Lists & Cloning: Reviewed. Widget record (
sp_widget) shows dependencies, providers, templates, usage (Included in Pages, Instances), version history. Cloning creates a customizable copy but loses OOB updates. -
Page Structure (Page Editor View): Reviewed. Hierarchical view (Page > Container > Row > Column > Instance > Widget). Alternative to Page Designer for inspecting structure and editing properties at different levels.
-
One-Time Binding (
::):-
Syntax:
{{ ::scopeVariable }}orng-bind="::scopeVariable". - Purpose: Binds the value only once during the initial rendering. Angular then stops watching this binding for changes.
- Benefit: Performance. Reduces the number of watchers Angular needs to track, especially useful for large lists or data that won't change after initial display.
-
Behavior: If
$scope.scopeVariablechanges after the initial bind, the UI element using::scopeVariablewill not reflect the change. -
Demo: Showed
{{ name }}updating on button click, while{{ ::name }}remained fixed at its initial value after the button click changed the underlying$scope.name.
-
Syntax:
Day 32: Angular Dependency
-
Purpose: Integrate external, third-party Angular libraries (modules containing directives, services, filters) into your Service Portal environment.
-
Dependency Record (
sp_dependency):- Represents the external library. Create via
Service Portal -> Dependencies -> New. -
Fields:
-
Name: Your descriptive name for the dependency (e.g., 'Angular UI Select', 'MomentJS'). -
Include on page load: Check if the library should load automatically with the portal framework. Usually checked. -
Angular module name: The exact name the library uses in itsangular.module('thisName', ...)definition. Crucial for Angular to recognize and load the module. Find this in the library's documentation.
-
- Represents the external library. Create via
-
JS/CSS Includes:
- Link the actual library files to the Dependency record via the 'JS Includes' and 'CSS Includes' related lists.
-
Source:
-
URL: Point to an external CDN URL hosting the library file (e.g.,
https://cdnjs.cloudflare.com/.../library.min.js). Common for popular libraries. -
UI Script / Style Sheet: If hosting the library code internally, create a
sys_ui_script(for JS) orsp_css(for CSS) record, paste the library code there, then create a JS/CSS Include record pointing to that internal record.
-
URL: Point to an external CDN URL hosting the library file (e.g.,
-
Using the Dependency in a Widget:
-
1. Link Dependency: Add the
sp_dependencyrecord to the Widget's 'Dependencies' related list. This tells the widget it needs this library. - 2. Follow Library Docs: Use the directives, inject the services, or apply the filters provided by the library within your widget's HTML and Client Controller, according to the external library's specific documentation. The 'Angular module name' step ensures Angular has loaded the necessary components.
-
1. Link Dependency: Add the
-
Demo:
- Goal: Implement an external 'AngularJS Dropdown Multiselect' library.
- Found the library's CDN URL (JS file).
- Created
sp_dependencyrecord ('AngularJS Multi Dropdown'). Set the correct 'Angular module name' based on library docs. - Created
sp_js_includerecord using the CDN URL, linked it to the dependency. - Added the dependency to a new widget's related list.
- Copied the directive usage (
<div ng-dropdown-multiselect ...>) from library docs into the widget HTML. - Copied the necessary
$scopevariable setup (options array, selected model array) from library docs into the widget's Client Controller. - Result: The widget displayed a functional multi-select dropdown, styled and behaving according to the external library, without writing the complex dropdown logic manually.
No Comments