I’m going to do one more CSR Template that doesn’t really render anything, but rather changes something that SPClientTemplates actually renders, an autocomplete template. This one is a little less invasive, because it also doesn’t override the render method, so it doesn’t have to call the out of box renderer and be aware of the consequences of doing that. It can do that because it only cares about a single field, the one it’s going to modify, so it can safely do it’s work as soon as that field is rendered. That means that it can just override OnPreRender and OnPostRender.
Autocomplete Setup
The setup for this is that I’m going to add two fields to my list, Company and JobTitle. I would like jQuery UI autocomplete functionality added to each of these fields with the data read from the lists Companies and JobTitles respectively. The two lists look like this:
So when I fill out the form and start typing in a job title, the form should suggest autocomplete values based on what’s in the JobTitles lists like so (and of course the same for Company and Companies):
Autocomplete Implementation
So here is the code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | (function($) { // this structure is the only thing that needs to be modified to override more or different fields var fields = [ { "internalName": "Company", "lookupList": "Companies", "lookupField": "Title" }, { "internalName": "JobTitle", "lookupList": "JobTitles", "lookupField": "Title" } ]; var keys = fields.map(function(f) { return f.internalName; }); // helper because IE doesn't have Array.find function arrayFind(array, value, equals) { for (var i = 0; i < array.length; i++) { if (equals(value, array[i])) return array[i]; } } var addedCss = false; // load the jquery ui css once var autoCompleteOnPreRender = function(ctx) { if (!addedCss) { $("head").append( "<link rel='stylesheet' type='text/css' href='" + _spPageContextInfo.siteAbsoluteUrl + "/Style Library/jquery-ui.css'>"); addedCss = true; } } // if the current field is in fields, setup the jquery ui autocomplete on it var autoCompletePostRender = function(ctx) { var schema = ctx.ListSchema.Field[0]; if ($.inArray(schema.Name, keys) > -1) { // get the field configuration by internal name var config = arrayFind(fields, schema.Name, function(v, o) { return v === o.internalName; }); // find the HTML input by naming convention (id starts with <InternalName>_<ID>) var $input = $("[id^='" + schema.Name + "_" + schema.Id + "']"); // setup view fields and caml for the query of the lookup list var viewFields = "<ViewFields><FieldRef Name='" + config.lookupField + "' /></ViewFields>"; var query = "<Query><OrderBy>" + "<FieldRef Name='" + config.lookupField + "' Ascending='True' />" + "</OrderBy></Query>"; // if the web url isn't in the config, assume the root web if(!config.webURL) { config.webURL = _spPageContextInfo.siteServerRelativeUrl; } // load the autocomplete data from the lookup list and initialize autocomplete $().SPServices({ operation: "GetListItems", async: true, listName: config.lookupList, CAMLViewFields: viewFields, CAMLQuery: query, completefunc: function(xData) { var autocompleteData = []; $(xData.responseXML).SPFilterNode("z:row").each(function() { // push the results into an array autocompleteData.push($(this).attr("ows_" + config.lookupField)); }); // initialize jquery ui autocomplete with the data if (autocompleteData.length > 0) { $input.autocomplete({ source: autocompleteData, minLength: 2 }); } } }); } } /* * Create an overrides object for pre and post render. */ var overrides = { OnPreRender: autoCompleteOnPreRender, OnPostRender: autoCompletePostRender, }; // register templates now for non-MDS and full page loads SPClientTemplates.TemplateManager.RegisterTemplateOverrides(overrides); // register template overrides for partial page loads if MDS is enabled if (typeof _spPageContextInfo != 'undefined' && _spPageContextInfo != null) { var base = _spPageContextInfo.siteServerRelativeUrl; var url = (base === '/' ? "" : base).toLowerCase(); RegisterModuleInit(url + '/style%20library/autocompletecsr.js', function() { SPClientTemplates.TemplateManager.RegisterTemplateOverrides(overrides); }); } })(jQuery); |
A few items of interest in no particular order:
- The field configuration at the top should now look vaguely familiar, and follows the conventions I established while doing the cascading lookups template in a previous post. It doesn’t need as much template-specific configuration, just a list and a field in that list from which it’s going to read it’s autocomplete data.
- The pre-render method just brings in the jquery-ui.css file. But remember that OnPreRender gets called back for each field in the form, and we don’t want to add that css link 5, or 10, or more times depending on how many fields are in the form. So it just uses a flag defined outside itself to ensure it only inserts the link once.
- The post-render method does the heavy lifting. It first needs to check is this field an autocomplete field (because again, it’s going to get called for every field). If so, it finds it’s config, formulates a query based on that, and calls the Lists.asmx web service with that query. On success, it loads the results into an array and calls jquery ui’s autocomplete method on the input field.
- Finally, the overrides object is quite a bit simpler, because I’m only overriding OnPreRender and OnPostRender, and they aren’t field specific so I don’t even need to look at my configuration to build the overrides object.
Wrap-up
In my next post I’m going to show an even better utility page for deploying Client Side Rendering templates.
AutocompleteCSR.zip – the source code for this post.