Monday, November 15, 2010

Tool Part is moving at the Bottom of the page in Edit Mode

Recently I encountered an issue in my project which was related with custom Master page. I have provided "Exit Edit Mode" option on my web part pages so that after editing the pages, user can exit from the edit mode of the page (as shown below).

As most of you aware of, I have added "DesignModeConsole" control in my master page (as shown below) to incorporate this requirement.

<div class="content_mid_pad">

    <asp:ContentPlaceHolder ID="WSSDesignConsole" runat="server">

     <wssuc:DesignModeConsole id="IdDesignModeConsole" runat="server"/>

    </asp:ContentPlaceHolder>

</div>

 

After this I found that the tool part pane of my web parts is moving at the bottom of the page in the page edit mode. In normal mode the page was working fine. It was weird. I googled and found that the "DesignModeConsole" control renders a pair of <tr> & <td> tags. Since I wrapped this control within a <div> tag, the design gets distorted in the page edit mode. Now I tweaked the above code to accommodate the auto generated <tr> & <td> tags (as shown below).

<div class="content_mid_pad">

    <table width="100%">

<tbody>

        <asp:ContentPlaceHolder ID="WSSDesignConsole" runat="server">

        <wssuc:DesignModeConsole id="IdDesignModeConsole" runat="server"/>

        </asp:ContentPlaceHolder>

    </tbody>

    </table>

</div>

 

 

Thursday, May 13, 2010

Wrapping of text around image in HTML / XSLT

Sometimes we have requirement of wrapping text around images (Left/ Right). We can achieve this functionality by using "align" attribute of the <img> tag. The catch here is the Image and text should be in same "div" tag.

However we do have requirement of having different CSS design specification for Image and Text wrapped around the image. In this case, we will put the image tag in one outer DIV tag and then put the wrapped text in different DIV tag inside the outer DIV.

Let's take an example where some text is wrapped around an Image which is left aligned. The typical XSL code would be like:


<div >
<!-- Image tag with align attribute-->
<img class="Article_Image" src="@Article_Iamge" align="left"/>
<!-- Title Div-->
<div class="Article_Title">
<b>
<a id="Article_Link_Title" href="{$SafeLinkUrl}" target="{$LinkTarget}">
<xsl:value-of select="$DisplayTitle"/>
</a>
</b>
</div>
<!-- Description Div -->
<div class="Article_Description">
<xsl:value-of selectsubstring(@Abstract_Text,1,250)" disable-output-escaping ="yes"/>
<a id="Article_Link_Description" href="{$SafeLinkUrl}" target="{$LinkTarget}">
...
</a>
</div>
</div> <!-- Outer Div tag ends here -->

This code will align the image in the left with Title & description wrapped around it.

Tuesday, May 4, 2010

Custom XML web part with ASP.NET controls in the XSLT

Sometimes requirement from customer is very peculiar. For instance, once customer wanted a web part whose layout (positioning of controls within the web part, styling, design specs etc. ) can be changed by some authorized user without changing the web part code. Even he wants that the authorized user can add/remove controls from/to web part without code change. Now this is where the XML/XSLT comes into picture.

In this case we can either use the OOTB Data Form web part or we can create our own custom web part. I will take you through the custom web part implementation approach (just to demonstrate how exactly the XML/XSL transformation takes place).

Here for our custom web part, we'll put the ASP.NET controls in the XSLT (code will be shown below) and they will read their properties (ID, Text, CSS Class name) and default values from a XML file. These XML/XSL files can be stored in either a list/library or their values can be stored within a list item of a list/library. For our custom web part, we were storing the XSL file within the Style Library and the XML file as a list item of a list.

  1. Master XML File

The Master XML file will look like:


<?xml version="1.0" ?>
<CONTROLS xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<CONTROL Name="WebPartControl1">
<PROPERTIES>
<PROPERTY Name="Hidden" ID="Hidden" type="Boolean" controltype="CheckBox">
<VALUE>False</VALUE>
</PROPERTY>
<PROPERTY Name="List URL" ID="NavigationListName" type="string" controltype="TextBox">
<VALUE>lists/CustomList</VALUE>
</PROPERTY>
<PROPERTY Name="XSLT File Path" ID="XSLTFilePath" type="string" controltype="TextBox">
<VALUE>Style Library/customXSL/ListMenusTransformXSLT.xsl</VALUE>
</PROPERTY>
<PROPERTY Name="CSS Class Name" ID="CSS" type="string" controltype="TextBox">
<VALUE>leftNav</VALUE>
</PROPERTY>
<PROPERTY Name="Orientation" ID="Orientation" type="string" controltype="DropDownList">
<OPTIONS>
<OPTION>Horizental</OPTION>
<OPTION>Vertical</OPTION>
</OPTIONS>
<VALUE>Vertical</VALUE>
</PROPERTY>
</PROPERTIES>
</CONTROL>
</CONTROLS>

 

