In my last post I showed a utility page allowing you to set the JSLink property of a site column. In this post I’m going to dump the code on you, and then explain parts of it. It’s just a SharePoint wiki page with some JavaScript in it using the JavaScript Client-side Object Model (JSOM), so you can just drop it in the Style Library (or any document library really) and click on it to start using it.
And here’s 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 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 | <%@ Assembly Name="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> <%@ Page Language="C#" Inherits="Microsoft.SharePoint.WebPartPages.WikiEditPage" MasterPageFile="~masterurl/default.master" %> <%@ Register TagPrefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> <%@ Import Namespace="Microsoft.SharePoint" %> <asp:Content ContentPlaceHolderID='PlaceHolderPageTitle' runat='server'> Set JSLink on Field </asp:Content> <asp:Content ContentPlaceHolderID='PlaceHolderPageTitleInTitleArea' runat='server'> </asp:Content> <asp:Content ContentPlaceHolderID='PlaceHolderAdditionalPageHead' runat='server'> <meta name='CollaborationServer' content='SharePoint Team Web Site' /> <style type="text/css"> #pageStatusBar { display: none !important; } h2 { margin-bottom: 20px; } button { margin-top: 20px; } #form { display: none; } </style> </asp:Content> <asp:Content ContentPlaceHolderID='PlaceHolderMiniConsole' runat='server'> <SharePoint:FormComponent TemplateName='WikiMiniConsole' ControlMode='Display' runat='server' id='WikiMiniConsole'></SharePoint:FormComponent> </asp:Content> <asp:Content ContentPlaceHolderID='PlaceHolderLeftActions' runat='server'> <SharePoint:RecentChangesMenu runat='server' id='RecentChanges'></SharePoint:RecentChangesMenu> </asp:Content> <asp:Content ContentPlaceHolderID='PlaceHolderMain' runat='server'> <div id="form"> <h2>Set the JSLink Property of a Site Column</h2> <table cellpadding="5"> <tr> <td>Group:</td> <td> <select id="groupSelect"> <option></option> </select> </td> </tr> <tr> <td>Field:</td> <td> <select id="fieldSelect"> <option></option> </select> </td> </tr> <tr> <td>JSLink:</td> <td> <textarea title="Enter paths to JavaScript files to load for this field. JavaScript files must be stored in this site collection and the path must begin with ~sitecollection." id='jslink' rows='10' cols='100'></textarea> </td> </tr> </table> <button type="button" id="setJsLink">Set JSLink</button> </p> <script type="text/javascript"> (function() { // yes, I'm evil, but I'm in a limited scope here and I'll do what I want to the prototype tyvm! Array.prototype.contains = function(obj) { var i = this.length; while (i--) { if (this[i] === obj) { return true; } } return false; } if (!window.intellipoint) window.intellipoint = {}; //////////////////////////////////////////////////////////////////////////////// // Form code behind class //////////////////////////////////////////////////////////////////////////////// intellipoint.jslinkSetter = { groups: [], options: {}, //////////////////////////////////////////////////////////////////////////////// // Initialize the SharePoint object model context, populate the drop-down // of column groups and columns, and attach event handlers. //////////////////////////////////////////////////////////////////////////////// init: function() { // get the context jslinkSetter.ctx = new SP.ClientContext.get_current(); jslinkSetter.web = jslinkSetter.ctx.get_web(); // load the site columns jslinkSetter.fields = jslinkSetter.web.get_availableFields(); jslinkSetter.ctx.load(jslinkSetter.fields); // exec the query async jslinkSetter.ctx.executeQueryAsync( function() { // on success enumerate the fields and initialize the form controls jslinkSetter.enumerateFields(); jslinkSetter.initGroupSelect(); jslinkSetter.initFieldSelect(); jslinkSetter.initButton(); document.getElementById("setJsLink").disabled = true; document.getElementById("form").style.display = "block"; }, function() { alert("Could not load site columns.") } ); }, //////////////////////////////////////////////////////////////////////////////// // Enumerate the site columns and store information about them to be used // to populate the drop-downs. //////////////////////////////////////////////////////////////////////////////// enumerateFields: function() { var enumerator = jslinkSetter.fields.getEnumerator(); while (enumerator.moveNext()) { var current = enumerator.get_current(); var option = {} option.value = current.get_internalName(); option.text = current.get_internalName() + " [" + current.get_title() + "]"; option.group = current.get_group(); option.jslink = current.get_jsLink(); if (!jslinkSetter.groups.contains(option.group)) { jslinkSetter.groups.push(option.group); } jslinkSetter.options[option.value] = option; } }, //////////////////////////////////////////////////////////////////////////////// // Initialize the group drop-down and add an onchange listener to it. //////////////////////////////////////////////////////////////////////////////// initGroupSelect: function() { var groupSelect = document.getElementById("groupSelect"); jslinkSetter.groups = jslinkSetter.groups.sort(); for (var i = 0; i < jslinkSetter.groups.length; i++) { var current = jslinkSetter.groups[i]; var o = document.createElement('option'); o.value = current; o.text = current; groupSelect.appendChild(o); } // when the group changes, trim the fields select groupSelect.onchange = function(e) { e = e || event; var fieldSelect = document.getElementById("fieldSelect"); var keys = Object.keys(jslinkSetter.options).sort(); fieldSelect.innerHTML = "<option></option>"; document.getElementById("jslink").value = ""; document.getElementById("setJsLink").disabled = true; for (var i = 0; i < keys.length; i++) { var o = jslinkSetter.options[keys[i]]; if (e.target.value.length === 0 || o.group === e.target.value) { var newOption = document.createElement("option"); newOption.value = o.value; newOption.text = o.text; fieldSelect.appendChild(newOption); } } } }, //////////////////////////////////////////////////////////////////////////////// // Initialize the field drop-down and add an onchange listener to it. //////////////////////////////////////////////////////////////////////////////// initFieldSelect: function() { var fieldSelect = document.getElementById("fieldSelect"); var keys = Object.keys(jslinkSetter.options).sort(); for (var i = 0; i < keys.length; i++) { var current = jslinkSetter.options[keys[i]]; var o = document.createElement('option'); o.value = current.value; o.text = current.text; fieldSelect.appendChild(o); } // when the field changes, initialize the jslink text area and enable the button fieldSelect.onchange = function(e) { e = e || event; if (e.target.value.length > 0) { var option = jslinkSetter.options[e.target.value]; document.getElementById("jslink").value = option.jslink.split("|").join("\n"); document.getElementById("setJsLink").disabled = false; } else { document.getElementById("jslink").value = ""; document.getElementById("setJsLink").disabled = true; } } }, //////////////////////////////////////////////////////////////////////////////// // Initialize the button and. //////////////////////////////////////////////////////////////////////////////// initButton: function() { var button = document.getElementById("setJsLink"); button.onclick = function(e) { e = e || event; var name = document.getElementById("fieldSelect").value; // get and load the field // cannot be applied to Taxonomy fields, Related Items field, and Task // Outcome field, jsLink is read-only on those objects var field = jslinkSetter.web.get_availableFields().getByInternalNameOrTitle(name); jslinkSetter.ctx.load(field); jslinkSetter.ctx.executeQueryAsync( function() { // on success, set the jslink and update field.set_jsLink(document.getElementById("jslink").value.split("\n").join("|")); field.updateAndPushChanges(true); jslinkSetter.ctx.executeQueryAsync( function() { // on success, update the cache and display a dialog jslinkSetter.options[name].jslink = document.getElementById("jslink").value.split("\n").join("|"); alert("Successfully updated site column '" + name + "'."); }, function() { alert("Could not update site column '" + name + "'."); } ); }, function() { alert("Could not load site column '" + name + "'."); } ); }; } }; var jslinkSetter = intellipoint.jslinkSetter; })(); SP.SOD.executeFunc("sp.js", "SP.ClientContext", function() { intellipoint.jslinkSetter.init(); }); </script> </asp:Content> |
Initialization (lines 241-243 and 93-117)
First, I need my code to start running after the JSOM client context has been initialized, so I call my init method in a callback to SP.SOD.executeFunc, waiting for SP.ClientContext to be initialized by loading sp.js.
In the init method, I get the client context, and from it the current web. Then I ask it for its list of available fields, which executes asynchronously. When the fields have been loaded, I enumerate them and shove them into a property called options. I also load a unique list of field groups into the group’s property. Then I initialize my DOM elements based on the results.
At this point, the field select contains all available fields, and the group select contains all field groups. If you select a field group, the fields select will be trimmed down to just fields in that field group, theoretically making it easier to find what you’re looking for.
Note that when you select a field, the text box will be initialized with the current value of the JSLink property for that field. If you’ve never set it before, it should be just clienttemplates.js, which is the JavaScript file that SPClientTemplates is in. If you have set a field’s JSLink property to something else, and want to revert back to the default, don’t set it to clienttemplates.js but set it to blank instead. If you reload the page and look at the field again, you will see that it says clienttemplates.js again.
Updating the JSLink Property (lines 204-237)
This is the meat of the utility; the button click event. It gets the field again by name, asynchronously. Once it has it, it sets the JSLink property of the field to whatever you typed in the JSLink text area (sort of).
JSLink can store multiple JavaScript files in a single property, but it doesn’t store them newline separated like the text area. I chose to newline separate them in the UI because it’s easier to read and work with, but when I write them back out I replace the newlines with pipes (|), which is what SharePoint expects to separate multiple files.
And finally, I call updateAndPushChanges asynchronously to persist the changes back to SharePoint.
Wrap
I love writing little utility pages like this using JSOM and a wiki page. Most of the Client-side Object Model code I see for this kind of job are written in PowerShell. I’ve got nothing against PowerShell, but at many larger organizations, your average SCA isn’t going to have sufficient access to their desktop to add the Microsoft.SharePoint.Client DLLs to it, and it may take an act of Congress to get an administrator to do it for you. Doing it with JSOM on a wiki page makes it more easily accessible to any SCA. And BTW, you do need to be an SCA to run this code (or at least be able to write to site columns, which requires an array of permissions).
And while this code operates on site columns, this same technique works on web scoped columns or list columns as well, in which case you would only need web scoped permissions or list scoped permissions to run it.