Category Archives: Requests

Ratings for SharePoint lists

03.03.2010 Updated code for the file “RatingForSharePoint.js” due to a bug occurring when the ID column was hidden, but not placed far right in the view.

I got this request after enabling “rating” of the blog comments:

…How can we implement the thumbs up or down functionality you have on your site to a sharepoint list?

Tony


I have made a solution that uses a separate list to hold all the ratings. There will be written one line in this list for each rating of an item (list item or document).

This solution allows for one rating per item, per user, per session (browser session – new window = new session). There are an option to restrict or allow multiple ratings per user in different sessions.

The rating of the items are available in a list view (plain type, no boxed style), and in grouped plain list views. It is also available in DispForm.

ListView:
IMG

DispForm:
IMG

DispForm if already rated:
IMG

To enable rating of items, you add a calculated column to your list with this code:

=""

Yes, only “equals” and two double quotes. You then assures that this new calculated column and the ID column is visible in the list view (there is an option in the script to hide the ID column).

Then you create a custom list for the ratings, with the name: RatingForSharePoint, and add these fields:

  • Plus The type of information in this column is: Number
  • Minus The type of information in this column is: Number
  • ListUrl The type of information in this column is: Single line of text
  • ItemID The type of information in this column is: Single line of text
  • ListGuid The type of information in this column is: Single line of text
  • ListName The type of information in this column is: Single line of text

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.

The scripts “interaction.js” and “stringBuffer.js” is created by Erucy and published on CodePlex.

The file “sessvars.js” is found here.

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

Add a CEWP below the listView and add 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" src="/test/English/Javascript/sessvars.js"></script>
<script type="text/javascript" src="/test/English/Javascript/RatingForSharePoint.js"></script>
<script type="text/javascript">
ratingForSharePoint('Rating',true,true);
</script>

Parameters explained:

  • FieldInternalName: FieldInternalName of the calculated column created above.
  • hideIdCol: true to hide the ID column, false to let it be visible.
  • rateOnlyOnce: true to allow only one rating per user, false to allow multiple ratings (in different sessions)

Add a CEWP below your DispForm, and add 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" src="/test/English/Javascript/sessvars.js"></script>
<script type="text/javascript" src="/test/English/Javascript/RatingForSharePoint.js"></script>
<script type="text/javascript">
// You must edit this GUID to match THIS list's GUID - this is NOT the GUID of the "RatingForSharePoint"
thisListsGuid = '{77C35652-1D4D-4B09-B58C-D941068D251E}';
ratingForSharePointDispForm('Rating',true);
</script>

Parameters explained:

  • thisListsGuid: This list’s GUID – this is NOT the GUID of the “RatingForSharePoint”
  • FieldInternalName: FieldInternalName of the calculated column created above
  • rateOnlyOnce: true to allow only one rating per user, false to allow multiple ratings (in different sessions)

Read here how to add a CEWP to the NewForm, DispForm or EditForm, how to find the list Guid of your list, and how to find the FieldInternalName of your columns.

The sourcecode for the file “RatingForSharePoint.js” looks like this:

/* Rating for SharePoint lists
 * ---------------------------------------------
 * Created by Alexander Bautz
 * alexander.bautz@gmail.com
 * https://spjsblog.com
 * v1.1
 * LastMod: 03.03.2010
 * ---------------------------------------------
 * Must include reference to:
 *  jQuery - http://jquery.com
 *  interaction.js - http://spjslib.codeplex.com
 *  stringBuffer.js - http://spjslib.codeplex.com
 *  sessvars.js - http://www.thomasfrank.se
 *  RatingForSharePoint.js - This file
 * ---------------------------------------------
*/

