Category Archives: List view modification

Edit date, single line text, number or boolean columns directly in list view

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”.
IMG

In this picture all “TD’s” are double clicked on.
IMG

The people picker is created with the jQuery UI widget “Autocomplete”.
IMG

When saved OK – a picture is appended to indicate “Save OK”.
IMG

A different picture is appended on save error.
IMG

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”):
IMG

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.

Read here how to find the list Guid of your list, and how to find the FieldInternalName of your columns.

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(" <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> <img style='vertical-align:middle' title='" + hoverImgMouseOver + "' src='" + hoverImgSrc + "'></td>");
						}else{
							TD.append("<div style='padding-left:12px'> <img style='vertical-align:middle' title='" + hoverImgMouseOver + "' src='" + hoverImgSrc + "'></div>");
						}
					}else{
						TD.append(" <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  
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> <a title='Cancel' href='javascript:' onclick='javascript:cancelCustomSaveLine(""+intName+"",""+id+"",""+currVal+"")'>cancel</a> | " +
			"<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> <a title='Save' href='javascript:' onclick='javascript:customSaveLine(""+intName+"",""+id+"",""+currVal+"")'>save</a> | " +
				"<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> <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> <a title='Cancel' href='javascript:' onclick='javascript:cancelCustomSaveLine(""+intName+"",""+id+"",""+currVal+"")'>cancel</a> | " +
				"<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 + " <img style='vertical-align:middle' src='" + hoverImgSrc + "'></div>");
	}else if(type=='User'){
		pTD.html("<div style='padding-left:12px'>" + currVal + " <img style='vertical-align:middle' src='" + hoverImgSrc + "'></div>");
	}else{
		pTD.html(currVal + " <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 + " <img style='vertical-align:middle' title='Saved' src='" + successImgSrc + "' width='14' height='14' border='0'></div>");
			}else if(type=='Boolean'){
				pTD.html(newValText + " <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 + " <img style='vertical-align:middle' title='Saved' src='" + successImgSrc + "' width='14' height='14' border='0'></div>");
			}else{
				pTD.html(newVal + " <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 + " <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 + " <img style='vertical-align:middle' title='" + res.errorText + "' src='" + failureImgSrc + "' width='14' height='14' border='0'></div>");
			}else{
				pTD.html(currVal + " <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 + " <img style='vertical-align:middle' title='" + hoverImgMouseOver + "' src='" + hoverImgSrc + "'></div>");
		}else if(type=='User'){
			pTD.html("<div style='padding-left:12px'>" + newVal + " <img style='vertical-align:middle' title='" + hoverImgMouseOver + "' src='" + hoverImgSrc + "'></div>");
		}else{
			pTD.html(newVal + " <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

Hide menu items in list view toolbar and views in view selector

This is a short one on how to remove menu items from the “New”, “Actions”, “Settings”, and “List view’s” menu in a list or document library.

This method requires a CEWP added below the list view web part with the code, and is a “per view code” which must be added to every view where the menu items shall be removed.

I will first give a list of the “names” of the “standard” elements found in the list view toolbar menu and list view menu, then i will give an example of how to remove selected items from the menu’s.

The menu items in the “Actions”, and the “Settings” menu has these “names” (the part of the ID used to locate them):

  • _EditInGridButton
  • _ExportToSpreadsheet
  • _ExportToDatabase
  • _ViewRSS
  • _SubscribeButton
  • _AddColumn
  • _AddView
  • _ListSettings

The “New” menu (the one containing all the “New-button” for all your content types) uses “_New0” for the first “_New1” for the next and so on.

The “view’s” menu uses these names for the “standard” menu items:

  • _DefaultView
  • _ModifyView
  • _CreateView

The custom made views uses the “DisplayName” of the view to identify it.

Code for hiding elements in “New”, “Actions”, “Settings” and “View’s” menu:

<script type="text/javascript" src="/test/English/Javascript/jquery-1.3.2.min.js"></script>
<script type="text/javascript">
/* Hide menu items in list view toolbar and views in view selector
 * ---------------------------------------------
 * Created by Alexander Bautz
 * alexander.bautz@gmail.com
 * https://spjsblog.com
 * v1.0
 * LastMod: 26.11.2009
 * ---------------------------------------------
 * Include reference to:
 *  jquery - http://jquery.com
 * ---------------------------------------------
*/

// Remove menu items - in this example all items in the "Settings" menu are removed (and therefore the menu is removed)
// This is the array of menu items to hide
var arrOfMenuItemsToHide = ['_EditInGridButton','_AddView','_AddColumn','_ListSettings'];
$.each(arrOfMenuItemsToHide,function(idx,item){	
	$("*[id$='" + item + "']").next().andSelf().remove();
});

// Views - hides "Modify", "Create" and the custom made "My Test View"
// This is the array of view's or to hide
var arrOfViewsToHide = ['_ModifyView','_CreateView','My Test View'];
$.each(arrOfViewsToHide,function(idx,item){	
	$.each($("*[id$='_ViewSelectorMenu']").children(),function(){
		if($(this).attr('id').match(item) || $(this).attr('text')==item){
			$(this).next().andSelf().remove();
		}
	});
});

// Remove the menu if all menu items are removed
$(".ms-menutoolbar").find('menu').each(function(){
	if($(this).children().length==0){
		$(this).parents('td.ms-toolbar:first').prev().andSelf().remove();
	}
});
</script>

The jQuery-library is found here. The sourcecode refers to jquery-1.3.2.min. If you download another version, be sure to update the script reference in the sourcecode.

This code can be adapted to hide elements based on group membership of the logged in user. I have written about a method to get the group collection for a user and to verify group membership here Showing or hiding list fields based on membership in a SharePoint group.

Ask if something is unclear.

Regards
Alexander

Count groups in grouped list view

This is a short one on counting groups in a grouped list view like this (group count in the list view toolbar):
IMG

Note: This counts only visible groups. If you set the “Number of groups to display per page” in the list view settings you will get max that number of groups when counting. An alternate approach would be to query the list for the total number of unique items based on the items the view is grouped by.

Add a CEWP below the list view with this code (alter the location of jQuery as needed):

<script type="text/javascript" src="/test/English/Javascript/jquery-1.3.2.min.js"></script>
<script type="text/javascript">

// Grouping on level 1
var CountGrouping1 = $(".ms-listviewtable td.ms-gb").length;
var NameGrouping1 = $(".ms-listviewtable td.ms-gb:first a:eq(1)").text();

// Grouping on level 2
var CountGrouping2 = $(".ms-listviewtable td.ms-gb2").length;
var NameGrouping2 = $(".ms-listviewtable td.ms-gb2:first a:eq(1)").text();

var str = NameGrouping1 + "'s: " + CountGrouping1 + " | " + NameGrouping2 + "'s: " + CountGrouping2;

$("td.ms-toolbar[width='99%']").append("<div class='ms-listheaderlabel' style='text-align:center;margin-top:-15px'>" + str + "</div>");

</script>

The jQuery-library is found here. The sourcecode refers to jquery-1.3.2.min. If you download another version, be sure to update the script reference in the sourcecode.

Ask if something is unclear.

Regards
Alexander

Preview metadata in list view on mouseover

12.09.2011 A new version is posted here.

30.07.2010 A major update of the script to tidy up the code and to support previewing in a image library. Please read trough the article to get the changes to the CEWP code. The solution is tested in IE 8, Firefox v3.6.8 and in Google Chrome v5.0.375.125.

22.06.2010 Small update in line 118 and 125 to prevent “star” to be appended to lookup columns.

23.03.2010 Updated the code for “Preview_DispForm_metadata.js”. This update fixed compatibility with folders (thanks to Deano for finding the bug). Added support for a mix of lists and document libraries in the same webpart page.

19.02.2010 Fixed a bug if two different document libraries were present in the same webpartpage. The file “Preview_DispForm_metadata.js” is updated. Thanks to Ben for finding the bug.

09.01.2010: Fixed a major performance issue when viewing only selected fields from the metadata. Replace the code for the file “Preview_DispForm_metadata.js” to get the latest fixes.

10.12.2009: Fixed a hard coded “hoverImg” in the code – thanks to Amy.


By request from some of my readers i have, with basis in a solution created by Paul Grenier and published on EndUserSharepoint, made a solution that preview’s metadata from a list item or a document on mouse over.

The script i used as basis previewed metadata from DispForm in a CEWP beside a calendar view.

This modification adapts it to present a “floating” pop-up on mouse over when hovering over a table row, a calendar item, or on a small image added before a selected field.

The original script, made by Paul Grenier, previewed the full form from DispForm. I have adapted it so that one can display the full form, or specify an array of columns to display.

This script can be used in plain list view, grouped list views and calendar views.

New in this version is that the ID column must be in the view (not true for calendar view). The column can be hidden in the script.

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”):
IMG

The jQuery-library is found here. The pictures and the sourcecode refers to jquery-1.4.2.min. If you download another version, be sure to update the script reference in the sourcecode.

The sourcecode for the file “Preview_DispForm_metadata.js” is found below.

Add a CEWP below the list view and add the code – examples shown below the screen-shots.

Here are some screenshots and the CEWP code:
Plain list view – all fields
IMG

<script type="text/javascript" src="/test/English/Javascript/jquery-1.4.2.min.js"></script>
<script type="text/javascript" src="/test/English/Javascript/Preview_DispForm_metadata.js"></script>

To hide the ID column, change the CEWP code like this:

<script type="text/javascript" src="/test/English/Javascript/jquery-1.4.2.min.js"></script>
<script type="text/javascript" src="/test/English/Javascript/Preview_DispForm_metadata.js"></script>
<script type="text/javascript">
    hideIdColumn = true;
</script>

Plain list view – selected fields
IMG

<script type="text/javascript" src="/test/English/Javascript/jquery-1.4.2.min.js"></script>
<script type="text/javascript" src="/test/English/Javascript/Preview_DispForm_metadata.js"></script>
<script type="text/javascript">
    hideIdColumn = true;
    arrOfFieldsToShow = ['MultilinePlainText','Responsible'];
</script>

“hoverImg” used
IMG

<script type="text/javascript" src="/test/English/Javascript/jquery-1.4.2.min.js"></script>
<script type="text/javascript" src="/test/English/Javascript/Preview_DispForm_metadata.js"></script>
<script type="text/javascript">
    hideIdColumn = true;
    arrOfFieldsToShow = ['MultilinePlainText','Responsible'];
    hoverImg = '/_layouts/images/OPENDB.GIF';
    hoverImgDescription = 'Hover mouse over this image to preview the metadata'; // If left blank, no description is displayed in the pagetitle area
    prependHoverImageTo = 'LinkTitle'
</script>

Parameters explained:

  • hideIdColumn: true to hide the ID column. Defaults to false
  • arrOfFieldsToShow: Array of fields to show. Defaults to all fields.
    To have only a selection of fields, add to the array like this: [‘Title’,’MultilinePlainText,’Responsible’].
    To have only the value and not the label showing, add to the array like this: [‘MultilinePlainText|0’]
  • hoverImg: If you want to hover over an image and not the whole row, add the “src” to the image here. You must also set the parameter “prependHoverImageTo”.
  • prependHoverImageTo: A FieldInternalName to prepend the “hoverImg” to.
  • hoverImgDescription: A description that will be added to the page title area.

All parameters are optional.

Sourcecode for “Preview_DispForm_metadata.js” is found here

Note:

When new versions are released, they will be placed in a folder with the version number as label. Be sure to download the latest version.

If you are using a browser other than IE, right click the file and select “Save link as” or “Save linked content as…”.

Ask if something is unclear.

Regards
Alexander

Move site actions to the left

21.02.2010: Updated the post with the code for floating the site action menu to the right on the screen (as requested by Larry).

This one proved a bit tricky because the site action menu is part of the master page.

See codeblock below for updated code. Please let me know if you find any bugs.


After i posted the solution to Move view selector to the left, i got a request from Charlie Epes for the same solution regarding the “Site Actions-menu”.

This is the default placement of the Site Actions-menu:
IMG

Here the menu is inserted after the last toplink (dynamic):
IMG


Here is how it’s done
Add a CEWP below the list view, and add this code (alter the location of the jQuery-scipt as needed)

<script type="text/javascript" src="../../Javascript/jquery-1.3.2.min.js"></script>
<script type="text/javascript">
$("table.ms-bannerframe table:first td:last").after($("table.ms-bannerframe td:last"));
</script>

Code for floating the menu to the right (replace the code supplied above with this one):

<script type="text/javascript" src="../../Javascript/jquery-1.3.2.min.js"></script>
<script type="text/javascript">
// Site action float right
$(document).ready(function(){
	setTimeout(function(){
		siteActionFloatRight();
	},500);
});

function siteActionFloatRight(){
	var menuWidth = $("table.ms-siteaction").width();
	var doc = $(document).width();
	var win = $(window).width();
	var scr = $(window).scrollLeft();

	left = doc-win-scr+menuWidth;
	if(left<120){
		left=100;
	}
	// Move the site action menu to the new position
	$("table.ms-siteaction").css({'position':'absolute','top':0,'left':-left});	
}

// Handle resize and scroll
$(window).resize(function() {
	siteActionFloatRight();
}).scroll(function() {
	siteActionFloatRight();
});

// Make it adapt to changing document width due to expanding groups
$("td[class^='ms-gb']").click(function(){
	setTimeout(function(){
		siteActionFloatRight();
	},250);
});
</script>

Regards
Alexander

Move view selector to the left

06.02.2010 Added another method for floating the view selector on the right side of the page.


I got a request from “tecrms” that sounded like this:

As you know on all list views and document views, the view selector is on the right hand side of the menu bar. This can make it quite cumbersome for users looking at lists with many columns to change the view. A better option would be for the view selector to be on the left hand side and right hand side of the menu bar. I know I can move the view selector via SPD but would rather use a JavaScript options if one was available. Would this be something you would be interested in creating?

It’s often harder to think out the question than to actually solve the issue…

This is the default placement of the view-selector:
IMG

Here the selector is inserted after the “Settings-menu”:
IMG


Here is how it’s done
Add a CEWP below the list view, and add this code (alter the location of the jQuery-scipt as needed)

<script type="text/javascript" src="../../Javascript/jquery-1.3.2.min.js"></script>
<script type="text/javascript">
$("td.ms-toolbar:last").insertBefore($("td.ms-toolbar[width='99%']"));
</script>

Use this code for a floating menu on the right side of the page:

<script type="text/javascript" src="../../Javascript/jquery-1.3.2.min.js"></script>
<script type="text/javascript">
$(document).ready(function(){
	viewMenuFloatRight();
});

function viewMenuFloatRight(){
	var top = $("td.ms-toolbar:last").offset().top+2;
	var menuWidth = $("td.ms-toolbar:last table").width()+15;
	var left = $(window).width() - menuWidth + $(window).scrollLeft();	
	// Position the menu
	$("td.ms-toolbar:last table").css({'position':'absolute','top':top,'left':left});
	// Paging
	$("td.ms-toolbar td[id='topPagingCellWPQ1'] table").css({'position':'absolute','top':top,'left':left-50});
}

// Handle resize and scroll
$(window).resize(function() {
	viewMenuFloatRight();
}).scroll(function() {
	viewMenuFloatRight();
});
</script>

Regards
Alexander

Reformat URL from calculated column with decent clickable link text

19.11.2009 Fixed the bugs from yesterday…
18.11.2009 Added support for grouped views.
17.11.2009 Updated code to remove the filter in the viewheader for the field with the calculated column “Link”. Noted by Paulo Sousa in the post comments. I have also added a code to use for the DispForm to format the link.

If you create a link from a calculated column it is not formatted right in the list view. Here it a small jQuery script to fix this issue.

The list has these columns:
IMG

The code in the calculated column “Link” looks like this:

="<a title='The mouseover text goes here' href='"&URL&"'>"&Description&"</a>"

The NewForm looks like this:
IMG

The list view looks like this before the script is added:
IMG

And like this after the script is added:
IMG

How is it done?

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”):
IMG

The jQuery-library is found here. The pictures and the sourcecode refers to jquery-1.3.2.min. If you download another version, be sure to update the script reference in the sourcecode.

Add a CEWP below the list view like this:
IMG

With this code:

<script type="text/javascript" src="/test/English/Javascript/jquery-1.3.2.min.js"></script>
<script type="text/javascript">
// Updated 19.11.2009
// Reformat the url in the calculated column
reformatCalculatedColumnUrl();

function reformatCalculatedColumnUrl(){
	$(".ms-listviewtable td.ms-vb2:contains('href')").each(function(){
		$(this).html($(this).text())
	});
}

// If grouped by the calculated column - reformat the group header
var groupHeaderClickableLink = true; // Set to false to remove the link on the groupheader
$(".ms-listviewtable td[class^='ms-gb']").each(function(){
	if($(this).html().indexOf('<')>0){
		// Get full HTML of group header
		var rawHTML = $(this).html();
		// Extract the part before the calculated column's content
		var preWrap = rawHTML.substring(0,rawHTML.indexOf("<"));
		// Extract the part after the calculated column's content
		var postWrap = rawHTML.substring(rawHTML.lastIndexOf('>')+4);
	
		if(!groupHeaderClickableLink){
			// Find the clickable part of the calculated column's content 
			var linkTextStart = rawHTML.indexOf('>') + 4;
			var linkTextStop = rawHTML.lastIndexOf('<');
			var linkText = rawHTML.substring(linkTextStart,linkTextStop);
			// Write back the HTML
			$(this).html(preWrap + linkText + postWrap);
		}else{
			// Find the clickable part of the calculated column's content 
			var linkStart = rawHTML.indexOf('<');
			var linkStop = rawHTML.lastIndexOf('>') + 4;
			// Find raw link				
			var rawLink = rawHTML.substring(linkStart,linkStop);
			// Find the parts to keep
			var pre = rawLink.substring(0,rawLink.indexOf('href=') + 6);
			var mid = rawLink.substring(rawLink.lastIndexOf('href=')+6,rawLink.indexOf('>')-1);
			var post = rawLink.substring(rawLink.indexOf('>')-1);
			// Get the full url and replace the < and >
			var fullUrl = (pre + mid + post).replace(/</g,'<').replace(/>/g,'>');
			// Write back the HTML
			$(this).html(preWrap + fullUrl + postWrap);	
		}
	}
});

// Disable the filter for the field named "Link"
$(".ms-viewheadertr table[displayname='Link']").parents('th:first').removeClass('ms-vh2').addClass('ms-vh2-nograd').html("Link");

// Attaches a call to the function "reformatCalculatedColumnUrl" 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);
reformatCalculatedColumnUrl();
}
</script>

The “group by calculated column – feature” is requested by Paulo Sousa. You can specify whether to have a clickable link or a plain text in the group header by setting the parameter “groupHeaderClickableLink” in line 14 to true or false.

In line 50 in this codeblock i have disabled the filter link in the view header for the field with DisplayName “Link” – you must insert your own field’s DisplayName here.

I do not know of a way to “reformat” the filter values for a calculated column, and therefore the filter values will be like the calculated column’s raw value – therefore i found that disabling the filter was he best option.

For use with DispForm – add code to CEWP below list form (edit the location of jquery as needed):

<script type="text/javascript" src="/test/English/Javascript/jquery-1.3.2.min.js"></script>
<script type="text/javascript">
fields = init_fields();

// This is an array of FieldInternalNames for all fields to reformat
var arrOfFieldsToReformat = ['Link'];
// Loop trough all fields in array and reformat to decent link
$.each(arrOfFieldsToReformat,function(idx,item){
	var LinkField = $(fields[item]).find('.ms-formbody');
	LinkField.html(LinkField.text());
});

function init_fields(){
var res = {};
$("td.ms-formbody").each(function(){
if($(this).html().indexOf('FieldInternalName="')<0) return; 
var start = $(this).html().indexOf('FieldInternalName="')+19;
var stopp = $(this).html().indexOf('FieldType="')-7; 
var nm = $(this).html().substring(start,stopp);
res[nm] = this.parentNode;
});
return res;
}
</script>

In line 6 you must insert your own FieldInternalNames!
You can add multiple FieldInternalNames, comma separated.

This will result in a DispForm like this:
IMG

That’s it!

Regards
Alexander

Show Workflow history in Workflow status column in list view

In this post i will show you how to display the workflow history log in the workflow status column instead of just “In Progress” or “Completed”.

The default behavior is like this:
IMG

The status is “Completed”, but what did it do? A click on “Completed” brings up the workflow history log:
IMG

I think that is to complicated!

How about we display it like this:
IMG
Of course it is fully clickable, and takes you right to the good old workflow status page.

Here’s how it’s done

The list:
IMG

The workflow:
IMG
IMG

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”):
IMG
The code for the file “WorkflowHistoryInListView.js” is supplied below.

The jQuery-library is found here. The pictures and the sourcecode refers to jquery-1.3.2.min. If you download another version, be sure to update the script reference in the sourcecode.

The scripts “interaction.js” and stringBuffer.js” is created by Erucy and published on codeplex – you can find them here.

Add a CEWP below your ListView like this:
IMG

With this code:

<script type="text/javascript" src="/test/English/Javascript/jquery-1.3.2.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">
   WfListGuid = "{49293528-0c47-4150-b70c-77e876548d12}"; 
</script>
<script type="text/javascript" src="/test/English/Javascript/WorkflowHistoryInListView.js"></script>

You could also use WfListGuid = “Workflow History”; for a english language site – but remember that the displayname of your workflow history list will change depending of your site’s language. I personally always use list GUID.

Note! The workflow history list is only present in a site if there is at least one workflow configured. If it’s not present in your site – it will be created with your first workflow. All subsites has it’s own workflow history list.

The list GUID for your Workflow history list is found like this:
Browse to your list by its “displayname” – Workflow History for a english site (it’s hidden from browser’s and do not display in “View all site content”). When in doubt – use SharePoint designer to get the name of your list among the other lists in the “Lists-folder”.

When you have browsed to the list – right click and view source. Search for “listName” and you will find it like this:
ctx.listName = “{49293528-0C47-4150-B70C-77E876548D12}”;

Here’s the sourcecode for the file “WorkflowHistoryInListView.js”:

/* Display the "Workflow History" instead of just "In Progress" or "Completed" in the Workflow-status column
 * ---------------------------------------------
 * Created by Alexander Bautz
 * alexander.bautz@gmail.com
 * https://spjsblog.com
 * Version: 1.0
 * LastMod: 01.10.2009
 * ---------------------------------------------
*/

listGuid = ctx.listName; // SharePoint provides this

WriteLogg();
function WriteLogg(){
if(typeof(wfListObj)=="undefined")wfListObj = getWfLogg(); // If not already createt - build an "object" containing all WF-history for the current list
$('a[href*="WrkStat.aspx"]').each(function(){ // Find all a-tags that have a href containing WfkStat.aspx
	if($(this).text()!=''){	
		var wfGuidRaw = $(this).attr('href'); // Get the href
		var wfGuid = unescape(wfGuidRaw.substring(wfGuidRaw.lastIndexOf('WorkflowInstanceID=')+19)).toLowerCase(); // Get the GUID of the current WF from the href
		var wfLogg = wfListObj[wfGuid] // Get the actual history from the WF-object by "asking" for the log on this list items GUID
		if(wfLogg!=undefined){ // If the workflow history isn't empty
			wfLogg = wfLogg.split('|').join('<br>'); // Separate the lines		
			$(this).html(wfLogg); // Set the clickable part of the a-tag to the actual history from the log
		}	
	}
});
}

function getWfLogg(){
wsBaseUrl = L_Menu_BaseUrl + '/_vti_bin/'; // Set the path to the webservice "lists.asmx"
var query = "<Where><Eq><FieldRef Name='List' /><Value Type='Text'>" + listGuid + "</Value></Eq></Where>"; // Get all WF-history for the current list	
	var res = queryItems(WfListGuid,query,['ID','WorkflowInstance','Description']); 
	obj = {};
		if(res.count == -1){
			alert("An error occured in the query:n" + query); // On error
		}else{
			$.each(res.items,function(idx,item){
				if(item['Description']!=null){
					// Is there history already logged on this GUID?
					if(obj[item['WorkflowInstance']]==undefined){ 
						// No
						obj[item['WorkflowInstance']] = item['Description']; 
					}else{
						// Yes - add to it so that all log on current WF-run is displayed together
						obj[item['WorkflowInstance']] = obj[item['WorkflowInstance']] + "|" + item['Description']; 
					}
				}	
			});
			return obj;	// Return object containing all WF-history on current list	
		}	
}

// 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);
WriteLogg(); // Call the script - the rest of the function "ExpGroupRenderData" is a unmodified SharePoint function from the file "BFORM.JS"
}

That’s it!

Feel free to ask if something is unclear.
Alexander