Showing posts with label Webpart. Show all posts
Showing posts with label Webpart. Show all posts

Saturday, January 14, 2012

SharePoint TextArea Auto resize

Scenario:
Client wanted to have all the multi-line text-boxes to adjust width automatically like Facebook.

And I love SharePoint for its great flexibility where you can use all great jQuery plugin's of the world within SharePoint ( with small tweaks )

Solution:
I have used the Elastic project , jQuery plugin.
You can download files here Elastic

Code:

<script type="text/javascript" src="/_layouts/ClientFolder/jquery-1.7.1.min.js"></script>
<script type="text/javascript" src="/_layouts/ClientFolder/jquery.elastic.source.js"></script>

<script type="text/javascript">
_spBodyOnLoadFunctionNames.push("AdjustTextArea");

function AdjustTextArea() {

jQuery.noConflict();

jQuery('textarea').elastic();
jQuery('textarea').trigger('update');

}
</script>

<asp:TextBox ID="txtComments" runat="server" TextMode="MultiLine" Rows="6" Width="100%" />
Found a Bug:
As I was trying to use it with SP2010, I applied a CssClass="ms-long" and got a weird behavior of text box restricting to show only 29-30 lines. So removed it.

Wednesday, June 1, 2011

Displaying paged discussion board

Scenario:
We were working on to display item from a discussion list from a paged result set.

Solution:
While I am still not 100% convinced why someone need to fetch all records from 1 to last record of the page you want to display.

Here's the code which worked. Once can use this to work with cache object to make performance faster.

Code:

  public override void RenderControl (System.Web.UI.HtmlTextWriter writer)
{
writer.WriteLine("============================");

if (null != HttpContext.Current.Request.QueryString["pageno"])
{
int pageno = 1;
int.TryParse(HttpContext.Current.Request.QueryString["pageno"].ToString(), out pageno);

SPWeb oWebsite = SPContext.Current.Web;
SPList oList = oWebsite.Lists["Team Discussion"];

SPQuery oQuery = new SPQuery();
oQuery.RowLimit = 2;
oQuery.Query = "<OrderBy Override='TRUE'>" +
"<FieldRef Name='Modified' Ascending='FALSE' /></OrderBy>";

int intIndex = 1;

do
{
writer.WriteLine("Page: " + intIndex );

SPListItemCollection collListItems = oList.GetItems(oQuery);

if (intIndex == pageno)
{
foreach (SPListItem oListItem in collListItems)
{
writer.WriteLine(SPEncode.HtmlEncode(oListItem["Title"].ToString()) );
}

break;
}

oQuery.ListItemCollectionPosition = collListItems.ListItemCollectionPosition;
if (null != collListItems.ListItemCollectionPosition)

intIndex++;

} while (oQuery.ListItemCollectionPosition != null);

writer.Write("=================================");
}

base.RenderControl(writer);
}
Article: SPListItemCollectionPosition

Tuesday, April 26, 2011

Related Items webpart

Scenario:
I always wondered why this web part is missing. On the edit form / new form of any item, we always wanted to show things which are connected to a particular entity.

Solution:
Related Items webpart

relateditems

Code:

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls.WebParts;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;

namespace SKN.SPWebparts
{
[Guid("027a38e8-684a-4f0a-8dde-11c3abaad1cd")]
public class RelatedItems : System.Web.UI.WebControls.WebParts.WebPart
{
private const string const_configuationListName = "Session Docs";
private string m_configuationListName = const_configuationListName;

// Configuration List Name property.
[Personalizable(PersonalizationScope.Shared)]
[WebBrowsable(true)]
[Category("Custom Properties")]
[WebDisplayName("Configuration List Name")]
[Description("Configuration List Name")]
[DefaultValue(const_configuationListName)]
public string ConfigurationListName
{
get
{
return m_configuationListName;
}

set
{
m_configuationListName = value;
}
}

private const string const_configuationFieldName = "ProjectCode";
private string m_configuationFieldName = const_configuationFieldName;

// Configuration Field Name property.
[Personalizable(PersonalizationScope.Shared)]
[WebBrowsable(true)]
[Category("Custom Properties")]
[WebDisplayName("Configuration Field Name")]
[Description("Configuration Field Name")]
[DefaultValue(const_configuationFieldName)]
public string ConfigurationFieldName
{
get
{
return m_configuationFieldName;
}

set
{
m_configuationFieldName = value;
}
}


private const string const_configuationAddNewItemLink = "";
private string m_configuationAddNewItemLink = const_configuationAddNewItemLink;

// Configuration Add new item link.
[Personalizable(PersonalizationScope.Shared)]
[WebBrowsable(true)]
[Category("Custom Properties")]
[WebDisplayName("Add new item link")]
[Description("Set this property to show add new item link.Leave blank for newform.aspx")]
[DefaultValue(const_configuationAddNewItemLink)]
public string ConfigurationAddNewLink
{
get
{
return m_configuationAddNewItemLink;
}

set
{
m_configuationAddNewItemLink = value;
}
}

private const string const_configuationAddNewItemText = "Add new item";
private string m_configuationAddNewItemLinkText = const_configuationAddNewItemText;

// Configuration Add new item link text.
[Personalizable(PersonalizationScope.Shared)]
[WebBrowsable(true)]
[Category("Custom Properties")]
[WebDisplayName("Add new item link text")]
[Description("Set this property to set the text for the add new item link.")]
[DefaultValue(const_configuationAddNewItemText)]
public string ConfigurationAddNewLinkText
{
get
{
return m_configuationAddNewItemLinkText;
}

set
{
m_configuationAddNewItemLinkText = value;
}
}

const string const_AddNewItemLinkHTML = @"<TABLE border=0 cellSpacing=0 cellPadding=0 width='100%'>
<TBODY>
<TR>
<TD class=ms-partline colSpan=2><IMG alt='' src='/_layouts/images/blank.gif' width=1 height=1></TD></TR>
<TR>
<TD style='PADDING-BOTTOM: 3px;PADDING-TOP: 5px;PADDING-LEFT: 5px' class=ms-addnew><IMG alt='' src='/_layouts/images/rect.gif'> <A id=idHomePageNewEvent class=ms-addnew onclick='javascript:NewItem('{0}', true);javascript:return false;' href='{0}' target=_self>{1}</A> </TD></TR>
<TR>
<TD><IMG alt='' src='/_layouts/images/blank.gif' width=1 height=5></TD>
</TR>
</TBODY>
</TABLE>";

public RelatedItems ()
{

}

protected override void CreateChildControls ()
{
base.CreateChildControls();
}

public override void RenderControl (HtmlTextWriter writer)
{
if (!string.IsNullOrEmpty(ConfigurationListName))
{
ListViewByQuery listviewByQueryWP = new ListViewByQuery();

SPWeb currentweb = SPContext.Current.Web;
SPListItem currentList = SPContext.Current.ListItem;

SPList relatedList = currentweb.Lists[ConfigurationListName];
SPQuery query = new SPQuery(relatedList.DefaultView);

query.Query = string.Format("<where><contains><fieldref name='{0}'><value type='Text'>{1}</value></fieldref></contains></where>", ConfigurationFieldName, currentList.Title);

listviewByQueryWP.List = relatedList;
listviewByQueryWP.Query = query;
EnsureChildControls();
listviewByQueryWP.RenderControl(writer);
RenderChildren(writer);

if (relatedList.DoesUserHavePermissions(SPBasePermissions.EditListItems))
{
if (string.IsNullOrEmpty(ConfigurationAddNewLink))
{
ConfigurationAddNewLink = SPContext.Current.Web.Url + "/" + relatedList.Forms[PAGETYPE.PAGE_NEWFORM].Url;
}
string queryParamValue = string.Format("?{0}={1}&Source={2}", ConfigurationFieldName, currentList.Title, HttpContext.Current.Server.UrlEncode(HttpContext.Current.Request.Url.AbsoluteUri));
writer.Write(string.Format(const_AddNewItemLinkHTML, ConfigurationAddNewLink + queryParamValue, ConfigurationAddNewLinkText));
}
}
}
}
}

