praveenmuramal1
Kilo Expert

Istanbul Service Portal

Custom Layout For Form Widget

(sp-model / spModel)

NOTE:

This is not the only way to create provide custom layout, there are other ways to do it. Always make sure if ServicePortal has a new feature to do the same with low code approach.

*** Make sure you test all your changes ***.

Understanding this feature will enable you to customize other angular directives that Service Portal has created.

It is considered good practice to use decorators when we extend third party directive, but in this document I am not using decorators.

When using this feature make sure you test the layout looks good on your mobile device too. One reason I believe that ServiceNow is sticking to the 2 column layout is to make the UI responsive.

Finally I appreciate if you can provide some feedback about this approach 🙂

There is much less intrusive way to change the layout, which I will explain in a subsequent post.



  • Why do we need to extend the Form-Widget
    • ServiceNow Form Views
    • How does Service Portal layouts the Fields
    • Can we display more than 2 fields in a row ?
    • Can we Hide the label of a field if required ?
    • Can I display a label next to a field rather than on the top of the field ?
    • Can we create a form which doesn't look like the way we created in ServiceNow Platform.

  • AngularJS Directive : spModel
    • Usage
    • Properties to Set
    • templateURL
    • sp_model.xml

  • Extending AngularJS Directive : spModel
    • Create ng-template
    • Include the ng-template in widget




Why do we need Custom Layout for the Form-Widget

ServiceNow Form Views

ServiceNow platform allows to create different views for a form - this allows users to use certain view.

Incident Form in ServiceNow Standard Platformhttps://istanbul.service-now.com/incident.do

How does Service Portal layouts the Fields in a Form-Widget


The Layout of Fields is derived from the FORM-View that is configured in ServiceNow Standard Platform.
ServicePortal Form Widget can be configured to use a specific view that is created in the Standard ServiceNow Platform. The Form will look the same in both ServiceNow Platform and ServicePortal.
Form-Widget retrieves the layout of the configured view and uses OTB template (sp_model.xml)   to render the form. sp_model.xml is HTML code that can be processed by AngularJS.

Can we configure to display more than 2 fields in a row in the ServiceNow Form View ?

No

Can we Hide the label of a field if required in the ServiceNow Form View ?

  No

Can we change position of the label in the ServiceNow Form View ?

  No

Can we create a form which doesn't look like the way we created in ServiceNow Platform.

OTB ServicePortal Form-Widget retrieves the view layout and renders the fields. If we use OTB ServicePortal Form-Widget we can't change the   layout of the fields.

Incident Form in ServicePortalhttps://istanbul.service-now.com/lbl_test/?id=form&sysparm_domain_restore=false&sysparm_stack=no&tab...

Components of OTB Form-Widget:

  • List of all the Attachments
  • UI Actions Links
  • ServiceNow Form-View
  • Related Lists
  • Save or New Button
  • Display which Mandatory Fields needs to be filled.



AngularJS Directive : spModel

ServicePortal uses AngularJS directive called spModel to render the fields of a Form-View. spModel directive is developed by ServicePortal and is automatically available for all the widgets.spModel directive provides many features that are required for a form-fields.

  • required fields
  • associate client scripts to the fields
  • Handle UI Policies
  • Field length validation and many more

Usage of spModel in Form-Widget:

HTML Code:

<sp-model form_model="data.f" mandatory="mandatory"></sp-model>

form_model - requires JSON Data with information about Form Sections / Fields / Layout of the Fields / required fields / Field Labels / Client Script functions / Save or New Functions.

Calling $sp.getForm in server script is required to pass data to the spModel

mandatory - JSON Data with any additional required attributes.

Note:

spModel directive is referred as sp-model in the HTML(AngularJS specific)

Server Script:

data.f = $sp.getForm(data.table, data.sys_id, data.view);

data.table = table name

data.sys_id = valid Sys ID or -1 for a new record

data.view = name of the FORM View / Don't pass the parameter for default View.

