[This appears to be completely different, loading angular explicitly from Jelly: https://developer.servicenow.com/dev.do#!/learn/courses/quebec/app_store_learnv2_angularjs_quebec_building_apps_with_angularjs/app_store_learnv2_angularjs_quebec_introduction_to_angularjs_in_servicenow/app_store_learnv2_angularjs_quebec_introduction_to_angularjs_apps_in_servicenow_objectives Says to use Studio App { UI Pages. HTML. UI Scripts. Client-side Logic. Jelly templates. } UI Pages is root element. You get a default template. You must load angularjs lib in jelly or via UI Script. ] PAGE URLS: .../?id=&page=param1... N.b. the id param doesn't need to be first, and it is not first in some widget-generated URLs. SP Technologies: Bootstrap + CSS 3 Flexboot + AngularJS 1.x. (not Angular Framework 2.x) Never uses Jelly (incl. sys_ui_page's). SP Components { Themes portal: sp_portal, consists of... sp_pages: "do not have a defined relationship to portal records" ??? Each has ID used in url with ?id= (defaulting to portal home) Widgets. sp_widget, sp_instance Superset of an Angular directive. NOT sys_ui_widget which are for dashboards and use sys_ui_pages. Instances of widgets are added to pages. Incorporate: Base widge template, HTML templates, CSS, clients&server scripts, JS dependencies. Widget editor. } sp_page. Page layout. Use containers, rows, columns Pages really are not portal-specific!! Use existing pages as examples. Use SP Designer to author by drag&drop; or Page Editor for map view. Use .dynamic_title_structure and 'Dynamic page title variables' related list to specify a lookup table to map any input http param to a %variable used in .dynamic_title_structure to generate page title. SC. compatible sc_cat_items displayed using SC Catalog Item widget. Always displayed in 2-column layout. (UI Macros never supported) Compatible items use mbible GlideForm API for client scripts and UI Policies. Header portal menu = { header or footer widget. Select the widget on sp_themes form page (contrary to docs), sp_widget.header or sp_widget.footer. menu widget. Instantiate under Service Portal > Menus (contrary to docs) Then in new sp_instance_menu form use New in Menu Items related list to add menu items. Select menu widget instance on sp_portal form page (.sp_rectangle_menu). } N.b. headers and footers are contained in sp_themes not sp_portals. Themes sp_theme + Styling done by CSS style sheets or with real-time Branding Editor. Styling priority: sp_instance sp_widget other individual components (other than widgets) sp_page sp_theme branding editor bootstrap Can specify bootstrap helper classes in any SP field that accepts CSS. E.g. visible-lg, hidden-md. SP App sp_config gives an overview of all Service Portals. Seems dumb that the SP sys properties are not individual-SP-specific. *InfoMessages auto-dismiss by default. Can change with a property. User Criteria is an optional authorization facility more fine-grained than roles. Create portal > Quick start config is Branding Editor schema in JSON map format. Create portal > CSS variables defines Sass and LESS includes to override theme variables. Branding editor color settings end up here. There is a page URL/ID redirecton facility, Page Rout Maps. Easy to attach a font icon file to any page. Attach and add a page-specific CSS line. See docs. Standard Ticket is a predefined layout for "requests" (tasks???). Requires extra configs for cross-scope access. Widget { Good Example: Simple List Data table is always sp_instance or a subtable thereof. .template HTML Template Do not need to add any attr, but it seems that for top-level widgets and if using templateUrl, non-tag code at root level somehow is silently ignored (or at least does not render to the pages). For controller .this accesses, only has access to .data and .options due to SN widget "isolation" settings (can override by dot-walking from the controllerAs reference var). WTF!!!! Today I have access to everything in the controller (i.e. this of controller function body). N.b. this contains only things in the controller by default or explicitly added to 'this' (no declared or unscoped assignments). .controller_as reference of controller function body made available to HTML. Instead of using this attr, can specify multiple sp_ng_template's in related list 'Angular ng-templates' and (typically) choose one via .options. (See uibModal section below for another use of ng-templates). .client_script == Client controller <-- receives server's 'data' (auto-encoded) dict as scoped data. This controller is bound to automatically created div that your .template is inserted into:
Therefore jQuery("#div.x".scope() gets the scope. .scope() of your top level template element(s) has scame .scope() value (can easily compare with scope.$id). Damned editor still does not allow ECMAScript6 such as "let"! Must contain a function definition. Somehow it can be anonymous, but just to quiet editor warnings, start it with "api.controller = function" Run on page load and actions. Can call server.update At page load 'inputs' always empty (can update) At top level this is 'window' yet 'api' and variables declared here are not accessible to client debugger. (A tip to get access to objects in scope top-level window scope is to assign to new undeclared variables. But don't need to access from browser debugger, seems that scripts can access variables if you declare them at root level of .client_script. api.controller=function() { fnThisRef = this;... Angular (not JS) var of name via widget.controller_as is this. apiRef = api; ) Inside the api.controller callback body, Client gets options only on page load, and can update that only via server.refresh() or programmatically loading from server.get promise. If call server.update() then automatically send all data -> server .input. AND promise gets data populated by server + same updates client's data. If call server.get() then you explicitly server's input AND promise gets big structure incl. data, options, widget but data has only NEW server ADDITIONS; and NO merging happens. .script Server script <-- executes first with these vars in scope: <-- can run again when client scripts call server.update/.get/.refresh input (undefined unless invoked from client), options, data (empty first) data to send to client controller. It constains either entire client data (if called server.update()) or specified dict (server.get(dict)). updating options: Updates to options are not persisted, only sent to client. Client only auto-merges 'options' on first (input undef) invocation (and I think upon server.refresh()). But server.get() calls get updated options though client may not use it. Add to provided 'data' dictionary which is always empty {}. Don't recreate it or client will get nothing! CSS. Empirically, can't use SCSS here. Link function. Direct DOM manipulation. .option_schema JSON Array of dicts, all have string values except 'ed' required: name, section, label, type section values: Presentation, Data, Behavior type values: string, field_name, field_list, reference boolean values: true, false optional: hint, displayValue, value, default_value, ed reference type attrs: value is a page id, ed (only allowed here) value is a dictionary: { reference: "" } .demo_data JSON map NORMALLY has top level attrs of options and/or data, can have nesting, but OOTB widget 'link-button' has different keys. I have no idea how to get this to work. Somehow it works for wiget 'hello-world'. .controller_as Angular (not JS) variable name (see.clien_script above) } LIFECYCLE: server -->data--> client server.update() -->data->input--> server as many times as needed. To modify, clone a RO OOTB. Instance Option vals set by RMB (for widget context menu). Generally do not display if don't contain info (no rows, etc.). SCSS is a subset of Sass, which adds tons of conveniences and programmatic abilities for style sheets. REVERSE-ENGINEERING TEMPLATES Every server invocation gets data object of {} + input of undefined or precisely what client passed. Client controller script api.controller callback gets 'this' containing: { data Dict. This gets the server's 'data' options. Gets back only a direct properly-typed dictionary unrelated to widget's .option_schema. Read-only here. server. Functions this.server.get, this.server.refresh, this.server.update widget. Dictionary exactly matching the sp_widget GR. } These are all dicts/plainObjs, even though server members are all functions. N.b. in JS barewords do not default to this members, so must explicity specify 'state.data', etc. Can (inside the function) add function members to this/the-controller-name which can then be invoked from the HTML by DOM event triggers or ng attrs. Client-side behavior Has jQuery! The new 'data' has server-provided data merged into it. Must update the PROVIDED data dict. If replace then has no effect for this exec. Promise server.update() server.update().then(function(v) { SUCCESS; }, function (v) { FAIL; }); Simple List widget Displays as
    rather than table. For each record displays vertically: Image field, Display Fields, others (hor) .sp_page (Link to this page) Specifies link target from the record display fields. redirects to the sp page via sys_id param of the record. with these additional params: view=sp id=target-page-name table=table-of-the-list See .widget_parameters of existing widget instances for how to configure further. List page field is supposed to be target for View-all in widget footer, but I'm getting no footer and don't even see the field displayed in existing instances. Core tabular widget is ID 'widget-data-table' This is nested by widget "Data Table from Instance Definition" (has no ID). Body template can access only {{options.x}} or {{data.y}} due to SN isolation setting (I think no such restriction if called by spUtil.get). ${ JS expression which may contain {{z}} } No. No idea what ${...} does :( ${AngularStringExpr???} like New, Keyword Search, table, page Useful client-side utilities spModal spUtil incl. add*Message() Server-side $sp methods documented at https://developer.servicenow.com/dev.do#!/reference/api/quebec/server/no-namespace/c_GlideSPScriptableScopedAPI?navFilter=glidespscriptable Most important method is $sp.getParameter("http_param"); Returns native JS string or null. SCOPE HIERARCHY No idea how $rootContext $id==1 gets instantiated Apparently by directive, body el gets $id==2 specified "spPageCtrl as main". Widget Options By reverse engineering, you set available options by: Specify data_table (misnomer) and then add fields from that table with field_list. For each additional option, add a dictionary to the option_schema JSON list. Per Anil, to style embedded widgets to hide form heading add widget css: .form-widget-container.panel-heading, .form-widget-container .container-caption { display: none !important; } Anil's embed template code:
    NESTING widgets Static nesting: .template contains: .script contains: data.widgVar = $sp.getWidget("widgetId", {options}); Dynamic nesting: Some HTML template must contain: .client_script contains: function(...spUtil...) { spUtil.get("widgetId", { options? }).then(function(widgetRef)); (I think this variant has non-isolated scope). DATA MODELS: this.data === $scope.data AND this.options === $scope.options. REFS to same! (from controller function) At original page load, the .data and .options (from above) are exactly the data and options dictionaries populated by the .script. Upon server.update invocation of .script Complete current client .data -> input Upon server.update callbacks 1. NO UPDATING OF .options, 2. 'then' callback param is just the new data object from new .script invocation 3. this.data === $scope.data automatically gets previous merged into it. uibModal General, incl. API capabilities: https://angular-ui.github.io/bootstrap/ Directives > Modal For some reason, the template HTML does not have default access to controller 'this' members (do no special access to data/options here). You must use scope variables or use controllerAs dot-walking. SN usage. In sp_widget.client_script: function(...$uibModal...) { ...function(...) { // INSTANTIATE const modalInstance = $uibModal.open({ // See doc URL above for many more options templateUrl: "sp_ng_template.id", //TODO must be in rel list? // sp_ng_template.sp_widget is the 1-to-many OR template: 'string' // Must specify one of these two. windowClass: "class-name", scope: $scope, // parent scope, defaulting to $rootScope size: 'sm', // determines "modal-X" class controller: function(...) {...}, //same as .client_script // in here can use spUtil.get() to nest a widge in modal controllerAs: '?', TODO: Only if controller is set, I think isolation allows this ref in addition to 'data' and 'options'. Either names controller 'this', or a variable in controller. resolve: {}, // ??? undocumented }); // CONTROL modalInstance.close(result); modalInstance.dismiss(result); // PROCESS modalInstance.result.then(closeFunction, dismissFunction); // Similarly for: opened, closed, rendered (in place of "result") // Upon open get x.result.then(function(true)); // Upon close get x.closed.then(function(undefined)); AND // x.result.then(-, function("backdrop click")); // Upon x.dismiss(param) get x.closed.then(function(undefined)); AND // x.result.then(-, function(param)); // Upon x.close(param) get x.closed.then(function(undefined)); AND // x.result.then(function(param)); // CLEANUP $scope.close = function() { modalInstance.close(); }; // OR $scope.on("$destroy", function() { modalInstance.close(); }); } } Option settings availble set by table fields + JSON. Use JSON and select no table fields unless need a complex type. Except that using tables may give capability to select field(s) within the specified table. Set edit the JSON with UI, use hamburger on widget designer. I don't know where $sp.getRecordElements gets .limit from, but it definitely does not come from sys_dictionary.max_length.