<!-- CERN title --> <!-- Styles are global for the slide deck --> <style> mark {background: #f7d40f;} li strong {color: white;} code {color: hotpink;} .subitem {color: DarkSeaGreen;} .titlemoji {font-size: 0.75em !important;} .titlemoji-sm {font-size: 0.55em !important;} .danger-text {color:coral;font-weight:bold!important;} .side-note {color:DarkSeaGreen; font-style:italic!important;font-size:0.50em;} .white {color: white;} .alert-warning { color: #8a6d3b; background-color: Moccasin; border-color: #faebcc; border-radius: 15px; } </style> --- # ⚡️ ## Service Portal ### *Development Workshop* --- #### *Jorge García Cuervo* ##### *IT-CF-SM* ##### <span class="titlemoji">📧</span> &nbsp;*jorge.garcia@cern.ch* --- # AngularJS ![](https://codimd.web.cern.ch/uploads/upload_a2a500c874cc55602ef9f20f2338c790.png) --- # AngularJS #### Key concepts * AngularJS is a **Model-View-Controller** framework * Higher level of **abstraction** * Decouples DOM manipulation from app logic * improve testability ---- # AngularJS #### MVC Interaction ![](https://codimd.web.cern.ch/uploads/upload_19d42381c3aa2bbca7f194571aa317f4.png) --- # AngularJS #### Key concepts * **Template** * HTML with additional AngularJS markup * **View** * What the user sees (the `DOM`) * **Model** * Values stored in variables in the `Scope` * **Controller** * <span class="white">++Business logic++</span> behind views * <span class="white">++Expose++</span> variables and functionality ---- # AngularJS #### Key concepts * **Scope** * <span class="white">++Binding++</span> between `View` and `Controller` _(context)_ * **Expressions** * Access variables and functions located in the Scope * **NOT** JS, but similar ```javascript {{ expression | filter }} ``` ---- # AngularJS #### Key concepts * **Services** * <span class="white">++Reusable++</span> business logic independent of views * **Filter** * Format value of an expression for display to the user --- # AngularJS #### Data Binding * **Automatic sync** between Model and View * ==Two↔️way== Data Binding * <span class="white">++Change in the Model -++</span> <span class="side-note">reflected on the view</span> * _An exposed variable value changes in your `Controller`_ * _It changes automatically in the HTML the user sees_ * <span class="white">++Change in the View -++</span> <span class="side-note">reflected in the model</span> * _User modifies/selects something in the `View`_ * _If bound with something in the Controller, it will change as well_ * ++**Simplifies coding**++ ---- # AngularJS #### Data Binding ![](https://codimd.web.cern.ch/uploads/upload_9848aaf33350312388ec88cae4ff473c.png) --- # AngularJS #### Directives * OOB & **Custom** directives * Types * **Element** _(`E`)_, **Attribute** _(`A`)_, Class Names _( C)_, Comments _(M)_ ```html <my-dir></my-dir> <span my-dir="exp"></span> <!-- directive: my-dir exp --> <span class="my-dir: exp;"></span> ``` :::warning :warning::eyes: **Normalization:** Directive names in *camelCase* are specified in ++lower case with dashes++. E.g `ngModel` <-> `ng-model` ::: --- # AngularJS #### Directives - OOB * AngularJS provides a set of built-in directives * They begin with the <span class="white">++prefix++</span> **`ng-`** * `ng-click`, `ng-if`, `ng-repeat`, `ng-hide`, `ng-show` * Consider beginning each directive <span class="white">++on a separate line++</span> * _Improves readability_ ```html= <div> Enter your name: <input type="text" ng-model="c.data.sometext" ng-change="c.display()"/> </div> ``` ---- # AngularJS #### Directives - ngModel * **Binds** a form control (e.g. input, select…) on the View **to a property** on the Model * Input from an `ng-model` (when user changes the value) <span class="white">++overrides the value in the Controller++</span> ---- # AngularJS #### Directives - ngModel example ![](https://codimd.web.cern.ch/uploads/upload_42a84cdad221decfb623937b389d350d.png) ![](https://codimd.web.cern.ch/uploads/upload_4e62492bcd52989dd9401176a69bb10f.gif) <!-- .element: class="fragment" data-fragment-index="1" --> ---- # AngularJS #### Directives - ngClick * Specify custom behaviour when an element is selected * Parameters can be passed ---- # AngularJS #### Directives - ngClick example ![](https://codimd.web.cern.ch/uploads/upload_03d8565214c7924b5d89bad75af960eb.png) ![](https://codimd.web.cern.ch/uploads/upload_e5b79fb73662f30c091954c7550daf86.gif) <!-- .element: class="fragment" data-fragment-index="1" --> ---- # AngularJS #### Directives - ngIf * Removes the HTML element completely from the `DOM` ![](https://codimd.web.cern.ch/uploads/upload_d15d8c1a046cfffe17fb283ce2a1499f.png) ![](https://codimd.web.cern.ch/uploads/upload_71e49adee8c2ff11a07a8d41553c77eb.gif) <!-- .element: class="fragment" data-fragment-index="1" --> --- # AngularJS #### Directives - Custom * Create your own directives * <span class="white">prefix</span> them to avoid future collisions * `cernTable` (`cern-table`) * <span class="white">++Anatomy++</span> * Controller * Template * `$scope` ---- # AngularJS #### Directives - Custom * Directives are defined returning a JS object * that will be interpreted ty the `$compile` service ```javascript return { controllerAs: 'c', controller: function(){var c = this;}, //controller: myController, template: '<div> Hello, I am directive :)</div>', //templateUrl: 'myTemplate.html' } ``` ---- # AngularJS #### Directives - Custom ###### Properties when defining a Directive * `restrict` * By default a directive is only recognized as <span class="white">_Element_</span> * `scope` :star: * <span class="white">++Isolate and initialize++</span> the directive's `$scope` * Pass parameters into the directive * One way (`@`) / two way (`=`) binding and functions (`&`) * <span class="side-note">pass parameters from its parent</span> * `bindToController` * Bind scoped inherited properties to the Controller instead of the `$scope` object. <span style="font-size:0.75em">[_more info_ :link:](https://ultimatecourses.com/blog/no-scope-soup-bind-to-controller-angularjs)</span> ---- # AngularJS #### Directives - Custom ```javascript return { restrict: 'EA', controllerAs: 'c', controller: myController, scope: { myParam: '=', myReadOnly: '=myTitle', submit: '&' }, bindToController: true, templateUrl: 'myTemplate.html' } ``` ```html <div> <myDirective my-param="hello" my-title="world" submit="c.updateRecord()" /> </div> ``` --- # AngularJS #### Controllers * Initial state of `$scope` and add behaviour * Contain **business logic** for a single view * Don't do too much. No heavy lifting * Dependency injection * **DON'T** * ++Manipulate the DOM++ (:no_entry: `jQuery`) * <span class="side-note">Link Function in `Widgets` for this</span> * Format Input (AngularJS form controls instead) * Filter Output (AngularJS filters) * Share code or state (Services) ---- # AngularJS #### ControllerAs Syntax * Hides complexity of `$scope` * Best practice * **DO NOT** assign properties to `$scope` * ++Assign them to the controller++ instead * _which is an object **inside** the scope_ ```javascript function() { /* ControllerAs syntax */ var c = this; c.data.msg1 = "Hello"; c.data.msg2 = "World"; } ``` ---- # AngularJS #### Controllers ```javascript function cernCustomTableCtrl(cTableService, $rootScope, $window) { // "Controller as" syntax var c = this; // Exposed variables c.tableOptions; c.table; c.showAddNewRow = false; // Exposed methods - available from the view c.editCell = editCell; c.createRow = createRow; c.deleteRow = deleteRow; c.saveRow = saveRow; c.saveEdit = saveEdit; // CONTROLLER IMPLEMENTATION _init(); // ... // FUNCTIONS DEFINITION function _init() = {...} function editCell() = {...} // ... ``` --- # AngularJS #### Scope * Context for expressions and functions to be evaluated * Contains the Application Model * Glue between controller and view * Scope Inheritance and Hierarchy * `$rootScope` * `$parentScope` * `$scope` * **Isolated Scopes** :star: * Initialized "empty" to isolate directives * Components outside cannot change the Model ---- # AngularJS #### Scope * <span class="white">++Watch Expressions++</span> * `$watch` * <span class="white">++Propagate Events++</span> * `$emit` to parent (or root) scope * `$broadcast` to children scopes * _<span class="white">Communication</span> amongst directives_ --- # AngularJS #### Services > A <span class="white">**Service**</span> is an object containing a set of functions to perform a task. * Organize and **share** code across your application * Share data between directives * MUST be ++injected++ into the `Controller` * [OOB Services](https://docs.angularjs.org/api/ng/service) * `$` prefix * `$http`, `$location`,... ---- # AngularJS #### Services Injecting a Service in the Controller ```javascript function($http) { /* widget controller */ var c = this; } ``` --- # Service Portal ![](https://codimd.web.cern.ch/uploads/upload_96d62fdfeaccfb1e30279557e7ead7eb.png) --- # Service Portal Anatomy * **Pages** `[sp_page]` * **Widgets** `[sp_widget]` * **Widget Instances** `[sp_widget_instance]` * *Specific, parametrized* ---- # Service Portal Anatomy #### Pages, Widgets and Widget Instances ![](https://codimd.web.cern.ch/uploads/upload_2c44fe016e38bdd24b48162fb3a2fefb.jpg) ---- # Service Portal Anatomy #### Pages, Widgets and Widget Instances ![](https://codimd.web.cern.ch/uploads/upload_648d9c2f55d10856baae79df38409e55.jpg) --- # Editing the Portal * Service Portal Configuration * [/sp_config :link:](https://cerndev.service-now.com/sp_config) ---- # Editing the Portal #### *SP Config* ![](https://codimd.web.cern.ch/uploads/upload_e624b15ccdc2222f42e36a8b1425ad1e.JPG) --- # Editing the Portal #### Pages * **Alternatives** * **Designer** 🌟 * *preferred way of editing a page* * **Page editor** * *useful to see ++dependencies++ and ++structure++* * Always clone our CERN page template to start * breadcrumbs * search * footer --- # Editing the Portal #### Widgets --- # Widgets #### What is a Widget? * Building blocks of Service Portal Pages * Performs one task * and does it good :v: * :recycle: Reusable components * 🛠 Configurable ---- # Widgets #### What is **REALLY** a Widget? * AngularJS 'Directive' * Teach HTML some new tricks :dog: * Extend HTML vocabulary * Anatomy * HTML `[view]` * CSS-SCSS * Client Script (controller) * Client & Server side execution * No need for `GlideAjax` magic :eyes: * [OOB Widgets available :link:](https://docs.servicenow.com/bundle/london-servicenow-platform/page/build/service-portal/concept/widget-showcase.html) --- # Widgets #### Lifecycle 1. **Server Script** executes first * <span class="white">++Available:++</span> `options` * <span class="white">++Initializes:++</span> `data` and `input` <span class="side-note">(`undefined` in first execution)</span> 2. **Client Script** executes, receiving the `data` object 3. The Widget is **rendered** in the Portal 4. **User interacts** with it and ++generates input++ 5. The Widget **sends** the `input` object ++back to the server++ with `server.update()` 6. The ++Server Script is executed again++ and **updates** the `data` sent to the client 7. … ---- # Widgets #### Lifecycle ![](https://codimd.web.cern.ch/uploads/upload_4467aeb1dbc6c1752be93ce314975742.png) --- # Widgets #### Server Script * Populate the `data` object * `GlideRecord`, `GlideAggregate` * `GlideSPScriptable API` <span class="side-note">(Portal Context)</span> * `$sp.getRecord()` <span class="side-note">gets a GR for the current record</span> * `$sp.canReadRecord(gr)` <span class="side-note">user can read?</span> * Syntax **data.**_variable_name_ * `data` is JSON serialized and sent to the client ---- # Widgets #### Server Script ```javascript (function() { // `input` is not null if coming from the CLIENT if (!gs.nil(input.keywords)) data.items = getCatalogItems(input.keywords); function getCatalogItems(keywords) { var sc = new GlideRecord('sc_cat_item'); sc.addActiveQuery(); /* ... */ sc.setLimit(100); sc.orderByDesc("ir_query_score"); sc.query(); var results = []; while (sc.next()) { if (!$sp.canReadRecord(sc)) continue; var item = {}; $sp.getRecordDisplayValues(item, sc, 'name,price,sys_id'); item.category = sc.getValue('category'); results.push(item); } return results; } })(); ``` --- # Widgets #### Client Side * We got the `data` object from the Server * Time to show the user something :sunglasses: * HTML + CSS * Client Script * `AngularJS` ---- # Widgets #### Client Side - AngularJS * Framework * Extends HTML with new attributes: ==Directives== * Binds data to HTML with Expressions * _“““Replaces”””_ `Jelly` * ServiceNow uses Angular Version 1 (AngularJS) * AngularJS ≠ Angular * Take this into consideration when searching online for documentation --- # Widgets #### Service Portal Utilities * **Client-side** APIs * <span class="white">`spModal`</span> * Alerts, prompts, confirmation dialogs * complexity needed? <span class="side-note">`$uibModal`</span> directive * <span class="white">`spUtil`</span> * Common ServiceNow functions for the Portal * `recordWatch()` * Monitors a table for changes to its records * No page refresh needed to update lists, counts, etc * Impacts performance: use with care --- # Widgets #### Reuse Code * **Angular Providers** * Script Includes for AngularJS Code * Different posibilities * Services :star: * Directives :+1: * ~~Factories~~ :no_entry: * <span class="white">Angular ng-templates</span> `sp_ng_template` * Reuse HTML for Directives and Widgets --- # Widgets #### Debugging * <span class="white">++Server Script++</span> * `sp.log()` * `gs.debug()` * `gs.info()` * <span class="white">++Client Script++</span> * `console.log()` * `spUtil` * `debugger` instruction * <span class="white">++HTML++</span> * `{{ data | json }}` --- # Service Portal #### Not covered topics ###### :warning: Remember they exist too * Themes * CSS includes (`SCSS`) * remember we can use **variables**! * Page Route Maps * "swap" contents of a requested page server side * E.g: Page Route map from `page1` to `page2` * _http://..../page1 -> ServiceNow returns page2_ --- # Congratulations! 🐱‍👤 `Now you're an AngularJS and Service Portal ninja` --- # Any questions? 🤔🤷‍♂️ ---
{"subheadline":"workshop","title":"Service Portal Development Workshop","tags":"presentation, AngularJS, ServiceNow, Workshop","type":"slide","slideOptions":{"transition":"slide","theme":"cern3"}}