$sp.getForm is implemented by ServiceNow. It calls ServiceNow Server Function to retrieve data related to Fields / Sections / Client Scripts / UI Policies / field Lengths.

templateURL

spModel directive uses sp_model.xml (ng-template) to render the fields on a form.   ServicePortal includes sp_model.xml in all of its pages. ng-template is HTML Code that could include AngularTags and is processed by AngularJS.

The templateUrl in the spModel directive uses sp_model.xml that is automatically included by the ServicePortal in all its pages. It is not possible to change the way spModel renders the Form View.





As a ServicePortal Developer we don't have control to make changes to spModel directive and the template it will use. But we can create a new Directive ( Widget Angular Provider ) which extends the OTB spModel Directive but provide a new source for templateUrl attribute.

One Time Task 1: Admin will create a new Widget to copy the OTB sp-model.xml in a widget for easy access. Developers can always use the browser tools to retrieve the sp-model.xml

       

Step 1: Navigate to ServicePortal > Widgets > create a new widget with widget id "lbl-sp-model-xml"

Step 2: Copy the code from the script tag of sp_model.xml using browser tools

Step 3: Paste the code in the HTML Template of the widget.

Screen Shot 2017-08-18 at 4.12.52 PM.png

One Time Task 2: Admin will create a new Widget Angular Provider that extends the OTB spModel Directive. The new directive lblspModelCustomTemplate will inherit all the features of spModel but with a new source for the templateUrl.

Now we need to use the new Directive in the place of spModel Directive.

Since the layout for a form is specific to the table we will create a new Form widget by Cloning the existing Form - widget.

Step 1: Navigate to ServicePortal > Widgets > search for widget id "widget-form"

Step 2: Choose option Clone Widget.

Step 3: Provide a new name to the form. Make sure you start the widget name with LBL and end with Table Name.

  Naming convention: LBL Form < Table Name >

  Widget ID : lbl-form-<TableName>

 

Create ng-template:

Step 4: Scroll down to the bottom of the Widget to select the tab Angular ng-templates and select New.

  Step 4.1: In a different browser tab Navigate to Service Portal > Widgets and Search for lbl-sp-model-xml

Copy the "HTML Template" of the widget which contains the HTML code of OTB sp_model.xml that is used by spModel for rendering the form fields.

Step 5: Paste the HTML Template code as a starting point for customizing the field layout. create a ng-template for a widget which can then be passed to the Extending spModel Directive.








Step 7:   Include the ng-template in the widget HTML template

Add following code in the beginning of HTML Template of the new Form-Widget created in Step 3

The Id of the script tag is same as the source name of templateUrl that is provided in the Extended spModel Directive

ServiceNow's spModel Directive is an angularjs Directive. Inorder to extend spModel we need to use the same principles we use to extend an angularjs directive.

by following step 7 we can extend the spModel Directive.

**************************************************************************************************

// argument we pass here to the function is spModelDirective is an array,

// angularJs library interprets spModelDirective as below

//           angularJs finds all the directives that has a name spModel and creates and array

//           and passes the array to the function as an argument

**************************************************************************************************

function(spModelDirective){

                  // 3 arguments are passed to angular.extend

                  // argument 1 : {}   and empty object

                  // argument 2:   spModelDirective[0] - since we only have one spModel directive we use first element in the array.

                  // argument 3: {templateUrl : 'lbl_custom_template.xml'}   - the argument that we want to pass to replace the existing attributes value

              return angular.extend(   {} , spModelDirective[0]   ,     {templateUrl : 'lbl_custom_template.xml'}   ) ;

}

breaking down above code:

spModelDirective   >> Servicenow directive we want to extend ( spModel +

Directive )

angular.extend           >> angular.extend is an angularjs function copies 2

objects

spModelDirective[0]   >> argument to angular.extend function. In angularjs

the way to refer to a directive is by joining 2 words spModel and Directive

templateUrl : 'lbl_custom_template.xml'   >> new attribute that we update

in the spModel directive

The ng-include is the same name as the ng-template that is created for the widget.

