7/12/2016

InplaceSearchEnabled doesn't work when set programmatically in XsltListViewWebPart

There are a lot of forum threads about the error that InplaceSearchEnabled property of XsltListViewWebPart (XLV) cannot be changed programmatically, only through UI and WebPart properties.
I experienced the same issue when was trying to add XLV to sub site (display items of root level list on subsite) - the search input was not displayed and the InplaceSearchEnabled property was set to null.
Finally I found the solution (actually a workaround) and it is described below.

First I was trying to debug SharePoint.dll assembly (thanks to .NET Reflector) but the code was to complicated and full of Reflection. So I decided to look into the client-side part of the search functionality.
And here what I found:

  1. Search input is located inside inplaceSearchDiv_<wpq> div which is added to HTML in sp.ui.listsearchboxbootstrap.js. So we need to include this script to our page.
  2. The div is rendered for the web part that is registered in g_listSearchBoxInfo global array and if ListSchema's property InplaceSearchEnabled is set to true.
  3. The content of that div is added in sp.ui.listsearchbox.js.
  4. In the code of the page with working search sp.ui.listsearchboxbootstrap.js is added as simple script, sp.ui.listsearchbox.js is registered as SOD with a bunch of dependencies:
<script src="/_layouts/15/sp.ui.listsearchboxbootstrap.js?rev=XoxCCd1ZTetlKjaD1x2A7A%3D%3D" type="text/javascript"></script>
<script type="text/javascript">RegisterSod("clientrenderer.js", "\u002f_layouts\u002f15\u002fclientrenderer.js?rev=PWwV4FATEiOxN90BeB5Hzw\u00253D\u00253D");</script>
<script type="text/javascript">RegisterSod("srch.resources.resx", "\u002f_layouts\u002f15\u002fScriptResx.ashx?culture=en\u00252Dus\u0026name=Srch\u00252EResources\u0026rev=T\u00252BEc8BFdsTQbQuVAobKatQ\u00253D\u00253D");</script>
<script type="text/javascript">RegisterSod("search.clientcontrols.js", "\u002f_layouts\u002f15\u002fsearch.clientcontrols.js?rev=5P1PlD9u0nLal8ZgtuGr6w\u00253D\u00253D");RegisterSodDep("search.clientcontrols.js", "clientrenderer.js");RegisterSodDep("search.clientcontrols.js", "srch.resources.resx");</script>
<script type="text/javascript">RegisterSod("profilebrowserscriptres.resx", "\u002f_layouts\u002f15\u002fScriptResx.ashx?culture=en\u00252Dus\u0026name=ProfileBrowserScriptRes\u0026rev=J5HzNnB\u00252FO1Id\u00252FGI18rpRcw\u00253D\u00253D");</script>
<script type="text/javascript">RegisterSod("sp.ui.listsearchbox.js", "\u002f_layouts\u002f15\u002fsp.ui.listsearchbox.js?rev=wws9SqyBBuflNZ2j2YOIAA\u00253D\u00253D");RegisterSodDep("sp.ui.listsearchbox.js", "search.clientcontrols.js");RegisterSodDep("sp.ui.listsearchbox.js", "profilebrowserscriptres.resx");</script>
So based on these observations I decided to add search using JavaScript (even if the InplaceSearchEnabled property itself was not set).
Here are the actions to initialize all we need:

  1. Add JSLink property to Web Part to load custom code when the Web Part is rendering
  2. Load all needed scripts and register them as SOD if needed
  3. Get Web Part's wpq identifier and register it in g_listSearchBoxInfo 
  4. Set ListSchema.InplaceSearchEnabled to true
  5. Enjoy the result
