Building AJAX Web Part Parts for WSS


Because we have built the server-side APIs for data sources, including XML feeds and Web service endpoints, the only components that go in the Web Part are JavaScript components and functions, HTML placeholders that get rendered within Web Part boundaries, and JavaScript runtime. The bulk of the server processing is moved from the Web Part to the services layer, exposed to the client runtime through XML feeds and Web services. We build JavaScript classes that can be instantiated on the client from within the Web Part and deploy these classes through the ScriptManager control. The System.Web.UI.ScriptManager class is an ASP.NET control that manages scripts within the framework. It is responsible for deploying our scripts, ensuring that the Microsoft ASP.NET AJAX Framework is included, and providing JavaScript proxy support for Web service references.

To use ASP.NET AJAX components in your Web Parts, you need to add an instance of the ScriptManager class to the page. Because there can be only one ScriptManager instance per page, be sure to obtain a reference to ScriptManager through its static GetCurrent method. To obtain a reference to ScriptManager, use a reference-safe property, such as the following property of the AjaxWebPart base class in the SharePoint AJAX Toolkit.

 private ScriptManager scriptMan; public ScriptManager AtlasScriptManager {   get {     if (scriptMan == null){     scriptMan = ScriptManager.GetCurrent(this.Page);     if (scriptMan == null) {       scriptMan = new ScriptManager();       this.Controls.Add(scriptMan);     }   }   return scriptMan;  } }

WebConfig Requirements for ASP.NET AJAX

The ASP.NET AJAX runtime requires the following web.config modification to enable the client runtime. The following HTTP handler needs to be added in each IIS Web site’s web.config. (When using the SharePoint AJAX Toolkit, the Feature Installer automatically adds this.)

 <add verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandl er, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad36 4e35" validate="false" />