// If anonymous the SharePoint variable "_spUserId" does not exist
if(typeof(_spUserId)=='undefined'){
	_spUserId = '';
}
/************************************************************
*********************** ListView Code ***********************
************************************************************/
function ratingForSharePoint(FieldInternalName,hideIdCol,rateOnlyOnce){
if(typeof(ctx)=='object'){
	var thisListsGuid=ctx.listName;
}
	if(typeof(currentRatingObj)=='undefined'){
		currentRatingObj = getRating();
	}
	if(typeof(FieldInternalName)!='undefined'){
		intName = FieldInternalName;
		hideId = hideIdCol;
		rateOnce = rateOnlyOnce;
		// Fiend index of "Rating" column
		$(".ms-viewheadertr th").each(function(){
			if($(this).find('table:first').attr('name')==intName){
				colIndex = $(this).attr('cellIndex');
				displayName = $(this).find('table:first').attr('displayname');
				// Remove filtering possibility
				$(this).removeClass('ms-vh2').addClass('ms-vh2-nograd').html(displayName);
			}
		});
		// Find index of ID column
		$(".ms-viewheadertr th").each(function(){
			if($(this).find('table:first').attr('name')=='ID'){
				IdColIndex = $(this).attr('cellIndex');
				// Hide ID column
				if(hideId){
					$(this).remove();
				}
			}
		});
		if(typeof(colIndex)=='undefined' || typeof(IdColIndex)=='undefined'){
			alert("Both the column with FieldInternalName ""+FieldInternalName+"" and the ID-column must be in the view.");
			return false;
		}
	}

	$("table.ms-listviewtable tbody:not([id^='aggr']) >tr[beenthere!=1]:has(td.ms-vb2)").each(function(){
		$(this).attr('beenthere',1);
		var itemAlreadyRate = false;
		var thisTd = $(this).find("td[cellIndex=" + colIndex + "]");
		var thisIdColumn = $(this).find("td[cellIndex=" + IdColIndex + "]");
		var thisId = thisIdColumn.text()
		if(hideId){
			thisIdColumn.remove();
		}
		var rPos = 0;
		var rNeg = 0;
		if(currentRatingObj[thisId]!=undefined){
			// Previously rated by current user
			if(rateOnce && currentRatingObj[thisId]['ratedByMe']==true){
				itemAlreadyRate = true;
			}
			rPos = currentRatingObj[thisId]['Plus'];
			rNeg = currentRatingObj[thisId]['Minus'];
		}
		// Rated in this session
		if(sessvars[thisListsGuid+thisId]==1 && currentRatingObj!=false){
			itemAlreadyRate = true;
		}

		var str = "<div style='color:gray'>";
		str += "<span><img ";
		if(!itemAlreadyRate){
			str += "onclick='javascript:rateMe("up","+thisId+")' title='Rate Up' ";
			str += "style='vertical-align:middle;cursor:pointer' ";
		}else{
			str += "title='You have already rated this item "+currentRatingObj[thisId]['ratedByMeVal']+"' ";
			str += "style='vertical-align:middle;cursor:no-drop' ";
		}
		str += "src='/_layouts/images/arrupi.gif'></span>";
		str += "<span style='font-weight:bold;' id='rateUp_"+thisId+"'>"+rPos+"</span>";
		str += "<span><img ";
		if(!itemAlreadyRate){
			str += "onclick='javascript:rateMe("down","+thisId+")' title='Rate Down' ";
			str += "style='vertical-align:middle;cursor:pointer' ";
		}else{
			str += "title='You have already rated this item "+currentRatingObj[thisId]['ratedByMeVal']+"' ";
			str += "style='vertical-align:middle;cursor:no-drop' ";
		}
		str += "src='/_layouts/images/arrdowni.gif'></span>";
		str += "<span style='font-weight:bold;' id='rateDown_"+thisId+"'>"+rNeg+"</span>";
		str += "</div>";
		// Write HTML
		thisTd.html(str);
	});
}

function getRating(){
// Path to webservices
wsBaseUrl = L_Menu_BaseUrl + '/_vti_bin/';
var query = "<Where><Eq><FieldRef Name='ListGuid' /><Value Type='Text'>"+ctx.listName+"</Value></Eq></Where>";
var rating = queryItems('RatingForSharePoint',query,['Plus','Minus','ItemID','Author']);
	if(rating.count==-1){
		alert("An error occured in the query:n"+query);
	}else if(rating.count>0){
	ratingObj = {};
		$.each(rating.items,function(i,item){
		var authorIdRaw = item['Author'];
		var authorId = authorIdRaw.substring(0,authorIdRaw.indexOf(';#'));
		var plusVal = item['Plus'];
		var minusVal = item['Minus'];
		if(plusVal==null)plusVal=0;
		if(minusVal==null)minusVal=0;
			if(ratingObj[item['ItemID']]==undefined){
				ratingObj[item['ItemID']]={'Plus':parseInt(plusVal),'Minus':parseInt(minusVal)};
			}else{
				ratingObj[item['ItemID']]['Plus']+=parseInt(plusVal);
				ratingObj[item['ItemID']]['Minus']+=parseInt(minusVal);
			}
			// Rated by current user
			if(_spUserId==authorId){
				ratingObj[item['ItemID']]['ratedByMe']=true;
				if(item['Plus']>0){
					ratingObj[item['ItemID']]['ratedByMeVal']="+1";
				}else{
					ratingObj[item['ItemID']]['ratedByMeVal']="-1";
				}
			}
		});
		return ratingObj;
	}else{
		return false;
	}
}