Tuesday, August 17, 2010

ListView from other site collection

Scenario:
ListView Webpart and new XsltListViewWebpart are great new controls to display list/library information from with in the sub-site.

But we had a little bit different requirement. As we wanted to display list views from other site collection / sub-site.

Solution:
For control List is a list , as long as we can code it and bind it to right control.

Few options we tried/thought of

1. Create a new Datatable and load it with view items and then using SPGridView bind it. ( Good 30-40 lines of code )

2. Create a new SPDataSource pointing to right list id and bind it to SPGridView.
( 10-15 lines of code )

3. Create a ListViewWebpart and point the ListId and ViewGuidId property to right values. ( 10-15 lines of code )

4. Create a XsltListViewWebpart and point the ListId and ViewGuidId property to right values. ( 10-15 lines of code )

5. How about get right SPView and render it with 2 line. I am still showing from same site collection but you can create a new object from other site collection and do the same. See below

Code:

 protected override void Render(HtmlTextWriter writer)
{
base.Render(writer);
writer.Write(SPContext.Current.Web.Lists["Tasks"].DefaultView.RenderAsHtml());
}
CRUX of the post is RenderAsHtml is AWESOME :)

Sunday, March 28, 2010

A-Z Navigation for Core Search Result Webpart

Scenario:
XSLT means flexibility , and so for one of the requirement I was given the task to display Core Search Result Webpart results to support A-Z navigation.

Solution:
You need to patient first. I am providing the modified copy of Core Search Result Webpart XSLT which supports this. So download and use it as it is.

Download ( free ) for SP 2007

Download ( free ) for SP 2010


Result will look like :
az

Added Variables and Templates:

  <!-- Added some new variables : Start -->
<xsl:variable name="smallcase" select="'abcdefghijklmnopqrstuvwxyz'" />
<xsl:variable name="uppercase" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'" />

<xsl:key name="keyTitle" match="Result" use="translate(substring(title,1,1),'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')" />
<!-- Added some new variables : End -->
<xsl:template name="Alphabets">
<Alpha>A</Alpha>
<Alpha>B</Alpha>
<Alpha>C</Alpha>
<Alpha>D</Alpha>
<Alpha>E</Alpha>
<Alpha>F</Alpha>
<Alpha>G</Alpha>
<Alpha>H</Alpha>
<Alpha>I</Alpha>
<Alpha>J</Alpha>
<Alpha>K</Alpha>
<Alpha>L</Alpha>
<Alpha>M</Alpha>
<Alpha>N</Alpha>
<Alpha>O</Alpha>
<Alpha>P</Alpha>
<Alpha>Q</Alpha>
<Alpha>R</Alpha>
<Alpha>S</Alpha>
<Alpha>T</Alpha>
<Alpha>U</Alpha>
<Alpha>V</Alpha>
<Alpha>W</Alpha>
<Alpha>X</Alpha>
<Alpha>Y</Alpha>
<Alpha>Z</Alpha>
</xsl:template>
<xsl:template name="dvt_1.rowview">
<xsl:param name="Rows" />
<xsl:for-each select="$Rows[generate-id(.) = generate-id(key('keyTitle', translate(substring(title,1,1),$smallcase,$uppercase))[1])]">
<xsl:sort select="translate(substring(title,1,1),$smallcase,$uppercase)" order="ascending" />
<xsl:if test ="string-length(title) &gt; 0">
<tr>
<td class="letterHeading" colspan="7">
<a>
<xsl:attribute name ="name">
<xsl:value-of select="translate(translate(substring(title,1,1),$smallcase,$uppercase),$smallcase, $uppercase)" />
</xsl:attribute>
</a>
<xsl:value-of select="translate(substring(title,1,1),$smallcase,$uppercase)" />
</td>
</tr>
<tr>
<td>
<xsl:variable name="thisTitle" select="translate(substring(title,1,1),$smallcase,$uppercase)"/>
<xsl:call-template name="DisplayItemRow">
<xsl:with-param name="curTitle" select="$thisTitle" />
</xsl:call-template>
</td>
</tr>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template name="DisplayItemRow">
<xsl:param name="curTitle" />
<xsl:for-each select="key('keyTitle', translate(substring(title,1,1),$smallcase,$uppercase))[translate(substring(title,1,1),$smallcase,$uppercase) = $curTitle]">

