Brian104
Tera Expert

Prerequisites

  • Scoped Application
  • Custom Form Widget (i.e. cloned Form Widget)

Introduction

So you want to use all the Out of the Box (OOTB) functionality that

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

gives you when displaying a form, but want to add some extra HTML to one of the the fields that is generated through the sp_form_field.xml template?    Well look no further as I will show you how to add a "Driver tip message" to the "Driver" column of my clone Car Drivers widget:

find_real_file.png

Imagine that you want to add a bullet list of tips under the driver reference field.   To accomplish this, you need to override the sp_form_field.xml template.  To do this, you'll need to:

  1. Copy the entire script id="sp_form_field.xml" type="text/ng-template"> element block from your form service portal page and add it as an Angular ng-templates for your form widget.

  2. Use an ng-if to determine if the HTML element being rendered is the one you want to act on and add your custom HTML

Step 1: Copy the sp_form_field.xml

This bit of code is roughly 200 lines of HTML, so I won't paste it here (but I've added it to the bottom of the article for an easy copy and paste).   However, to get this code block, you just need to view the page source and search for "sp_form_field.xml".   Once you have this, add a new Angular ng-template to your custom form widget call "sp_form_field.xml" (this will override the default one) and paste the code you just copied as the template.    

find_real_file.png

Step 2: Use ng-if to add your HTML

So I want to add a bulleted list under my Driver reference field?   To do this, go to the bottom of the sp_form_field script you added and add the following right before the closing </div>:

   <div ng-show="field.messages" id="sp_formfield_{{::field.name}}_fieldmsgs_container">
      <div ng-repeat="message in field.messages | unique: 'message'" class="wrapper-xs r m-t-xs"
        ng-class="{'bg-danger': message.type == 'error', 'bg-warning': message.type == 'warning', 'bg-info': message.type == 'info'}">
        {{message.message}}</div>
    </div>
<!-- MY SPECIAL HTML CODE -->
    <div ng-if="::field.name == 'driver'">
    	<h2>Tips:</h2>
    	<ul>
      	<li>Clean your tires</li>
        <li>Fill your gas tank</li>
        <li>Wash your car</li>
      </ul>
   </div>
   <div ng-if="::field.name == 'license'">...nothing to see here...</div>
<!-- END MY SPECAL HTML CODE -->
  </div>

and wala! You have custom HTML per whatever field you need them on

find_real_file.png

Reference Code: sp_form_field.xml