Step 8: Associate the Extended Angular Directive to the Cloned Form-Widget created in Step 3

Step 8.1: Scroll to the bottom of the page and select tab - Angular Providers

Step 8.2: Select Edit and choose the Extended Angular Directive

( lblspModelCustomTemplate)

  This will provide the ability for the cloned form-widget to use the extended angular directive.

Step 9: Replace the spModel Directive tag to use the new Extended lblspModelCustomTemplate Directive.

Step 9.1: Comment the sp-model tag

Step 9.2: Add new tag lblsp-model-custom-template

              <lblsp-model-custom-template form_model="data.f" mandatory="mandatory">

</lblsp-model-custom-template>



Comments
sajan0192
Giga Expert

Hi Praveen,

I am working on Kingston env. I am trying to inspect the elements on page But I am getting below code not exactly which you have added in discussion.

find_real_file.png

<script id="sp_model.xml" type="text/ng-template"><div><sp-variable-layout></sp-variable-layout><div ng-init="execItemScripts()"></div></div></script>

Please let me know if I am doing anything wrong.

Regards,
Sajan

 

 

jayr
Tera Contributor

I've got the same issue as sajan

jayr
Tera Contributor

I have the same issue!  I need a copy of the 'sp-model.xml' file from Jakarta - only reason I need a custom template is because URLs aren't rendered correctly (they aren't clickable at this point).  Has anyone been able to get this working?

jayr
Tera Contributor

Hey @dylan, thanks for the info.  Really helpful! I need to create a custom template so that fields of type URL get rendered correctly on the portal (i.e. clickable rather than just a String).  Have you managed to get a copy of the 'sp-model.xml' template in Jakarta?

dale_
Tera Guru

Hey guys,

I got this working in London. Here's what I did:

  1. Open your clone of the Form widget and find the sp-model tag in the Body HTML Template.
  2. Add a parameter within this tag, such as: template-url="custom-sp-model.xml"
    <sp-model form_model="data.f" mandatory="mandatory" template-url="custom-sp-model.xml"></sp-model>​
  3. Create a new Angular ng-template.
    ID: custom-sp-model.xml
    Template: 
    <div><sp-variable-layout></sp-variable-layout><div ng-init="execItemScripts()"></div></div>​
  4. On your portal page, within the same block where you found sp_model.xml, you'll see a bunch of other xml files contained inside script tags. Find sp-variable-layout.xml
  5. Copy the contents of sp-variable-layout.xml, as you did with sp_model.xml
  6. Return to your Angular ng-template and completely replace:
    <sp-variable-layout></sp-variable-layout>​
    with the data you copied from sp-variable-layout.xml.
  7. Customise new Angular ng-template "custom-sp-model.xml" to your liking.

That's it. The original sp-variable-layout.xml script tag continues to be included in your page, unused. It's not the cleanest, but it does work. 🙂

Monika2
Giga Guru

Hi Dale,

I am on Kingston and I am also getting the single line HTML code while inspecting 

find_real_file.png

I tried all the steps mentioned in your post but still it is not working at my end. Has anyone facing the same issue and able to get this working?

 

Regards,

Monika

dale_
Tera Guru

Hi Monika,

I've captured the steps in my personal dev instance and attached the update set.

Please be aware that I've edited the OOB page "Form" to replace the OOB Form widget with the custom widget.

I recommend importing this into a pdi and reviewing the changes there.

You can verify the changes by navigating to the below URL (assuming OOB personal dev instance):

https://<instance name>.service-now.com/sp?id=form&table=sc_req_item&sys_id=aeed229047801200e0ef563dbb9a71c2&view=sp

find_real_file.png 

 

Cheers 🙂

Dale

Jobi
Giga Contributor

Hi Dale,

 

For one of requirement, i need to add a button and a click event to it via template. I tried to develop it using the script and the update set u have shared. But im facing one challenge.

I had to add one button with a ng-lick event to it.  But the button click event is not working. Not sure if this is the angular scope issue.