/************************************************************
*********************** DispForm Code ***********************
************************************************************/
function ratingForSharePointDispForm(FieldInternalName,rateOnlyOnce){
	var queryStr = getQueryParameters();
	if(typeof(fields)=='undefined')fields = init_fields();
	var itemAlreadyRate = false;
	var thisId = queryStr.ID;
	var currentRatingObj = getRatingDispForm(thisId);
	if(currentRatingObj[thisId]==undefined){
		rPos = 0;
		rNeg = 0;
	}else{
		// Previously rated by current user
		if(rateOnlyOnce && currentRatingObj[thisId]['ratedByMe']==true){
			itemAlreadyRate = true;
		}
		rPos = currentRatingObj[thisId]['Plus'];
		rNeg = currentRatingObj[thisId]['Minus'];
	}
	// Rated in this session
	if(sessvars[thisListsGuid+thisId]==1 && currentRatingObj!=false){
		itemAlreadyRate = true;
	}
	var thisTd = $(fields[FieldInternalName]).find('.ms-formbody');
	var str = "<div style='color:gray'>";
	str += "<img ";
	if(!itemAlreadyRate){
		str += "onclick='javascript:rateMe("up","+thisId+")' title='Rate Up' ";
		str += "style='vertical-align:middle;cursor:pointer' ";
	}else{
		str += "title='You have already rated this item "+currentRatingObj[thisId]['ratedByMeVal']+"' ";
		str += "style='vertical-align:middle;cursor:no-drop' ";
	}
	str += "src='/_layouts/images/arrupi.gif'>";
	str += "<span style='font-weight:bold;' id='rateUp_"+thisId+"'>"+rPos+"</span>";
	str += "<img ";
	if(!itemAlreadyRate){
		str += "onclick='javascript:rateMe("down","+thisId+")' title='Rate Down' ";
		str += "style='vertical-align:middle;cursor:pointer' ";
	}else{
		str += "title='You have already rated this item "+currentRatingObj[thisId]['ratedByMeVal']+"' ";
		str += "style='vertical-align:middle;cursor:no-drop' ";
	}
	str += "src='/_layouts/images/arrdowni.gif'>";
	str += "<span style='font-weight:bold;' id='rateDown_"+thisId+"'>"+rNeg+"</span>";
	str += "</div>";
	// Write HTML
	thisTd.html(str);
}

function getRatingDispForm(itemID){
// Path to webservices
wsBaseUrl = L_Menu_BaseUrl + '/_vti_bin/';
var query = "<Where><And><Eq><FieldRef Name='ListGuid' /><Value Type='Text'>"+thisListsGuid+"</Value></Eq>"+
			"<Eq><FieldRef Name='ItemID' /><Value Type='Text'>"+itemID+"</Value></Eq></And></Where>";
var rating = queryItems('RatingForSharePoint',query,['Plus','Minus','ItemID','Author']);
	if(rating.count==-1){
		alert("An error occured in the query:n"+query);
	}else if(rating.count>0){
	ratingObj = {};
		$.each(rating.items,function(i,item){
			var authorIdRaw = item['Author'];
			var authorId = authorIdRaw.substring(0,authorIdRaw.indexOf(';#'));
			var plusVal = item['Plus'];
			var minusVal = item['Minus'];
			if(plusVal==null)plusVal=0;
			if(minusVal==null)minusVal=0;
			if(ratingObj[item['ItemID']]==undefined){
				ratingObj[item['ItemID']]={'Plus':parseInt(plusVal),'Minus':parseInt(minusVal)};
			}else{
				ratingObj[item['ItemID']]['Plus']+=parseInt(plusVal);
				ratingObj[item['ItemID']]['Minus']+=parseInt(minusVal);
			}
			// Rated by current user
			if(_spUserId==authorId){
				ratingObj[item['ItemID']]['ratedByMe']=true;
				if(item['Plus']>0){
					ratingObj[item['ItemID']]['ratedByMeVal']="+1";
				}else{
					ratingObj[item['ItemID']]['ratedByMeVal']="-1";
				}
			}
		});
		return ratingObj;
	}else{
		return false;
	}
}

/************************************************************
*********************** Shared Code ***********************
************************************************************/
function rateMe(upORdown,id){
if(typeof(ctx)=='object'){
	var listGuid=ctx.listName;
	var listTitle = $.trim($("td.ms-pagetitle").text());
}else{
	var listGuid=thisListsGuid;
	var listTitle = $.trim($("td.ms-pagetitle a").text());
}
// Only rate item if not already rated in this session
if(sessvars[listGuid+id]==undefined){
	plus='';
	minus='';
	if(upORdown=='up'){
		sessvars[listGuid+id]=1;
			currVal = parseInt($("#rateUp_"+id).text());
			$("#rateUp_"+id).text(currVal+1)
				.prev().css({'background-color':'#C5E3BF'})
				.parent().find('img').attr({'onclick':'','title':'Rated +1'});
			// Prepare for writing rating
			plus=1;
	}else{
		sessvars[listGuid+id]=1;
		currVal = parseInt($("#rateDown_"+id).text());
		$("#rateDown_"+id).text(currVal+1)
			//.parent().css({'background-color':'#FFE4E1'})
			.prev().css({'background-color':'#FFE4E1'})
			.parent().find('img').attr({'onclick':'','title':'Rated -1'});
		// Prepare for writing rating
		minus=1;
	}

		// Get url
		var urlDir = location.pathname;

		// Path to webservices
		wsBaseUrl = L_Menu_BaseUrl + '/_vti_bin/';
		// Add item
		var res = addItem('RatingForSharePoint',
							{'Title':"Rating for: " + listTitle,
							'Plus':plus,
							'Minus':minus,
							'ListUrl':urlDir,
							'ListGuid':listGuid,
							'ListName':listTitle,
							'ItemID':id});
	}
}

