I wanted to use multiple small Flex applications in Microsoft Sharepoint and have them communicate with one another. Embedding Flex applications within Sharepoint can be quite simple, Rahul Mainkar has written an excellent article, Embedding Flex 3 applications within the SharePoint Portal Server, but I wanted to use SWFObject to check Flash player versions and handle multiple browsers.
The solution has three parts, a simple .NET class that inherits from System.Web.UI.WebControls.WebParts.WebPart that acts as a wrapper around SWFObject, an ActionScript class that creates a singleton JavaScript object that acts as an event dispatcher for all SWF files on the page and the JavaScript singleton itself.
Before coming up with this solution, I looked at several options for SWF-to-SWF communication. LocalConnection wasn’t an option since I needed more than one-to-one communication and LocalConnection has no broadcast capability. The Flex Ajax Bridge was a little over complicated and added additional dependencies I didn’t want. Sharepoint WebParts that inherit from Microsoft.SharePoint.WebPartPages.WebPart (WSS.WebPart) can create client-side connections, but these WebParts can only be used in Sharepoint, while WebParts that inherit from System.Web.UI.WebControls.WebParts.WebPart (ASP.WebPart) can be used in other ASP.NET sites.
In 2007, with the release of Windows Sharepoint Services (WSS) 3.0 and Microsoft Office Sharepoint Server (MOSS) 2007, Microsoft began using ASP.NET 2.0 as the base technology for Sharepoint. Despite this, there are still two options, alluded to in the problem summary, for creating WebParts, Microsoft.SharePoint.WebPartPages.WebPart (WSS.WebPart) and .Web.UI.WebControls.WebParts.WebPart (ASP.WebPart). It should be noted that the WSS.WebPart inherits from ASP.WebPart. I decided to implement my own ASP.WebPart.
The Wrapper class is pretty simple. There is a single RenderContents method that outputs the HTML to the page. This method uses a constant string as the base and simply does string replacement using properties for the input. Sharepoint pages have multiple display modes including browse, design, edit, catalog and connect. We only want the Flex application to be active when the page is in browse mode. This makes the page more responsive in the other modes. This is done by checking the WebManager DisplayMode and simply commenting out the swfobject.embedSWF method call. This makes the full HTML output by the class visible in edit mode within Sharepoint, which can be useful for debugging.
Here is the RenderContents method.
protected override void RenderContents(HtmlTextWriter writer){ System.Text.StringBuilder html = new System.Text.StringBuilder(SWF.HTML); html = html.Replace(SWFREPLACE, this.SWFpath); html = html.Replace(GUIDREPLACE, this._divGuid); html = html.Replace(IDREPLACE, this.SWFID); html = html.Replace(SWFOBJECTPATH, this.SWFObjectPath); html = html.Replace(EXPRESSINSTALLPATH, this.ExpressInstallPath); html = html.Replace("HEIGHT", this.Height.ToString()); html = html.Replace("WIDTH", this.Width.ToString());if (base.WebPartManager.DisplayMode != WebPartManager.BrowseDisplayMode){html = html.Replace("MESSAGE_REPLACE", SWF.EDITMESSAGE);html = html.Replace("swfobject.embedSWF", "//swfobject.embedSWF");}else{html = html.Replace("MESSAGE_REPLACE", SWF.FLASHMESSAGE);}writer.Write(html);}Below is the base string. Most of the replacement strings are provided by properties exposed to the user. The GUIDREPLACE string value is created on instantiation and is only to give a unique ID to the DIV that SWFObject will add the SWF to. The comment block is only there to make troubleshooting easier. All of the other properties are exposed to edit in the Sharepoint editing pane. The default property values I chose all refer to files included in the SWFObject 2.2 download and I set the virtual path to “_controltemplates”. This will be explained later.;
private const string HTML = @"
<!-- SWF Path = SWFREPLACE; GUID = GUIDREPLACE; ID = IDREPLACE; height = HEIGHT; width = WIDTH -->
<script type=""text/javascript"" src=""SWFOBJECTPATH""></script>
<div id=""GUIDREPLACE"">MESSAGE_REPLACE </div>
<script type=""text/javascript"">
var flashvars = {};
var params = {};
var attributes = {id:""IDREPLACE"",name:""IDREPLACE""};
swfobject.embedSWF(""SWFREPLACE"", ""GUIDREPLACE"", ""100%"", ""100%"", ""9.0.0"", ""EXPRESSINSTALLPATH"", flashvars,params,attributes);
</script>
";
All of the properties are exposed through the editing pane within Sharepoint. The custom properties are in a category called “Flex Properties”. To expose these properties you just decorate your properties with the Personalizable attribute and set the WebBrowsable attribute to true. The System.ComponentModel.Category creates the custom category and the WebDisplayName is pretty self-explanatory. I have no idea where the WebDescription is supposed to show up, but added it anyway. The complete class can be found in SWF.cs.
The property definition below, for the SWFPath property shows these attributes in use. All of the properties exposed to Sharepoint have these.
[Personalizable(PersonalizationScope.User),WebBrowsable(true), WebDisplayName("SWF Path"),System.ComponentModel.Category("Flex Properties"),WebDescription("Virtual path to SWF file")]
public string SWFpath{get { return this._swfPath; }set { this._swfPath = value; }
}
To create your own wrapper file, create a new Class Library in Visual Studio. Add a reference to System.Web. This will allow you to use the System.Web.UI.WebControls.WebParts namespace. Go to project properties and change the target framework to .NET 2.0, this class doesn’t need a newer version. The easiest way to deploy the DLL on the Sharepoint server will be to install it to the Global Assembly Cache, which requires a strong name, so make sure you sign the assembly. We’ll go through the installation procedure later.
The ActionScript class, JavascriptEventHandler has four methods. The constructor takes the id of the Flex application (this.id) and makes several calls to ExternalInterface.
The first call, using the string constant shown below is creating a singleton Javascript object called EventHandler. This object will be used by the other methods of the JavascriptEventHandler class. The next few lines of code are used to determine whether the browser supports the “document.id” or “window.id” Javascript syntax. This will be used in the RegisterCallback function, so the result is persisted in a private variable.
private static const HANDLER:String = "if (window.EH == undefined){EH = {};EH.EventHandler = {list:[]," + "addCallback:function(eventName,func){this.list[this.list.length] = [eventName,func];}," + "version:'1.3',errors:[],creator:'CREATOR'," + "dispatch:function(eventName, eventArgs){for(i=0;i<this.list.length;i++){if(eventName == this.list[i][0]){try{this.list[i][1](eventArgs);}catch(err){this.errors[this.errors.length] = err;}}};}," + "events:[],registerEvent:function(id,eventName){this.events[this.events.length] = [id,eventName];}}}";The constant string contains the Javascript object literal syntax to create the EventHandler object in the EH namespace. This uses a singleton pattern to ensure that events raised by one SWF can be seen by all others
Each method of the JavaScript EventHandler object has a corresponding ActionScript method.
The addCallback method allows Flex applications to register a callback function for a specific event, defined elsewhere. This seems to be the only really browser dependent piece as the callback functions need to refer to the registered object by using either “document.id” or “window.id”, however it has only been tested with Firefox 3 and MSIE 7. Here is the ActionScript method that uses it.
public function RegisterCallback(methodName:String, method:Function):void { if (ExternalInterface.available){ ExternalInterface.addCallback(methodName, method); var e:String = "EH.EventHandler.addCallback('" + methodName + "',function(msg){UNKNOWN." + this._id + "." + methodName + "(msg);});"; if(this._useDocument) e = e.replace("UNKNOWN","document"); e = e.replace("UNKNOWN","window"); ExternalInterface.call("eval",e); }}public function DispatchEvent(eventName:String,...args):void{ if (ExternalInterface.available){ ExternalInterface.call("EH.EventHandler.dispatch",eventName,args); }}The registerEvent method allows Flex applications to publish the events they raise to the page. This is useful during development and testing.
public function RegisterEvent(eventName:String):void{ if (ExternalInterface.available){ ExternalInterface.call("EH.EventHandler.registerEvent",this._id,eventName); }} What all this does is make passing events from multiple Flex applications quite simple. All the JavaScript needed is emitted by the JavaScriptEventHandler class. You just create a new instance of the class, most likely in an initialize or creationComplete event handler and use the RegisterEvent method to publish any events the application raises. You use the RegisterCallback function to hook into events raised by any other SWF file. Then you just call DispatchEvent in your own event handlers.
As previously mentioned, the simplest way to install the wrapper DLL is to sign the assembly and put it in the Global Assembly Cache on the Sharepoint server. But you aren’t done yet. To use the DLL within Sharepoint you need to edit the web.config of the Sharepoint site. This is in the home directory, usually c:\inetpub\wwwroot\wss\VirtualDirectories\PORT. What you need to do is add a SafeControl entry under configuration > SharePoint > SafeControls. You should see twenty or more entries in there already, so it isn’t hard to find.
You then want to add an entry for your DLL, as shown below. You really need to make sure that full Assembly name is in there, including the public key token, Namespace, everything. I strongly suggest this is not done on a production Sharepoint server at first and make a backup copy of the web.config before you edit it!
<SafeControl Assembly="FlexWebPartWrapper, Version=1.0.0.0, Culture=neutral, PublicKeyToken=TOKEN" Namespace="Flex" TypeName="*" Safe="True"/>
Ok, almost done. Now to make the WebPart available you need to add it to the Web Part Gallery. This can be found in Site Settings for the top-level Sharepoint site. Click on Web Parts, then click on New. You’ll now see a list of Web Parts that can be added. If your web.config edits were successful, you should see your FlexWebPartWrapper in there. Just select it then click Populate Gallery. If not, try recycling the Application Pool for the Sharepoint site. If that doesn’t work, triple-check that the SafeControl entry is 100% correct.
The last steps are pretty simple. Put the SWFObject files (test.swf, expressInstall.swf, and swfobject.js) wherever the _controltemplates virtual directory is. Of course, if you changed the code to point to a different directory, put them there. Now, finally, you are done. The good news is that all of the above only needs to be done once. Now any Flex application can be hosted with Sharepoint.
Finally, we get to actually use this! For demo purposes, I’ve created two simple MXML applications, a sender and a receiver, Exhibits C & D respectively. The sender class has a TextInput, a NumericStepper, a Button and three labels. When it initializes, it creates an instance of the JavascriptEventHandler, registers an “OnClick” event and registers a callback to itself. This last is a little pointless, but fun. It has a btnClick method that will pass the string from the TextInput and the number from the NumericStepper and call the JavascriptEventHandler’s DispatchEvent method.
The receiver is even simpler; its only control is a label. It also has an initialization method that creates an instance of the JavascriptEventHandler and registers a callback. We just need to build these and put them in the _controltemplates virtual directory of the Sharepoint server.
Now, create a new Web Part page anywhere on the Sharepoint site, I created one with three zones. Once that is done, go to edit page, choose a Web Part Zone and click on “Add a Web Part”. Down under Miscellaneous you will see the class created here, SWF. Check that and hit Add. If you don’t edit the control and used the exact class written here, you should see the test.swf file (shows Flash player version) on the page. I added three instances of the SWF webpart, edited one to be a Sender and the other two to be Receivers.
Congratulations, you did it!