Could you please have a look once.

Attached the script and template modified by me.

Jobi
Giga Contributor

Hi Praveen,

 

Came across this post for one of my Service portal requirement. I have created a custom template and followed the same steps as mentioned by You. I could successfully implement a custom template. However, as part of requirement i had to add a button in the template. ANd to that button i gave an ngClick event in the template and the declared the function in Client controller in Widget. But the ngclick seems to be not triggered. Could you help me here. Attached the snaps for reference. Thanks in Advance.

Request your support.

 

Thank You.

praveenmuramall
Kilo Contributor

Can you try adding your function to $scope 

ex. 

$scope.clearCart = function() { console.log("Find me in Browser Console");} 

and

ng-click="clearCart()"

 

praveenmuramal1
Kilo Expert

Can you try adding your function to $scope 

ex. 

$scope.clearCart = function() { console.log("Find me in Browser Console");} 

and

ng-click="clearCart()"

Jobi
Giga Contributor

Praveen, thanks for the response . I did tried this but was not successful. ngclick event is not getting triggered due to two different angular scope is the issue what i understood.

Could you please help me with some ideas. Let me know if you want me to attach the updateset.

Thank you.

 

praveenmuramal1
Kilo Expert
Yes please attach the update set
praveenmuramall
Kilo Contributor

Try this

HTML Code that goes in the template
 
<button class="testbutton">Test Button</button>
 
 
and Add below code in the link function:
 
function(scope, elem){

setTimeout(function () {
var testbutton = elem.find(".testbutton"); // testbutton is the class name of the button
 
testbutton.on('click', function(event) {
alert("TestButton");
$(this).animate({width: "150px"});
});
 
}, 0);
 
}
praveenmuramall
Kilo Contributor
Try this

HTML Code that goes in the template
 
<buttonclass="testbutton">Test Button</button>
Add below code in the link function:
 
function(scope, elem){

setTimeout(function () {
var testbutton = elem.find(".testbutton"); // testbutton is the class name of the button
 
testbutton.on('click', function(event) {
alert("TestButton");
$(this).animate({width: "150px"});
});
 
}, 0);
 
}
Jobi
Giga Contributor

Hi Praveen,

 

I have attached the updatetset with all the changes captured. I tried to implement the link function as well. But i was not able to trigger the ng-click some how. Please help.

Thank You.

Jobi
Giga Contributor

Hi Praveen,

I tried all the possible ways but not able to trigger ng-click event from button element created within the ng-template. Request some help please.

 

Thanks in Advance.

praveenmuramall
Kilo Contributor

http://recordit.co/5IUtbHvgZJ

Jobi
Giga Contributor

Thanks a lot for this recording. I was able to do it as you mentioned.

But one observation, if the button element is added at the penultimate line the alert popup. Since i need an iterative new button beside each fields i have added it within the fieldset tag element. Then the alert didn't trigger.

When this button is clicked, the goal is to capture the corresponding fields object and popup a dialog window with that field details.

Please find my template code and link code as below.

Custom_temple_form:

==========================================