// 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);
ratingForSharePoint();
}

// Function to separate each url search string parameters
function getQueryParameters(){
qObj = {};
var urlSearch = window.location.search;
	if(urlSearch.length>0){
		var qpart = urlSearch.substring(1).split('&');
		$.each(qpart,function(i,item){
			var splitAgain = item.split('=');
			qObj[splitAgain[0]] = splitAgain[1];
		});
	}
return qObj;
}

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;
}

Save as “RatingForSharePoint.js”, mind the file extension, and upload to the scriptlibrary as shown above.

Ask is something is unclear, and please report any bugs.

Regards
Alexander

Multiple default values in a column of type “Checkboxes”

I got this request from Larry:

choice – Multiple select, check boxes, is there a way to have multiple default values. I may want the same 3 items to always be selected. by default SP only allows one option. This field for 90% may need those 3 requests. Can it be done?


Add this code to a CEWP below the form in NewForm or EditForm: Alter the reference to jQuery as needed.

<script type="text/javascript" src="../../Javascript/jquery-1.3.2.min.js"></script>
<script type="text/javascript">
/* Multiple default values in a column of type "Checkboxes (allow multiple selections)"
 * Created by Alexander Bautz
 * alexander.bautz@gmail.com
 * https://spjsblog.com
 * v1.0
 * LastMod: 22.02.2010
 *	
 Sets the default value of a column of type "Checkboxes (allow multiple selections)",
 by passing the FieldInternalName of the SharePoint-field with an array of values to prefill.
*/
fields = init_fields();
// Define the array if values
var myArr = ['Choice 4','Choice 9'];
// Call the function with the FieldInternalName of the field and the array.
setMultiSelectDefaultValues('MyMultiChoice',myArr);

function setMultiSelectDefaultValues(FieldInternalName,arrOfValues){
	$(fields[FieldInternalName]).find('input:checkbox').each(function(){
		var thisText = $(this).next().text();
		if($.inArray(thisText,arrOfValues)>-1){
			$(this).attr('checked',true);
		}
	});
}

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>

You also might want to take a look at this one:
Limit number of allowed selections in Checkboxes choice

Regards
Alexander

Expand empty subgroups and remove group header in grouped list view

I got this request from Kale:

Do you have an ideas on how to remove “empty/blank” items when using the “group by” feature? See the example below. In other words, for category ‘Test 1′ you have to expand the blank item just to see the documents since they don’t have any subcategories assigned to them.

Example:
* Category: Test 1
* Subcategory 1:
– Document 1
– Document 2
* Category: Test 2
* Subcategory: SharePoint
– Document 3
– Document 4
* Subcategory: CRM
– Document 5
– Document 6

This is how I want it to work:
* Category: Test 1
– Document 1
– Document 2
* Category: Test 2
* Subcategory: SharePoint
– Document 3
– Document 4
etc.

Thanks!
Kale

This script checks if the “groupBy” is empty, and if so, expands the content of the group and hides the group header.

Add this script in a CEWP below the list view – alter the location of jQuery to reflect your location:

<script type="text/javascript" src="../../Javascript/jquery-1.3.2.min.js"></script>
<script type="text/javascript">
$(document).ready(function(){
	// Add onclick to the first grouping
	$(".ms-listviewtable td.ms-gb").click(function(){
		expandEmptyCategory();
	});	
	// Call function on load
	isTbodyLoaded();	
});

var isloadedCheck = 0;
// Looping function to call "expandEmptyCategory" when subgroups are loading - looping every 200ms for a maximum of 4 seconds
function isTbodyLoaded(){
	setTimeout(function(){
		tbodyIsLoadedLength = $(".ms-listviewtable tbody[isLoaded='true']").length;
		if(tbodyIsLoadedLength>0){
			expandEmptyCategory();
		}else{
			isloadedCheck += 1;
			if(isloadedCheck<=20){
				isTbodyLoaded();
			}
		}
	},200);
}