The XML above has defined the controls required in the web part "WebPartControl1". Each <PROPERTY></PROPERTY> node represents a control. It contains Check Boxes, Textboxes & Dropdown lists etc. we can add any type of control. The value of these controls will be stored in the <VALUE></VALUE> node.

2. Master XSLT File

The corresponding XSLT file will look like:


<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:asp="remove">

<!-- Parmeters passed through Code -->
<xsl:param name="controlID"></xsl:param>
<xsl:param name="mainCSSClass"></xsl:param>

<xsl:template match="CONTROLS">
<div class="{$mainCSSClass}">
<xsl:if test="string-length($controlID)!=0">

<asp:Panel ID="PROPERTIES" runat="server">
<table>
<!-- ControlID the the value of Control Node's Name attribute value -->
<xsl:for-each select="CONTROLS/CONTROL[@Name=$controlID]">
<div ID="propertiesDiv" class="propertiesDiv">
<table>
<!-- Iterate through all the property nodes -->
<xsl:for-each select="PROPERTIES/PROPERTY">
<tr>
<td>
<xsl:value-of select="@Name"/>
</td>
<td>
<!-- Read the value of the Property in a variable -->
<xsl:variable name="selectedValue">
<xsl:value-of select="VALUE"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="@controltype='DropDownList'">
<!-- Render Drop down list -->
<asp:DropDownList ID="{@ID}" runat="server">
<xsl:for-each select="OPTIONS/OPTION">
<xsl:choose>
<xsl:when test="$selectedValue=current()">
<asp:ListItem selected="selected">
<xsl:value-of select="current()"/>
</asp:ListItem>
</xsl:when>
<xsl:otherwise>
<asp:ListItem >
<xsl:value-of select="current()"/>
</asp:ListItem>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</asp:DropDownList>
</xsl:when>
<xsl:when test="@controltype='CheckBox'">
<!-- Render Check box control -->
<asp:CheckBox ID="{@ID}" runat="server">
<xsl:attribute name="Checked">
<xsl:value-of select="$selectedValue"/>
</xsl:attribute>
</asp:CheckBox>
</xsl:when>
<xsl:otherwise>
<!-- Render Text box control -->
<asp:TextBox ID="{@ID}" runat="server" >
<xsl:attribute name="Text">
<xsl:value-of select="$selectedValue"/>
</xsl:attribute>
</asp:TextBox>
</xsl:otherwise>
</xsl:choose>
</td>
</tr>
</xsl:for-each>
</table>
</div>
</xsl:for-each>
</table>
</asp:Panel>
<div ID="buttonDiv" class="buttonDiv">
<table>
<tr>
<td>
<asp:Button ID="btnSave" runat="server" Text="Save" />
</td>
<td>
<asp:Button ID="btnCancel" runat="server" Text="Cancel" />
</td>
</tr>
</table>
</div>
</xsl:if>
</div>
</xsl:template>
</xsl:stylesheet>

 

As you can see, here we are iterating through all the <PROPERTY></PROPERTY> nodes of the web part control whose ID is passed as a parameter into the XSLT. If the "ControlType" attribute of the control (in the XML, its PROPERTY) is Dropdown list, then we are adding a ASP.NET drop down control. The list items of this dropdown list are stored inside <OPTIONS><OPTION></OPTION></OPTIONS> node. Also the selected value is stored within the <VALUE></VALUE> node. We've assigned all the required properties of the ASP.NET control in XSLT. Similarly you can set the properties of Check box and Text box ASP.NET controls also. Also you can add other ASP.NET controls in the same way.

3. Web part Code

In the web part you need to read the XML data from the file or a list item. Then use the XSlTransform class to transform XML data into HTML using the XSL file. I will not go deep into this; however I'll write few lines of code for your reference which returns the HTML string data from the transformation of XML using the XSLT.