<div><fieldset ng-repeat="container in containers" ng-show="paintForm(container)">
  <div ng-if="(container.caption || container.captionDisplay)">
    <legend class="h4">{{container.captionDisplay || container.caption}}</legend>
    <p ng-if="::formModel._fields[container.name].help_text" title="{{::formModel._fields[container.name].help_tag}}" class="help-block" ng-bind="::formModel._fields[container.name].help_text"></p>
    <span ng-if="::formModel._fields[container.name].instructions" ng-bind-html="::formModel._fields[container.name].instructions"></span></div>
  <div class="row"><div ng-repeat="column in container.columns" class="col-md-{{::12 / container.columns.length }}">
  <div ng-switch="::f.type" ng-repeat="f in ::column.fields" id="{{::getVarID(f)}}" ng-class="::{'form-inline': isInlineForm === true }">
    <div ng-switch-when="label" ng-if="formModel._fields[f.name]" ng-show="formModel._fields[f.name].isVisible()">
      <label ng-bind="f.label"></label>
      <p ng-if="formModel._fields[f.name].help_text" title="{{formModel._fields[f.name].help_tag}}" class="help-block" ng-bind="formModel._fields[f.name].help_text"></p>
      <span ng-if="formModel._fields[f.name].instructions" ng-bind-html="::trustedHTML(formModel._fields[f.name].instructions)"></span>
  <hr class="sp_label_hr"/>
  </div>   
  <sp-form-field ng-switch-when="field" ng-if="formModel._fields[f.name]" form-model="formModel" field="formModel._fields[f.name]" glide-form="getGlideForm()" ng-show="formModel._fields[f.name].isVisible()" default-value-setter="setDefaultValue(fieldName,fieldInternalValue,fieldDisplayValue)"></sp-form-field>
  <sp-variable-layout ng-switch-when="container" ng-init="containers=[f]"></sp-variable-layout>
  <sp-checkbox-group ng-switch-when="checkbox_container" name="f.name" form-model="formModel" containers="f.containers" glide-form="getGlideForm()" class="checkbox-container"></sp-checkbox-group>
  <sp-widget ng-switch-when="formatter" ng-if="formModel._formatters[f.id].widgetInstance" widget="formModel._formatters[f.id].widgetInstance" page="{g_form: getGlideForm()}"></sp-widget>
  <hr ng-switch-when="break" ng-show="formModel._fields[f.name].isVisible()"/>
    <button class="buttonBeforediv">${Edit}</button>
    </div></div></div></fieldset>
<div ng-attr-id="{{::formModel.table}}.do" ng-init="execItemScripts()"></div>
<button class="testButtonafterDiv">${Edit}</button></div>

====================================

Link function:

====================================

function(scope, elem){
    
    setTimeout(function () {
            
            var testbutton = elem.find(".buttonBeforediv");
            testbutton.on('click', function(event) {
                alert("TestButton buttonBeforediv");
            $(this).animate({width: "400px"}); });
            
            
        }, 0);
        setTimeout(function () {
            
            var testbutton = elem.find(".testButtonafterDiv");
            testbutton.on('click', function(event) {
                alert("TestButton testButtonafterDiv");
            $(this).animate({width: "400px"}); });
            
            
        }, 0);
    
    scope.setFocusToAttachment = function () {
        setTimeout(function () {
            var inboxArray = elem.find("a.view-attachment");
            inboxArray.focus();
        }, 100);
    }
}

 

 

ryanlitwiller
Giga Guru

Is this still possible in London??

In particular is it still possible to extend an angular directive via a new Angular Provider to point to a new template? I noticed some workarounds above but it seemed to only work to set template url when calling the directive in the HTML template ie:

<sp-model form_model="data.f" mandatory="mandatory" template-url="custom-sp-model.xml"></sp-model>

I'd like to make some modifications to sp-form-field referenced in sp-variable-layout referenced in sp-model. Can get the custom-sp-model.xml template to render using above code but extending the directive has not been successful.

Update:

Just fired up a Jakarta PDI and still not able to get this to work as described here. This is a snapshot of my widget with the angular provider and template.

find_real_file.png

When I try to extend angular from the console it isn't able to find spModelDirective:

find_real_file.png

praveenmuramall
Kilo Contributor

@ryanlitwiller  did you try "link" functionality ? that might solve your needs. check the below articles

Akash Kumar7
Tera Expert

HI , i am have same issue in paris , please let me know if anyone has solved this issue . 
thanks in advance 

 

Brian104
Tera Expert

I did something a bit different that might not be the best practice (San Diego), but it works.  In short, just copy the sp_form_field.xml script block and add it as an Angular ng-template to a cloned form widget and modify as needed:

 https://community.servicenow.com/community?id=community_article&sys_id=37fe9a641b9dd15056b699b8bd4b...

Version history
Last update:
‎08-01-2017 11:05 AM
Updated by: