Portal widget: Data Table from URL Definition vs "view" parameter

adrianps
Kilo Expert

On a portal page, I want to list "Requests created for or created by me", and I've almost got what I want with OOTB widgets.

I use a Simple List widget configured as follows (only showing the significant settings):

Tablesc_request
Filter

active=true^requested_forDYNAMIC90d1921e5f510100a9ad2572f2b477fe^

ORopened_byDYNAMIC90d1921e5f510100a9ad2572f2b477fe^EQ

List pagelist
Link to this pagerequests
ViewCustom Requests

The "Custom Requests" view is a sc_request view that displays only the Number and Description columns.

My problem is that when I click the "View All" link, the columns being displayed are: Number, Task type, Short description and Priority.

So I started digging.

I cloned the "list" page and added some debug statements to the Server script and worked out that the view parameter is not being passed to the page.  The very top dozen lines of the script are something like this:

(function() {
	var zlog = new ZET_ScriptLog('WDG ZET Data Table from URL Definition - server');
	deleteOptions(['table','field_list','filter','order_by', 'order_direction','order','maximum_entries']);
	if (input) {
		data.table = input.table;
		data.view = input.view;
	} else {
		zlog.Log('input is null');
		zlog.Log('$sp.getParameter(table) = ' + $sp.getParameter('table'));
		zlog.Log('$sp.getParameter(t) = ' + $sp.getParameter('t'));
		data.table = $sp.getParameter('table') || $sp.getParameter('t');
		data.view = $sp.getParameter('view');
	}
	
	zlog.Log('data.table = ' + data.table);
	zlog.Log('data.view = ' + data.view); <-- data.view is not being initialised

So I went back to the link that is accessed by the Simple List widget's View All link, and it also does not pass in the View parameter.  I cloned the Simple List widget and added a view parameter to the Body HTML template (towards the bottom of the code.  Before:

  <div class="panel-footer" ng-if="!c.options.hide_footer && c.options.maximum_entries && c.data.count > c.options.maximum_entries">
    <div class="h4 number-shown-label">{{c.getMaxShownLabel(c.options.maximum_entries, c.data.count)}}</div>
    <a class="pull-right" ng-href="?id={{c.seeAllPage}}&table={{c.options.table}}&filter={{c.options.filter}}{{c.targetPageID}}">${View all}</a>
  </div>
</div>

After (note the <a> element):

  <div class="panel-footer" ng-if="!c.options.hide_footer && c.options.maximum_entries && c.data.count > c.options.maximum_entries">
    <div class="h4 number-shown-label">{{c.getMaxShownLabel(c.options.maximum_entries, c.data.count)}}</div>
    <a class="pull-right" ng-href="?id={{c.seeAllPage}}&table={{c.options.table}}&view={{c.options.view}}&filter={{c.options.filter}}{{c.targetPageID}}">${View all}</a>
  </div>
</div>

With this change, my custom Simple List widget now indicates that the sys_id of the view is being passed in.  If only that were the end of it.  The columns being displayed changed to: Number, Requested for, Opened by, Request state, Due date.

In the "list" page, the view parameter eventually gets consumed by a call to $sp.getListColumns( data.table, data.view ).

After some experimentation, I worked out that if data.view is the name of the table view to be applied (i.e. "z_sc_requests" rather than the sys_id), then the correct columns get displayed when clicking on "View All".

(and to anyone that has made it this far, you have my most profound gratitude)

So my question(s) is(are): Should I have had to go through all the edits made above in order to control columns displayed on a list page?  If not, where am I going wrong?  Incidentally, am I right in concluding that $sp.getListColumns is expecting strings rather than sys_ids as parameters (and if that's the case should view be getting passed in as a name instead of a sys_id)?

1 ACCEPTED SOLUTION

adrianps
Kilo Expert

For the benefit of anyone to follow, here's my workaround / solution:

Clone the "Simple LIst" widget.

Replace the Body HTML template with:

<div class="panel panel-{{::c.options.color}} b" ng-if="c.data.isValid && (c.options.always_show || c.data.filterText || c.data.list.length)">
  <div class="panel-heading" ng-if="::!c.options.hide_header">
    <h2 class="h4 panel-title">
      <span ng-if="c.options.glyph">
        <fa name="{{::c.options.glyph}}" />
      </span>{{::c.options.title}}</h2>
    <!-- <i class="fa fa-filter" ng-click="c.toggleFilter()" ng-class="{'disabled-filter': !c.showFilter}"></i> -->
    <div ng-show="c.showFilter">
      <input aria-label="${Filter}" ng-model="c.data.filterText" ng-model-options="{debounce: 300}" sn-focus="c.showFilter" placeholder="{{::data.filterMsg}}" ng-change="c.update()" class="form-control input-sm filter-box">
    </div>
  </div>
  <ul class="list-group hide-x-overflow" ng-style="::{maxHeight: c.getMaxHeight()}" style="overflow-y: auto;">
    <li ng-if="c.data.list.length > 0" ng-repeat="item in c.data.list track by item.sys_id" class="list-group-item">
      <a class="focus-inline-block" ng-click="c.onClick($event, item, item.url, {})" href="javascript:void(0)" >
        <span ng-repeat="action in c.data.actions" href="" ng-click="c.onClick($event, item, action.url, action)" ng-if="action.glyph"
              class="list-action l-h-40 pull-right">
          <fa name="{{action.glyph}}" ng-class="c.getActionColor(action)" />
        </span>
        <span ng-if="c.options.image_field" class="pull-left m-r"
              ng-class="{'avatar': c.options.rounded_images, 'thumb-sm': c.options.rounded_images}">
          <img ng-src="{{item.image_field}}" alt="..." class="img-sm" ng-class="{'img-circle': c.options.rounded_images}">
        </span>
        <div>
          <div ng-switch on="item.display_field.type" ng-class="{'l-h-40': !item.secondary_fields.length}">
            <span class="translated-html" ng-switch-when="translated_html" ng-bind-html="item.display_field.value"></span>
            <div ng-switch-default>{{item.display_field.display_value}}</div>
          </div>
          <small class="text-muted" ng-repeat="f in item.secondary_fields">
            <span ng-if="!$first"> • </span>
            <span ng-switch="f.type" title="{{::f.label}}">
              <span ng-switch-when="glide_date"><sn-time-ago timestamp="::f.value" /></span>
              <span ng-switch-when="glide_date_time"><sn-time-ago timestamp="::f.value" /></span>
              <span ng-switch-default="">{{f.display_value}}</span>
            </span>
          </small>
        </div>
      </a>
    </li>
     <div ng-if="!c.data.list.length" class="list-group-item">
     ${No records found}
    </div>
  </ul>
  <div class="panel-footer" ng-if="!c.options.hide_footer && c.options.maximum_entries && c.data.count > c.options.maximum_entries">
    <div class="h4 number-shown-label">{{c.getMaxShownLabel(c.options.maximum_entries, c.data.count)}}</div>
    <a class="pull-right" ng-href="?id={{c.seeAllPage}}&table={{c.options.table}}&view={{c.options.view}}&filter={{c.options.filter}}{{c.targetPageID}}">${View all}</a>
  </div>
</div>

The above includes the view parameter in the "View All" link.

Secondly, clone the "Data Table from URL Definition" widget.

Replace the server script with:

(function() {
	deleteOptions(['table','field_list','filter','order_by', 'order_direction','order','maximum_entries']);
	if (input) {
		data.table = input.table;
		data.view = input.view;
	} else {
		data.table = $sp.getParameter('table') || $sp.getParameter('t');
		data.view = $sp.getParameter('view');
		
		
		// Convert data.view to a view Id (data.view is probably a sys_id at this point.  $sp.getListColumns expects an Id)
		var grPage = new GlideRecord('sys_ui_view');
		var grPageOr = grPage.addQuery('sys_id',data.view);
		grPageOr.addOrCondition('name',data.view);
		grPage.query();
		if(grPage.next()){
			data.view = grPage.getValue('name');
		}
	}
	
	if (!data.table) {
		data.invalid_table = true;
		data.table_label = "";
		return;
	}

	var gr = new GlideRecordSecure(data.table);
	if (!gr.isValid()) {
		data.invalid_table = true;
		data.table_label = data.table;
		return;
	}

	// page is where the record URLs go, URL parameter wins
	data.page_id = $sp.getParameter("target_page_id");
	zlog.Log('data.page_id = ' + data.page_id);
	if (!data.page_id) {
		zlog.Log('data.page_id is null');
		var sp_page = $sp.getValue('sp_page');
		if (sp_page) {
			var pageGR = new GlideRecord('sp_page');
			pageGR.get(sp_page);
			data.page_id = pageGR.id.getDisplayValue();
		}
	}

	// widget parameters
	data.table_label = gr.getLabel();
	data.fields = $sp.getListColumns(data.table, data.view);
	copyParameters(data, ['p', 'o', 'd', 'filter']);
	copyParameters(data, ['relationship_id', 'apply_to', 'apply_to_sys_id']);
	data.filterACLs = true;
	data.show_new = true;
	data.show_keywords  = true;
	data.show_breadcrumbs = true;
	data.fromUrl = true;
	data.headerTitle = (options.use_instance_title == "true") ? options.title : gr.getPlural();
	data.enable_filter = input.enable_filter || options.enable_filter == true || options.enable_filter == "true";
	data.dataTableWidget = $sp.getWidget('widget-data-table', data);
	
	function copyParameters(to, names) {
		names.forEach(function(name) {
			data[name] = $sp.getParameter(name);
		})
	}

	// in case this widget is tied to the wrong instance type
	function deleteOptions(names) {
		names.forEach(function(name) {
			delete options[name];
		})
	}
})()

In this I've added some code (after line 😎 that reads the data.view parameter (which is a sys_id), and converts it into a string (the name of the view to be used by the table).

Next, clone the "list" page, and open it in page editor.  Change the page widget to the cloned "Data Table from URL Definition" record created above.

You now need to create a table view that contains the columns you want to display in the portal.  Make a note of the name of this view.

Open your portal page that is to host the cloned Simple List widget, and place your cloned Simple List widget to an appropriate place.  Edit the widget's options.

Set List Page to the cloned page created above.

Set View to the table view created above.

Assuming I haven't forgotten any steps, the View All link should now present the table view you have specified.

 

View solution in original post

5 REPLIES 5

Priyanka Mohapa
ServiceNow Employee
ServiceNow Employee

The data.view is set either by input or by the view in url params, so we have to pass the view in the url if that is not available in the input from any other widget else it will be undefined. 

getListColumns(String tableName, String view)

Parameter(s):
Name                  Type                    Description
tableName          String                Name of the table
view                   String                The view by which to filter the columns

 

 

adrianps
Kilo Expert

Hi Priyanka,

You said that data.view is set by input or by url params, and I saw references to both in the "list" page's Server script.

I configured my Simple List widget to use the list page, but what confused me is that the "View All" link on the Simple List widget does not include the view parameter.  This is regardless of whether I configure the View parameter in the Simple List widget's options.

So I'm at a loss to understand how the View parameter gets passed at all to the list page (that is, in the absence of any customisation).

(And now I've just noticed that my HTML snippets above have been altered from what I originally entered.  Editing now...it looks like the double curly braces are causing issues with the formatting of my post...I've replaced any double curly braces with single, so as a warning to anyone copy/pasting the snippets below, DON'T!!!)

HTML before:

  <div class="panel-footer" ng-if="!c.options.hide_footer && c.options.maximum_entries && c.data.count > c.options.maximum_entries">
    <div class="h4 number-shown-label">{c.getMaxShownLabel(c.options.maximum_entries, c.data.count)}</div>
    <a class="pull-right" ng-href="?id={c.seeAllPage}&table={c.options.table}&filter={c.options.filter}{c.targetPageID}">${View all}</a>
  </div>

 

HTML after:

  <div class="panel-footer" ng-if="!c.options.hide_footer && c.options.maximum_entries && c.data.count > c.options.maximum_entries">
    <div class="h4 number-shown-label">{c.getMaxShownLabel(c.options.maximum_entries, c.data.count)}</div>
    <a class="pull-right" ng-href="?id={c.seeAllPage}&table={c.options.table}&view={c.options.view}&filter={c.options.filter}{c.targetPageID}">${View all}</a>
  </div>

Notice that the 'after' snippet contains &view={c.options.view}.  I had to add this into a clone of the Simple List widget.

When I was debugging I noticed that the input variable was never populated (I don't know how this parameter gets populated).

In the end, I have a workaround available to me, but I feel as if it's not best practice, and I can't see how I can interpret the Simple List widget as anything but broken for sc_request records...but I'm happy to be shown the error of my ways.

HI,

I have one question: Your view always going to be fixed right? Also can you tell me which widget you see once you click on View all. It is Data Table widget with URL or INstance?


Thanks,
Ashutosh

I expect the view will always be fixed - I'm still early on in learning about portal development so I'm trying to avoid doing anything exotic.

When I click on View All, the "list" page is opened which contains the "Data Table from URL Definition" widget.  fwiw, the list page record is an sp_page record - it doesn't show up in the 'regular' list of available portal pages.