/// <summary>
/// Render xml/xsl into html using path of xsl and content of xml
/// </summary>
/// <param name="XMLString"> string formate of XML</param>
/// <returns></returns>
public virtual string RenderIntoHTML(string XMLString)
{
string XHTML = "";
string sXslUrl = "";
try
{
if (XMLString == null) { throw new System.ArgumentException("XSL/XML file is missing"); }
if (xslPath == null) { throw new System.ArgumentException("XSL file is missing"); }
sXslUrl = GetXslLocation + xslPath;

using (StringWriter swRenderedContents = new StringWriter())
{
if (xmlDocument == null) { xmlDocument = new XmlDocument(); }//creating objects
if (xslTransform == null) { xslTransform = new XslCompiledTransform(); }//creating objects
if (xslArguList == null) { xslArguList = new XsltArgumentList(); }//creating objects

xmlDocument.LoadXml(XMLString);

byte[] byteArray = System.Text.Encoding.UTF8.GetBytes(xslPath.Replace("\\\"", "\""));

MemoryStream streamd = new MemoryStream(byteArray);

XmlTextReader ob = new XmlTextReader(streamd);

xslTransform.Load(ob);
//xslTransform.Load(sXslUrl);



xslTransform.Transform(xmlDocument, xslArguList, swRenderedContents);

XHTML = swRenderedContents.ToString().Replace("<?xml version=\"1.0\" encoding=\"utf-16\"?>", "").Replace("xmlns:asp=\"remove\"", "").Replace("<br>", "<br/>").Replace("xmlns:SharePointWebControls=\"Microsoft.SharePoint.WebControls\"", "").Replace("xmlns:PublishingNavigation=\"Microsoft.SharePoint.Publishing.Navigation\"", "");
streamd.Close();
ob.Close();
}
}
catch (FileNotFoundException ex)
{

throw ex;
}
catch (Exception ex)
{

throw ex;
}
finally
{
xslTransform = null;
xslArguList = null;
xmlDocument = null;
}
return XHTML;

}

Now the most important section is parsing the HTML string into the ASP.NET controls. Therefore in the CreateChildControls method the web part, just after calling the above method we will write following code.

Control ctrl = null;

string strRenderHtml = string.Empty;

strRenderHtml = RenderIntoHTML(strXML);

ctrl = Page.ParseControl(strRenderHtml);

this.Controls.Add(ctrl);

We are using the ParseControl() method of the page to parse the HTML string into ASP.NET controls.

That's it. Done! Your ASP.Net controls defined in the XSLT are rendered in your web part without any custom rendering logic written in the web part code. Thus, if tomorrow customer wants to add new dropdown list/text box etc. in your web part, you just need to add corresponding nodes in the XML file. Also if you need to change the display of your web part (controls positioning change etc.), you just need to make these changes in the XSLT file without the web part code change.

Monday, May 3, 2010

Connectable Web part with AJAX functionality Implementation

This approach is implemented to avoid the page refresh on an event occurred in the Provider web part (e.g. Button click etc.) to post data that will be consumed by the consumer web part. There are two steps in implementing this approach: 1. Using AJAX UpdatePanel to avoid Page Refresh The web part zone containing all the web parts (e.g. – Provider and all the consumers) needs to be put inside an Ajax “UpdatePanel”. This change needs to be done in the layouts page. The default code for a webpart zone is as below.
<WebPartPages:WebPartZone runat="server" Title="<%$Resources:cms,WebPartZoneTitle_Header%>" ID="Header"><ZoneTemplate></ZoneTemplate> </WebPartPages:WebPartZone> Above code should be replaced with the following code. The highlighted code is added for Ajax functionality:
<asp:UpdatePanel ID="UpdatePanel1" runat="server"> <contenttemplate> <WebPartPages:WebPartZone runat="server" Title="<%$Resources:cms,WebPartZoneTitle_Header%>" ID="Header"><ZoneTemplate></ZoneTemplate> </WebPartPages:WebPartZone> </contenttemplate> </asp:UpdatePanel>
Now put all the web parts inside this web part zone.
2. Passing values through Connectable Web Part For connectable web parts, implement the interface that best suits your requirement. You have plenty of choices for connection interfaces. For example – IcellProvider, IRowProvider etc. You can find the approach to create connectable web parts here. Basically you need to override some required methods (e.g. - EnsureInterfaces, CanRunAt, PartCommunicationConnect etc.) and also have to implement some event handlers (e.g. RowProviderInit and RowReady etc. for IRowProvider). IMP: - Here you need to know that the “PreRender” event is the first method in the consumer class where you can read the values from the Provider web part. Therefore if you need the provider values in the CreateChildControls method (for control creation etc.), you need to call the base.CreateChildControls() method in “PreRender” event of the consumer class. Otherwise sometimes the consumer data will not get refreshed on provider value change.