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
admin
orsp_admin
roles. 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
/sp
after 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_portal
record): Defines the overall portal (URL suffix, theme, menu, homepage). -
Page (
sp_page
record): A specific view within a portal (e.g., homepage, category page, form page). Identified byid
in the URL. - Container/Row/Column: Bootstrap layout elements defined within the page structure.
-
Instance (
sp_instance
or related tables): A specific placement of a widget on a page. Holds configuration options for that widget placement. -
Widget (
sp_widget
record): 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_widget
record):- 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_portal
table):-
Key Fields:
-
Title
: Browser tab title. -
URL suffix
: The/suffix
used to access the portal. -
Homepage
:sp_page
record 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_menu
record defining the header navigation. -
Theme
:sp_theme
record 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_case
widget 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'. DefineName
andID
. Can edit code directly here or use 'Open in Widget Editor'.
- Navigate:
-
Page Designer:
- Access:
Service Portal -> Pages
-> Open Page record -> ClickOpen Designer
UI 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_task
page. Added 6/6 column layout. Draggedincident_details
widget.
- Access:
-
Fetching Multiple Records (Server Script Pattern):
- Initialize an empty array on the data object:
data.records = [];
- Query the table (e.g.,
incident
wherecaller_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');
(UsegetValue
for raw values,getDisplayValue
for user-friendly ones). - Push the temporary object into the
data
array:data.records.push(item);
- Create a temporary JavaScript object:
- The
data.records
array (containing multipleitem
objects) is passed to the client.
- Initialize an empty array on the data object:
-
Displaying Lists in HTML (
ng-repeat
):- Use the
ng-repeat
directive 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
data
object in HTML:<pre>{{data | json}}</pre>
. Use thejson
filter 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.data
object to the server.
-
input
vsdata
Objects:-
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.data
object as it was whenc.server.update()
was called. Available only within the Server Script execution triggered byc.server.update()
. On initial page load,input
is typically null or empty.
-
-
Workflow for Client-Triggered Server Action:
-
1. Client (Action Trigger): User clicks a button (e.g., 'Close Incident').
ng-click
calls a client functionc.closeIncident(sysId)
. -
2. Client (Prepare Data): Inside
c.closeIncident(sysId)
:- Set properties on
c.data
to 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
input
exists 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
data
object 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_table
which providestable
,filter
,title
fields). 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 table
definition.
-
1. Open Widget Record (Platform View): Navigate to
-
Accessing Options (Widget Server Script):
- Use the
$sp
API 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_details
widget into a genericrecord_details
widget. - Enabled Instance Options (
table
,filter
,title
,short_description
). - Used
$sp.getValue()
in the server script to fetch these options. - Placed two instances of
record_details
on themy_task
page. - Configured the left instance via Instance Options for 'Incident' table,
active=true
filter. - 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 schema
field (JSON format) on thesp_widget
record. -
Accessing Schema Options:
-
Server Script: Use the global
options
object.-
var color = options.optionName;
- Example:
data.titleColor = options.title_color;
-
-
Client Script / HTML: Options are also available client-side via the
options
object (passed implicitly).- Example (HTML):
<h1 style="color: {{options.title_color}}">...</h1>
- Example (Client):
if (options.showDetails) { ... }
- Can also pass from server via
data
object 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 table
setting.
-
-
Demo Summary:
- Added
title_color
(String) andsd_color
(String) options via the Option Schema to therecord_details
widget. - Configured different colors for these options in the Incident and Change Request instances.
- Accessed the colors via
options.optionName
in the Server Script (assigning todata
) and directly viaoptions.optionName
in the HTMLstyle
attribute.
- 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 theform
page. -
Ticket Conversations
: Renders a simplified, activity-stream-focused view of a task record for end users. Core of theticket
page. -
SC Catalog Item
: Renders a service catalog item. Core of thesc_cat_item
page. -
SC Order Guide
: Renders a service catalog order guide. Core of thesc_order_guide
page. -
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_announcement
table.
-
-
Useful OOB Pages (Identified by
id
parameter):-
index
: Default homepage for many portals. -
form
: Displays a single record using theForm
widget (?id=form&table=<table>&sys_id=<sys_id>
). -
ticket
: Displays a single record using theTicket Conversations
widget (?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 Link
andSimple List
widgets to the custom portal's homepage, configuring them via Instance Options. Discussed the purpose ofform
,ticket
,sc_cat_item
,list
,lf
pages.
Day 10: Typeahead Search
-
Widget: Typically the
Homepage Search
widget (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 Source
section:-
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
).
-
-
Typeahead
section:-
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_page
record to redirect to when a suggestion is clicked (e.g.,kb_article_view
for KB,sc_cat_item
for 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 Sources
related list. - Click 'Edit...' and add the desired
sp_search_source
records. - 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 Search
widget often includes functionality to show recent user searches or popular searches (may require additional configuration or OOB setup). -
Demo: Added the
Homepage Search
widget to the custom portal homepage. Created a new Search Source for theincident
table (searching active incidents, displaying number/short description, linking to theform
page). Added this Search Source to the customPortal Training
portal record. Demonstrated typing incident numbers or keywords and getting suggestions.
Day 11: SP Modal (Part 1 - Alert, Confirm, Prompt)
-
spModal
Service:- 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:
spModal
modals match the portal's theme; JS pop-ups use browser defaults. -
Blocking Behavior: JS pop-ups block script execution until dismissed.
spModal
methods are non-blocking; execution continues immediately. Use the.then()
promise pattern to handle actions after the modal is closed. -
Customization:
spModal
offers much more control over content and buttons.
-
Styling:
-
spModal.alert('message')
:- Displays
message
with 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
message
with "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
confirmed
argument passed to the.then()
function istrue
for OK,false
for 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 totrue
to 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
nameEntered
argument contains the string entered by the user.
- The
- Use the generic
-
Demo: Showcased each
spModal
method (alert
,confirm
,open
for 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_widget
inside 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_widget
record. -
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 itsinput
object.- 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-click
triggers a function. -
2. Calling Widget (Client): Function prepares
widgetInput
object 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-id
executes. It can access the data passed in step 3 via its owninput
object (e.g.,var sysId = input.recordSysId;
). It populates itsdata
object as usual. -
6. Embedded Widget (Client/HTML): The embedded widget's HTML, CSS, and client script load and render inside the modal, using the
data
object 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.open
executes.
-
1. Calling Widget (Client):
-
Demo: Modified the custom list widget's 'Open Record' action.
- Instead of
window.location.href
, usedspModal.open
. - Set
widget
to'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)
-
spUtil
Service:- Another core Service Portal Angular provider service.
- Provides client-side utility functions.
- Inject into Client Controller:
function($scope, spUtil) { ... }
.
-
Comparison with
gs.addInfoMessage()
etc.:-
gs
methods are server-side only. -
spUtil
methods 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
filterString
on thetableName
is 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$scope
object. 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 thetable
andfilter
.
-
-
Callback Function Signature:
-
function(eventData) { ... }
- The
eventData
object contains details about the change:-
eventData.data.action
: String indicating the type of change ('insert', 'update', 'delete'). -
eventData.data.sys_id
: Thesys_id
of 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
spUtil
into 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
eventData
to 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-repeat
list 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
$scope
are 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
$scope
objects 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 foreventName
will receive it. - Most common method for widget-to-widget communication.
- Sends an
-
$scope.$emit(eventName, data)
:- Sends an event upwards from the current
$scope
towards$rootScope
. Only ancestor scopes (and$rootScope
) listening foreventName
will 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
eventName
on the current$scope
. - When an event with
eventName
is 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$broadcast
or$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
options
object to the embedded widget's server script. The embedded widget accesses these via theoptions
global 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
.widgetOptions
becomes theoptions
object 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
response
in.then()
is the widget model. Assign it to ac.data
variable.
- Inject
-
Calling Widget (HTML):
-
<sp-widget widget="c.data.embeddedWidget"></sp-widget>
- Renders the widget model after the
spUtil.get
promise 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
)
-
$sp
Object:- A server-side API object available only within the Widget Server Script.
- Provides utility methods specifically useful in the Service Portal context.
-
Common
$sp
Methods:-
$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 aGlideRecord
object for that record, pre-loaded. Returnsnull
otherwise. -
$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. Returnstrue
orfalse
.
-
-
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_custom
widget and page. - Modified the list redirect to point to
?id=form_custom&table=...&sys_id=...
. - In
form_custom
Server 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 atitle
instance option to theform_custom
widget 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_widget
Body HTML field) can become cluttered and hard to manage, especially with complexng-if
orng-switch
logic 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-include
directive within the main widget's Body HTML. -
Syntax:
-
<div ng-include="templateUrlExpression"></div>
- The
templateUrlExpression
must 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-include
in 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_custom
widget.- Created two
sp_ng_template
records:incident.html
andchange.html
, linked to theform_custom
widget. - Moved the incident-specific display markup into
incident.html
. - Moved the change-specific display markup into
change.html
. - Updated the
form_custom
widget's Body HTML to use the conditionalng-include
structure 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
). Settrue
for 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, checkspModal
docs 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
result
argument passed to.then()
is thevalue
property of the button object that was clicked. - Check the
result
to 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
class
property in thebuttons
array (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_page
record -> 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.confirm
withspModal.open
. - Used the
message
property with HTML (<b>Please confirm</b> you want to...
). - Defined a
buttons
array:-
{ 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.
-
$timeout
Service:-
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
-
$location
Service:- 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$timeout
function to redirect the user to the list page after the delay.
- Alerted
Day 21: Angular Services ($uibModal, $watch)
-
$uibModal
Service:- 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
:-
spModal
is often simpler for basic alerts, confirms, prompts, or embedding existing widgets. -
$uibModal
encourages separation usingtemplateUrl
and dedicated controllers, better for complex, self-contained modal logic. Often feels more "Angular-native".
-
-
Demo: Created a basic
test.html
Angular 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
$scope
for 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$scope
property to watch (e.g.,'data.name'
,'userInput'
). -
callbackFunction
: Executes when the value changes. Receives thenewValue
andoldValue
.
-
- 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-model
changes 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)
-
$http
Service:- 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:
$http
requests 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.data
using thejson
filter. - 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.data
in 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 ifexpression
is falsey; recreates it if truthy. Creates a child scope. -
ng-show="expression"
/ng-hide="expression"
: Adds/removes the.ng-hide
CSS 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 ofsrc
for<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$scope
variable. -
ng-bind="expression"
: One-way binding. Sets the element'stextContent
to the result ofexpression
. Alternative to{{expression}}
. -
ng-bind-html="expression"
: One-way binding. Sets the element'sinnerHTML
to the result ofexpression
, rendering HTML content. Requires sanitization (ngSanitize
module) for security. Use only with trusted HTML sources. -
ng-init="expression"
: Executesexpression
once 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
,$odd
withinng-if
. Comparedng-if
(element disappeared from DOM Inspector) vs.ng-show
(element hidden via CSS). Showedng-bind-html
rendering bold tags correctly whileng-bind
showed 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.limit
is 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.expression
is 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:3
and 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-list
widget. UsedInstance with Table
data table, exposingtable
andfilter
options. 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_list
andheader_list
strings into arrays (data.fields
,data.headers
). - Queried the specified
table
with thefilter
. - Looped (
while gr.next()
), createditem
object for each record. -
Dynamically populated
item
by looping throughdata.fields
:item[data.fields[i]] = gr.getDisplayValue(data.fields[i]);
. Addedsys_id
. - Pushed
item
intodata.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:reverse
filter to bodyng-repeat
. Added conditional icons (ng-show
) in<th>
based onsortKey
andreverse
.
- Client:
-
Filtering (Client & HTML):
- HTML: Added
<input ng-model="searchText">
. - HTML: Added
filter:searchText
filter 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-repeat
ongetNumber(totalPages)
. Usedng-click="setPage($index)"
. Appliedng-class="{ 'active': isPageActive($index) }"
for styling. - HTML: Added
limitTo:itemsPerPage:currentPage*itemsPerPage
filter to bodyng-repeat
(chained last).
- Client: Calculated
-
(Optional) Breadcrumbs: Embedded
widget-filter-breadcrumbs
via$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_message
table. Contains records mapping an EnglishKey
to a translatedMessage
for a specificLanguage
. -
Translation Methods:
-
Method 1: HTML (
$\{...}
syntax):- Wrap static text directly in HTML:
<span>$\{Submit}</span>
. - ServiceNow attempts to find a
sys_ui_message
record whereKey
is 'Submit' andLanguage
matches 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
data
object:data.buttonLabel = gs.getMessage('Submit Request');
. - Bind in HTML:
{{data.buttonLabel}}
. - Reliable for translating text determined on the server.
- Use
-
Method 3: Client Script (
i18n
service / filter):-
a)
i18n
Service (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)
i18n
Filter (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_message
for 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 thei18n
filter 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-picker
andsp-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$scope
function 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 boundfield
object within that function.
-
Directive:
-
Demo: Implemented both pickers.
-
sn-record-picker
for Group table, configured attributes. Listened viafield.change
event. -
sp-date-picker
, boundfield
, usedsn-change
to trigger an alert showing the selected date value from the bound field object.
-
Day 28: Themes & Branding
-
Theme (
sp_theme
record):- 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_footer
record):- 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_footer
record 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_provider
record. -
Naming: Provider Name (camelCase, e.g.,
myCustomButton
). HTML Usage (kebab-case, e.g.,<my-custom-button>
ormy-custom-button
attribute). -
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_provider
record 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
spClickButton
directive.- 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-text
andcustom-page-id
attributes 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_provider
record. - 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_provider
record 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
jsonUtil
service.- Added a
jsonSort
method that sorted an array of objects based on anorder
property. - Injected
jsonUtil
into 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.scopeVariable
changes after the initial bind, the UI element using::scopeVariable
will 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_dependency
record 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_dependency
record ('AngularJS Multi Dropdown'). Set the correct 'Angular module name' based on library docs. - Created
sp_js_include
record 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
$scope
variable 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