31.03.2010 updated the code and reworked the article:
This solution enables you to edit a column of type “Date and Time (Date Only)”, “Single line of text”, “Number”, “Currency”, “Yes/No” and “single choice people picker” directly in a list view.
I’m planning on updating with support for columns columns later on.
Double click on a “TD” to edit – this action is per “TD”.
In this picture all “TD’s” are double clicked on.
The people picker is created with the jQuery UI widget “Autocomplete”.
When saved OK – a picture is appended to indicate “Save OK”.
A different picture is appended on save error.
This can be used in lists and document library’s, but requires that a the ID column is in the view (it can be hidden in the script by setting the argument “hideIdColumn” to true).
I have added support for “date”, “single line text”, “number”, “currency”, “boolean” and “single choice people picker” columns. I will update this article with support for choice columns later on.
All these columns – for filtering purposes – supply their FieldInternalName in the list header. This way i do not have to specify the column index where it is found – it dynamically adapts to changing column order.
This solution can be used with plain list view’s and grouped view’s.
As always we start like this:
Create a document library to hold your scripts (or a folder on the root created in SharePoint Designer). In this example i have made a document library with a relative URL of “/test/English/Javascript” (a sub site named “test” with a sub site named “English” with a document library named “Javascript”):
In addition to the above scripts, i have the jQuery UI 1.8 in a separate folder. See the CEWP code and point the links to your jQuery UI location.
The jQuery UI-library is found here. The pictures and the sourcecode refers to jquery-ui-1.8. The autocomplete widget is not found in previous releases.
The jQuery-library is found here. The pictures and the sourcecode refers to jquery-1.4.min. The autocomplete widget is not supported in previous releases.
The scripts “interaction.js” and “stringBuffer.js” is created by Erucy and published on CodePlex.
I have made one modification in the file “jquery-ui-1.8.custom.css” to correct the datepicker positioning of the date and year dropdowns. The style “.ui-datepicker select.ui-datepicker-year { width: 49%;}” is modified like this: “.ui-datepicker select.ui-datepicker-year { width: auto;}”. This might not affect your setup, and is merely a detail.
The sourcecode for “EditInListView.js” is provided below.
Add a CEWP below the list view webpart, and add this code:
<style type="text/css"> .ui-menu .ui-menu-item { font-size:xx-small; } </style> <link type="text/css" href="/test/English/jQueryUI18/smoothness/jquery-ui-1.8.custom.css" rel="stylesheet" /> <script type="text/javascript" src="/test/English/Javascript/jquery-1.4.min.js"></script> <script type="text/javascript" src="/test/English/jQueryUI18/jquery-ui-1.8.custom.min.js"></script> <script type="text/javascript" src="/test/English/Javascript/interaction.js"></script> <script type="text/javascript" src="/test/English/Javascript/stringBuffer.js"></script> <script type="text/javascript" src="/test/English/Javascript/EditInListView.js"></script> <script type="text/javascript"> // Set variables hoverImgSrc = '/_layouts/images/DOWNARRW.GIF'; hoverImgMouseOver = 'Double click to edit'; successImgSrc = '/_layouts/images/ServiceInstalled.gif'; failureImgSrc = '/_layouts/images/ServiceNotInstalled.gif'; dateFormat = 'm/d/yy'; // alternative d.m.yy decimalSeparator = '.'; // The symbol used to mark the boundary between the integral and the fractional parts of a decimal number boolYesNoText = 'Yes|No'; // The display text in list view for a Yes/No-field userListGuid = '570D772F-0EAB-45A8-8C54-9CCD4EC6A0AF'; userListBaseUrl = '' arrToEdit = ['MyDateField','SingleLine','Number','YesNo','MyPeoplePicker']; // Call function initCustomEditFunction(true); </script>
Edit the array “arrToEdit” to hold your FieldInternalNames, the “userListGuid” to hold your list guid, and other parameters if necessary.
Parameter’s explained:
- hoverImgSrc: The source of the image indicating editable field
- hoverImgMouseOver: The mouse over on the “hoverImgSrc-image”
- successImgSrc: The source of the image displayed when saving the new value succeeds
- failureImgSrc: The source of the image displayed when saving the new value fails
- dateFormat: ‘m/d/yy’ – alternative ‘d.m.yy’
- decimalSeparator: The symbol used to mark the boundary between the integral and the fractional parts of a decimal number
- boolYesNoText: The display text in list view for a Yes/No-field – format: Yes|No
- userListGuid: The list guid for the userlist, used for the people picker
- userListBaseUrl: Base URL for the list “People and Groups”. If in a managed path, reflect this path, else set to “” (blank string)
- arrToEdit: Array of FieldInternalNames to address
The sourcecode for the file “EditInListView.js” looks like this:
/* Edit date, single line text, number, currency, boolean or single choice people picker directly in list view * --------------------------------------------- * Created by Alexander Bautz * alexander.bautz@gmail.com * https://spjsblog.com * v1.1 * LastMod: 31.03.2010 * --------------------------------------------- * Must include reference to: * jquery-1.4 - http://jquery.com * jquery-ui-1.8 - http://jqueryui.com/ * interaction.js - http://spjslib.codeplex.com * stringBuffer.js - http://spjslib.codeplex.com * --------------------------------------------- * * Call from CEWP BELOW the list view like this: <style type="text/css"> .ui-menu .ui-menu-item { font-size:xx-small; } </style> <link type="text/css" href="/test/English/jQueryUI18/smoothness/jquery-ui-1.8.custom.css" rel="stylesheet" /> <script type="text/javascript" src="/test/English/Javascript/jquery-1.4.min.js"></script> <script type="text/javascript" src="/test/English/jQueryUI18/jquery-ui-1.8.custom.min.js"></script> <script type="text/javascript" src="/test/English/Javascript/interaction.js"></script> <script type="text/javascript" src="/test/English/Javascript/stringBuffer.js"></script> <script type="text/javascript" src="/test/English/Javascript/EditInListView.js"></script> <script type="text/javascript"> // Set variables hoverImgSrc = '/_layouts/images/DOWNARRW.GIF'; hoverImgMouseOver = 'Double click to edit'; successImgSrc = '/_layouts/images/ServiceInstalled.gif'; failureImgSrc = '/_layouts/images/ServiceNotInstalled.gif'; dateFormat = 'm/d/yy'; // alternative d.m.yy decimalSeparator = '.'; // The symbol used to mark the boundary between the integral and the fractional parts of a decimal number boolYesNoText = 'Yes|No'; // The display text in list view for a Yes/No-field userListGuid = '570D772F-0EAB-45A8-8C54-9CCD4EC6A0AF'; userListBaseUrl = '' arrToEdit = ['MyDateField','SingleLine','Number','YesNo','MyPeoplePicker']; // Call function initCustomEditFunction(true); </script> */ function initCustomEditFunction(hideIdColumn){ if(typeof(hideIdColumn)!='undefined'){ hideId = hideIdColumn; $(".ms-viewheadertr th").each(function(){ if($(this).find('table:first').attr('name')=='ID'){ IDcolIndex = $(this).attr('cellIndex'); // Hide ID column if(hideId){ $(this).addClass('dummyHideClass'); } } }); } if(typeof(IDcolIndex)=='undefined'){ alert("The ID column must be in the view.nYou may hide it in the script call by setting the argument "hideIdColumn" to true."); } if(typeof(arrTH)=='undefined'){ arrTH = []; $(".ms-viewheadertr th").each(function(){ var colIndex = $(this).attr('cellIndex'); var table = $(this).find('table'); if(table.attr('name')!=undefined){ var fldIntName = table.attr('name'); var fldType = table.attr('fieldtype'); if($.inArray(fldIntName,arrToEdit)>-1){ arrTH.push({'colIndex':colIndex,'fldType':fldType,'fldIntName':fldIntName}); } } }); } addCustomEditFunction(arrTH); // Hide ID column if specified if(hideId){ $(".dummyHideClass").hide(); } } function addCustomEditFunction(arrToInclude){ $("table.ms-listviewtable tbody").each(function(){ if($(this).attr('id').match('aggr')==null){ $(this).find("tr:has(td.ms-vb2)[beenthere!='1']").each(function(){ $(this).attr('beenthere','1'); var itemID = $(this).find(">td[cellIndex=" + IDcolIndex + "]").text(); // Hide ID column if(hideId){ $(this).find("td[cellIndex="+IDcolIndex+"]").addClass('dummyHideClass'); } var pTR = $(this); $.each(arrToInclude,function(idx,obj){ var col = obj['colIndex']; var type = obj['fldType']; var intName = obj['fldIntName']; var TD = pTR.find(">td[cellIndex=" + col + "]"); var currVal = TD.text(); // Add onclick and append image TD.dblclick(function(){editCurrentLine(type,itemID,intName)}) .attr({'id':'customEdit_'+intName+"_"+itemID,'fieldType':type}) .css({'cursor':'pointer'}); if(type=='Number' || type=='Currency'){ TD.find('div').append("&nbsp;<img style='vertical-align:middle' title='" + hoverImgMouseOver + "' src='" + hoverImgSrc + "'>"); }else if(type=='User'){ if(TD.find('tr').length>0){ TD.find('tr:first').append("<td>&nbsp;<img style='vertical-align:middle' title='" + hoverImgMouseOver + "' src='" + hoverImgSrc + "'></td>"); }else{ TD.append("<div style='padding-left:12px'>&nbsp;<img style='vertical-align:middle' title='" + hoverImgMouseOver + "' src='" + hoverImgSrc + "'></div>"); } }else{ TD.append("&nbsp;<img style='vertical-align:middle' title='" + hoverImgMouseOver + "' src='" + hoverImgSrc + "'>"); } }); }); }else if($(this).attr('id').match('aggr')!=null && $(this).attr('beenthere')!='1' && hideId){ $(this).attr('beenthere','1'); $(this).find("td[cellIndex="+IDcolIndex+"]").addClass('dummyHideClass'); } }); } function editCurrentLine(type,id,intName){ var currField = $("#customEdit_" + intName+"_"+id); var currVal = currField.text(); // Remove &nbsp; currVal = $.trim(currVal.replace(/xA0/g,'')); if(type=='DateTime'){ if(currVal.indexOf(' ')>-1){ currVal = currVal.substring(0,currVal.indexOf(' ')); // Strip off any "time-value" } if($("#customEditInput_"+intName+"_"+id).length==0){ currField.css({'width':currField.width()}).html("<div>&nbsp;<a title='Cancel' href='javascript:' onclick='javascript:cancelCustomSaveLine(""+intName+"",""+id+"",""+currVal+"")'>cancel</a>&nbsp;|&nbsp;" + "<a title='Clear' href='javascript:' onclick='javascript:customSaveLine(""+intName+"",""+id+"",""+currVal+""," + true + ")'>clear</a><br>" + "<input class='ms-input' id='customEditInput_"+intName+"_"+id+"' style='width:1px;height:1px;border:0px' value='" + currVal + "' /></div>"); // Datepicker $("#customEditInput_" + intName+"_"+id).datepicker({ dateFormat: dateFormat, changeMonth: true, changeYear: true, showButtonPanel: true, onSelect: function(dateText, inst){ customSaveLine(intName,id,currVal); } }).datepicker('show'); } }else if(type=='Text' || type=='Number' || type=='Currency'){ if($("#customEditInput_"+intName+"_"+id).length==0){ currField.css({'width':currField.width()}).html("<div>&nbsp;<a title='Save' href='javascript:' onclick='javascript:customSaveLine(""+intName+"",""+id+"",""+currVal+"")'>save</a>&nbsp;|&nbsp;" + "<a title='Cancel' href='javascript:' onclick='javascript:cancelCustomSaveLine(""+intName+"",""+id+"",""+currVal+"")'>cancel</a><br>" + "<input class='ms-input' id='customEditInput_"+intName+"_"+id+"' style='width:99%' value='" + currVal + "' /></div>"); // Focus on the input setTimeout(function(){currField.find('input').focus();},250); // Add keyup/down function currField.find('input').keyup(function(e){ // If number - restrict input to numbers only - may need a little more tweaking if(type=='Number' || type=='Currency'){ var reg = new RegExp("[^0-9|\" + decimalSeparator + "]",'g'); var thisVal = $(this).val().replace(reg,''); $(this).val(thisVal); } }).keydown(function(e){ // Enter = save if(e.keyCode==13){ customSaveLine(intName,id,currVal) } }).keyup(); } }else if(type=='Boolean'){ if($("#customEditInput_"+intName+"_"+id).length==0){ if(currVal=='' || currVal.toLowerCase().indexOf('n')>-1){ // 'n' is found in 'No' and in Norwegian 'Nei' chk=''; }else{ chk='checked'; } currField.css({'width':currField.width()}).html("<div>&nbsp;<a title='Cancel' href='javascript:' onclick='javascript:cancelCustomSaveLine(""+intName+"",""+id+"",""+currVal+"")'>cancel</a><br>" + "<input type='checkbox' id='customEditInput_"+intName+"_"+id+"' " + chk + "/></div>"); $("#customEditInput_"+intName+"_"+id).click(function(){ customSaveLine(intName,id,currVal) }).focus(); } }else if(type=='User'){ if($("#customEditInput_"+intName+"_"+id).length==0){ currField.css({'width':currField.width()}).html("<div>&nbsp;<a title='Cancel' href='javascript:' onclick='javascript:cancelCustomSaveLine(""+intName+"",""+id+"",""+currVal+"")'>cancel</a>&nbsp;|&nbsp;" + "<a title='Clear' href='javascript:' onclick='javascript:customSaveLine(""+intName+"",""+id+"",""+currVal+""," + true + ")'>clear</a><br>" + "<input title='Type into the textfield to get a list of users' class='ms-input' id='customEditInput_"+intName+"_"+id+"' style='width:99%' value='" + currVal + "' /></div>"); // Autocomplete if(typeof(allUsers)=='undefined'){ allUsers = getUsers(); } currField.find('input').autocomplete({ source: allUsers, select: function(event, ui){ $(this).attr('hiddenVal',ui.item.userID); customSaveLine(intName,id,currVal); return false; } }); // Focus on the input setTimeout(function(){currField.find('input').focus();},250); } } } function getUsers(){ var query = "<Where><And><IsNotNull><FieldRef Name='EMail' /></IsNotNull>" + "<Eq><FieldRef Name='ContentType' /><Value Type='Text'>Person</Value></Eq></And></Where>" + "<OrderBy><FieldRef Name='Title' Ascending='TRUE'/></OrderBy>"; wsBaseUrl = userListBaseUrl + '/_vti_bin/'; var res = queryItems(userListGuid,query,['ID','Title','Name','EMail','ContentType']); var ret = []; $.each(res.items,function(idx,item){ ret.push({label:item['Title']+"<br>"+item['EMail']+"<br>"+item['Name'],value:item['Title'],userID:item['ID']}); }); return ret; } function cancelCustomSaveLine(intName,id,currVal){ var pTD = $("#customEdit_"+intName+"_"+id); var type = pTD.attr('fieldType'); if(type=='Number' || type=='Currency'){ pTD.html("<div align='right'>" + currVal + "&nbsp;<img style='vertical-align:middle' src='" + hoverImgSrc + "'></div>"); }else if(type=='User'){ pTD.html("<div style='padding-left:12px'>" + currVal + "&nbsp;<img style='vertical-align:middle' src='" + hoverImgSrc + "'></div>"); }else{ pTD.html(currVal + "&nbsp;<img style='vertical-align:middle' src='" + hoverImgSrc + "'>"); } } function customSaveLine(intName,id,currVal,clear){ wsBaseUrl = ctx.HttpRoot + '/_vti_bin/'; listGuid = ctx.listName; if(clear==undefined)clear=false; var pTD = $("#customEdit_"+intName+"_"+id); var type = pTD.attr('fieldType'); var inpField = pTD.find('input'); // Determine type and get value if(type=='Boolean'){ var bSplit = boolYesNoText.split('|'); var newVal = inpField.attr('checked'); if(newVal){ newVal='1'; newValText=bSplit[0]; }else{ newVal='0'; newValText=bSplit[1]; } }else{ var newVal = $.trim(inpField.val()); } // Check if value has changed if(newVal!=currVal || clear){ var data = {}; if(type=='DateTime'){ if(clear){ data[intName] = ''; newVal = ''; }else{ var isoDate = parseISO8601Date(newVal); data[intName] = isoDate; } }else if(type=='User'){ if(clear){ data[intName] = ""; newVal = ''; }else{ data[intName] = inpField.attr('hiddenVal'); } }else{ data[intName] = newVal; } // Write back data var res = updateItem(listGuid,id,data); // Success or failure if(res.success){ // Save success if(type=='Number'){ pTD.html("<div align='right'>" + newVal + "&nbsp;<img style='vertical-align:middle' title='Saved' src='" + successImgSrc + "' width='14' height='14' border='0'></div>"); }else if(type=='Boolean'){ pTD.html(newValText + "&nbsp;<img style='vertical-align:middle' title='Saved' src='" + successImgSrc + "' width='14' height='14' border='0'>"); }else if(type=='User'){ pTD.html("<div style='padding-left:12px'>" + newVal + "&nbsp;<img style='vertical-align:middle' title='Saved' src='" + successImgSrc + "' width='14' height='14' border='0'></div>"); }else{ pTD.html(newVal + "&nbsp;<img style='vertical-align:middle' title='Saved' src='" + successImgSrc + "' width='14' height='14' border='0'>"); } }else{ // Failed to save if(type=='Number'){ pTD.html("<div align='right'>" + currVal + "&nbsp;<img style='vertical-align:middle' style='vertical-align:middle' title='" + res.errorText + "' src='" + failureImgSrc + "' width='14' height='14' border='0'></div>"); }else if(type=='User'){ pTD.html("<div style='padding-left:12px'>" + currVal + "&nbsp;<img style='vertical-align:middle' title='" + res.errorText + "' src='" + failureImgSrc + "' width='14' height='14' border='0'></div>"); }else{ pTD.html(currVal + "&nbsp;<img style='vertical-align:middle' title='" + res.errorText + "' src='" + failureImgSrc + "' width='14' height='14' border='0'>"); } } }else{ // If value is not changed, restore currVal if(type=='Number'){ pTD.html("<div align='right'>" + newVal + "&nbsp;<img style='vertical-align:middle' title='" + hoverImgMouseOver + "' src='" + hoverImgSrc + "'></div>"); }else if(type=='User'){ pTD.html("<div style='padding-left:12px'>" + newVal + "&nbsp;<img style='vertical-align:middle' title='" + hoverImgMouseOver + "' src='" + hoverImgSrc + "'></div>"); }else{ pTD.html(newVal + "&nbsp;<img style='vertical-align:middle' title='" + hoverImgMouseOver + "' src='" + hoverImgSrc + "'>"); } } } function parseISO8601Date(str){ if(str!=''){ var strSplit = str.split(dateFormat.charAt(1)); switch(dateFormat){ case 'm/d/yy': return strSplit[2] + "-" + strSplit[0] + "-" + strSplit[1] + "T12:00:00Z"; // set's clock to 12:00 PM break; case 'd.m.yy': return strSplit[0] + "-" + strSplit[2] + "-" + strSplit[1] + "T12:00:00Z"; // set's clock to 12:00 PM break; } }else{ return ''; } } // Attaches a call to the function to the "expand grouped elements function" for it to function in grouped listview's function ExpGroupRenderData(htmlToRender, groupName, isLoaded){ var tbody=document.getElementById("tbod"+groupName+"_"); var wrapDiv=document.createElement("DIV"); wrapDiv.innerHTML="<TABLE><TBODY id="tbod"+groupName+"_" isLoaded=""+isLoaded+"">"+htmlToRender+"</TBODY></TABLE>"; tbody.parentNode.replaceChild(wrapDiv.firstChild.firstChild,tbody); initCustomEditFunction(); } // Size of datepicker $("body").css({'font-size':'55%'});
Save this code as “EditInListView.js”, and upload to your scriptlibrary as shown above.
Note: Due to the number of DOM-modifications there might be a performance issue (prolonged load time of the page) when used.
To minimize the issue you can try:
- Limit the number of columns this feature is applied to
- Limit the item count in each page
- Group your items and set “By default, show groupings: Collapsed”
Test it in your environment to find what settings suites you.
Ask if something is unclear.
Regards
Alexander