And here is the code:
/// we need to wait until clienttemplates.js is loaded
SP.SOD.executeFunc('clienttemplates.js', 'SPClientTemplates', function () {
    // code that will be run before rendering the script. It'a good time to configure everything for search input
    function onPreRender(ctx) {
        if (!window.g_listSearchBoxInfo)
            window.g_listSearchBoxInfo = [];
        var hasSearch = false;
        // if the searci is configured for this web part we won't want to add it twice
        for (var i = 0, len = window.g_listSearchBoxInfo.length; i < len; i++) {
            if (window.g_listSearchBoxInfo[i].wpq === ctx.wpq) {
                hasSearch = true;
                break;
            }
        }

        if (!hasSearch) {
            // the structure of the object was got during debugging session
            window.g_listSearchBoxInfo.push({
                fullSearchSiteUrl: _spPageContextInfo.webAbsoluteUrl,
                loadInProgress: false,
                searchBoxConstructor: null,
                sodFunc: null,
                sodKey: null,
                wpq: ctx.wpq
            });

            // setting InplaceSearchEnabled in ListSchema
            ctx.ListSchema.InplaceSearchEnabled = true;
        }
    }

    // registering CSR template override
    var overrideCtx = {};
    overrideCtx.OnPreRender = onPreRender;

    SPClientTemplates.TemplateManager.RegisterTemplateOverrides(overrideCtx);
    
    // adding scripts (with condition if there were previously added or not)
    var searchScript = jQuery('script[src*="listsearchboxbootstrap.js"]');
    if (!searchScript.length) {
        RegisterSod('clientrenderer.js', '/_layouts/15/clientrenderer.js');
        RegisterSod('srch.resources.resx', '/_layouts/15/ScriptResx.ashx?culture=en-us&name=Srch.Resources');
        RegisterSod('search.clientcontrols.js', '/_layouts/15/search.clientcontrols.js')
        RegisterSodDep('search.clientcontrols.js', 'clientrenderer.js');
        RegisterSodDep('search.clientcontrols.js', 'srch.resources.resx');
        RegisterSod('profilebrowserscriptres.resx', '/_layouts/15/ScriptResx.ashx?culture=en-us&name=ProfileBrowserScriptRes');
        RegisterSod('sp.ui.listsearchbox.js', '/_layouts/15/sp.ui.listsearchbox.js');
        RegisterSodDep('sp.ui.listsearchbox.js', 'search.clientcontrols.js');
        RegisterSodDep('sp.ui.listsearchbox.js', 'profilebrowserscriptres.resx');

        var script = document.createElement('script');
        script.type = 'text/javascript';
        script.src = '/_layouts/15/sp.ui.listsearchboxbootstrap.js';
        document.head.appendChild(script);
    }
});
This will add search input to the web part and it will work as expected.
Additional remarks: resources (.resx SODs) are loaded with culture parameter which is set to en-us here (I don't need any other culture). But if your portal will potentially use different cultures you can get the name of current culture from _spPageContextInfo. It will be available in onload event. The same I can say about /_layouts/15/ path. You can use _spPageContextInfo.layoutsUrl to get Layouts folder Url.
This code shows you how to enable the search. If it is enabled and you want to disable it you can just set ListSchema.InplaceSearchEnabled to false and remove registration of the wpq from g_listSearchBoxInfo.

I believe that's it.
Have fun!

7 comments:

  1. Thanks buddy, you saved my time. It works fine.

    ReplyDelete
  2. Thanks for this great post, the only one that I found on that subject.

    I've tried to implement this solution for one of my project. However, I'm facing some issues, especially with Mozilla Firefox. In fact, the search box is not displayed even if the Javascript is executed (with no error in the browser console).

    Have you faced the same issues and how have you resolved them ?

    Thanks in advance for your help (and sorry for my english).

    ReplyDelete
    Replies
    1. Hello Pauline,

      I tested the code in Firefox and it works correctly.
      Did you test your code in other browsers? Did you debug it? Any errors in console?

      Delete
    2. Hi,

      No there's no error in the console. It's just like the registered scripts (with RegisterSod) are not loaded, so the code in the onPreRender function has no effect.

      Thanks for your help.

      Delete
    3. Hello Pauline,

      Is it working in other browsers?

      Maybe your list is in 'Server Render' mode. In that case JSLink is not working at all.

      Delete
    4. Hi,
      Yes, it's working in Chrome and IE.

      Delete
    5. Well, it's really weird.
      Maybe you can try to clear your browser cache, see if FF blocks something.

      Delete