<!-- Copied the content of OOTB Result template : Start -->
<xsl:variable name="id" select="id"/>
<xsl:variable name="url" select="url"/>

<span class="srch-Icon">
<a href="{$url}" id="{concat('CSR_IMG_',$id)}" title="{$url}">
<img align="absmiddle" src="{imageurl}" border="0" alt="{imageurl/@imageurldescription}" />
</a>
</span>
<span class="srch-Title">
<a href="{$url}" id="{concat('CSR_',$id)}" title="{$url}">
<xsl:choose>
<xsl:when test="hithighlightedproperties/HHTitle[. != '']">
<xsl:call-template name="HitHighlighting">
<xsl:with-param name="hh" select="hithighlightedproperties/HHTitle" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="title"/>
</xsl:otherwise>
</xsl:choose>
</a>
<br/>
</span>

<xsl:choose>
<xsl:when test="$IsThisListScope = 'True' and contentclass[. = 'STS_ListItem_PictureLibrary'] and picturethumbnailurl[. != '']">
<div style="padding-top: 2px; padding-bottom: 2px;">
<a href="{$url}" id="{concat('CSR_P',$id)}" title="{title}">
<img src="{picturethumbnailurl}" alt="" />
</a>
</div>
</xsl:when>
</xsl:choose>
<div class="srch-Description">
<xsl:choose>
<xsl:when test="hithighlightedsummary[. != '']">
<xsl:call-template name="HitHighlighting">
<xsl:with-param name="hh" select="hithighlightedsummary" />
</xsl:call-template>
</xsl:when>
<xsl:when test="description[. != '']">
<xsl:value-of select="description"/>
</xsl:when>
</xsl:choose>
</div >
<p class="srch-Metadata">
<span class="srch-URL">
<a href="{$url}" id="{concat('CSR_U_',$id)}" title="{$url}" dir="ltr">
<xsl:choose>
<xsl:when test="hithighlightedproperties/HHUrl[. != '']">
<xsl:call-template name="HitHighlighting">
<xsl:with-param name="hh" select="hithighlightedproperties/HHUrl" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="url"/>
</xsl:otherwise>
</xsl:choose>
</a>
</span>
<xsl:call-template name="DisplaySize">
<xsl:with-param name="size" select="size" />
</xsl:call-template>
<xsl:call-template name="DisplayString">
<xsl:with-param name="str" select="author" />
</xsl:call-template>
<xsl:call-template name="DisplayString">
<xsl:with-param name="str" select="write" />
</xsl:call-template>
<xsl:call-template name="DisplayCollapsingStatusLink">
<xsl:with-param name="status" select="collapsingstatus"/>
<xsl:with-param name="urlEncoded" select="urlEncoded"/>
<xsl:with-param name="id" select="concat('CSR_CS_',$id)"/>
</xsl:call-template>
</p>

<!-- Copied the content of OOTB Result template : End -->

</xsl:for-each>
</xsl:template >

<xsl:template name="Header">
<tr>
<th>Title</th>
</tr>
</xsl:template>

<xsl:template name="Footer">
<tr>
<th>Title</th>
</tr>
</xsl:template>
Modified Templates:
 <xsl:template name="dvt_1.body">
<!-- Added some more new variables : Start-->
<xsl:variable name="Rows" select="All_Results/Result" />
<xsl:variable name="dvt_RowCount" select="count($Rows)" />
<xsl:variable name="IsEmpty" select="$dvt_RowCount = 0" />
<!-- Added some more new variables : Start-->

<div class="srch-results">
<xsl:if test="$ShowActionLinks">
<div class="srch-sort">
<xsl:value-of select="$ResultsBy" />
<xsl:if test="$ViewByUrl">
|
<a href ="{$ViewByUrl}" id="CSR_RV" title="{$ViewByValue}">
<xsl:value-of select="$ViewByValue" />
</a>
</xsl:if>
<xsl:if test="$AlertMeLink">
|
<span class="srch-alertme" >
<a href ="{$AlertMeLink}" id="CSR_AM2" title="{$AlertMeText}">
<img style="vertical-align: middle;" src="/_layouts/images/bell.gif" alt="" border="0"/>
<xsl:text disable-output-escaping="yes">&amp;nbsp;</xsl:text>
<xsl:value-of select="$AlertMeText" />
</a>
</span>
</xsl:if>
<xsl:if test="string-length($SrchRSSLink) &gt; 0">
|
<a type="application/rss+xml" href ="{$SrchRSSLink}" title="{$SrchRSSText}" id="SRCHRSSL">
<img style="vertical-align: middle;" border="0" src="/_layouts/images/rss.gif" alt=""/>
<xsl:text disable-output-escaping="yes">&amp;nbsp;</xsl:text>
<xsl:value-of select="$SrchRSSText"/>
</a>
</xsl:if>
</div>
<br />
<br />
</xsl:if>
</div>

<!-- Added to render the A-Z Navigation Bar : Start-->
<ul>

<xsl:variable name="AlphabetNodes">
<xsl:call-template name="Alphabets"></xsl:call-template>
</xsl:variable >