Within Web service application directories, typically deployed in subdirectories of 12\ISAPI (served through the virtualized path http://[serverroot]/[sitepath]/_vti_bin), you want to deploy a configuration file that enables JavaScript proxy generation. This configuration file removes the System.Web.Services.Protocols.WebServiceHandlerFactory handler for ASMX Web services and adds the System.Web.Script.Services.ScriptHandlerFactory handler. This handler builds the JavaScript proxies for endpoints marked with the ScriptService attribute defined in System.Web.Script.Services and passes control to the System.Web.Services.Protocols .WebServiceHandlerFactory for Web service methods. Following is a reference web.config for Web service applications. Note that you need to deploy only SOAP (ASMX) Web services in the ISAPI folder; REST Web services defined as HTTP handlers can be deployed within the root IIS application.

 <?xml version="1.0"?> <configuration>  <system.web>   <httpHandlers>    <remove verb="*" path="*.asmx"/>    <add verb="*" path="*.asmx" validate="false" type="System.Web.Script.Services.ScriptHandl erFactory, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3 856ad364e35"/>   </httpHandlers>  </system.web> </configuration>

AJAX Web Parts

Because we have already defined a wiki Web service API and prototyped the WikiControl AJAX component, we are now ready for a wiki Web Part for WSS. In the following example, we will define the MicroWikiWebPart, which is a simple wrapper for the WikiControl AJAX component. Because Web Parts are deployed independently from the pages on which they run, AJAX components within Web Parts require using the Web Part Client Control Registration Pattern. With this design pattern, a placeholder is registered with the page and processed through a convention in the JavaScript application load event. The template is written to an array named by a convention based on the script component, such as the WikiControlTemplates array for the WikiControl JavaScript class. This script, along with a div element referenced in the component template, is written to the page by each Web Part within the RenderContents method. This enables multiple Web Parts based on the same AJAX component to be deployed on the same page with unique instances per control. For the MicroWiki Web Part, we want to generate the following HTML and JavaScript to process the template upon application load:

 <div  ></div> <script type="text/javascript" language="javascript"> if (window.WikiControlTemplates == null)     window.WikiControlTemplates = new Array(); var wikiTemplate = new Object(); wikiTemplate.Control = 'WikiComponentControlctl00_m_g_95135f04'; wikiTemplate.WikiID = 'home'; window.WikiControlTemplates.push(wikiTemplate); </script>

From the window.WikiControlTemplates array, the code within the WikiControl script component initializes components on application load. The control reference is passed by the control ID in the Control property, and other properties of the component, such as WikiID, are also set in the template object. The processing of this template array happens in the component’s Init method that is added to the application load event, as demonstrated in the following addition to the WikiControl component:

 // WebPart initialization script: Litware.WikiControl.Init = function(){   if( window.WikiControlTemplates ){     for(var i=0;i<window.WikiControlTemplates.length;i++){       var o = window.WikiControlTemplates[i];       if (o != null){         var wiki = new Litware.WikiControl( $get(o.Control), o.WikiID );         wiki.initialize();         o = null;       }     }     window.WikiControlTemplates = null;   } } Sys.Application.add_load(Litware.WikiControl.Init);

When calling Web services in the SharePoint site context, the path of the Web service proxy needs to be set before it is called. Otherwise, you are accessing the Web service for the root site. To set the proxy’s path, use the set_path method. You can set a window property of spWebUrl through your Web Part as follows:

 this.Page.ClientScript.RegisterClientScriptBlock( this.GetType(),   "spweburl", string.Format("window.spWebUrl='{0}';",   SPContext.Current.Web.Url), true);

In the JavaScript component, you can then set the Web service proxy’s path by using set_path. You should set the path before each call, as other components may set this to arbitrary site URLs based on their actions. The following JavaScript call sets the Litware.WikiWebService’s path property to the current WSS site context:

 Litware.WikiWebService.set_path(    window.spWebUrl + '/_vti_bin/Litware/WikiWebService.asmx');

With the control initialization code in place, the MicroWiki Web Part only needs to render the initialization template and register the JavaScript resources and Web service proxy through the ScriptManager. The MicroWiki Web Part defined in Listing 5-6 demonstrates this principle-the AJAX Web Part simply registers script components and writes a placeholder control for the JavaScript component to initialize.

Listing 5-6: The MicroWiki WebPart demonstrates a simple AJAX implementation.

image from book
 MicroWiki Web Part using System; using System.Web.UI; using Microsoft.SharePoint; using System.Web.UI.WebControls.WebParts; namespace LitwareAjaxWebParts {   public class MicroWikiWebPart : WebPart {     private ScriptManager scriptMan;     public ScriptManager AtlasScriptManager {       get {         if (scriptMan == null) {           scriptMan = ScriptManager.GetCurrent(this.Page);           if (scriptMan == null) {             scriptMan = new ScriptManager();             this.Controls.Add(scriptMan);           }         }         return scriptMan;       }     }     private const string scriptFormat =       @"if (window.WikiControlTemplates == null){{       window.WikiControlTemplates = new Array();       }}       var xc = new Object();       xc.Control = 'WikiComponentControl{0}';       xc.WikiID = 'home';       window.WikiControlTemplates.push(xc); ";     // Register controls and scripts     protected override void CreateChildControls() {       base.CreateChildControls();       ScriptReference editableControlScript = new ScriptReference(         "LitwareAjaxWebParts.Resources.EditableControl.js",         "LitwareAjaxWebParts");       this.AtlasScriptManager.Scripts.Add(editableControlScript);       ScriptReference wikiControlScript = new ScriptReference(         "LitwareAjaxWebParts.Resources.WikiControl.js",         "LitwareAjaxWebParts");       this.AtlasScriptManager.Scripts.Add(wikiControlScript);       ServiceReference serviceReference = new ServiceReference(         SPContext.Current.Web.Url         + "/_vti_bin/Litware/WikiWebService.asmx");       this.AtlasScriptManager.Services.Add(serviceReference);     }     protected override void RenderContents(HtmlTextWriter writer) {        base.RenderContents(writer);       writer.Write(@"<div id=""WikiComponentControl{0}"" ></div>",         this.ClientID);       writer.Write(@"<script type=""text/javascript"" language=""javascript"">");       writer.Write(scriptFormat, this.ClientID);       writer.Write(@"</script>");     }   } }
image from book

Warning 

AJAX proxies to WSS Web services will not be created with the proper WSS site context. You must set the path before each call.

The MicroWiki Web Part demonstrates an AJAX Web Part that uses a simple Web service to persist the text content in a WSS list. In the following examples, we will look at more advanced Web Parts that utilize XML and XSLT transforms on the client. We’ll also look at the SharePoint AJAX Toolkit, a framework for AJAX components that handles most of the heavy lifting for WSS AJAX components.

Introducing the SharePoint AJAX Toolkit

The SharePoint AJAX Toolkit is a developer framework for writing AJAX code for WSS and Microsoft Office SharePoint Server (MOSS) by using the ASP.NET AJAX Library. It includes a core script library for client-side data loading and XSLT transformations, base Web Part classes targeted for AJAX code, and a Solution Package installer that registers the required elements in web.config. The XML Web Part included in the Toolkit can be used for simple XML data loads and XSLT transforms and can be extended to add functionality. By default, the XML Web Part uses an XSLT style sheet for use with RSS data sources because WSS uses RSS for list syndication. The SharePoint AJAX Toolkit is available in both source code and binary distributions from http://www.codeplex.com/sharepointajax and is also included with the code samples for this book. Whether you use the SharePoint AJAX Toolkit or write your own AJAX implementation, the principles demonstrated in the Toolkit are central to AJAX programming.

The SharePoint AJAX Toolkit takes a purist approach to AJAX development and ASP.NET- HTML elements and JavaScript components are used to program against XML data sources by using XSLT transforms and Web service endpoints. This separates development projects into two categories: API-related components, including XML data streams and Web service endpoints; and user interface–related elements built with JavaScript and XSLT and deployed in Web Parts. Because much of the WSS framework is based on XML and already has XML APIs, you should find that this approach makes for an integrated architecture that is straightforward and easily extended by lightweight user interface components. Common user interface code can be used to consume a library of rich data sources based on RSS, Collaborative Application Markup Language (CAML) queries, OPML, and other XML data sources.

The core Toolkit consists of two primary JavaScript files that form a library of WSS-friendly JavaScript functionality. The first file defines the SharePoint.Ajax JavaScript namespace and includes the utility functions used for everyday tasks. Primarily, these include data-loading functions and cross-browser XSLT transform code. Data loading in the Toolkit happens primarily with the SharePoint.Ajax.DataLoader method, which loads data from a given URL and performs a delegate callback upon completion by utilizing the Sys.Net.WebRequest object internally. For example, to call the static DataLoader method and call the set_Xml method of the current object instance, you could use the following method syntax, which would fire an asynchronous Web request and call the set_Xml method upon completion:

 SharePoint.Ajax.DataLoader(url,'set_Xml',this)

When the LoadXml method is called, the instance reference is passed into the DataLoader, which calls the instance’s set_Xml method in the callback, passing in the XML DOM object returned from the data source. Without the DataLoader function, you would need to create a static callback function and then call the instance method. By using the DataLoader function, the generic DataLoaderCallback function handles this for you.

The other technique that is central to AJAX programming is client-side XSLT. Making XML data requests and applying an XSLT transform on the client enables dynamic user interfaces that can evolve at runtime. The SharePoint AJAX Toolkit provides the XmlTransform method that takes an XML DOM parameter, an XSLT DOM parameter, and a Control parameter into which the content is transformed. Following is the source code for the cross-browser XSLT transform as well as the HtmlDecode method that is used for the Mozilla compatibility layer. The Internet Explorer method is simpler, using the transformNode function of the MSXML XMLDOM object, whereas Mozilla uses the browser’s XSLTProcessor object and requires further decoding of HTML-encoded elements via the HtmlDecode method. Following is the source code for the XmlTransform method, a key component in the AJAX application architecture:

 SharePoint.Ajax.XmlTransform = function (xml, xsl, control, decode){   if(decode == null)decode = true;   if (!window.XSLTProcessor){ // IE     var content = xml.transformNode(xsl);     control.innerHTML = content;   }else{ // MOZILLA   var processor = new XSLTProcessor();   processor.importStylesheet(xsl);   var content = processor.transformToFragment(xml, document);     control.innerHTML = '';     if(decode){       var div = document.createElement('div');       div.appendChild(content);       control.innerHTML = SharePoint.Ajax.HtmlDecode(div.innerHTML);     }else{       control.appendChild(content);     }   } } // Client side version of the Server.HtmlDecode method // for Mozilla RSS transform compatibility. SharePoint.Ajax.HtmlDecode = function HtmlDecode(enc) {   return enc.replace(/&quot;/gi,String.fromCharCode(0x0022)) .replace(/&amp;/gi,String.fromCharCode(0x0026)) .replace(/&lt;/gi,String.fromCharCode(0x003c)) .replace(/&gt;/gi,String.fromCharCode(0x003e)); }

Introducing the SharePoint.Ajax.XmlComponent

The main client component used in the SharePoint AJAX Toolkit is the XmlComponent, which is a simple JavaScript extension to an HTML element (such as a div) that adds XML data loading and transforming capabilities. The XmlComponent is a simple object to work with and provides the main building block of the AJAX user interface. By performing simple calls to the LoadXml and LoadXsl methods, you can build a dynamic AJAX user interface by using server data sources. These methods demonstrate the main principles of AJAX development-data loads happen asynchronously and must be handled in callbacks. The source code for the SharePoint.Ajax.XmlComponent is included in Listing 5-7.

Listing 5-7: The SharePoint.Ajax.XmlComponent demonstrates an AJAX component for client-side XML loading and transforms.

image from book
  SharePoint.Ajax.XmlComponent // XmlComponent Component SharePoint.Ajax.XmlComponent = function(element){   SharePoint.Ajax.XmlComponent.initializeBase(this);   this.element = element;   element.XmlComponent = this;   element.controlType='XmlComponent'; } SharePoint.Ajax.XmlComponent.prototype={   initialize : function() {     SharePoint.Ajax.XmlComponent.callBaseMethod(this, "initialize");   },   _rendered : false,   _xml : null,   _xsl : null,   _visible : true,   element : null,   interval : null,   timerID : null,   xmlUrl : null,   xslUrl : null,   // defines a connection that can be used in custom code.   Connection : null,   set_Xml : function (xml){     this._xml = xml;     if (this._xml != null & this._xsl != null) this.Render();   },   set_Xsl : function (xml){     this._xsl = xml;     if (this._xml != null & this._xsl != null)       this.Render();   },   set_Visible : function(visible){     this._visible = visible;     if (this.element == null) return;     if (visible){       this.element.style.display = 'block';       if (this.parentElement != null)         this.parentElement.style.display = 'block';       }else {       this.element.style.display = 'none';       if (this.parentElement != null)         this.parentElement.style.display = 'none';   } }, set_Refresh : function(int){   if (this.timerID != null){     window.clearInterval(this.timerID);     this.timerID = null;   }   this.interval = int;   if (int > 0){     this.timerID = window.setInterval(       String.format("SharePoint.Ajax.XmlComponent.Refresh('{0}')",         this.element.id),int);   } }, set_Control : function(control){   this.element = control;   if (this._xml != null & this._xsl != null)     this.Render(); }, parentElement : null, set_Parent : function(control){   this.parentElement = control; }, Reload : function(){   this.LoadXml(this.xmlUrl); }, LoadXml : function(url){   this.xmlUrl = url;   SharePoint.Ajax.DataLoader(url,'set_Xml',this); }, LoadXsl : function(url){   this.xslUrl=url;   SharePoint.Ajax.DataLoader(url,'set_Xsl',this); }, Render : function(){   if (this.element == null || this._xml == null || !this._visible)     return;   var control = this.element;   SharePoint.Ajax.XmlTransform(this._xml, this._xsl, control);   if (this.parentElement != null)     this.parentElement.style.display = '';   control.style.display = '';   this._rendered = true; }, // Dispose of any resources used by the component dispose: function() {   if (this.timerID != null)     window.clearInterval(this.timerID);   this._xml = null;   if (this.element != null){     this.element.innerHTML = '';     this.element.parentNode.removeChild(this.element);   }     this.element = null;     SharePoint.Ajax.XmlComponent.callBaseMethod(this, 'dispose');   } } SharePoint.Ajax.XmlComponent.Refresh = function(arg){   Sys.Debug.trace( String.format("XmlComponent.Refresh({0})", arg) );   var element = $get(arg);   var xmlComponent = element.XmlComponent;   if (xmlComponent != null)     xmlComponent.Reload();   else     Sys.Debug.trace( String.format("Component not found:{0}", arg) ); } SharePoint.Ajax.XmlComponent.Init = function(){   if( window.XmlComponentTemplates != 'undefined'       && window.XmlComponentTemplates != null){     for(var i=0;i<window.XmlComponentTemplates.length;i++){       var o = window.XmlComponentTemplates[i];       if (o != null){         var XmlControl =           new SharePoint.Ajax.XmlComponent( $get(o.Control) );         XmlControl.LoadXml(o.XmlUrl);         XmlControl.LoadXsl(o.XsltUrl);         XmlControl.set_Refresh(o.Refresh);         if( o.Connection != null && o.Connection.length > 0)           XmlControl.Connection = o.Connection;         o = null;       }     }   } } Sys.Application.add_load(SharePoint.Ajax.XmlComponent.Init); 
image from book

You can create XmlComponent objects by passing in an element, such as a div, in the constructor and setting the XML and XSLT URLs, and the component then handles the processing. The LoadXml and LoadXsl functions use the DataLoader utility method to set the XML and XSL properties, in which the Render method is called when the data and transform are loaded. The following JavaScript code example creates an XmlComponent from the div element with the ID “Placeholder.” This code could be executed in a page load script. Later in the chapter, we will build on this example to create arbitrary XmlComponent instances from Web Parts.

 var formElement = $get('Placeholder'); var xmlControl = new SharePoint.Ajax.XmlComponent(formElement); xmlControl.LoadXml('opml.aspx'); xmlControl.LoadXsl('opml.xslt);

The interface of the XmlComponent is described in Table 5-1. When using instances of XmlComponent, you work primarily with the methods listed in this table.

Table 5-1: SharePoint.Ajax.XmlComponent JavaScript Class
Open table as spreadsheet

Name

Description

element

Defines the HTML element to which the behavior is added

LoadXml(xmlUrl)

Loads XML data specified by the xmlUrl parameter

LoadXsl(xmlUrl)

Loads XSL data specified by the xmlUrl parameter

set_Xml(XMLDOM)

Sets the manually loaded XMLDOM to the XML data source

set_Xsl(XMLDOM)

Sets the manually loaded XSLT style sheet

Render()

Redraws the component; automatically called when both the XML and XSL data sources are loaded

set_Refresh(Number)

Sets the refresh interval in milliseconds

Connection

Specifies the ID of the optionally connected component

Within a Web Part, we create the XmlComponent instance based on a placeholder that is rendered in the RenderContents method. The XML Web Part in the SharePoint AJAX Toolkit handles this for you in the Web Part’s RenderContents method and serves as a pattern for custom AJAX Web Parts. Before looking at the XML Web Part, let’s look first at the base Web Part class used for AJAX infrastructure.

AjaxWebPart in the SharePoint AJAX Toolkit

The AjaxWebPart class is part of the Toolkit that handles core framework requirements, such as ensuring that the Script Manager exists on the page and that the core Toolkit JavaScript libraries are included. The Script Manager is provided through the protected AtlasScriptManager property. The abstract class AjaxWebPart is included in Listing 5-8.

Listing 5-8: The AjaxWebPart provides a common base class for AJAX Web Parts.

image from book
  SharePoint.Ajax.AjaxWebPart Base Class namespace SharePoint.Ajax.WebParts {   // A base Web Part class for AJAX Web Part implementations.   public abstract class AjaxWebPart :       System.Web.UI.WebControls.WebParts.WebPart {     private ScriptManager scriptMan;     //Gets the ScriptManager for this page     public ScriptManager AtlasScriptManager {       get {         if (scriptMan == null) {           scriptMan = ScriptManager.GetCurrent(this.Page);           if (scriptMan == null) {             scriptMan = new ScriptManager();             this.Controls.Add(scriptMan);           }         }         return scriptMan;       }     }     // Creates the required controls/script components     // for the SharePoint.Ajax framework     protected override void CreateChildControls() {       base.CreateChildControls();              ScriptReference framework =           new ScriptReference("SharePoint.Ajax.Script.SharePoint.Ajax.js",           "SharePoint.Ajax");       ScriptReference xmlComponent = new           ScriptReference("SharePoint.Ajax.Script.XmlComponent.js",           "SharePoint.Ajax");              this.AtlasScriptManager.Scripts.Add(framework);       this.AtlasScriptManager.Scripts.Add(xmlComponent);     }   } } 
image from book

Tip 

ASP.NET AJAX was code-named Atlas and is still referred to as Atlas in some code libraries.

XmlWebPart in the SharePoint AJAX Toolkit

The toolkit’s XmlWebPart class is a basic Web Part wrapper of a JavaScript XmlComponent that can serve as a base class for custom AJAX Web Parts using Xml data loads and transforms. This makes it easy to create a dynamic user interface by using SharePoint.Ajax.WebParts.XmlWebPart, writing some simple JavaScript functions, and providing XSLT for rendering logic. The XmlWebPart has an XmlUrl property and XsltUrl property, along with an optional refresh timer property that is used to refresh the data of the XmlComponent at the specified interval. By using SharePoint.Ajax.WebParts.XmlWebPart as a base class, 90 percent of the functionality needed for AJAX Web Part implementations is already provided.

Building the Litware AJAX Web Part Library

After examining the plumbing of the SharePoint AJAX Toolkit, we are now ready to make use of it in the Litware AJAX Web Part library. For clarity, we will build a pure AJAX solution for the examples that follow. However, you might often wish to use a hybrid architecture by using a combination of “traditional” ASP.NET components for static content and AJAX components for dynamic content that can change in the life cycle of the page instance.

Because we are leveraging the SharePoint AJAX Toolkit, the implementation should be rather simple, thereby letting you focus on design elements using XSLT, data sources using the previously built XML API, and a small library of JavaScript objects and methods. Because the framework handles the plumbing, the amount of custom JavaScript code required is minimized.

In the following examples, we will build an AJAX List View Web Part (based on the SharePoint.Ajax.WebParts.XmlWebPart) for use with WSS lists as well as a Feed List Web Part that will be used to browse lists within the site collection and send a client-side connection to the AJAX List View Web Part by using standard Web Part connections. To begin, we will define an AjaxListViewWebPart class that derives from SharePoint.Ajax.XmlWebPart. Because the XmlWebPart provides all the initial implementation for an RSS view, there will be little code needed for our basic functionality. We will later extend this class to accept a connection from the AJAX Feed List Web Part. For now, the initial class declaration will be as follows:

 namespace LitwareAjaxWebParts {   // An AJAX version of the Litware RSS Web Part,   // based on the SharePoint AJAX Toolkit   public class AjaxListViewWebPart : XmlWebPart {   } }

To test the Web Part, compile and deploy the Web Part as previously explained in Chapter 4 and set the XML URL property to a list RSS feed from within the Editor Part. The Web Part is displayed in Figure 5-3.

image from book
Figure 5-3: The AJAX List View Web Part renders the list’s RSS feed using client-side XSLT.

Next, we will build a Feed List Web Part. The Feed List Web Part is also based on the SharePoint.Ajax.XmlWebPart and uses the previously defined FeedListHandler XML REST endpoint that renders security-trimmed OPML for the site. Within the Feed List Web Part, we will define an interface for the connection in the List View Web Part as well as register the XSLT and JavaScript components we will use in the client. These resources are compiled into the assembly as embedded resources and marked as WebResources within the assembly information file as discussed in Chapter 4. Because we are leveraging the XmlComponent JavaScript class and the XmlWebPart infrastructure, all we must do is create the additional script resources and XSLT transform used by our component. The Feed List Web Part is listed in Listing 5-9.

Listing 5-9: The Feed List Web Part implements the XmlWebPart.

image from book
  Feed List Web Part using System; using System.Collections.Generic; using System.Text; using SharePoint.Ajax.WebParts; using System.Web.UI.WebControls.WebParts; using Microsoft.SharePoint; using System.Web.UI; using System.Globalization; namespace LitwareAjaxWebParts {   // A WebPart for feed lists   public class FeedListWebPart : XmlWebPart {     protected override void CreateChildControls() {       base.CreateChildControls();       ScriptReference scriptReference =         new ScriptReference(           @"LitwareAjaxWebParts.Resources.FeedListWebPart.js",           @"LitwareAjaxWebParts");       this.AtlasScriptManager.Scripts.Add(scriptReference);     }     protected override void OnPreRender(EventArgs e) {       base.OnPreRender(e);       string xsl = this.Page.ClientScript.GetWebResourceUrl(         typeof(FeedListWebPart),         @"LitwareAjaxWebParts.Resources.OPML.xslt");       this.XsltUrl = SPContext.Current.Web.Url + @"/" + xsl;       this.XmlUrl = SPContext.Current.Web.Url + @"/opml.ashx";     }   } } 
image from book

Within the CreateChildControls method, we will add the JavaScript components for our Web Part, as in previous examples. The base class framework registers the core script runtime, including the Microsoft AJAX Library, the SharePoint.Ajax namespace, and the SharePoint.Ajax.XmlComponent. Within the OnPreRender method, we will set the XML and XSLT URLs that let the base XmlWebPart create the specified XmlComponent instance.

The FeedListWebPart demonstrates the typical AJAX Web Part. It uses the XmlComponent from the base class framework, defines the initial data source URL, and adds a custom JavaScript component. Similar to the RSS view used in the List View Web Part, the Feed List Web Part builds its user interface from a transformed XML data source. It also embeds calls to JavaScript methods that reload the data so that the user can navigate through the site collection. For example, by clicking the node that represents a site, we call the Litware.FeedList.Load command to reload the site-relative OPML data source for the Feed List Web Part, as shown in the following XSLT example.

 <span onclick="javascript:Litware.FeedList.Load(     this, '{@htmlUrl}/opml.ashx');">   xsl:value-of select="@title" /> </span>

For List nodes, we will generate elements that call the Litware.FeedList.Navigate method used to send a Navigate command to the object to which the Feed List is connected:

 <div onclick="javascript:Litware.FeedList.Navigate(this, '{@xmlUrl}');">   <xsl:value-of select="@title" /> </div>

The full source of the OPML transform is shown in Listing 5-10. This architecture demonstrates the client-side XSLT pattern common in AJAX applications. The UI is rendered using XSLT with embedded script calls that perform data manipulation or navigational commands by calling methods defined in the Web Part’s script component.

Listing 5-10: The OPML XSLT defines the user interface for the AJAX component.

image from book
  Feed List OPML XSLT <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">    <xsl:output method="html" indent="yes" standalone="no"       omit-xml-declaration="yes" />    <xsl:template match="/">       <xsl:apply-templates select="opml" />    </xsl:template>    <xsl:template match="opml">       <xsl:apply-templates select="body" />    </xsl:template>    <xsl:template match="body">       <xsl:if test="outline/@parentWeb">          <div onclick="javascript:Litware.FeedList.Load(                this, '{outline/@parentWeb}/opml.ashx');"                style="cursor:hand;margin-top:3px;">             <img src="/books/4/221/1/html/2//_layouts/images/DOCLINK.GIF"/>             Up to parent site          </div>       </xsl:if>       <ul style="margin-left:15px; margin-top:0px;">          <xsl:apply-templates select="outline" />       </ul>    </xsl:template>    <xsl:template match="outline">       <li>          <xsl:choose>             <xsl:when test="@type='rss'">                <xsl:attribute name="style">cursor:hand;                list-style-image:url(/_layouts/images/LIST.GIF);                padding-left:6px;margin:3px;</xsl:attribute>                <div onclick="javascript:Litware.FeedList.Navigate(                      this, '{@xmlUrl}');">                   <xsl:value-of select="@title" />                </div>             </xsl:when>             <xsl:when test="@type='site'">                <xsl:attribute name="style">cursor:hand;                list-style-image:url(/_layouts/images/sts_web16.gif);                padding-left:6px;margin:3px;</xsl:attribute>                <span onclick="javascript:Litware.FeedList.Load(                      this, '{@htmlUrl}/opml.ashx');">                   <xsl:value-of select="@title" />                </span>                <ul style="margin-left:15px; margin-top:0px;">                   <xsl:apply-templates select="outline" />                </ul>             </xsl:when>          </xsl:choose>       </li>    </xsl:template> </xsl:stylesheet> 
image from book

Tip 

XSLT is a core technology for AJAX applications. For a primer on XSLT, we recommend the XSLT: Programmer’s Reference by Michael Kay (Wiley Publishing, Inc.).

When creating AJAX interfaces based on the XmlComponent, you might need only simple utility functions and not need to create custom object-oriented classes. For the Feed List Web Part, we will simply define Navigate and Load utility functions. In these methods, we will use the function SharePoint.Ajax.FindParentControl that returns the first parent control of the element to which an XmlComponent is attached. This common pattern is one in which the user action inside an element needs to find its parent control. The FindParentControl function is listed in the following code:

 // Looks up the control tree to find the parent control // with the controlType expando property SharePoint.Ajax.FindParentControl = function(child, controlType){   var control = child;   while (control != null && control.controlType != controlType){   control = control.parentElement;  }  if (control.controlType == controlType){   return control;  }  return null; }

With the XmlComponent reference, we have the information needed to either reload the component’s data from another URL in the case of the Load command or issue a LoadXml command to the connected object’s XmlComponent. (We will look at client-side connections in the following section.) The Litware Feed List Component JavaScript Load and Navigate functions are simple methods that find the parent XmlComponent using SharePoint.Ajax.FindParentControl and call methods of the XmlComponent:

 // Loads a new FeedList from the xmlUrl Litware.FeedList.Load = function (ref, xmlUrl){   var xmlControl = SharePoint.Ajax.FindParentControl(ref,'XmlComponent');   if (xmlControl != null && xmlControl.XmlComponent != null)     xmlControl.XmlComponent.LoadXml(xmlUrl); } // Issues a Navigate command to the feed at the xmlURL Litware.FeedList.Navigate = function (ref, xmlUrl){   var xmlControl = SharePoint.Ajax.FindParentControl(ref,'XmlComponent');   if (xmlControl == null || xmlControl.XmlComponent == null) return;   var id = xmlControl.XmlComponent.Connection;   if (id != null){     var control = $get(id);     if (control != null && control.XmlComponent != null){       control.XmlComponent.LoadXml(xmlUrl);     }   } }

Client-Side Connections for AJAX Web Parts

AJAX Web Parts can be much more interesting when connected together. For example, a List Reader component can be connected to a List Browser, enabling the user to quickly browse through the site’s feeds. The Web Parts connect together via the Web Part connection infrastructure and are then able to issue commands to each other on the client. To connect AJAX Web Parts, we will define a known interface and register the connection on the client. The XmlComponent defines a Connection property that is set when it is registered through the client component template.

The connection interface definition is simple and is the same technique used for non-AJAX Web Parts, as discussed in Chapter 4. Because the connection is specific to the client-side functionality, we will define an interface for use as a marker to identify the connectable components and provide the required server parameter. For this connection, we will define a simple IFeedProvider interface that specifies a component providing a feed to the XmlComponent of the XmlWebPart. The IFeedProvider interface simply defines a string that defines the connected Web Part’s XmlComponent element, from which the client can obtain a reference to the object. In this model, it is the connection provider that maintains a reference to the connection consumer. Also note that the connection does not define the feed URL as does the connection defined in Chapter 4, but the connection defines components that interact on the client rather than on the server. The connection happens once on the server, and the client components can issue multiple arbitrary control commands in response to user actions on the client.

The following interface is implemented in server code by the FeedListWebPart, which provides a connection to the ListViewWebPart to send client-side commands to the client-side XmlComponent of the ListViewWebPart:

 public interface IFeedProvider {   string FeedConsumerID { get; set; } }

Within the FeedListWebPart, the following property and GetFeedProvider connection method are used to provide the connection, which the consumer writes to just before the Render method is called. The connection provider attribute identifies the connection to the Web Part Manager to let the page designer set the connection in the page.

 string feedConsumerID; public string FeedConsumerID {   get { return this.feedConsumerID; }   set { this.feedConsumerID = value; } } [ConnectionProvider("Feed", AllowsMultipleConnections = false)] public IFeedProvider GetFeedProvider() {   return this; }

Within the Feed List Web Part, we enable the connection by setting the connection provider’s FeedConsumerID with the ID of the XMLComponent. The XmlComponent created on the client for the Feed List has a Connection property of the consumer’s ID. This can then be used to get a reference of its XmlComponent and issue JavaScript commands against its interface. To complete the Connection, we need to implement the connection in the List View Web Part. In the connection code, it simply obtains a reference to the IFeedProvider custom interface that we will set in the OnPreRender method. Because the XmlUrl is now connection based and we don’t want to allow customization of the RSS XSLT, we will also override the CreateEditorParts method. The final AjaxListViewWebPart is shown in Listing 5-11.

Listing 5-11: The AJAX List View Web Part accepts connections from the AJAX Feed List Web Part.

image from book
  AJAX List View Web Part using System; using SharePoint.Ajax.WebParts; using System.Web.UI.WebControls.WebParts; using System.Web.UI; namespace LitwareAjaxWebParts {   // An AJAX version of the Litware RSS Web Part,   // based on the SharePoint AJAX Toolkit   public class AjaxListViewWebPart : XmlWebPart {     private IFeedProvider feedProvider;     [ConnectionConsumer("Feed")]     public void RegisterCustomerProvider(IFeedProvider provider) {       this.feedProvider = provider;     }     // Don't allow any editor parts     public override EditorPartCollection CreateEditorParts() {       return new EditorPartCollection();     }     protected override void OnPreRender(EventArgs e) {       // Handle connection BEFORE base onprerender       if (this.feedProvider != null)       this.feedProvider.FeedConsumerID = this.XmlComponentID;       this.XmlUrl = null;       base.OnPreRender(e);     }   } } 
image from book




Inside Microsoft Windows Sharepoint Services Version 3
Inside Microsoft Windows Sharepoint Services Version 3
ISBN: 735623201
EAN: N/A
Year: 2007
Pages: 92

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net