function expandEmptyCategory(){
var NameGrouping2 = $(".ms-listviewtable td.ms-gb2:first a:eq(1)").text();
	$(".ms-listviewtable td.ms-gb2").each(function(){	
		var grNameRaw = $(this).text().replace(NameGrouping2+' :','');
		grName = grNameRaw.substring(0,grNameRaw.indexOf('(')-1).replace(/s|xA0/g,'');
		if(grName.length==0){
		var parentTbody = $(this).parents('tbody:first');
			if(parentTbody.css('display')!='none'){
			var tIdRaw = parentTbody.attr('id');
			var tId = tIdRaw.substring(4);
			var tb = $("#tbod"+tId+"_");
				if(tb.attr('isLoaded')){
					if(tb.css('display')=='none'){
						$(this).find('a:first').click();
						$(this).parents('tbody:first').hide();
					}else{
						$(this).parents('tbody:first').hide();
					}
				}
			}		
		}	
	});
}
</script>

This solution is not fully tested so please post any bugs you might find.

SharePoint 2013

Here is a version that works with SP 2013. Due to the asynch loading of the view, this one uses a setInterval, and an initial delay to try to let the view finish loading before kicking in. Try it and see how it behaves, but keep in mind this is most likely not a 100% solution.

Place code in a HTML form web part, or linked using the content link option of a CEWP below the list view.

<script type="text/javascript" src="https://spjsblog.sharepoint.com/DFFS/SPJS/jquery-1.11.1.min.js"></script>
<script type="text/javascript">
$(document).ready(function(){
	setTimeout(function(){
		$("td.ms-gb").each(function(){
			$(this).parents("tbody:first").on("click",function(){
				$(this).next().find(".spjs_expanded").removeClass("spjs_expanded");
			});
		});
		setInterval(function(){
			$("td.ms-gb2:visible:not(.spjs_expanded)").each(function(i,td){
				var a = $(td).find("a:first").parent().text().split(":")[1].split("(")[0], b, c, d;
				if($.trim(a) === ""){
					b = $(td).parents("tbody:first").attr("id");
					if(typeof b === "string"){
						b = b.substring(4);
						c = $("#tbod"+b+"_").attr("isloaded") === "true";
						d = $("#tbod"+b+"_").is(":visible");
						if(!c || !d){
							$(this).find("a:first").trigger("click");
						}
						$(this).addClass("spjs_expanded");
						$(this).parents("tbody:first").hide();
					}
				}
			});		
		},500);
	},1000);
});
</script>

Alexander

SharePoint form: present fields side-by-side

I got this request from Khan:

Alexander,
… Another question I have is how I can display two fields (and their labels) in the same row? I know I am asking too much. But I really hope you can give me some advice o this. Thanks man.

Here is an image of a few random fields put side-by-side:
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 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 your NewForm/DispForm/EditForm with this code:

&lt;script type=&quot;text/javascript&quot; src=&quot;/test/English/Javascript/jquery-1.3.2.min.js&quot;&gt;&lt;/script&gt;
&lt;script type=&quot;text/javascript&quot; src=&quot;/test/English/Javascript/FieldsSideBySide.js&quot;&gt;&lt;/script&gt;
&lt;script type=&quot;text/javascript&quot;&gt;
  // Append fields to the field &quot;User&quot;
  arrToPutSideBySide = ['StartTime','EndTime'];
  customSideBySide(arrToPutSideBySide,'User');
  // Appends another set of fields to the field &quot;User&quot;
  arrToPutSideBySide = ['FirstName','MiddleName','LastName'];
  customSideBySide(arrToPutSideBySide,'User');

  // To append the fields to the top use this code:
  //arrToPutSideBySide = ['FirstName','MiddleName','LastName'];
  //customSideBySide(arrToPutSideBySide,'',true);
&lt;/script&gt;

This code is not the full code from the screenshot above. Each horizontal “section” is made with a separate call to the function “customSideBySide()”.

Parameters explained:

  • arr: Array or fields to put in one line.
  • insertAfterField: If you want to append the fields below another field, insert the FieldInternalName here. Set this parameter to “” if you want to use the next parameter.
  • appendToTop: To insert the fields in the top of the form, set it to true. To insert at the bottom of the form, set it to false.

The sourcecode for the file “FieldsSideBySide.js” looks like this:

/* Sharepoint form: Present fields side-by-side
 * ---------------------------------------------
 * Created by Alexander Bautz
 * alexander.bautz@gmail.com
 * https://spjsblog.com
 * v1.0
 * LastMod: 26.01.2010
 * ---------------------------------------------
 * Must include reference to:
 *  jQuery - http://jquery.com
 * ---------------------------------------------
*/

