SharePoint 2013 Custom AJAX filters on List View Web Parts

Requirement:
Implement ‘exact match’ filter against the list view web part on text values and drop down values using AJAX for great user performance. An alternative to filtering in a fly out menu of the list view web part’s column values.ajaxfilter1
Filter by ‘Title’
ajaxfilter2

Filter by ‘Task Status’
ajaxfilter3

Technique:

I am essentially emulating the column based filters that are out-of-the-box functionality of the SharePoint 2013 List view web parts. I do this by tracking down how the filtering is executed in the SharePoint inplview.js code. Then wrap the filtering JavaScript logic through custom text boxes.

ajaxfilter4

You can see the URL query string parameters reflect the filtering
/SitePages/Tickets.aspx#InplviewHashdba27ef6-7bff-4c0f-8ec1-b3e3e18fb0b8=FilterField1=LinkTitle-FilterValue1=Printer broken

For large lists (i.e. > 5,000 items), keep in mind to index the columns you want to filter by. You can find the setting in the List Settings page and find a link ‘Indexed Columns’

<table style="" border="0">
    <tr>
        <td>Title:</td>
        <td><input type="text" id="title" onblur="TitleFilter(); return false;" /></td>
        <td>
            <button onclick="TitleFilter(); return false;">Filter by Title</button>
        </td>
    </tr>
    <tr>
        <td>
            Task Status:
        </td>
        <td>
            <input type="text" id="taskStatus" onblur="TaskStatusFilter(); return false;" />
        </td>
        <td>
            <button onclick="TaskStatusFilter(); return false;">Filter by Task Status</button>
        </td>
    </tr>
    <tr>
        <td></td>
        <td></td>
        <td>
            <button onclick="TitleAndTaskStatusFilter(); return false;">Filter by Title AND Task Status</button>
        </td>
    </tr>
</table>
 

<script language='javascript'>
       function MyRefreshPageToEx(lvTableID, url, bForceSubmit) {

        // Hardcode reference to list view <table> Id attribute
        var tblv = document.getElementById("{BE9A90D1-BF5D-401B-8A9B-C44CCCD14241}-{DBA27EF6-7BFF-4C0F-8EC1-B3E3E18FB0B8}");
        var clvp = CLVPFromCtx(tblv);

        if (clvp != null && clvp.ctx.IsClientRendering) {
            clvp.RefreshPaging(url);
            clvp.ctx.queryString = url;
            if ((typeof clvp.ctx.operationType == "undefined" || clvp.ctx.operationType == SPListOperationType.Default) && Boolean(clvp.ctx.ListData)) {
                var fromPage = clvp.ctx.ListData.FirstRow - 1;
                var toPage = Number(GetUrlKeyValue("PageFirstRow", false, url));

                if (!isNaN(fromPage) && !isNaN(toPage) && fromPage != toPage)
                    fromPage < toPage ? (clvp.ctx.operationType = SPListOperationType.PagingRight) : (clvp.ctx.operationType = SPListOperationType.PagingLeft);
            }
        }
        else {
            SubmitFormPost(url, bForceSubmit);
        }
    }

    function TitleFilter() {
        var title = $('#title').val()
        var url;
        if (title) {
            url = "?List={BE9A90D1-BF5D-401B-8A9B-C44CCCD14241}&View={DBA27EF6-7BFF-4C0F-8EC1-B3E3E18FB0B8}&FilterField1=LinkTitle&FilterValue1=" + title
        }
        inplview.MyRefreshPage = MyRefreshPageToEx;
        inplview.MyRefreshPage(null, url, null);
    }

    function TaskStatusFilter() {
        var taskStatus = $('#taskStatus').val()
        var url;
        if (taskStatus) {
            url = "?List={BE9A90D1-BF5D-401B-8A9B-C44CCCD14241}&View={1F1B1E55-992B-4A03-98B2-9E5D34916611}&FilterField1=TaskStatus&FilterValue1=" + taskStatus
        }
        inplview.MyRefreshPage = MyRefreshPageToEx;
        inplview.MyRefreshPage(null, url, null);
    }

    function TitleAndTaskStatusFilter() {
        var title = $('#title').val()
        var taskStatus = $('#taskStatus').val()

        // HARD Code: query string to reference List GUID and View ID
        var url = "?List={BE9A90D1-BF5D-401B-8A9B-C44CCCD14241}&View={DBA27EF6-7BFF-4C0F-8EC1-B3E3E18FB0B8}&ViewCount=1&IsXslView=TRUE&IsCSR=TRUE&";
        if (title && taskStatus) {
          url = url + "FilterField1=LinkTitle&FilterValue1=" + title + "&FilterField2=TaskStatus&FilterValue2=" + taskStatus
        } else if (title && !taskStatus) {
          url = url + "FilterField1=LinkTitle&FilterValue1=" + title
        } else if (!title && taskStatus) {
          url = url + "FilterField1=TaskStatus&FilterValue1=" + taskStatus
        }
        inplview.MyRefreshPage = MyRefreshPageToEx;
        inplview.MyRefreshPage(null, url, null);
    }
</script>
 

There is some hard coding in the custom JavaScript code making references to the list view web part such as the table Id, list GUID and view GUID.

Find the table element Id as follows.
ajaxfilter5

Limitations

  • Inplview.js filtering only supports exact match of values and not partial match
  • OR operand is not supported in the inpview.js from what I can see.
  • This custom approach is a bit of “hack”. Since it is client side, there is no impact to the SharePoint farm on the server side. Nevertheless, follow any development guidelines in your team. At the least, regression testing is recommended on SharePoint product patching and upgrades for any breaking changes.

The solution fit

I think the better solution fit is where the user already knows exactly what to filter on, but doesn’t want to scroll a largest list of values through the OOTB column filters every single time. For example, filtering by exact name or a small number.

SharePoint 2013 Custom List View Web Part Refresh

In the SharePoint 2013 list view web part, the paging, filtering and sorting functionality is driven by the inplview.js file. This is supported by AJAX calls against a RESTful service inplview.aspx. I am able to extend the JavaScript inplview object to implement client side refresh as data is updated on the server.
refresh1

To configure the automatic refresh by OOTB (out-of-the-box) configuration, go to the Web Part Properties: 

refresh2

 

What I find unattractive of this approach is that it is using the older AJAX UpdatePanel introduced in ASP .NET 2.0.

  • There is at least 23kb transferred for each refresh which is a relatively heavier in bandwidth.
    refresh3
  • The http response time is longer.
  • Using IE 8 browser, there is a memory leak such that memory utilization by the browser maxes out.
  • The out of the paging, filtering and sorting with the AJAX options do not work well together. For example, the AJAX refresh option sometimes shows no rows even after repeated combination of filtering and manual OOTB AJAX refresh. To resolve, refresh the page without any query string parameters.

The applicable business scenario or requirement is when frequently added and updated list items by multiple users are to be seen updated in the browser without user requiring to manually refresh the page. 

In SharePoint 2013, when the user interacts with the list view web part for activities such as paging, column filtering and sorting, there is an AJAX request making a RESTful call to inplview.aspx.
refresh4 Compared to the OOTB refresh, the inplview.aspx REST calls are significantly lighter in bandwidth and has faster response time.

How inplview.js Works

The javascript code that supports the list view web part paging, sorting and filtering is in the inplview.js file

The sequence of calls are made as follows:

  1. Browser
    1. User clicks on list view web part to page, sort and filter
    2. invoke objects in various SharePoint .js files
    3. invoke objects inplview.js
  2. Server
    1. inplview.aspx that is a RESTful http service
    2. HTTP response back to browser with only JSON formatted row data.

Using the chrome JavaScript debugger against inplview.js, I have identified the following sequence of function calls. To support the refresh, I have “forked” the code with my own custom functions.

Original OOTB sequence Customized “forked” sequence
RestoreAllClvpsNavigation Refresh_RestoreAllClvpsNavigation
EnumCLVPs(RestoreClvpNavigation) EnumCLVPs(Refresh_RestoreClvpNavigation)
RestoreClvpNavigation(clvp) <skip>
clvp.RestoreNavigation(); <skip>
CLVPRestoreNavigation() Refresh_RestoreClvpNavigation(clvp)

The Implementation

There is no explicit data refresh support in the inplview javascript code so I have injected custom code that extends the inplview javascript object to have supporting functions to implement refresh.

// automatic refresh based on interval
function autoRefresh()
{
 window.setInterval(listViewRefresh, 2000); // 20 seconds
}

// refresh all list view web parts on the page
function listViewRefresh() {
 $('#lblMessage').text('refreshed ').fadeIn("slow").fadeOut("slow"); // debugging
 inplview.MyRestoreAllClvpsNavigation = MyRestoreAllClvpsNavigation;
 inplview.MyRestoreAllClvpsNavigation();
}

// Enumerate list view web parts
function MyRestoreAllClvpsNavigation()
{
 EnumCLVPs(MyCLVPRestoreNavigation);
}

// refresh referencing list view web part
function MyCLVPRestoreNavigation(clvp) {
 var strHash = ajaxNavigate.getParam("InplviewHash" + clvp.WebPartId());
 if (strHash == null)
 strHash = '';

 var strInpl = '?' + DecodeHashAsQueryString(strHash);
 var strShowInGrid = GetUrlKeyValue("ShowInGrid", true, strInpl);

 if (strShowInGrid == "True") {
 InitGridFromView(clvp.ctx.view, true);
 }
 else if (clvp.ctx.inGridMode) {
 ExitGrid(clvp.ctx.view, true);
 }
 clvp.strHash = strHash;
 clvp.fRestore = true;
 var curRootFolder = GetRootFolder2(this);

 if (curRootFolder != null)
 strInpl = SetUrlKeyValue("RootFolder", unescapeProperly(curRootFolder), true, strInpl);
 clvp.RefreshPagingEx(strInpl, true, null);

 }

$(document).ready(function() {
 autoRefresh();
});

refresh5

Benefits

  1. Overcome IE8 browser memory leak issue
  2. Noticeable performance improvement over OOTB Ajax option
  3. The ability to extend and customize the client side rendering of the list view web part functionality

Caveats

  1. Upgrades and patches:
    Since this is extending from product JavaScript code, in the event of upgrades or patching, there could breaking changes.
    To mitigate this risk, remember to regression test after a product upgrade and patch.

Supportability

  1. Still supports general list view paging, column filtering and sorting.
  2. Exception: column filtering and sorting is not supported on lookup columns as it is already with OOTB.
  3. Ability to refresh all list view web parts that exist on the same page.

I’ll have a another blog post to add filtering based on user input by customizing the inpvliew.js