<xsl:variable name ="AlphabetName">
<xsl:apply-templates select="msxsl:node-set($AlphabetNodes)/Alpha">
<xsl:sort select="." order ="ascending"/>
</xsl:apply-templates>
</xsl:variable>

<xsl:variable name="TitleNames">
<xsl:for-each select="$Rows[generate-id(.) = generate-id(key('keyTitle', translate(substring(title,1,1),$smallcase,$uppercase))[1])]">
<xsl:value-of select="translate(substring(title,1,1),$smallcase,$uppercase)" />
</xsl:for-each >
</xsl:variable>

<xsl:for-each select="msxsl:node-set($AlphabetNodes)/Alpha">
<xsl:if test ="position() &lt;= count(msxsl:node-set($AlphabetNodes)/Alpha)">
<xsl:variable name ="char" select ="child::node()[position()]"/>
<li style="display:inline;list-style-type:none;padding-right:3px;">
<xsl:choose>
<xsl:when test="contains($TitleNames,$char)">
<span>
<a>
<xsl:attribute name ="href">
#<xsl:value-of select ="child::node()[position()]"/>
</xsl:attribute>
<xsl:value-of select ="child::node()[position()]"/>
</a>
</span>
</xsl:when >
<xsl:otherwise >
<span class="empty">
<xsl:value-of select ="child::node()[position()]"/>
</span>
</xsl:otherwise>
</xsl:choose >
</li>
</xsl:if>
</xsl:for-each >

</ul>
<!-- Added to render the A-Z Navigation Bar : End-->

<div class="directoryListing">
<table class="standardDataTable">
<xsl:call-template name="Header" />
<xsl:call-template name="dvt_1.rowview">
<xsl:with-param name="Rows" select="$Rows" />
</xsl:call-template>
<xsl:call-template name="Footer"/>
</table >
</div >
<xsl:call-template name="DisplayMoreResultsAnchor" />
</xsl:template>

Wednesday, March 24, 2010

Content Query Webpart Header Footer issue

Scenario:
I have used Content Query Webpart and everytime I learn something but this time I think I found a BUG as OOTB xslt files available with Content Query does not support Header and Footer in fully controlled manner.

Solution:
I have re-adjusted few templates to make it easier for any one to edit.Even though I am dumping here all the changed templates , you can skip all these steps by simple downloading the updated file

ContentQueryMain.xsl for 2007 <- DOWNLOAD (free)

ContentQueryMain.xsl for 2010 <- DOWNLOAD (free, please rename file to ContentQueryMain before Use)

This updated version of ContentQueryMain.xsl supports Header , GroupHeader , GroupFooter and Footer templates.

Output will be something like this :
cqwp

Code:

    <xsl:template name="OuterTemplate.Body">
<xsl:param name="Rows" />
<xsl:param name="FirstRow" />
<xsl:param name="LastRow" />
<xsl:variable name="BeginColumn1" select="string('&lt;td id=&quot;column&quot; width=&quot;')" />
<xsl:variable name="BeginColumn2" select="string('%&quot; valign=&quot;top&quot;&gt;')" />
<xsl:variable name="BeginColumn" select="concat($BeginColumn1, $cbq_columnwidth, $BeginColumn2)" />
<xsl:variable name="EndColumn" select="string('&lt;/td &gt;')" />
<xsl:for-each select="$Rows">
<xsl:variable name="CurPosition" select="position()" />
<xsl:if test="($CurPosition &gt;= $FirstRow and $CurPosition &lt;= $LastRow)">
<xsl:variable name="StartNewGroup" select="@__begingroup = 'True'" />
<xsl:variable name="StartNewColumn" select="@__begincolumn = 'True'" />

<xsl:choose>
<xsl:when test="$cbq_isgrouping != 'True'">
<xsl:if test="$CurPosition = $FirstRow">
<xsl:value-of disable-output-escaping="yes" select="$BeginColumn" />
<!-- New call to the Header template-->
<xsl:call-template name="OuterTemplate.CallHeaderTemplate"/>
</xsl:if>
</xsl:when>
<xsl:when test="$StartNewGroup and $StartNewColumn">
<xsl:choose>
<xsl:when test="$CurPosition = $FirstRow">
<xsl:value-of disable-output-escaping="yes" select="$BeginColumn" />
<!-- New call to the Header template-->
<xsl:call-template name="OuterTemplate.CallHeaderTemplate"/>
<!-- Renamed the existing Header template to group header template -->
<xsl:call-template name="OuterTemplate.CallGroupHeaderTemplate"/>
</xsl:when>
<xsl:otherwise>
<!-- Renamed the existing Footer template to group footer template -->
<xsl:call-template name="OuterTemplate.CallGroupFooterTemplate"/>
<!-- New call to the Footer template-->
<xsl:call-template name="OuterTemplate.CallFooterTemplate"/>
<xsl:value-of disable-output-escaping="yes" select="concat($EndColumn, $BeginColumn)" />
<xsl:call-template name="OuterTemplate.CallHeaderTemplate"/>
<!-- Renamed the existing Header template to group header template -->
<xsl:call-template name="OuterTemplate.CallGroupHeaderTemplate"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:when test="$StartNewGroup">
<!-- Renamed the existing Footer template to group footer template -->
<xsl:call-template name="OuterTemplate.CallGroupFooterTemplate"/>
<!-- Renamed the existing Header template to group header template -->
<xsl:call-template name="OuterTemplate.CallGroupHeaderTemplate"/>
</xsl:when>
<xsl:when test="$StartNewColumn">
<xsl:choose>
<xsl:when test="$CurPosition = $FirstRow">
<xsl:value-of disable-output-escaping="yes" select="$BeginColumn" />
<!-- New call to the Header template-->
<xsl:call-template name="OuterTemplate.CallHeaderTemplate"/>
</xsl:when>
<xsl:otherwise>
<!-- New call to the Footer template-->
<xsl:call-template name="OuterTemplate.CallFooterTemplate"/>
<xsl:value-of disable-output-escaping="yes" select="concat($EndColumn, $BeginColumn)" />
<xsl:call-template name="OuterTemplate.CallHeaderTemplate"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
</xsl:otherwise>
</xsl:choose>

<xsl:call-template name="OuterTemplate.CallItemTemplate">
<xsl:with-param name="CurPosition" select="$CurPosition" />
</xsl:call-template>

<xsl:if test="$CurPosition = $LastRow">
<!-- Added an additional check to render the group footer only if grouping is enabled : start -->
<xsl:if test ="$cbq_isgrouping = 'True'" >
<!-- Renamed the existing Footer template to group footer template -->
<xsl:call-template name="OuterTemplate.CallGroupFooterTemplate"/>
</xsl:if>
<!-- Added an additional check to render the group footer only if grouping is enabled : end -->

<!-- New call to the Footer template-->
<xsl:call-template name="OuterTemplate.CallFooterTemplate"/>
<xsl:value-of disable-output-escaping="yes" select="$EndColumn" />
</xsl:if>

</xsl:if>
</xsl:for-each>
</xsl:template>

<!-- Added a new template for Header -->
<xsl:template name="OuterTemplate.CallHeaderTemplate">
<!-- Wrapper div : start ( See CallFooterTemplate for closing node tag )-->
<xsl:variable name="test" select="string('&lt;div id=&quot;test&quot;&gt;')" />
<xsl:value-of disable-output-escaping="yes" select="$test" />
<div id="mainHeader">Main Header ( Locate me in ContentQueryMain.xsl file to change me )</div>
</xsl:template>


<!-- Renamed template , was originally names as CallHeaderTemplate -->
<xsl:template name="OuterTemplate.CallGroupHeaderTemplate">
<div id="header">Group Header ( Locate me in ContentQueryMain.xsl file to change me )</div>
<xsl:apply-templates select="." mode="header">
</xsl:apply-templates>
</xsl:template>


<!-- Renamed template , was originally names as CallFooterTemplate -->
<xsl:template name="OuterTemplate.CallGroupFooterTemplate">
<div id="footer">Group Footer ( Locate me in ContentQueryMain.xsl file to change me )</div>
</xsl:template>

<!-- Added a new template for Footer -->
<xsl:template name="OuterTemplate.CallFooterTemplate">
<div id="mainFooter">Main Footer ( Locate me in ContentQueryMain.xsl file to change me )</div>

<!-- Wrapper div : end ( See CallHeaderTemplate for starting node tag)-->
<xsl:variable name="test" select="string('&lt;/div&gt; ')" />
<xsl:value-of disable-output-escaping="yes" select="$test" />

</xsl:template>

Tuesday, February 16, 2010

Programatically modifying ListView Webparts

Scenario:
I was working on a small POC item, on how to update the listview webparts toolbar and fields after they have been added to the page.

Solution:
Here's the console application I used to test the concept. You might need to move this code as a Application Page Code behind or may be as a Feature Receiver code

Code:

using System;
using System.Reflection;
using System.Xml;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebPartPages;

namespace SKN
{
class Program
{
static void Main(string[] args)
{
//This will be text for the link displayed in Summary toolbar for the webparts
string linkText = "Add a new item";

SPSecurity.RunWithElevatedPrivileges(delegate()
{
// get the site in this context
using (SPSite SiteCollection = new SPSite("http://ws2003/sitedirectory/try1"))
{
// get the web in this context
SPWeb myWeb = SiteCollection.RootWeb;

myWeb.AllowUnsafeUpdates = true;

SPLimitedWebPartManager mgr = null;
mgr = myWeb.GetLimitedWebPartManager("default.aspx", System.Web.UI.WebControls.WebParts.PersonalizationScope.Shared);

foreach (WebPart myWebPart in mgr.WebParts)
{
// Check if the webpart is a listview webpart
if (myWebPart.GetType().ToString().EndsWith("ListViewWebPart"))
{
//Convert the webpart to list view webpart type
ListViewWebPart listWebPart = (ListViewWebPart)myWebPart;

if (listWebPart != null)
{
//Get the list instance
SPList list = myWeb.Lists[new Guid(listWebPart.ListName)];
//Get the view details which is applied to webpart
SPView webpartView = list.Views[new Guid(listWebPart.ViewGuid)];

//Call the EnsureFullBlownXMLDocument for the view
webpartView.GetType().InvokeMember("EnsureFullBlownXmlDocument",
BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod,
null, webpartView, null);

PropertyInfo nodeProp = webpartView.GetType().GetProperty("Node",
BindingFlags.NonPublic | BindingFlags.Instance);
XmlNode node = nodeProp.GetValue(webpartView, null) as XmlNode;

// Get the handle for the toolbar node
XmlNode toolbarNode = node.SelectSingleNode("Toolbar");
if (toolbarNode != null)
{
toolbarNode.Attributes["Type"].Value = "FreeForm";
XmlAttribute positionNode = toolbarNode.OwnerDocument.CreateAttribute("Position");
positionNode.Value = "After";
toolbarNode.Attributes.Append(positionNode);
toolbarNode.InnerXml = @"<IfHasRights><RightsChoices><RightsGroup PermAddListItems=""required"" /></RightsChoices><Then><HTML><![CDATA[ <table width=100% cellpadding=0 cellspacing=0 border=0 > <tr> <td colspan=""2"" class=""ms-partline""><IMG src=""/_layouts/images/blank.gif"" width=1 height=1 alt=""""></td> </tr> <tr> <td class=""ms-addnew"" style=""padding-bottom: 3px""> <img src=""/_layouts/images/rect.gif"" alt="""">&nbsp;<a class=""ms-addnew"" ID=""idAddNewItem"" href=""]]></HTML><URL Cmd=""New"" /><HTML><![CDATA["" ONCLICK=""javascript:NewItem(']]></HTML><URL Cmd=""New"" /><HTML><![CDATA[', true);javascript:return false;"" target=""_self"">]]></HTML><HTML>" + linkText +@"</HTML><HTML><![CDATA[</a> </td> </tr> <tr><td><IMG src=""/_layouts/images/blank.gif"" width=1 height=5 alt=""""></td></tr> </table>]]></HTML></Then></IfHasRights>";
}

// Get the handle to the ViewFields ( in case you want to change the columns to be displayed )
XmlNode viewFieldsNode = node.SelectSingleNode("ViewFields");
if (viewFieldsNode != null)
{
viewFieldsNode.InnerXml = @"<FieldRef Name='LinkTitle' /><FieldRef Name='Attachments' />";
}

// Update the view
webpartView.Update();
}

}
}
myWeb.AllowUnsafeUpdates = false;
}
});
}
}
}
Enjoy :)

Tuesday, November 17, 2009

Webpart custom properties

Scenario:
I use lot of configurable properties in my Webpart.

Always end in spending 5-10 min to make it work because of stupid Microsoft article ( Creating a Web Part with Custom Properties ) , which always shows up as number 1 result when you google, which talk about WebPart inherited from Microsoft.SharePoint.WebPartPages.WebPart class

Solution:
Here's the 100% right way to do it.

Code:

using System.ComponentModel;

using System.Web.UI.WebControls.WebParts;

namespace MyClient
{
public class NewWP : System.Web.UI.WebControls.WebParts.WebPart
{

private const string const_configuationListName = "xConfigurationMasterList";
private string m_configuationListName = const_configuationListName;

// Configuration List Name property.
[Personalizable(PersonalizationScope.Shared)]
[WebBrowsable(true)]
[Category("Custom Properties")]
[WebDisplayName("Configuration List Name")]
[Description("Configuration List Name")]
[DefaultValue(const_configuationListName)]
public string ConfigurationListName
{
get
{
return m_configuationListName;
}

set
{
m_configuationListName = value;
}
}

...

}
}

Sunday, November 8, 2009

Displaying webparts under custom group

Scenario:
We had whole bunch of webparts developed and wanted to locate them easily in Add a Webpart dialogue , so wanted to display them in their own custom group name.

Solution:
While uploading web parts using Module tag, you can also specify group name like this.

wp group

Code:

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/" >
<Module Name="WebParts" List="113" Url="_catalogs/wp">
<File Path="WebPart1.webpart" Url="WebPart1.webpart" Type="GhostableInLibrary" >
<Property Name="Group" Value="Bogus" />
</File>
</Module>
</Elements>

Monday, October 12, 2009

Dataview webpart import issue

Scenario:
I use dataview web part very often and recently I faced a weird error.

Steps to reproduce error:
1. Add a ListView webpart
2. Go to SharePoint Designer and Convert to XSLT view
3. I just changed the layout to one of the options available in Display Tab of DataView properties.
4. Save the changes
5. Go to the page and Export the webpart
6. Now import the webpart on the same page.

Boom !!! Error : ViewFlag is not set to right value.

Solution:
While checking the Exported definition, I found this tag.

<ViewFlag xmlns="http://schemas.microsoft.com/WebPart/v2/ListView">9</ViewFlag>
1. Remove the previously added Webpart using Maintainance Page ( ?contents=1 )
2. Removed this tag altogether from the Export definition
3. Again imported it without issue.

Friday, September 18, 2009

Select All option for Lookup fields

Scenario:
Lookup with multi-select option is available as a field for any list we create. And it has Add and Remove buttons to make selection but misses Add All and Remove All.

Solution:
This is a small workaround. Add a check-box column to the list as shown below.
Now you need to find some control guids. You can use my favorite X-ray machine Internet Explorer Developer Toolbar.

Steps:
1. Enter into edit mode of the NewForm.aspx ( type javascript:MSOLayout_ToggleLayoutMode(); in the IE Address bar and hit enter )

2. Add a Content Editor Webpart at the bottom ( make sure it is the last webpart )

3. Paste the script given below ( make sure to change the control ids )
selectall

Code:

 <script type="text/javascript">

function button_click(controlid,AddButtonControlId)
{
var myselect = document.getElementById(controlid)

for (var i = 0; i < myselect.options.length; i++)
{
myselect.options[i].selected = true ;
}

for (var i = 0; i < myselect.options.length; i++)
{
if (myselect.options[i].selected == true)
{
GipAddSelectedItems(AddButtonControlId);

}
}
}

function SelectAll()
{

// passed paramters
// 1. select control ( must be in quotes ) - ends in SelectCandidate
// 2. Picker control id ( must not have quotes ) - ends in MultiLookupPicker_m
button_click("ctl00_m_g_dbc86119_02b0_465b_8e56_2764e7284a1d_ctl00_ctl04_ctl01_ctl00_ctl00_ctl04_ctl00_ctl00_SelectCandidate",ctl00_m_g_dbc86119_02b0_465b_8e56_2764e7284a1d_ctl00_ctl04_ctl01_ctl00_ctl00_ctl04_ctl00_ctl00_MultiLookupPicker_m) ;
}

// passed paramters
// 1. check box control ( must be in quotes )

var checkit = document.getElementById("ctl00_m_g_dbc86119_02b0_465b_8e56_2764e7284a1d_ctl00_ctl04_ctl03_ctl00_ctl00_ctl04_ctl00_ctl00");
checkit.onclick = SelectAll;

</script>

Wednesday, August 19, 2009

Monthly filter webpart

Scenario:
Monthly filter webpart to filter any list which contains datetime column.

monthly filter
Solution:
This solution depends upon one of the column of the list you want to filter. You need to create a new column name 'Month' with the following calculated formula.

=MONTH(Created)&"/"&"1"&"/"&YEAR(Created)
Code:
using System;
using System.Collections.Specialized;
using System.Runtime.InteropServices;
using System.Web.UI;

namespace MonthlyFilter
{
public class MonthlyFilter : System.Web.UI.WebControls.WebParts.WebPart
{
protected override void Render(HtmlTextWriter output)
{
base.Render(output);
string fieldName = "Month";

NameValueCollection variables = new NameValueCollection(this.Page.Request.QueryString);

string queryParameter1 = "FilterField1";
if (!string.IsNullOrEmpty(variables[queryParameter1]))
variables.Remove(queryParameter1);

string queryParameter2 = "FilterValue1";
if (!string.IsNullOrEmpty(variables[queryParameter2]))
variables.Remove(queryParameter2);

variables.Add(queryParameter1, fieldName);
variables.Add(queryParameter2, "{0}");

string queryString = string.Empty;

foreach (String s in variables)
{
queryString += s + "=" + variables[s] + "&";
}

output.WriteLine(@"<div style='background-color:yellow;float:right'>");

int year = DateTime.Today.Year;
for( int month=1 ; month <=12 ; month ++ )
{
DateTime tempdate = new DateTime(year, month, 1);
output.WriteLine(string.Format("<a href='{0}?{1}'>{2}</a> | ", this.Page.Request.Path, string.Format(queryString, tempdate.ToString("M/d/yyyy")), tempdate.ToString("MMM")));
}
output.WriteLine(@"</div>");
}
}
}
Scope for improvements:
A lot infact, but to start with making the field name, format, styling more generic can be helpful. I am tired ..will do it later.

Friday, August 14, 2009

RSS Aggregator Webpart using Syndication

Scenario:
I read about Syndication sometime back after getting some hint from my friend Srini. RSS is now main stream requirement for getting updates from application / blogs and other sources. Microsoft always keep enhancing the dot net framework to make it more richer so they added support for syndication with dot net framework 3.5

Solution:
Here's a small webpart on how to use Syndication for reading RSS feeds from any site.

Steps:
1. Make sure you are using Framework 3.5
2. You need to add reference to System.ServiceModel.Web.dll

RSS WP

Code:

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.ServiceModel.Syndication;
using System.Web.UI;
using System.Xml;

namespace SyndicationWP
{
public class RSSWP : System.Web.UI.WebControls.WebParts.WebPart
{
protected override void Render(HtmlTextWriter writer)
{
base.Render(writer);

XmlReader reader = XmlReader.Create("http://feeds.feedburner.com/sandeepnahta");

SyndicationFeed feed = SyndicationFeed.Load(reader);
writer.WriteLine(feed.Title.Text);
writer.WriteLine("Items:");
foreach (SyndicationItem item in feed.Items)
{
writer.WriteLine("Title: {0}", item.Title.Text);
writer.WriteLine("Summary: {0}", ((TextSyndicationContent)item.Summary).Text);
}
writer.WriteLine("Press <enter> to quit...");
}
}
}
Possible Errors:
401 Unauthorized error if you are trying to consume a feed which you are not authorized to access. Most of sharepoint list RSS feed are protected.

Possible Improvements:
1. It can be generalized for any RSS feed links

Article:
SyndicationFeed Class

Saturday, August 8, 2009

WSS Custom Layouts

Scenario:
In most of my implemenation there was always some need to create extra site pages other than default.aspx. Requirement is to have another page on root level and deciding the number of zones and their layout can be time consuming.

Solution:
SharePoint does give you options to create web part pages as shown below. So these out of box layouts are good options to choose a layout from.
layouts

So after a simple File System search I was able to find out these sample layouts @

C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\1033\STS\DOCTEMP\SMARTPGS
Simple make a copy of the one which is closest to your requirement and use it.

SharePoint is all about discovering.

Friday, July 31, 2009

Setting custom icon for your webparts

Scenario:
I was trying to solve a small mystery for having a custom icon for all my webparts to be able to identify them quickly.

Solution:
I knew Out of Box webpart can do this, so I started looking into existing webpart definition files and found PartImageLarge. PartImageLarge is the property you need to set to change the icon displayed.

custom icon
Code:

<?xml version="1.0" encoding="utf-8"?>
<WebPart xmlns="http://schemas.microsoft.com/WebPart/v2">
<Assembly>Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c</Assembly>
<TypeName>Microsoft.SharePoint.WebPartPages.ContentEditorWebPart</TypeName>
<Title>$Resources:core,ContentEditorWebPartTitle;</Title>
<Description>$Resources:core,ContentEditorWebPartDescription;</Description>
<PartImageLarge>/_layouts/images/customicon16.gif</PartImageLarge>
</WebPart>

Article:

Sunday, July 19, 2009

Anonymous access Webpart and List security

Scenario:
I was reading one of the article below from Mike Walsh and was also concerned about our Web part security displacing sensitive data.

Whats the concern :
In simple words , if you have a site with Anonymous Access enabled and have one more list / document library with inheritance broken. Users can still access the list data/documents because of Anonymous Access setting on web.

Is it Bug ?:
I don't think so, its kinda same behavior as we have for Nested folders on Windows, where if you open door for the top level folder then all sub folders become open to all.

Solution:
1. You can fix it from UI
a. Navigate the list / document library you want to restrict the permissions.
b. Settings > List Settings > Permission for this list
c. Settings > Anonymous Access
d. Remove the check box from View Items and save.

2. If you have written a custom web part to access the list data/documents, then you need to perform additional check before displaying data to the user as shown below.

Code:

if (!SPContext.Current.Web.AllowAnonymousAccess )
{
// Code goes here
}

Locking webparts in webpart zone

Scenario:
We wanted to lock some of company wide Web parts in some of web part zones so they cannot be deleted or modified by another one for consistency purpose.

Solution:
WebPartZone supports a property LockLayout.Setting it to true did the trick.

This property basically controls whether or not Web Parts within the zone can be added, deleted, resized, or moved. The LockLayout property works the same regardless of whether the Web Part Page is in personal or shared view.

Sample Declaration:

 <WebPartPages:WebPartZone runat="server" FrameType="TitleBarOnly" ID="Left" Title="loc:Left" AllowCustomization="false"   AllowLayoutChange="false" AllowPersonalization="false" LayoutOrientation="Vertical" LockLayout="true"  />
 locked

How does it work:
If set to True, the specific Web Part properties for each Web Part in the zone that are affected are: Zone (ZoneID), Part Order (PartOrder), Visible on Page (IsVisible), Height (Height), Width (Width), Allow Close (AllowRemove), and IsIncluded (the Close command on the Web Part menu). Other Web Part properties are not affected.

If set to False, the Web Part properties determine whether modifications can be made (along with the appropriate site permissions).

Sunday, July 12, 2009

Geting all subsites for the current user

Scenario:
I wanted to write a webpart which displays all the sub-sites user have access to. Note this is different Webpart from the one I wrote earlier here which displays all the site collection user has access to.

I thought of iterating through AllWebs property of the Site Collection and then checking for each one of them if user do have Access to the sub-web or not. But I got Access Denied while rending the webpart as a regular user with limited permission.

Solution:
GetSubwebsForCurrentUser, was the solution. SPWeb class has this method which returns the collection of subsites beneath the current site of which the current user is a member.

Code:

var subwebCollection = SPContext.Current.Web.GetSubwebsForCurrentUser();

Thursday, July 2, 2009

Feature Stapling bug

Scenario:
This was a strange situation, we had a stapling feature which replaces the default page of Team Site , with a new page with more web part on it. Now the issues was , when we create a new site , all the web part shows up but only listview webparts are missing.

Also this behavior was random and if you activate the same feature manually after site is created there was no such issue.

Reason:
With my knowledge with sharepoint only thing I could think of was, list view automatically go missing if you don't have the list in site. So I think it was because of while site is getting created, list is still not created and so web parts got missing. Also that is inline with our second observation, when you activate the feature manually, there is delay and lists are created in the meanwhile and so we don't have list views missing.

Bug:
Clearly its a bug as the code should fire at the end of the Web creation life cycle, which is not the case.

Solution:

-- Either you can stick to manually activating the feature
-- Write a timer job which does this for you. ( May not be best of the best solution but this guarantees that it will only fire certain number of times. Also this timer can be extended to do much more things in future.

Code:

using System;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;

namespace SPCore
{
[CLSCompliant(false)]
public class FeatureStaplingBugFixTimerJob : SPJobDefinition
{
public FeatureStaplingBugFixTimerJob() : base() { }

[Persisted]
private readonly string _featureIdList; // list of feature need to activated

public FeatureStaplingBugFixTimerJob(SPWebApplication webApp, string jobName, string featureIDs)
: base(jobName, webApp, null, SPJobLockType.ContentDatabase)
{
Title = jobName;
_featureIdList = featureIDs; //this is one time updated by feature receiver
}

public override void Execute(Guid targetInstanceId)
{
var featureIds = _featureIdList.Split(";".ToCharArray());

foreach (SPSite siteColl in WebApplication.Sites)
{
foreach (SPWeb web in siteColl.AllWebs)
{
// Check if the property name already exists
if (web.Properties.ContainsKey("FeatureStaplingFixDone"))
{
if (web.Properties["FeatureStaplingFixDone"] != "True")
{
foreach (var featureId in featureIds)
{
if (featureId.Length > 0)
{
var featureGuid = new Guid(featureId);
var spFeature = web.Features[featureGuid];
if (null == spFeature)
{
web.Features.Add(featureGuid);
}
}
}
// Update property with new value
web.Properties["FeatureStaplingFixDone"] = "True";
}
}
else
{
// Add new property with name and value
web.Properties.Add("FeatureStaplingFixDone", "False");
}
web.Properties.Update();
web.Dispose();
}
siteColl.Dispose();
}
}
}
}
Enhancements:
-- This code only works for Feature need to be stapled at Web Level
-- More error handling in case specified features are missing

Download:
You can download the complete solution from here. To be able to build the solution on your machine, make sure to point the location of WSPBuilder to right location in INSTALL.bat file

Monday, June 29, 2009

Restricting access to sharepoint views

Scenario:
Some one asked me how to restricting access to sharepoint views because he has a view with one column with information only meant for Contributors and not for Visitors.

Solution:
There is no out of box solution and they are many work-around for the problem.
Here's one which is simple and can be handled from sharepoint UI itself.

Steps ( Set 1 ) :

01. Go to the list you want to restrict the access.
02. Make sure AllItems.aspx , or your default view doesn't have the column you want to hide.
03. Now create a new View based on previous view, you can name it Contributors view.
04. Add the columns you want to show to Contributors View and save it.

Steps ( Set 2 ) :

05. Now go Site Actions > Create
06. Create a new Web Part Page with name "Contributors View", select the library you want to save it to.
07. Click on Add webpart option and Insert list view webpart by selecting the list name
08. Edit this webpart to point it to new View ( Contributors View )
09. Exit the editor mode

Steps ( Set 3 ) :

10. Go to library you have used in the above step.
11. Look for the page you have created.
12. From the context menu of the page > Manage Permission , Allow only Contributors

Steps ( Set 4 ) :

13. Now visit the list again ( trust me this is the last time )
14. List Settings > delete the view ( Contributors View ).

That's it , you are almost done 95%.
Missing 5% work goes to the solution you need to find out to provide some way for your Contributors to navigate to this newly created page. :-)