function customSideBySide(arr,insertAfterField,appendToTop){
if(typeof(fields)=='undefined')fields = init_fields();
// Does the field specified as &quot;insertAfterField&quot; exist?
if(insertAfterField!='' &amp;&amp; typeof(fields[insertAfterField])=='undefined'){
	alert(&quot;The FieldInternalName &quot;&quot; + insertAfterField + &quot;&quot;, which is specified as &quot;insertAfterField&quot; does not exist.&quot;);
	return false;
}
	var str = '';
	$.each(arrToPutSideBySide,function(i,fin){
		// Check if FieldInternalName is correct
		if(typeof(fields[fin])=='undefined'){
			alert(&quot;The FieldInternalName &quot;&quot; + fin + &quot;&quot; does not exist.&quot;);
			return false;
		}
		// Adapt width of single line and multi lines of text
		if($(fields[fin]).html().indexOf('FieldType=&quot;SPFieldText&quot;')&gt;0){
			$(fields[fin]).find('input').css({'width':'100%'});
		}else if($(fields[fin]).html().indexOf('FieldType=&quot;SPFieldNote&quot;')&gt;0){
			$(fields[fin]).find('textarea').css({'width':'100%'});
		}
		var tdWidth = Math.round(100/arr.length);
		str += &quot;&lt;td class='ms-formbody' style='width:&quot;+tdWidth+&quot;%'&gt;&quot; + $(fields[fin]).find('.ms-formlabel').html() + $(fields[fin]).find('.ms-formbody').html() + &quot;&lt;/td&gt;&quot;;
		// Remove original field
		$(fields[fin]).remove();			
	});
	str = &quot;&lt;tr&gt;&lt;td colspan='2'&gt;&lt;table cellpadding='0' cellspacing='0' style='width:100%'&gt;&lt;tr&gt;&quot; + str + &quot;&lt;/tr&gt;&lt;/table&gt;&lt;/td&gt;&lt;/tr&gt;&quot;;	
	if(insertAfterField!=''){
		$(fields[insertAfterField]).after(str);
	}else{
		if(appendToTop){
			$(&quot;table.ms-formtable tbody:first&quot;).prepend(str);
		}else{
			$(&quot;table.ms-formtable tbody:first&quot;).append(str);
		}
	}
}

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

Save as “FieldsSideBySide.js” – mind the file extension, and upload to the script library as shown above.

Ask if something is unclear.

Regards
Alexander

How to use these scripts in a customized form

03.08.2010 Updated the article with another example on how to build the fields-object in a customized form.


This article describes how to use most of the solutions found in this blog with a customized form. The scripts which this article addresses, is the ones involving the function “init_fields()_v2”.

This was originally an answer to a comment in one of the articles.


I get this question a lot:
How can i use these scripts in a customized form?

The answer is simple, but i will explain it so that you understand why it’s not working.

This script relays in the function init_fields() to identify and “objectify” the form by it’s <TR> tags (table rows). What the function does is to find all the FieldInternalNames in the code by this “information-tag”:

&lt;!-- FieldName=&quot;This is the DisplayName&quot;
FieldInternalName=&quot;ThisIsTheFieldInternalName&quot;
FieldType=&quot;SPFieldText&quot;
--&gt;

It then make an object which, when called with the FieldInternalName of a field, returns an object containing that table row (both formlabel and formtable).

When initiated like this:

fields = init_fields_v2();

You address a <TR> like this:

$(fields[FieldInternalName])

Where “FieldInternalName” is the FieldInternalName of your field. Read here how to identify the FieldInternalName.

This is the original function for use in standard SharePoint forms:

function init_fields_v2(){
	var res = {};
	$(&quot;td.ms-formbody&quot;).each(function(){
	var myMatch = $(this).html().match(/FieldName=&quot;(.+)&quot;s+FieldInternalName=&quot;(.+)&quot;s+FieldType=&quot;(.+)&quot;s+/);	
		if(myMatch!=null){
			// Display name
			var disp = myMatch[1];
			// FieldInternalName
			var fin = myMatch[2];
			// FieldType
			var type = myMatch[3];
			if(type=='SPFieldNote'){
				if($(this).find('script').length&gt;0){
					type=type+&quot;_HTML&quot;;
				}
			}
			if(type=='SPFieldLookup'){
				if($(this).find('input').length&gt;0){
					type=type+&quot;_Input&quot;;
				}
			}
			// Build object
			res[fin] = this.parentNode;
			res[fin].FieldDispName = disp;
			res[fin].FieldType = type;
		}		
	});
	return res;
}

Customized form

When you modify the list form in SharePoint Designer, you loose the FieldInternalName and therefore the function init_fields_v2() does not return anything.

To address the fields on a custom form you have to find another way of addressing these fields and to “objectify” them in the same way as the script init_fields_v2(). Basically you could insert some kind of “identifier-attribute” on the <TR> tags, or you can go with the displayName – only remember that if you change the displayName – you have to update your script…

To use the a custom made ID attribute, replace the init_fields_v2() function with this one:

function init_fields_v2(){
  var res = {};
  $(&quot;td.ms-formlabel&quot;).each(function(){
  	var thisID = $(this).attr('id');
	  res[thisID] = $(this).parents('tr:first');
  });
  return res;
}

This approach requires that you add a unique ID attribute to the <TD> holding the formlabel. Here is an example of code from a customized form:

&lt;table border=&quot;0&quot; cellspacing=&quot;0&quot; width=&quot;100%&quot;&gt;
	&lt;tr&gt;
		&lt;td width=&quot;190px&quot; valign=&quot;top&quot; class=&quot;ms-formlabel&quot; id=&quot;TitleColumnCustomID&quot;&gt;
			&lt;H3 class=&quot;ms-standardheader&quot;&gt;
				&lt;nobr&gt;Title&lt;span class=&quot;ms-formvalidation&quot;&gt; *&lt;/span&gt;
				&lt;/nobr&gt;
			&lt;/H3&gt;
		&lt;/td&gt;
		&lt;td width=&quot;400px&quot; valign=&quot;top&quot; class=&quot;ms-formbody&quot;&gt;
			&lt;SharePoint:FormField runat=&quot;server&quot; id=&quot;ff1{$Pos}&quot; ControlMode=&quot;New&quot; FieldName=&quot;Title&quot; __designer:bind=&quot;{ddwrt:DataBind('i',concat('ff1',$Pos),'Value','ValueChanged','ID',ddwrt:EscapeDelims(string(@ID)),'@Title')}&quot;/&gt;
			&lt;SharePoint:FieldDescription runat=&quot;server&quot; id=&quot;ff1description{$Pos}&quot; FieldName=&quot;Title&quot; ControlMode=&quot;New&quot;/&gt;
		&lt;/td&gt;
	&lt;/tr&gt;
	&lt;tr&gt;
		&lt;td width=&quot;190px&quot; valign=&quot;top&quot; class=&quot;ms-formlabel&quot; id=&quot;ChoiceColumnCustomID&quot;&gt;
			&lt;H3 class=&quot;ms-standardheader&quot;&gt;
				&lt;nobr&gt;MyChoiceColumn&lt;/nobr&gt;
			&lt;/H3&gt;
		&lt;/td&gt;
		&lt;td width=&quot;400px&quot; valign=&quot;top&quot; class=&quot;ms-formbody&quot;&gt;
			&lt;SharePoint:FormField runat=&quot;server&quot; id=&quot;ff2{$Pos}&quot; ControlMode=&quot;New&quot; FieldName=&quot;MyChoiceColumn&quot; __designer:bind=&quot;{ddwrt:DataBind('i',concat('ff2',$Pos),'Value','ValueChanged','ID',ddwrt:EscapeDelims(string(@ID)),'@MyChoiceColumn')}&quot;/&gt;
			&lt;SharePoint:FieldDescription runat=&quot;server&quot; id=&quot;ff2description{$Pos}&quot; FieldName=&quot;MyChoiceColumn&quot; ControlMode=&quot;New&quot;/&gt;
		&lt;/td&gt;
	&lt;/tr&gt;
&lt;/table&gt;

Note the id attributes “TitleColumnCustomID” and “ChoiceColumnCustomID” in line 03 and 15.

To “get” the Title field you address it like this:

fields = init_fields_v2();
var myTitleField = $(fields['TitleColumnCustomID']);
alert(myTitleField.html());

Ask if anything is unclear.

Regards
Alexander

SharePoint form: scroll to field as with an anchor tag

I got this request from Cookie:

Is there a way to create an anchor within a NewForm or EditForm? I have a long intake, or it could be long. When I assign the the item to an owner and provide them a link to the form, it would be easier to have an addition to the link with the anchor that links to a specific field in the form. Maybe a script that uses the fieldinternalname and converts that to the anchor name for simplicity.

Place this code in a CEWP below the form in NewForm, EditForm or DispForm:

&lt;script type=&quot;text/javascript&quot; src=&quot;../../Javascript/jquery-1.3.2.min.js&quot;&gt;&lt;/script&gt;
&lt;script type=&quot;text/javascript&quot;&gt;
/*
  This script is used to be able to scroll to a field based upon a url query string parameter like this:
  .../EditForm.aspx?ID=1&amp;focus=FieldInternalNameOfFieldToFocusOn
*/

fields = init_fields();

// If the query string parameter &quot;focus&quot; is set, and a field with that FieldInternalName is found, scroll to that field
$(document).ready(function(){
	var q = getQueryParameters();
	if(q['focus']!=undefined){
		var fin = q['focus'];
		if(fields[fin]!=undefined){
			var offset = $(fields[fin]).offset();
			scrollTo(0,offset.top);
		}
	}
});

// Function to separate each url search string parameters
function getQueryParameters(){
qObj = {};
var urlSearch = window.location.search;
	if(urlSearch.length&gt;0){
		var qpart = urlSearch.substring(1).split('&amp;');
		$.each(qpart,function(i,item){
			var splitAgain = item.split('=');
			qObj[splitAgain[0]] = splitAgain[1];
		});
	}
return qObj;
}

// Function to make an object of all tr-tags in the form
function init_fields(){
var res = {};
$(&quot;td.ms-formbody&quot;).each(function(){
if($(this).html().indexOf('FieldInternalName=&quot;')&lt;0) return; 
var start = $(this).html().indexOf('FieldInternalName=&quot;')+19;
var stopp = $(this).html().indexOf('FieldType=&quot;')-7; 
var nm = $(this).html().substring(start,stopp);
res[nm] = this.parentNode;
});
return res;
}
&lt;/script&gt;

Make your links up like this:
…/EditForm.aspx?ID=1&focus=FieldInternalNameOfFieldToFocusOn

Look here on how to find the FieldInternalName of your field.

Regards
Alexander

Add resize handle to multiline text field (plain text, rich text and enhanced rich text)

13.03.2011: Updated the code and made it easier to use by referring jQuery and jQueryUI from Google.


I got this request from Larry:

Maybe this time I can post something we can get to work. I have been trying to peice together several scripts, including yours to make the multiple line text fields expandable. I found a workiong script but it only worked on plain text. I am trying to modify it to work on rich text. When i add your script for fieldinternalname I can at least see the image under the rich text box, but it does not work. using the new script I can not get the plain text to work. this is the link to the jquery plugin for textarea resizer…

The plain text multi lines are no problem, but the rich text and enhanced rich text ones are a bit tricky. They are made up of <IFRAME>’s, and the input does not go in a <textarea> tag.

I have made this solution based on the jQuery UI “Interaction” Resizable.

Internet Explorer 8:
IMG
Firefox 3.5.7:
IMG

Add a CEWP below your NewForm and EditForm and add this code:
Get the code here

Ask if anything is unclear.

Regards
Alexander

Add a Success Ratio label to the first group header in a dual grouped list view

27.01.2012 Updated to support SharePoint 2010.


By request from Charlie Epes i have made a function for adding a “Success Ratio” to the ms-gb-row (first group in grouped list view):
IMG

This function takes two arguments:

  • leadText: The text added before the percentage value
  • txtToFind: The text to search for and to get the number of items from

The value is a product of the number of items in the group that matches the “txtToFind”, divided by the total number of items from the ms-gb-row (first group) times 100 to get the percentage.

Add a CEWP below the list view you want this to apply to, and add this code (Modify the text values in line 18 to match your values in the second group):

&lt;script type=&quot;text/javascript&quot; src=&quot;https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js&quot;&gt;&lt;/script&gt;
&lt;script type=&quot;text/javascript&quot;&gt;
/* Function for adding a &quot;Success Ratio&quot; to the ms-gb-row (first group in grouped list view).
 * ---------------------------------------------
 * Created by Alexander Bautz
 * alexander.bautz@gmail.com
 * https://spjsblog.com
 * LastMod: 27.01.2012
 * ---------------------------------------------
 This function takes two arguments:
   leadText - The text added before the percentage value
   txtToFind - The text to search for and to get the number of items from
 
  The value is a product of the number of items in the group that matches the &quot;txtToFind&quot; divided 
  by the total number of items from the ms-gb-row (first group) times 100 to get the percentage.
*/

successRatio('Success Ratio = ','Sold');

function successRatio(leadText,txtToFind){	
	$(&quot;.ms-gb&quot;).each(function(){
		var gbID, textRaw, gbText, gbVal, htmlRaw, part1, part2;
		gbID = $(this).parents('tbody:first')[0].id;
		textRaw = $(this).text();
		gbText = textRaw.substring(textRaw.indexOf(':')+2,textRaw.indexOf('(')-1);
		gbVal = textRaw.match(/d+/);
		getSubGroupes(leadText,gbVal,gbID,txtToFind,this);
		htmlRaw = $(this).html();
		part1 = htmlRaw.substring(0,htmlRaw.lastIndexOf('(')+1);
		part2 = htmlRaw.substring(htmlRaw.lastIndexOf('(')+1);
		$(this).html(part1 + &quot;total=&quot; + part2)	
	});
}

function getSubGroupes(lt,tot,gID,ttf,obj){
	$(&quot;tbody[id^='&quot;+gID+&quot;']:has('td.ms-gb2')&quot;).each(function(){
		var gb2ID, gb2TextRaw, gb2Text, gb2Val
		gb2ID = this.id;
		gb2TextRaw = $(this).text();
		gb2Text = gb2TextRaw.substring(gb2TextRaw.indexOf(':')+2,gb2TextRaw.indexOf('(')-2);
		gb2Val= gb2TextRaw.match(/d+/);
		if(gb2Text==ttf){
			val = Math.round((gb2Val/tot*100)*10)/10;
			$(obj).append(&quot;&lt;span style='padding-left:25px;color:red;font-weight:bold'&gt;&amp;nbsp;&quot; + lt + val + &quot;%&lt;/span&gt;&quot;);
		}
	});
}
&lt;/script&gt;

Ask if anything is unclear.

Regards
Alexander