<!-- Add < to the script> tag below  The attributes get stripped when posting the article -->
script id="sp_form_field.xml" type="text/ng-template">
  <div class="form-group" ng-class="{'form-group-has-focus': hasValueOrFocus()}">
    <div ng-if="::c.showLabel(field)">
      <label class="field-label"
        for="sp_formfield_{{::(field.type=='html' || field.type=='translated_html') ? (field.sys_id || field.name) : field.name }}"
        ng-attr-title="{{::accessible ? undefined : field.hint}}"
        ng-attr-aria-label="{{::accessible &amp;&amp; field.hint ? undefined : getFieldAriaLabel(field)}}">
        <span class="field-decorations">
          <span ng-if="field.mandatory" class="fa fa-asterisk mandatory"
            ng-class="{'mandatory-filled': field.mandatory_filled()}" style="padding-right: .25em"
            aria-label="{{field.mandatory_filled()? 'Required Filled ' : 'Required '}}"
            ng-attr-aria-hidden="{{getFieldAriaHidden(field)}}" role="img"></span><span
            ng-repeat="decoration in field.decorations" class="decoration {{decoration.icon}}"
            title="{{decoration.text}}"></span></span><span style="padding-right: .25em"
          ng-attr-title="{{::accessible ? undefined : field.hint}}" data-placement="right"
          data-toggle="tooltip">{{field.label}}</span>
        <sp-help-tag field="::field"
          ng-if="::(accessible &amp;&amp; field.hint &amp;&amp; !field.help_text &amp;&amp; !field.instructions)">
        </sp-help-tag>
      </label>
      <span>
        <sp-help-tag field="::field" ng-if="::(field.help_text || field.instructions)"></sp-help-tag>
      </span></div>
    <span class="type-{{::field.type}} field-actual question-width" ng-switch="::field.type"
      ng-class="{'state-mandatory': field.mandatory, 'state-readonly': field.read_only, 'state-hidden': field.hidden, 'has-error': field.isInvalid}"><span
        ng-switch-when="boolean"><label title="{{::field.hint}}" data-placement="right" data-toggle="tooltip">
          <input type="checkbox" name="{{::field.name}}" ng-model="fieldValue" ng-true-value="'true'"
            ng-false-value="'false'" ng-model-options="{getterSetter: true}" ng-disabled="field.isReadonly()"
            id="sp_formfield_{{::field.name}}" aria-required="{{field.isMandatory()}}"></input><span
            class="boolean-control"><span
              ng-if="!formModel._fields[field._parent].render_label &amp;&amp; formModel._fields[field._parent].mandatory &amp;&amp; formModel._fields[field._parent].isVisible()"
              class="fa fa-asterisk mandatory"
              ng-class="{'mandatory-filled': formModel._fields[field._parent].mandatory_filled()}" title="Required"
              style="padding-right: .25rem"
              aria-label="{{formModel._fields[field._parent].mandatory_filled()? 'Required Filled ' : 'Required '}}"
              ng-attr-aria-hidden="{{getFieldAriaHidden(field)}}" role="img"></span>{{field.label}}<span
              ng-if="::enhancePriceLabels(field)" class="inline">{{getCheckBoxPrice(field)}}</span></span></label><span
          class="sr-only">{{::field.hint}}</span></span>
      <sp-assessment-details ng-if="::field.details" html-details="field.details" label="field.label"
        padding-required="true" field-id="field.sys_id" clamp-line="6"
        ng-class="{'text-center': formModel.is_kiosk_survey}"></sp-assessment-details>
      <sp-choice-list ng-switch-when="choice" field="field" glide-form="getGlideForm()"
        default-value-setter="setDefaultValue(fieldName,fieldInternalValue,fieldDisplayValue)"
        sn-options="{placeholder: field.placeholder}"></sp-choice-list>
      <span ng-switch-when="boolean_confirm"><span class="fa fa-asterisk mandatory"
          ng-class="{'mandatory-filled': field.mandatory_filled()}" title="Required"
          style="padding-right: .25em"></span>
        <label title="{{::field.hint}}" data-placement="right" data-toggle="tooltip"
          for="sp_formfield_{{::field.name}}"><input type="checkbox" name="{{::field.name}}" ng-model="fieldValue"
            ng-true-value="'true'" ng-false-value="'false'" ng-model-options="{getterSetter: true}"
            ng-disabled="field.isReadonly()" id="sp_formfield_{{::field.name}}"></input><span
            class="boolean-control"><span class="sr-only" ng-show="!field.mandatory_filled()">Required -</span><span
              class="sr-only" ng-show="field.mandatory_filled()">Required Filled
              -</span>{{field.label}}</span></label><span class="sr-only">{{::field.hint}}</span><span
          ng-if="::field._pricing">{{getCheckBoxPrice(field)}}</span></span>
      <sp-color-picker ng-switch-when="color" field="field" glide-form="getGlideForm()" sn-change="fieldValue(newValue)"
        sn-disabled="field.isReadonly()" ng-if="c.depsLoaded"></sp-color-picker>
      <sp-css-editor ng-switch-when="css" class="form-control" field="field" id="sp_formfield_{{::field.name}}"
        data-length="4000" sn-disabled="field.isReadonly()" sn-change="stagedValueChange()"
        sn-blur="fieldValue(field.stagedValue)" glide-form="getGlideForm()" ng-if="c.depsLoaded"></sp-css-editor><span
        ng-switch-when="currency2|document_id|domain_id|schedule_date_time|integer_date" class="padder"
        ng-switch-when-separator="|">{{field.displayValue}}</span>
      <sp-duration-element ng-switch-when="glide_duration" field="field" ng-model="fieldValue"
        ng-model-options="{getterSetter: true}"></sp-duration-element>
      <sp-url-element ng-switch-when="url" name="{{::field.name}}" field="field"></sp-url-element>
      <sn-field-reference ng-switch-when="field_name" field="field" id="sp_formfield_{{::field.name}}"
        glide-form="getGlideForm()" sn-change="fieldValue(newValue)" sn-disabled="field.isReadonly()">
      </sn-field-reference>
      <sn-field-list-element ng-switch-when="field_list" field="field" glide-form="getGlideForm()"
        sn-disabled="field.isReadonly()" sn-change="fieldValue(newValue, displayValue)"></sn-field-list-element>
      <sp-date-picker sn-change="fieldValue(newValue, displayValue)" ng-switch-when="glide_date" field="field"
        sn-disabled="field.isReadonly()" ng-model="field.stagedValue" ng-model-options="{getterSetter: true}"
        ng-if="c.depsLoaded"></sp-date-picker>
      <sp-date-picker sn-change="fieldValue(newValue, displayValue)" ng-switch-when="glide_date_time" field="field"
        sn-disabled="field.isReadonly()" ng-model="field.stagedValue" ng-model-options="{getterSetter: true}"
        sn-include-time="true" ng-if="c.depsLoaded"></sp-date-picker>
      <sp-reference-element ng-switch-when="glide_list" field="field" sn-select-width="100%"
        ref-table="::formModel.table" ref-id="formModel.sys_id" glide-form="getGlideForm()"
        record-values="getEncodedRecordValues()" sn-options="{multiple: true, placeholder: field.placeholder}"
        sn-disabled="field.isReadonly()"></sp-reference-element>
      <sp-glyph-picker ng-switch-when="glyphicon" field="field" sn-disabled="field.isReadonly()"></sp-glyph-picker>
      <sp-html-editor ng-switch-when="xml" field="field" id="sp_formfield_{{::field.name}}" class="form-control"
        data-length="4000" sn-disabled="field.isReadonly()" sn-change="stagedValueChange()"
        sn-blur="fieldValue(field.stagedValue)" glide-form="getGlideForm()" ng-if="c.depsLoaded"></sp-html-editor>
      <sp-html-editor ng-switch-when="html_template" class="form-control" field="field"
        id="sp_formfield_{{::field.name}}" data-length="4000" sn-disabled="field.isReadonly()"
        sn-change="stagedValueChange()" sn-blur="fieldValue(field.stagedValue)" glide-form="getGlideForm()"
        ng-if="c.depsLoaded"></sp-html-editor>
      <sp-script-editor ng-switch-when="json" class="form-control" field="field" id="sp_formfield_{{::field.name}}"
        data-length="4000" sn-disabled="field.isReadonly()" sn-change="stagedValueChange()"
        sn-blur="fieldValue(field.stagedValue)" glide-form="getGlideForm()" ng-if="c.depsLoaded"></sp-script-editor>
      <sp-mask-element ng-switch-when="masked" field="field" placeholder="field.placeholder"
        sn-disabled="field.isReadonly()" sn-change="stagedValueChange()" glide-form="getGlideForm()"></sp-mask-element>
      <sp-radio-option ng-switch-when="multiple_choice" glide-form="getGlideForm()" field="field"
        cat-item-sys-id="formModel.sys_id"></sp-radio-option>
      <sp-radio-option ng-switch-when="numericscale" glide-form="getGlideForm()" field="field"
        cat-item-sys-id="formModel.sys_id"></sp-radio-option>
      <sp-textarea ng-switch-when="multi_two_lines" field="field" name="{{::field.name}}"
        id="sp_formfield_{{::field.name}}" autocomplete="{{getAutocompleteValue()}}" class="form-control"
        ng-model="field.stagedValue" ng-change="stagedValueChange()" data-type="{{::field.type}}"
        ng-readonly="field.isReadonly()" aria-invalid="{{field.isInvalid || false}}" glide-form="getGlideForm()">
      </sp-textarea>
      <sp-textarea ng-switch-when="multi_small" field="field" name="{{::field.name}}" id="sp_formfield_{{::field.name}}"
        autocomplete="{{getAutocompleteValue()}}" class="form-control multi-small" ng-model="field.stagedValue"
        ng-change="stagedValueChange()" data-type="{{::field.type}}" ng-readonly="field.isReadonly()"
        aria-invalid="{{field.isInvalid || false}}" glide-form="getGlideForm()"></sp-textarea>
      <sp-currency-element ng-switch-when="price" field="field" sn-disabled="field.isReadonly()"
        sn-change="stagedValueChange()" sn-blur="fieldValue(field.stagedValue)" glide-form="getGlideForm()">
      </sp-currency-element>
      <sp-currency-element ng-switch-when="currency" field="field" sn-disabled="field.isReadonly()"
        sn-change="stagedValueChange()" sn-blur="fieldValue(field.stagedValue)" glide-form="getGlideForm()">
      </sp-currency-element><input ng-switch-when="password" type="password" name="{{::field.name}}"
        id="sp_formfield_{{::field.name}}" autocomplete="off" class="form-control" ng-model="field.stagedValue"
        ng-change="stagedValueChange()" ng-disabled="field.isReadonly()"></input><input ng-switch-when="password2"
        type="password" name="{{::field.name}}" id="sp_formfield_{{::field.name}}" autocomplete="off"
        class="form-control" ng-model="field.stagedValue" ng-change="stagedValueChange()"
        ng-disabled="field.isReadonly()"></input>
      <sp-code-mirror ng-switch-when="properties" class="form-control" mode="properties" field="field"
        id="sp_formfield_{{::field.name}}" data-length="4000" sn-disabled="field.isReadonly()"
        sn-change="stagedValueChange()" sn-blur="fieldValue(field.stagedValue)" glide-form="getGlideForm()"
        ng-if="c.depsLoaded"></sp-code-mirror>
      <sp-reference-field ng-switch-when="reference" id="sp_formfield_reference_{{::field.name}}" tabindex="-1"
        ng-class="{'field-has-reference': field.value != '', 'field-empty-reference': field.value == ''}">
          <div>Hi</div>

      </sp-reference-field>
      <sp-script-editor ng-switch-when="script_server" class="form-control" field="field"
        id="sp_formfield_{{::field.name}}" data-length="4000" sn-disabled="field.isReadonly()"
        sn-change="stagedValueChange()" sn-blur="fieldValue(field.stagedValue)" glide-form="getGlideForm()"
        ng-if="c.depsLoaded"></sp-script-editor>
      <sp-script-editor ng-switch-when="script" class="form-control" field="field" id="sp_formfield_{{::field.name}}"
        data-length="4000" sn-disabled="field.isReadonly()" sn-change="stagedValueChange()"
        sn-blur="fieldValue(field.stagedValue)" glide-form="getGlideForm()" ng-if="c.depsLoaded"></sp-script-editor>
      <sn-table-reference ng-switch-when="table_name" field="field" sn-change="fieldValue(newValue)"
        sn-disabled="field.isReadonly()"></sn-table-reference>
      <sp-textarea ng-switch-when="textarea" field="field" name="{{::field.name}}" id="sp_formfield_{{::field.name}}"
        class="form-control" ng-model="field.stagedValue" ng-change="stagedValueChange()" data-type="{{::field.type}}"
        ng-readonly="field.isReadonly()" autocomplete="{{getAutocompleteValue()}}"
        aria-invalid="{{field.isInvalid || false}}" glide-form="getGlideForm()"></sp-textarea>
      <sp-tinymce-editor ng-switch-when="html" id="sp_formfield_{{::field.name}}"
        ng-attr-tabindex="{{field.isReadonly() ? undefined : '-1'}}" name="{{::field.name}}"
        text-id="sp_formfield_{{::field.sys_id || field.name}}" ng-model="field.stagedValue"
        ng-model-options="{getterSetter: true}" sn-blur="fieldValue(field.stagedValue)" field="field"
        glide-form="getGlideForm()" ng-change="stagedValueChange()" attachment-guid="c.getAttachmentGuid()">
      </sp-tinymce-editor>
      <sp-tinymce-editor ng-switch-when="translated_html" id="sp_formfield_{{::field.name}}"
        ng-attr-tabindex="{{field.isReadonly() ? undefined : '-1'}}" name="{{::field.name}}"
        text-id="sp_formfield_{{::field.sys_id || field.name}}" ng-model="field.stagedValue"
        sn-blur="fieldValue(field.stagedValue)" ng-model-options="{getterSetter: true}" field="field"
        glide-form="getGlideForm()" ng-change="stagedValueChange()" attachment-guid="c.getAttachmentGuid()">
      </sp-tinymce-editor><span ng-switch-when="user_image"><img ng-if="field.displayValue"
          ng-src="{{field.displayValue}}" style="max-height: 128px; max-width: 128px;"></img>
        <sn-image-uploader ng-if="!field.isReadonly()" table-name="{{formModel.table}}"
          sys-id="{{formModel._attachmentGUID || formModel.sys_id}}" field-name="{{::field.name}}"
          read-only="field.isReadonly()" on-upload="onImageUpload(thumbnail, sys_id)" on-delete="onImageDelete()"
          upload-message="{{field.isMandatory() &amp;&amp; !field.mandatory_filled() ? 'Required -' : ''}} Upload an image"
          src="field.displayValue"></sn-image-uploader>
      </span>
      <sp-widget ng-switch-when="widget" widget="field.widget"
        page="{g_form: getGlideForm(), field: field, fieldValue: fieldValue}"></sp-widget>
      <sp-widget ng-switch-when="widget_value" widget="field.widget"
        page="{g_form: getGlideForm(), field: field, fieldValue: fieldValue}"></sp-widget><span
        ng-switch-when="integer"><input name="{{::field.name}}" id="sp_formfield_{{::field.name}}" class="form-control"
          ng-readonly="field.isReadonly()" ng-model="field.stagedValue" ng-change="stagedValueChange()"
          ng-blur="formatNumber()" autocomplete="{{getAutocompleteValue()}}"
          ng-attr-placeholder="{{field.placeholder}}"></input></span><span ng-switch-when="decimal|float"
        ng-switch-when-separator="|"><input name="{{::field.name}}" id="sp_formfield_{{::field.name}}"
          class="form-control" ng-readonly="field.isReadonly()" ng-model="field.stagedValue"
          ng-change="stagedValueChange()" ng-blur="formatNumber()"
          autocomplete="{{getAutocompleteValue()}}"></input></span>
      <sp-catalog-variable ng-if="field._cat_variable" ng-switch-when="sc_multi_row"></sp-catalog-variable>
      <sp-email-element ng-switch-when="email" field="field" glide-form="getGlideForm()"></sp-email-element>
      <sp-rich-text-label ng-switch-when="rich_text_label" field="field"></sp-rich-text-label>
      <sp-variable-attachment ng-switch-when="sc_attachment" field="field"
        attachment-guid="formModel._attachmentGUID || formModel.sys_id" g-form="getGlideForm()">
      </sp-variable-attachment><span ng-switch-default="true"><input sp-ignore-composition=""
          ng-if="::!field.max_length || 256 &gt; field.max_length || field._force_single_line" name="{{::field.name}}"
          id="sp_formfield_{{::field.name}}" class="form-control" maxlength="{{::field.max_length}}"
          data-type="{{::field.type}}" ng-model="field.stagedValue" ng-model-options="{allowInvalid: true}"
          ng-change="stagedValueChange()" autocomplete="{{getAutocompleteValue()}}" ng-readonly="field.isReadonly()"
          ng-attr-placeholder="{{field.placeholder}}" type="text" aria-required="{{field.isMandatory()}}"
          aria-invalid="{{field.isInvalid || false}}"></input>
        <sp-textarea ng-if="::field.max_length &gt;= 256 &amp;&amp; !field._force_single_line" field="field"
          name="{{::field.name}}" id="sp_formfield_{{::field.name}}" class="form-control" data-type="{{::field.type}}"
          ng-model="field.stagedValue" ng-model-options="{allowInvalid: true}" ng-change="stagedValueChange()"
          autocomplete="{{getAutocompleteValue()}}" ng-readonly="field.isReadonly()"
          aria-invalid="{{field.isInvalid || false}}" glide-form="getGlideForm()"></sp-textarea>
      </span>
    </span>
    <div ng-show="field.messages" id="sp_formfield_{{::field.name}}_fieldmsgs_container">
      <div ng-repeat="message in field.messages | unique: 'message'" class="wrapper-xs r m-t-xs"
        ng-class="{'bg-danger': message.type == 'error', 'bg-warning': message.type == 'warning', 'bg-info': message.type == 'info'}">
        {{message.message}}</div>
    </div>
  </div>
</script>
Comments
Tjardo Knopper
Tera Contributor

Hi Brian,

 

nice Article! But how do you call in your html of your widget? It doesnt seem to work by me.

 

Thanks!

Version history
Last update:
‎08-10-2022 01:04 PM
Updated by: