Monthly Archive for January, 2010

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:

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

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

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

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 “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”:

<!-- FieldName="This is the DisplayName"
FieldInternalName="ThisIsTheFieldInternalName"
FieldType="SPFieldText"
-->

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 = {};
	$("td.ms-formbody").each(function(){
	var myMatch = $(this).html().match(/FieldName="(.+)"s+FieldInternalName="(.+)"s+FieldType="(.+)"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>0){
					type=type+"_HTML";
				}
			}
			if(type=='SPFieldLookup'){
				if($(this).find('input').length>0){
					type=type+"_Input";
				}
			}
			// 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 = {};
  $("td.ms-formlabel").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:

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

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:

<script type="text/javascript" src="../../Javascript/jquery-1.3.2.min.js"></script>
<script type="text/javascript">
/*
  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&focus=FieldInternalNameOfFieldToFocusOn
*/

fields = init_fields();

// If the query string parameter "focus" 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>0){
		var qpart = urlSearch.substring(1).split('&');
		$.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 = {};
$("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>

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

<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script type="text/javascript">
/* Function for adding a "Success Ratio" to the ms-gb-row (first group in grouped list view).
 * ---------------------------------------------
 * Created by Alexander Bautz
 * alexander.bautz@gmail.com
 * http://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 "txtToFind" 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){	
	$(".ms-gb").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 + "total=" + part2)	
	});
}

function getSubGroupes(lt,tot,gID,ttf,obj){
	$("tbody[id^='"+gID+"']:has('td.ms-gb2')").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("<span style='padding-left:25px;color:red;font-weight:bold'>&nbsp;" + lt + val + "%</span>");
		}
	});
}
</script>

Ask if anything is unclear.

Regards
Alexander

Check workflow status and refresh page when status equals “Completed”

11.01.2010: Updated code due to a bug (line 36 is updated). Thanks to Scott Muldowney for spotting it.


The scenario:
You open a list item in EditForm, and the list item has a running (not yet completed) workflow. Nothing prevents you from editing the item, but when you try to save your edited item:

Save Conflict
Your changes conflict with those made concurrently by another user. If you want your changes to be applied, click Back in your Web browser, refresh the page, and resubmit your changes.

In this article i will provide a solution for:

  • Checking the workflow status, and if not finished, set a timer that checks the workflow every 5 seconds for a set period of time.
  • Refresh the page if the workflow finishes within the maximum wait time.

If status is “In progress”, the timer runs, and the status is checked every 5 seconds:
IMG
If timed out, you get this:
IMG

This article is a follow-up on Prevent editing of a list item if the workflow has failed. You have to read that article before continuing with this one.

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 – you find them here.

Add a CEWP below your EditForm, and add this code:

<div id="customTimerMsg"></div>
<div id="customTimer"></div>
<div id="customTimerCheck"></div>
<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/RefreshWhenWorkflowCompletes.js"></script>
<script type="text/javascript">
  // How long will the timer run before aborting
  maxWaitSec = 20; 
  // Change ListGuid and Workflow "FieldInternalName"
  listNameOrGuid = 'A3749468-0C23-493B-8854-06DF04200B43';
  workFlowInternalName = 'MyWF';
  administratorEmail = 'alexander.bautz@gmail.com';
</script>

Parameters explained:

  • maxWaitSec: The max wait time in seconds
  • listNameOrGuid: Display name or Guid of current list
  • workFlowInternalName: The “FieldInternalName” of the workflow to check
  • administratorEmail: The administrator’s email – for when the workflow has failed

The code for the file “RefreshWhenWorkflowCompletes.js” is found here:

/* Refresh list item when workflow finishes
 * ---------------------------------------------
 * Created by Alexander Bautz
 * alexander.bautz@gmail.com
 * http://spjsblog.com
 * v1.0
 * LastMod: 10.01.2010
 * ---------------------------------------------
 * Include reference to:
 *  jquery - http://jquery.com
 *  interaction.js - http://spjslib.codeplex.com/
 *  stringBuffer.js - http://spjslib.codeplex.com/
 * ---------------------------------------------
 * Call from a CEWP below the list form in EditForm
*/

$(document).ready(function(){
	checkStatusAndRefresh();
});

var SiteTitle = $.trim($(".ms-sitetitle a").text());
var ListName = $.trim($(".ms-pagetitle a").text()); 

function checkStatusAndRefresh(){
var wfStatus = getWorkflowStatus();
	if(wfStatus==3){ // 3 = "Error Occured"
		$("#part1").hide(); // Hide the list form
		$("#part1").before("<div class='ms-sitetitle'>A workflow has failed. You cannot edit this item until this issue is resolved.</div>" + 
			"<div style='padding-left:10px'><a title='Click to send e-mail to an administrator' href='mailto:" + administratorEmail + "?subject=Failed workflow on site: " + SiteTitle + 
			", list: " + ListName + ", itemId: " + getID() + "'>Click to send e-mail to an administrator</a></div>");
	}else if(wfStatus==2){
		$("#part1").hide(); // Hide the list form
		waitingForRefresh = true;				
		customTimer();
	}else{
		if(typeof(waitingForRefresh)!='undefined' && waitingForRefresh){
			refreshPage();
		}
	}
}

function customTimer(s){ 
if(typeof(s)=='undefined')seconds = 0;
if(typeof(accumulated)=='undefined')accumulated = 0;
	if(accumulated<maxWaitSec){
		if(seconds<5){	
			seconds+=1
			$("#customTimerMsg").html("<div class='ms-sitetitle'>The workflow has not yet finished: Waiting for a maximun of " + maxWaitSec + " seconds,<br>checking the workflow every 5 seconds.</div>");
			$("#customTimer").html("<div style='padding-left:10px'>Waiting: " + (accumulated + seconds) + "</div>"); 
			setTimeout(function(){
				customTimer(seconds);
			},1000);
		}else{
			accumulated = accumulated + seconds;
			$("#customTimerCheck").html("<div style='padding:10px;color:red'>Checking WF...</div>").show().fadeOut(1750);
			checkStatusAndRefresh();
		}
	}else{
		$("#customTimerMsg").html("<div class='ms-sitetitle'>The workflow did not finish during the time specified.</div>" + 
		"<div style='padding:10px;font-size:12px'><a href='" + L_Menu_BaseUrl + "'>Return to site "" + SiteTitle + ""</a>&nbsp;|&nbsp;" + 
		"<a href='javascript:refreshPage();'>Wait for " + maxWaitSec + " new seconds.</a></div>");
		$("#customTimer").html(''); 
	}
}

function refreshPage(){
	window.location.reload();
}

function getWorkflowStatus(){
var thisID = getID();
	wsBaseUrl = L_Menu_BaseUrl + '/_vti_bin/';
    var item = getItemById(listNameOrGuid,thisID,[workFlowInternalName]); 
    if(item != null){
        return  item[workFlowInternalName];
    }
}

function getID() {
var ID = '';
var end = window.location.search.indexOf('&');
	if(window.location.search.indexOf('&')<0){
		ID = window.location.search.substring(4);
	}else{
		ID = window.location.search.substring(4,end);
	}
	return ID;
}

Save as “RefreshWhenWorkflowCompletes.js”, and upload to your scriptlibrary as shown above.

Ask if anything is unclear.

Regards
Alexander

Arrange webparts in tabs in webpart page

04.07.2010 Updated the code for the file “WebpartTabs.js” to fix an issue with grouped webparts being expanded by default. Thanks to Bullvan for noticing.

02.02.2010 Update to add possibility to set the tab titles in an array. Useful when viewing multiple views of the same list in a list view.

12.01.2010: Updated the function “getWPTitle” in the code for the file “WebpartTabs.js”.

11.01.2010: Updated the code to allow multiple instances in one page. Please read trough the article as the approach has changed from the code posted two days ago.


In this article i will describe a method for adding webparts in a webpart page as tabs somewhere in the page.

Christophe at PathToSharePoint has already made a solution like this, but i got a request for a method a bit different than his solution:

tecrms Says:
I really like your Tabs in SharePoint form you posted. Is there any chance that you might create a tab container that places webparts on the page into it? For maximum user could they be selected by webpart ID and not by the zone. This way more than one tab container could be placed anywhere are the page.

Alexander Says:
Hi,
Take a look at “Easy Tabs” from Christophe at PathToSharepoint.

No need to invent it again if his solution does what you want…

If this is not what you need, let me know and i will look at it.

Alexander

tecrms Says:
Christophe’s Easy Tabs is an excellent webpart but it grabs everything within a webpart zone. What would be nice is to allow the selection of which webparts are to be placed in the tabs from the webpart zone. This will give one more ability to have more than one “Eays Tab” type webpart within a webpart zone.

I decided to make my own solution:

  • Multiple instances in one page.
  • Optional arrays to specify which webpart’s to include and which to exclude from being “tabbed”.
  • If there are no titles specified in the array’s, the code “consumes” all webparts rendered before the CEWP containing the script
  • Preserves selected tab on page load – for filtering columns etc.
  • Works in webpartpages and in list views
  • …requests anyone?

Images:
IMG

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

A folder named “jQueryUI” containing the scripts and the “images”-folder for the selected theme:
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 jQuery UI-library is found here. Find the theme of your choice – mine is “smootness” – and download the files (the only necessary files are “UI Core” and “Tabs”).

The file “sessvars.js” is found here.

The code for the file “WebpartTabs.js” is provided below.

The way this script works is that you put a CEWP containing the code below the webparts you want to add to the tabs. If you want to add four webparts to the top of a webpart-page, you add the webparts from the top down, and places the CEWP with the script as webpart number five.

The code then (if the webparts has not been excluded) consumes all the webparts stacked above, and adds them as tabs in the order of appearance.

Add a CEWP below the webparts you want to “tab”, and insert this code

<link type="text/css" href="/test/English/Javascript/jQueryUI/jquery-ui-1.7.2.custom.css" rel="stylesheet" />
<script type="text/javascript" src="/test/English/Javascript/jquery-1.3.2.min.js"></script>
<script type="text/javascript" src="/test/English/Javascript/jQueryUI/jquery-ui-1.7.2.custom.min.js"></script>
<script type="text/javascript" src="/test/English/Javascript/sessvars.js"></script>
<script type="text/javascript" src="/test/English/Javascript/WebpartTabs.js"></script>
<script type="text/javascript">
// In a webpart-page: Specify whether to hide the title - it must be present initially to use as "tab title"
hideWPHeader = true;
// Specify webparts to exclude
arrOfTitlesToExclude = ['Announcements'];
// Specify webparts to include - if empty it consumes all webparts rendered before this CEWP
arrOfTitlesToInclude = [];
// Specify tab title - useful in a view of a list where webpart titles cannot be used
arrOfTabHeadings = [];
// Call the script with an unique identifier of the "tabs-collection"
initBuildTabs('one');
</script>

If you want another instance in the same page, modify the code like this (this webpart must be placed after the “primary” CEWP):

<script type="text/javascript">
// In a webpart-page: Specify whether to hide the title - it must be present initially to use as "tab title"
hideWPHeader = true;
// Specify webparts to exclude
arrOfTitlesToExclude = ['Announcements'];
// Specify webparts to include - if empty it consumes all webparts rendered before this CEWP
arrOfTitlesToInclude = [];
// Specify tab title - useful in a view of a list where webpart titles cannot be used
arrOfTabHeadings = [];
// Call the script with an unique identifier of the "tabs-collection"
initBuildTabs('two');
</script>

Parameters explained:

  • hideWPHeader: If in a webpart-page – true – hides the webpart title
  • arrOfTitlesToExclude: Array of webpart-titles to exclude from tabs
  • arrOfTitlesToInclude: Array of webpart-titles to include in tabs. If left empty, it consumes all visible webparts rendered before this CEWP (that are not actively excluded)
  • arrOfTabHeadings: Array of webpart titles. If this array contains a value for the current tabID, it is used in stead of the default title pulled from the webpart. To specify the title for tabID 2 you must set the array like this: ['','','This is the new title']. Note that tabID 0 and 1 is not altered as the value for those are empty strings.

The code for the file “WebpartTabs.js” looks like this:

/* Add webparts to tabs
 * ---------------------------------------------
 * Created by Alexander Bautz
 * alexander.bautz@gmail.com
 * http://spjsblog.com
 * Copyright (c) 2010 Alexander Bautz (Licensed under the MIT X11 License)
 * v1.4
 * LastMod: 04.07.2010
 * ---------------------------------------------
 * Include reference to:
 *  jQuery - http://jquery.com
 *  jQuery UI - http://jqueryui.com
 *  sessvars.js - http://www.thomasfrank.se/sessionvars.html
 * ---------------------------------------------
 * See this blog post for instructions:

http://spjsblog.com/2010/01/09/arrange-webparts-in-tabs-in-webpart-page/

*/

function initBuildTabs(uniqueID){
	if(typeof(arrOfTitlesToExclude)=='undefined')arrOfTitlesToExclude = [];
	if(typeof(arrOfTitlesToInclude)=='undefined')arrOfTitlesToInclude = [];
	if(typeof(arrOfTabHeadings)=='undefined')arrOfTabHeadings = [];
	// Insert the tabs placeholder
	document.write("<div id='skipThisCEWP'></div><div style='padding:5px;width:100%' id='MyTabs_" + uniqueID + "'><ul></ul></div>");
	var myDiv = $("#MyTabs_"+uniqueID);
	// Code inactive in "edit page mode"
	if($(".ms-WPAddButton").length==0){
	var tabID = 0;
		// Loop trough all webparts on the page
		$(".ms-bodyareaframe td[id^='MSOZoneCell_WebPartWPQ']").each(function(){
			var thisWP = $(this);
			if(!thisWP.data('done')){
				thisWP.data('done',true)
				// Is it hidden or skipped?
				tabTitle = includeThisWP(thisWP,tabID);
				if(tabTitle!=false){
					// Hide the webpart title?
					if(hideWPHeader){
						thisWP.find(".ms-WPHeader").hide();	
					}
					// Build the tabs	
					myDiv.find('ul').append("<li><a href='#tabContent-" + uniqueID + tabID + "' onclick='preserveTab(" + tabID + ","" + uniqueID + "")'>" + tabTitle + "</a></li>");
					var wrappedDiv = $("<div id='tabContent-" + uniqueID + tabID+"'></div>");
						wrappedDiv.append(thisWP.find('table:first'));
						myDiv.append(wrappedDiv);
						tabID+=1;
					// Remove the empty placeholders to prevent them from taking up space
					if(thisWP.parents('table:first').find('tr').length>1){					
						thisWP.parents('tr:first').remove();
					}else{
						thisWP.parents('table:first').remove();
					}
				}
			}
		});
		// Make tabs
		myDiv.tabs();
		// Fix CSS
		$(".ui-tabs-panel").css({'padding':'0px','text-align':'left','margin':'0 auto'});	
		// Select the active tab on page reload 
		if(sessvars[uniqueID]!='undefined'){
			myDiv.tabs('select', sessvars[uniqueID]);
		}
	}else{
		document.write("<div style='height:75px;background-color:#FFCC11;text-align:center;font-size:16px;padding-top:30px'>This is the Tabs CEWP</div>");
	}
}

function includeThisWP(obj,tID){
// Is it skipped or hidden?
	if(obj.find("#skipThisCEWP").length>0 || obj.find('table:first').css('display')=='none'){
		return false;
	}
	if(arrOfTabHeadings[tID]!=undefined && arrOfTabHeadings[tID]!=''){
		wpT = arrOfTabHeadings[tID];
	}else{
		wpT = getWPTitle(obj);
	}
	// Is it excluded
	if($.inArray(wpT,arrOfTitlesToExclude)>-1){
		return false;
	}else if(arrOfTitlesToInclude=='' || $.inArray(wpT,arrOfTitlesToInclude)>-1){
		return wpT;
	}else{
		return false;
	}
}

function getWPTitle(obj){
	// Lists and librarys
	if(obj.html().match(/ctx.ListTitle = "(.+)"/)!=null){
		var tRaw = obj.html().match(/ctx.ListTitle = "(.+)"/)[1];
		return unescape(tRaw.replace(/\u/g,'%u'))
	}
	// Other webparts
	if(obj.find(".ms-WPHeader h3").length>0){	
		return obj.find(".ms-WPHeader h3").text();						
	}else{
		return "<span title='Webpart title must be visible under "Chrome Type" in webpart settings.' style='color:red'>No title</span>";
	}
}

// Used to preserve the current tab when page loads (column filtering etc.)
function preserveTab(tabID,parentID){
	sessvars[parentID]=tabID;
}

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

This script may need a bit more testing so please let me know if you have some comments or finds a bug.

Regards
Alexander

How to post code in comments

If you want to post code in comments, please format it like this to make it readable:

<code>
// Your code here
</code>

Alexander




%d bloggers like this: