Advanced Microsoft Content Management Server Development
Content Management Server 2002 Book Excerpts
Advanced Microsoft Content Management Server Development
 

This article is an excerpt from Advanced Microsoft Content Management Server Development published by Packt Publishing; ISBN 1904811531; copyright Packt Publishing 2005; all rights reserved.

Lim Mei Ying is a senior consultant with Avanade and has extensive experience in setting up Microsoft Content Management Server (MCMS) systems at the enterprise level, and she is a Microsoft Valuable Professional for MCMS. Mei Ying's blog

Stefan Goßner works for Microsoft as an escalation engineer in the Developer Support department. He maintains an extensive MCMS 2002 FAQ on the Microsoft Web site and provides MCMS tips and tricks on his personal blog. Stefan's blog

Angus Logan is a product specialist at Data#3 Limited, and he is a MCAD.NET and MCDBA, as well as a Microsoft Valuable Professional for MCMS. Angus' blog

Andrew Connell is a client/server consultant for Fidelity Information Services, and he is a Microsoft Valuable Professional for MCMS. Andrew's blog

No part of these chapters may be reproduced, stored in a retrieval system, or transmitted in any form or by any meanselectronic, electrostatic, mechanical, photocopying, recording, or otherwisewithout the prior written permission of the publisher, except in the case of brief quotations embodied in critical articles or reviews.

Chapter 8: Useful Placeholder Controls (Part 2 of 2)

Contents

8.3 A Placeholder Control for Multiple Attachments
    8.3.1 The MultipleAttachmentPlaceholderControl Class
    8.3.2 Generating the Table of Attachments
    8.3.3 Deleting Attachments
    8.3.4 Reusing the Insert Attachment Dialog Box
    8.3.5 Saving the List of Attachments
    8.3.6 Retrieving Saved Content
    8.3.7 Displaying the Attachments
    8.3.8 Using the MultipleAttachmentPlaceholderControl
8.4 Summary

8.3 A Placeholder Control for Multiple Attachments

We have seen SingleImagePlaceholderControl and SingleAttachmentPlaceholderControl at work. They are nifty controls for managing single files. Authors are able to upload files directly from their local computers or pick from shared resources in the resource gallery.

However, the biggest drawback of these controls is that files must be handled one at a time. More often than not, authors will need to attach multiple images or attachments. They could have a list of documents that support an article or a series of images that accompany a story. Whatever the case may be, a single slot for attachments may not always be sufficient.

You can get around this single-file limitation in one of three ways:

  • Provide authors with an HtmlPlaceholderControl.
  • Increase the number of SingleAttachmentPlaceholderControl and SingleImagePlaceholderControl controls on the page. Provide as many as the author requires.
  • Build a custom placeholder control that allows the upload of multiple files and images.

Let's consider the first option, which calls for the use of the HtmlPlaceholderControl. With the WYSIWYG editor, authors can upload as many images and attachments as they need to. Alternatively, they can also add unwanted text to the placeholder control. While giving authors complete freedom keeps them happy, it may cause problems when authors get carried away applying their own styles and layouts. More often than not, site owners would like more control over how the uploaded content is displayed and perhaps even to limit the number of images and attachments included—none of which can be accomplished using an HtmlPlaceholderControl.

The second option of increasing the number of SingleAttachmentPlaceholderControl and SingleImagePlaceholderControl controls on the page could be feasible if the number of attachments and images required can be accurately estimated. One major shortcoming of this approach is that for every placeholder control introduced, a corresponding placeholder definition must be created. Should the estimated number of attachments be large, so would the number of corresponding placeholder definitions. The larger the number of placeholders used by a posting, the more information needs to be retrieved by database queries, potentially compromising the overall performance of the site.

Clearly, neither of these out-of-the box solutions satisfies this requirement completely. To bridge the gap, let's explore the last option and build our own custom placeholder control that accepts multiple attachments with a friendly graphical user interface.

The maximum number of attachments will be decided by the developer. As a placeholder control, it will have different views in presentation and authoring modes. In authoring mode, the control will be displayed as a table with multiple rows, each row providing a slot for authors to upload an attachment. One interesting aspect of this control is that it will reuse the Insert Attachment dialog box that is provided with Web Author.

Control reuses Insert Attachment dialog box (click to see larger image)

Figure 8-6. Control reuses Insert Attachment dialog box (click picture to see larger image)

In presentation view, the attachments are displayed as a list of hyperlinks. Developers can choose to display links as icons or text. Here, we have chosen to show them as a list of icons. You can of course enhance the code to arrange them in any way you like, horizontally, vertically, or even within a table.

Attachments displayed as icons in presentation view

Figure 8-7. Attachments displayed as icons in presentation view

8.3.1 The MultipleAttachmentPlaceholderControl Class

To begin, we add a class named MultipleAttachmentPlaceholderControl.cs to the UsefulCustomPlaceholders project.

The class file uses the methods from the namespaces highlighted below. Add them above the namespace declaration:

using System;
using System.Xml;
using System.Web;
using System.Text;
using System.Web.UI;
using System.Web.UI.WebControls;
using Microsoft.ContentManagement.Publishing;
using Microsoft.ContentManagement.WebControls;
using Microsoft.ContentManagement.WebControls.Design;
using Microsoft.ContentManagement.Publishing.Extensions.Placeholders;
namespace UsefulCustomPlaceholders
{
}  . . . code continues . . .

A good way to manage lists like the one we are creating here is to store them as XML. XML gives us the flexibility to present the attachments in a wide range of ways later on. The control will therefore support XmlPlaceholderDefinition and be built by inheriting from BasePlaceholder:

[SupportedPlaceholderDefinitionType(typeof(XmlPlaceholderDefinition))]
public class MultipleAttachmentPlaceholderControl : BasePlaceholderControl
{
}  . . . code continues . . .

The control will provide three properties for developers:

  • MaxAttachments   Specifies the maximum number of attachments supported by the control. The initial value will be set to 10. Developers can change this value at design time by adjusting the value in the control's Properties dialog box.
  • DisplayAsIcon   Specifies if the attachments are to be displayed as icons or text. Similar to the UseGeneratedIcon property in the HtmlPlaceholderDefinition class.
  • ResourceGalleryOnly   Specifies if files from the author's computer can be uploaded as an attachment or if authors must choose from resources within resource galleries.

Add the following three properties below the class constructor:

private static int m_maxAttachments = 10;
public int MaxAttachments
{
  get
  {
    return m_maxAttachments;
  }
  set
  {
    m_maxAttachments = value;
  }

}
private bool m_displayAsIcon = false;
public bool DisplayAsIcon
{
  get
  {
    return m_displayAsIcon;
  }
  set
  {
    m_displayAsIcon = value;
  }
}
private bool m_resourceGalleryOnly = false;
public bool ResourceGalleryOnly
{
  get
  {
    return m_resourceGalleryOnly;
  }
  set
  {
    m_resourceGalleryOnly = value;
  }
}

8.3.2 Generating the Table of Attachments

The user interface for authors consists of the following:

  • A table with multiple rows, one for each attachment.
  • Each row consists of a button labeled Change that calls Web Author's Insert Attachment dialog box, a Delete button to remove the attachment from the list, and text boxes for displaying the attachment's display text and URL.
  • An icon that represents the uploaded attachment.

The following figure shows the Multiple Attachment Placeholder control in authoring view.

Multiple Attachment placeholder control in authoring view (click to see larger image)

Figure 8-8. Multiple Attachment placeholder control in authoring view (click picture to see larger image)

Usually, when building custom placeholder controls, the logic for adding these controls is coded by overriding the CreateAuthoringChildControls() method. We won't do that here because we will be making heavy use of JavaScript within our control. The easiest way of adding all that client-side script would be to code in the old-fashioned way: as regular HTML.

Therefore, in the overridden CreateAuthoringChildControls() method, we will simply add a single Literal Web control. Later on, we will attach HTML code to the Text property of this Literal control. Add the following code below the ResourceGalleryOnly property:

private System.Web.UI.WebControls.Literal baseAuthoringContainer;
protected override void CreateAuthoringChildControls
                    (BaseModeContainer authoringContainer)
{
  baseAuthoringContainer = new Literal();
  authoringContainer.Controls.Add(baseAuthoringContainer);
}

Now, let's create the table. Because the table is dynamically generated, with placeholder content mixed with HTML, we'll code it in the overridden LoadPlaceholderContentForAuthoring() method.

The table consists of five columns, as shown in Table 8-3.

Table 8-3. Five-column table that is created

Column 1 Column 2 Column 3 Column 4 Column 5
Displays the attachment as an icon. The icon shown is related to the type of the file. Contains a TextBox control that shows the display text of the attachment.

It also holds a hidden form field for storing the URL of the icon displayed in Column 1. It's hidden, so you can't see it in presentation view, but its value will be used by the client-side script that we will be coding later on.

Contains a read-only text box that shows the URL of the attachment. Provides a button labeled Change that triggers the upload process. Contains a Delete button to remove the attachment from the list.

As we are writing the code from a class file, we can't insert HTML directly as we would with regular HTML or ASPX files. Instead, we will write the code as a string variable and add it to the Text property of the Literal control we created earlier. We shall start with the table's header. Append the following code to the class file:

private string crlf = "\n";
protected override void LoadPlaceholderContentForAuthoring(PlaceholderControlEventArgs e) 
{
  StringBuilder htmlCode = new StringBuilder();
  // table surrounding the authoring time placeholder control 
  // child controls
  htmlCode.Append("<table border=1><tr><td>" + crlf);
  htmlCode.Append("<table cellspacing=0 cellpadding=0>" + crlf);
  htmlCode.Append("<tr>" + crlf);
  htmlCode.Append("<th width=200>Icon</th>" + crlf);
  htmlCode.Append("<th width=200>Alt-Text</th>" + crlf);
  htmlCode.Append("<th>Attachment-URL</th>" + crlf);
  htmlCode.Append("<th>Change</th>" + crlf);
  htmlCode.Append("<th>Delete</th>" + crlf);
  htmlCode.Append("</tr>"  + crlf);
  baseAuthoringContainer.Text = htmlCode.ToString();
}

Next, we will generate the rows that make up the table. As a row is required for each attachment, we will construct as many rows as specified by the MaxAttachments property.

In the first column where the attachment's icon is displayed, we create an empty table cell. The cell's name is made unique by prepending MAPH_AttIcon and a number to the name of the placeholder to bind. The name of the cell will be used by the client-side script later to identify the cell for the addition of the attachment's icon. Add the highlighted code to the LoadPlaceholderContentForAuthoring() method.

protected override void 
LoadPlaceholderContentForAuthoring(PlaceholderControlEventArgs e)
{  . . . code continues . . .
  string sPhName = this.PlaceholderToBind;
  for (int i = 0; i < m_maxAttachments; i++)
  {
    htmlCode.Append("<tr>");
    // Add the cell for the attachment's icon
    htmlCode.Append("<td align=center id=\"MAPH_AttIcon_"
      + sPhName + "_" + i.ToString() + "\"></td>" + crlf);
  }  
  baseAuthoringContainer.Text = htmlCode.ToString();
}

The second cell holds two controls: a hidden control and a TextBox. The TextBox stores the attachment's display text and has a name that begins with MAPH_AttName_. It is editable, so authors can update the display text here. The hidden form field stores the icon and has a name prepended with MAPH_AttIconUrl_. Add the code that generates both controls to the LoadPlaceholderContentForAuthoring() method:

protected override void LoadPlaceholderContentForAuthoring
                             (PlaceholderControlEventArgs e) 
{  . . . code continues . . .
  for (int i = 0; i < m_maxAttachments; i++)
  {    . . . code continues . . .
    // Add the cell for the attachment's display text
    htmlCode.Append("<td align=center>");
    // Add a hidden form field to store the icon
 htmlCode.Append("<input type=\"hidden\" name=\"MAPH_AttIconUrl_" 
                  + sPhName + "_" + i.ToString() + "\">");
    // Add the Text box to display the attachment's display text
    htmlCode.Append("<input type=\"text\" name=\"MAPH_AttName_"
                  + sPhName + "_" + i.ToString() + "\">");
    // End the cell
    htmlCode.Append("</td>" + crlf);
  }
}  . . . code continues . . .

In the third cell, we generate a single read-only text box that displays the attachment's URL. We give it a unique name by appending the placeholder's name and a number to the string MAPH_AttLink_. Later on, we will display the URL here using client-side script.

protected override void LoadPlaceholderContentForAuthoring
                             (PlaceholderControlEventArgs e) 
{  . . . code continues . . .
  for (int i = 0; i < m_maxAttachments; i++)
  {
    . . . code continues . . .
    // Add the cell for that attachment's URL
    htmlCode.Append("<td align=center>");

    // Add the read-only text box to display the attachment's URL
    htmlCode.Append(
            "<input readonly type=\"text\" name=\"MAPH_AttLink_"
            + sPhName + "_" + i.ToString() + "\">");

    // End the cell
    htmlCode.Append("</td>" + crlf);
  }  
}  . . . code continues . . .

Next, we add the cell with the Change button as a fourth column. The button opens the Insert Attachment dialog box.

protected override void LoadPlaceholderContentForAuthoring
                             (PlaceholderControlEventArgs e) 
{  . . . code continues . . .
  for (int i = 0; i < m_maxAttachments; i++)
  {
    . . . code continues . . .

     // Add the cell that contains the button
     htmlCode.Append("<td width=100 align=center>");
     // Add the Change button to trigger the upload process
     htmlCode.Append("<INPUT type=BUTTON value=\"Change...\" 
                   onclick=\"" + GetAttachmentDialog(i) + "\">");
     // End the cell
     htmlCode.Append("</td>" + crlf);
  }
}  . . . code continues . . .

Finally, we finish the table by adding the cell that holds the Delete button.

protected override void LoadPlaceholderContentForAuthoring
                             (PlaceholderControlEventArgs e) 
{  . . . code continues . . .
  for (int i = 0; i < m_maxAttachments; i++)
  {    . . . code continues . . .
    // Add the cell for the Delete button
    htmlCode.Append("<td align=center>");
    // Add the Delete button
    htmlCode.Append("<input type=\"button\" value=\"Delete\" "
                  + "onclick=\"DeleteAttachment("+i+")\">");

    // End both the cell and the row
    htmlCode.Append("</td></tr>" + crlf);
  }
  // End the first table
  htmlCode.Append("</table>" + crlf);
  // End the second table
  htmlCode.Append("</table>" + crlf);
  baseAuthoringContainer.Text = htmlCode.ToString();
}

8.3.3 Deleting Attachments

To delete attachments, we will write a client-side function that clears the contents of the text boxes, table cells, and hidden form field for the selected attachment. Add the RenderDeleteAttachmentJavascript() helper function to the code.

private void RenderDeleteAttachmentJavascript()
{
  StringBuilder script = new StringBuilder();
  // Get the name of the placeholder
  string sPhName = this.BoundPlaceholder.Name;
  script.Append("<SCRIPT>");
  script.Append("function DeleteAttachment(i)");
  script.Append("{" + crlf);
  script.Append("eval('document.all.MAPH_AttIcon_"+sPhName+"_'
              + i).innerHTML='';");
  script.Append("eval('document.all.MAPH_AttIconUrl_" 
              + sPhName+"_' + i).value='';");
  script.Append("eval('document.all.MAPH_AttName_" + sPhName 
              + "_' + i).value='';");
  script.Append("eval('document.all.MAPH_AttLink_" + sPhName 
              + "_' + i).value='';");
  script.Append("}" + crlf);
  script.Append("</SCRIPT>");
  Page.RegisterClientScriptBlock("MAPH_DeleteAttachment", 
              script.ToString());
}

At the end of the LoadPlaceholderContentForAuthoring() method, call the RenderDeleteAttachmentJavascript() method.

protected override void LoadPlaceholderContentForAuthoring(PlaceholderControlEventArgs e) 
{   . . . code continues . . .
   RenderDeleteAttachmentJavascript();
}

8.3.4 Reusing the Insert Attachment Dialog Box

We need a dialog box for authors to specify the file to attach and its display text. While it is possible to write one from scratch, a better idea would be to reuse the Insert Attachment dialog box that is provided with Web Author.

The Insert Attachment dialog box provides the author with the option of either attaching a shared resource or a local attachment. Depending on whether the developer has allowed local attachments, the author may be restricted to only selecting attachments from resource galleries. In such cases, the Insert Local Attachment link will not be available. After the author has made the selection, he or she is provided with a field for entering the display text of the attachment.

Insert Attachment dialog box with field for entering attachment's display text (click to see larger image)

Figure 8-9. Insert Attachment dialog box with field for entering attachment's display text (click picture to see larger image)

8.3.4.1 Calling the Dialog Box

To call the Insert Attachment dialog box, we borrow the WBC_launchAttachmentGallery() method found in the AuthFormClientIE.js used by Web Author. The method accepts six arguments that affect the dialog box's behavior, as described in the following table.

Table 8-4. Six arguments for WBC_launchAttachmentGallery() method

Argument What it Does
strPostQueryString A query string containing information about the posting in unpublished mode such as its GUID or, if it's a new posting, the channel's GUID.
strPhName The name of the placeholder.
strPhType The value here determines how the Insert Attachment dialog box passes information about the attachment back to the calling control. When dealing with attachments, Web Author recognizes the following values:
  • MultiPurpose. Applies to controls that accept HTML with full formatting.
  • SingleAttachment. Used by the SingleAttachmentPlaceholderControl for single file uploads.
  • ThinEditIE. Similar to MultiPurpose. The difference is that insertions to the placeholder use the placeholder document object model (DOM) instead. We will use this option in this example.
bAllowUpload A Boolean to indicate if local attachments are allowed. A value of false means that only resource gallery items can be attached.
bAttachIcon A Boolean flag to indicate if the attachment should be rendered as an icon. When it is set to true, the attachment is added to the content as an icon. Otherwise, it will be displayed as a text link.
bAllowVideo The last Boolean indicates if video attachments are supported. This is a throwback to the previous version of MCMS where videos were supported. In MCMS 2002, this value should always be set to false.

Putting it all together, here's the script that launches the Insert Attachment dialog box. Of course, the GUIDs and the placeholder name are variables that will have to be dynamically generated. The following code snippet would allow local attachments and use icons to represent attachments:

WBC_launchAttachmentGallery('wbc_purpose=Basic&NRMODE=Unpublished
&WBCMODE=PresentationUnpublished&FRAMELESS=true&NRCHANNELGUID={C4
0686DA-83CE-452F-AF24-3D990FA2BAC1}&NRNODEGUID={7AF93078-5FF8-
4963-A9F9-B1A0977258A7}', 'NewHtmlPlaceholderDefinition1', 
'ThinEditIE', true, true, false);

The GetAttachmentDialog() method generates the required client-side script. The script gathers information about the following:

  • The channel's and posting's GUIDs.
  • The name of the placeholder the control is bound to.
  • Whether or not local attachments are allowed.
  • Whether the attachments should be displayed as icons or text. For this control, we will always set this parameter to true as we want icons to be displayed in the authoring table.

After it has collected the necessary data, it puts the data together as arguments of the WBC_launchAttachmentGallery() function. Add the GetAttachmentDialog() method now:

private string GetAttachmentDialog(int i)
{
   // Check to see if the NRCHANNELGUID querystring contains a 
   // value. If it does, then it's most likely a new posting
   string channelGUID = 
       Page.Request.QueryString["NRCHANNELGUID"];
   if (channelGUID != "")
   {
     channelGUID = "&NRCHANNELGUID="+channelGUID;
   }
   // Get the GUID from the NRNODEGUID querystring parameter
   string postingGUID = Page.Request.QueryString["NRNODEGUID"];
   // Get the name of the placeholder to bind
   string placeholderName = this.PlaceholderToBind;
   // Are local attachments allowed?
   string allowLocalAttachments = 
       (!m_resourceGalleryOnly).ToString();
   StringBuilder script = new StringBuilder();
   // Specify the name of the placeholder that we are adding the 
   // attachment to
   script.Append("MAPH_DestPH ='" + placeholderName + "_" 
               + i.ToString() + "';");
   // Launch the Insert Attachment dialog box
   script.Append("WBC_launchAttachmentGallery('" 
    + "wbc_purpose=Basic&" + "NRMODE=Unpublished&" 
    + "WBCMODE=PresentationUnpublished&" 
    + "FRAMELESS=true" + channelGUID + "&"  + "NRNODEGUID=" 
    + postingGUID + "'," + "'" + placeholderName + "'," 
    + "'ThinEditIE'," + allowLocalAttachments.ToLower() + "," 
    + "true," + "false);");
  script.Append("return false");
  return script.ToString();
}

8.3.4.2 Returning Values from the Dialog Box

The Insert Attachment dialog box allows the author to specify the attachment and its display text. It's not a modal dialog box. Therefore, in order for content to be written back to the placeholder control, the dialog box accesses client-side functions within the calling window.

Depending on the chosen placeholder type, Web Author prepares different client-side routines. The "ThinEditIE" placeholder type used in this example calls the WBC_setThinEditIEAttachment() method found within the AuthFormClientIE.js script (located in the <install directory>\Microsoft Content Management Server\Server\IIS_CMS\WebAuthor\Client\PlaceholderControlSupport\ directory).

We need to modify the WBC_setThinEditIEAttachment() routine to update the authoring table with the attachment's icon, display text, and URL. The tricky part is doing so without modifying the original code (or we will cross Microsoft support boundaries, plus not modifying the original file safeguards the code from being overwritten when future service packs are applied). As a workaround, we will create a modified version of the method and add it to the page. When the browser sees two JavaScript methods with the same name, one defined within the page and a second in a separate *.js file, the browser will choose to run the one embedded within the page.

To begin, let's take a look at the original WBC_setThinEditIEAttachment() routine shown below. It accepts three input parameters:

  • The name of the placeholder that's accepting the attachment
  • The URL of the uploaded attachment
  • The display name of the attachment

It uses the URL and display name of the attached file to construct an HTML anchor tag. The anchor tag is added to the placeholder by calling the insertHtml() method of the placeholder's DOM. Don't worry about typing in the code for now. We will be writing a server-side function to generate this script on the fly later.

function WBC_setThinEditIEAttachment(strPhName, strURL, 
   strDispText) 
{
  var strMyDispText = strDispText;
  if (strMyDispText == "") 
  {
    strMyDispText = strURL;
  }
  document.all["NCPHRICH_" + strPhName].insertHtml("<a href=\"" 
             + strURL + "\">" + strMyDispText + "</a>");
}

While the WBC_setThinEditIEAttachment() method works perfectly for regular HtmlPlaceholderControl controls, in order for it to function correctly within the MutipleAttachmentPlaceholderControl, we need to make several modifications:

  • The method calls an object with an ID of NCPHRICH_(PlaceholderName): our custom control has a different naming convention and executing this line of code will result in an error.
  • Instead of inserting an entire anchor tag in an HtmlPlaceholderControl, we need to pass information about the attachment back to the appropriate hidden fields and cells within the table in the multiple attachment placeholder control.

8.3.4.3 Client-Side Script for All Types of Placeholder Controls

Let's tackle the first problem. By default, the WBC_setThinEditIEAttachment() method works only with the out-of-the-box placeholder controls. Placeholder controls that are provided with MCMS have IDs prepended with the text NCPHRICH to differentiate them from custom controls that we build. Our control does not have the ID of NCPHRICH_(PlaceholderName). As a result, the following line of code will lead to 'document.all[...]' is null or not an object errors when the dialog box is used with custom controls.

document.all["NCPHRICH_" + strPhName].insertHtml("<a href=\"" 
                 + strURL + "\">" + strMyDispText + "</a>");

On the other hand, we can't remove the line of code entirely as there may be some out-of-the-box placeholder controls on the same page that require it. To get around this, we will wrap the line in a check to see if the object is undefined (which will be the case for the multiple attachment placeholder control). Again, don't worry about adding the code to the class file for now, we will show you how the JavaScript gets injected later.

function WBC_setThinEditIEAttachment(strPhName, strURL, 
   strDispText) 
{
  var strMyDispText = strDispText;
  if (strMyDispText == "") 
  {
    strMyDispText = strURL;
  }
  if(typeof document.all["NCPHRICH_" + strPhName] != 'undefined')
  {
     document.all["NCPHRICH_" + strPhName].insertHtml("<a 
       href=\"" + strURL + "\">" + strMyDispText + "</a>");
  }
}

8.3.4.4 Passing Attachment Information to the Placeholder Control

Now, on to the second problem of passing information about the attachment back to the multiple attachment placeholder control. We will populate the hidden fields and text boxes with information about the attachment's icon, display text, and URL.

To get this information, let's look at two input parameters passed into the WBC_setThinEditIEAttachment() method, as shown in the following table.

Table 8-5. Input parameters passed into WBC_setThinEditIEAttachment()

Input Parameter What it Means
strUrl As the name suggests, this parameter contains the URL of the attachment.
strMyDispText Stores the icon. The returned HTML is an image tag, which looks like this:

<img alt="Plants are Green!" src="/NR/rdonlyres/670B7551-5A53-4B0A-99782B7AD3EE7BC8/0/MyAttachment.doc? thumbnail=true&label=Plants%20are%20Green%21" border="0">

So strUrl contains the URL of the attachment and strMyDispText holds the icon as shown in the script below:

function WBC_setThinEditIEAttachment(strPhName, strURL, strDispText) 
{
  var strMyDispText = strDispText;
  if (strMyDispText == "") 
  {
    strMyDispText = strURL;
  }
  if(typeof document.all["NCPHRICH_" + strPhName] != 'undefined')
  {
    document.all["NCPHRICH_" + strPhName].insertHtml("<a 
             href=\"" + strURL + "\">" + strMyDispText + "</a>");
  }
  else
  {  
    // Return the image of the icon
    if (typeof document.all["MAPH_AttIcon_" + 
             MAPH_DestPH] !='undefined')
    {         
      document.all[\"MAPH_AttIcon_\" + 
             MAPH_DestPH].innerHTML = strMyDispText;
    }
    // Return the attachment's URL
    if (typeof document.all[\"MAPH_AttLink_\" + 
             MAPH_DestPH] != 'undefined')
    {
      document.all[\"MAPH_AttLink_\" + 
              MAPH_DestPH].value = strURL;
    }
    // Return the icon
    if (typeof document.all[\"MAPH_AttIconUrl_\" + 
              MAPH_DestPH] != 'undefined') 
    {
      document.all[\"MAPH_AttIconUrl_\" + 
              MAPH_DestPH].value = strMyDispText;
    }
  }
}

What about the display text? We need to extract the display text from the alt attribute of the icon's image tag. To do so, we will split strMyDispText using double-quotation marks (") as the separator. Let's say the original HTML is as follows:

<img alt="DisplayText" src="AttachmentUrl" border="0">

The array formed after splitting is shown below where the second element of the resulting array contains the display text from the alt attribute:

Array Item 0:  <img alt=
Array Item 1:   DisplayText
Array Item 2:  src=
Array Item 3:  AttachmentUrl
Array Item 4:  border=
Array Item 5:  0
Array Item 6:  >

The extracted values are deposited into the hidden form fields and text boxes generated earlier.

function WBC_setThinEditIEAttachment(strPhName, strURL, strDispText) 
{  . . . code continues . . .
  if(typeof document.all["NCPHRICH_" + strPhName] != 'undefined')
  {    . . . code continues . . .
  }
  else
  {    . . . code continues . . .
    // Split strMyDispText using the double-quote character as 
    // the separator
    elements = strMyDispText.split('\"');
    // Return the Display Text
    if (typeof document.all[\"MAPH_AttName_\" + 
             MAPH_DestPH] != 'undefined')
    {
      // The display name is the second element of the array
      document.all[\"MAPH_AttName_\" + 
             MAPH_DestPH].value = elements[1];
    }  
  }
}

8.3.4.5 Generating the WBC_setThinEditIEAttachment() Method

As our custom placeholder control is constructed entirely from server-side code, we need to generate the client script above on the fly and register it with Page.RegisterStartUpScript(). To do so, we must add the helper function RenderResourceGalleryRelatedJavascript() shown below to the code:

private void RenderResourceGalleryRelatedJavascript()
{
StringBuilder script = new StringBuilder();
// Start the script block
script.Append("<SCRIPT language=\"javascript\">" + crlf);
// Declare the MAPH_DestPH variable
script.Append("var MAPH_DestPH = \"\";" + crlf);
// Define WBC_setThinEditIEImage function to replace one defined 
// by MCMS 
script.Append("function WBC_setThinEditIEAttachment(strPhName, 
    strURL," + "strDispText){" + crlf);
script.Append("var strMyDispText = strDispText;" + crlf 
    + "if (strMyDispText == \"\") " + "{" 
    + "strMyDispText = strURL;" + "}" + crlf);
script.Append("if(typeof document.all[\"NCPHRICH_\" + strPhName] 
    != 'undefined')" + "{" + "document.all[\"NCPHRICH_\" 
    + strPhName].insertHtml" + "(\"<a href=\\\" + strURL 
    + \\\">\"  + strMyDispText + \"</a>\");" + "}" + crlf);
script.Append("else"
    + "{" + "elements = strMyDispText.split('\"'); " + crlf);
script.Append("if(typeof document.all[\"MAPH_AttIcon_\"
    + MAPH_DestPH]!='undefined')" + "{" 
    + "document.all[\"MAPH_AttIcon_\"
    + MAPH_DestPH].innerHTML=strMyDispText;" + "}" + crlf);
script.Append("if(typeof document.all[\"MAPH_AttLink_\"
    + MAPH_DestPH]!='undefined')" + "{" 
    + "document.all[\"MAPH_AttLink_\"  
    + MAPH_DestPH].value = strURL; " + "}" + crlf);
script.Append("if(typeof "
 + "document.all[\"MAPH_AttIconUrl_\"+MAPH_DestPH]!='undefined')"
    + "{" + "document.all[\"MAPH_AttIconUrl_\"
    + MAPH_DestPH].value =strMyDispText;" + "}" + crlf);
script.Append("if (typeof "
    + "document.all[\"MAPH_AttName_\"+MAPH_DestPH]!='undefined')"
    + "{" + "document.all[\"MAPH_AttName_\"
    + MAPH_DestPH].value = elements[1];" + "}" + crlf);
// Close the if-else block
 script.Append("}" + crlf);
// Close the function
script.Append("}" + crlf);
// Close the script block
script.Append("</SCRIPT>");
// Register the script    
this.Page.RegisterStartupScript("WBC_setThinEditIEAttachment", 
     script.ToString());
}

Finally, at the end of the LoadPlaceholderContentForAuthoring() method, we add a call to the RenderResourceGalleryRelatedJavascript() method:

protected override void LoadPlaceholderContentForAuthoring(
                                        PlaceholderControlEventArgs e) 
{  . . . code continues . . .
  RenderResourceGalleryRelatedJavascript();
}

8.3.5 Saving the List of Attachments

When the posting is saved, the SavePlaceholderContent() method is called. Here, we will loop through all rows in the table to look for attachments. If an attachment is found, we create an XML node consisting of the attachment's URL, display text, and icon. At the end of the routine, the list of attachments is saved.

For example, if we have chosen to display attachments as icons, the saved XML would be:

<Attachments>
  <Attachment>
    <DisplayText>Aloe Vera</DisplayText>
    <Url>/NR/rdonlyres/73C94F98-1424-41EE-B4FA-
       D2301314A7FA/0/AloeVera.JPG 
    </Url>
    <Icon><img alt="AloeVera.JPG" src="/NR/rdonlyres/73C94F98-
       1424-41EE-B4FA-D2301314A7FA/0/AloeVera.JPG?thumbnail=true
       &label=AloeVera.JPG" border="0">
    </Icon>
  </Attachment>
</Attachments>

Add the overridden SavePlaceholderContent() method directly below the LoadPlaceholderContentForAuthoring() method.

protected override void 
   SavePlaceholderContent(PlaceholderControlSaveEventArgs e)
{
  string sPhName = this.PlaceholderToBind;
  EnsureChildControls();
  // Save the list of attachments as XML
  XmlPlaceholder xph = (XmlPlaceholder) base.BoundPlaceholder;
  XmlDocument xd = new XmlDocument();
  XmlNode xnRoot = xd.CreateElement("Attachments");
  for (int i = 0; i < m_maxAttachments; i++)
  {
     if (Page.Request.Form["MAPH_AttLink_" + sPhName 
                      + "_"+i.ToString()] != "")
     {
       XmlNode xAttachment = xd.CreateElement("Attachment");
   
       // Store the attachment's display text
       XmlNode xDisplayName = xd.CreateElement("DisplayText");
       xDisplayName.InnerText = 
                      Page.Request.Form["MAPH_AttName_" + sPhName 
                      + "_" + i.ToString()];
       xAttachment.AppendChild(xDisplayName);
       // Store the attachment's URL
       XmlNode xUrl = xd.CreateElement("Url");
       xUrl.InnerText = Page.Request.Form["MAPH_AttLink_" 
                      + sPhName + "_"
                      + i.ToString()];
       xAttachment.AppendChild(xUrl);
       // Store the icon
       XmlNode xIcon = xd.CreateElement("Icon");
       xIcon.InnerText = Page.Request.Form["MAPH_AttIconUrl_"
                       + sPhName + "_"
                       + i.ToString()];
       xAttachment.AppendChild(xIcon);
       // Add the node to the XML Doc
       xnRoot.AppendChild(xAttachment);
     }
  }
  xph.XmlAsString = xnRoot.OuterXml;
}

8.3.6 Retrieving Saved Content

To display the stored content, we first have to retrieve it. Here's the plan:

  • First prepare three arrays for storing the attachment URLs, icons, and display text.
  • Next, retrieve the saved XML from the underlying XmlPlaceholder.
  • After we have the XML, we extract information about the attachment from the tags and store them in the arrays that we have prepared.
  • Finally, we load the content back into the table of all uploaded attachments.

8.3.6.1 Preparing Arrays for Storing Information about the Attachments

Let's prepare three string arrays for storing a list of attachment URLs, icons, and display text. Add the following code to the start of the LoadPlaceholderContentForAuthoring() method:

// Stores the URL of the attachment
private string[] attUrl = new string[m_maxAttachments];
// Stores the icon
private string[] attIcon = new string[m_maxAttachments];
// Stores the Display text
private string[] attName = new string[m_maxAttachments];
protected override void LoadPlaceholderContentForAuthoring
                     (PlaceholderControlEventArgs e) 
{  . . . code continues . . .
}

8.3.6.2 Retrieving Previously Saved XML

We retrieve the saved XML from the XmlPlaceholder.XmlAsString property. If the string is empty, as will be the case for a new posting, we add the root element to make it valid:

protected override void 
LoadPlaceholderContentForAuthoring(PlaceholderControlEventArgs e) 
{
   // Get saved content
   XmlPlaceholder xph = (XmlPlaceholder) base.BoundPlaceholder;
   string xmlContent = xph.XmlAsString;
   if (xmlContent == "")
   {
     xmlContent = "<Attachments />";
   }
   XmlDocument xd = new XmlDocument();
   xd.LoadXml(xmlContent);
}   . . . code continues . . .

8.3.6.3 Extracting Information about the Attachments from the XML

Now that we have the saved XML, we need to extract the attachment's URL, icon, and display text from it. The attachment's display text is taken from the <DisplayText> element; its URL is retrieved from the <URL> element, and the image of the entire icon from the <Icon> element.

protected override void LoadPlaceholderContentForAuthoring(PlaceholderControlEventArgs e) 
{  . . . code continues . . .
  xd.LoadXml(xml);
   
  // Extract URLs and AltText
  int index = 0;
  foreach (XmlNode xn in xd.DocumentElement.ChildNodes)
  {        
    attUrl[index] = xn.SelectSingleNode("Url").InnerText;
    attIcon[index] = xn.SelectSingleNode("Icon").InnerText;
    attName[index] = 
             xn.SelectSingleNode("DisplayText").InnerText;
    index++;
  }
}  . . . code continues . . .

We are now ready to populate the table with the saved content.

8.3.6.4 Populating the Table with the Saved Attachments

There are four fields in the table that require information about the attachments:

  • The cell with an ID beginning with MAPH_AttIcon for holding the attachment's icon
  • The hidden form field with an ID beginning with MAPH_AttIcon for holding the attachment's icon
  • The text box with an ID beginning with MAPH_AttName for storing the display text
  • The read-only text box with an ID beginning with MAPH_AttLink for storing the URL of the attachment

Let's start with the icons. The attachment's icon is displayed in the first cell of each row. We will get the icon stored in the attIcon[] array and add it to the cell. Add the code highlighted below:

protected override void LoadPlaceholderContentForAuthoring(
                            PlaceholderControlEventArgs e) 
{  . . . code continues . . .
  // Add the cell for the attachment's icon
  htmlCode.Append("<td align=center id=\"MAPH_AttIcon_" + sPhName 
                + "_" + i.ToString() + "\">" + attIcon[i] + 
                "</td>" + crlf;
}  . . . code continues . . .

The icon is also stored in a hidden field in the second column of the table. The hidden field has a name starting with MAPH_AttIcon and, like the cell in the first column, will be populated with the contents of the attIcon[] array.

protected override void 
   LoadPlaceholderContentForAuthoring(
          PlaceholderControlEventArgs e) 
{  . . . code continues . . .
  // Add a hidden form field to store the icon
  htmlCode.Append("<input type=\"hidden\" 
                name=\"MAPH_AttIconUrl_" + sPhName
                + "_"+i.ToString() + "\" value='" + attIcon[i] 
                + "'>");
}  . . . code continues . . .

The display text of the attachment is shown in a text box with a name that starts with MAPH_AttName. We will set it to hold the contents of the attName[] array.

protected override void 
   LoadPlaceholderContentForAuthoring(
         PlaceholderControlEventArgs e) 
{  . . . code continues . . .
  // Add the text box to display the attachment's display text
  htmlCode.Append("<input type=\"text\" name=\"MAPH_AttName_" 
                + sPhName 
                + "_" + i.ToString() + "\" value='" + attName[i] 
                + "'>");
}  . . . code continues . . .

The last item to fill in is the URLs of the attachments. These are displayed in the text boxes that begin with MAPH_AttLink. The contents of the attUrl[] array will be used to fill in their values.

protected override void LoadPlaceholderContentForAuthoring(
                        PlaceholderControlEventArgs e) 
{  . . . code continues . . .
  // Add the read-only text box to display the attachment's URL
  htmlCode.Append("<input readonly type=\"text\" 
                name=\"MAPH_AttLink_" 
                + sPhName + "_" + i.ToString()
                + "\" value='"+attUrl[i]+"'>");
}  . . . code continues . . .

8.3.7 Displaying the Attachments

In presentation view, we'll display the attachments in HTML. Let's use the same method as we did earlier for the authoring control and use a Literal control to store the generated HTML code.

private Literal basePresentationContainer;
protected override void CreatePresentationChildControls(
                 BaseModeContainer presentationContainer)
{
  basePresentationContainer = new Literal();
  presentationContainer.Controls.Add(basePresentationContainer);
}

With the underlying data in XML, you can format the layout of the attachments any way you like. For example, you could display them vertically, horizontally, or even within a table. The LoadPlaceholderContentForPresentation() method below displays the attachments side by side. Basically, for each attachment in the list, an anchor tag is created. At the end of the routine, we get a series of anchor tags, with single spaces between them.

protected override void LoadPlaceholderContentForPresentation(
                                PlaceholderControlEventArgs e)
{
  // The output HTML
  StringBuilder html = new StringBuilder();
  // Retrieve previously saved content
  string xml = 
      ((XmlPlaceholder)this.BoundPlaceholder).XmlAsString;
  // Load the XML into an XmlDocument
  XmlDocument xd = new XmlDocument();
  xd.LoadXml(xml);
  // Display the list of attachments
  foreach(XmlNode xn in xd.DocumentElement.ChildNodes)
  {
    // Get the display text
    string displayText =
          xn.SelectSingleNode("DisplayText").InnerText;
    // Get the URL
    string url = xn.SelectSingleNode("Url").InnerText;
    // Get the icon
    string icon = xn.SelectSingleNode("Icon").InnerText;
    // Create the opening anchor tag
    html.Append("<a href=\"" + url + "\">");
    if (m_displayAsIcon)
    {
      // Display attachments as icons
      html.Append(icon);
    }
    else
    {
      // Display attachments as text
      html.Append(displayText);
    }
    // Create the closing anchor tag
    html.Append("</a>");
    // Add a space
    html.Append("&nbsp;");
  }
  basePresentationContainer.Text = html.ToString();
}

Here, we used the XML DOM to format the content. You could also consider using an Extensible Stylesheet (XSL) to perform the transformation. Using XSL is probably a more flexible solution, as it makes changing the format a lot easier if you need to do so.

The Multiple Attachment placeholder control is complete. Save and compile the solution.

8.3.8 Using the MultipleAttachmentPlaceholderControl

Follow the steps outlined in 8.2.8 Adding the Placeholder Control to a Template File in Chapter 8: Useful Placeholder Controls (Part 1 of 2) to add the MultipleAttachmentPlaceholderControl to a template file of your choice. Set the DisplayAsIcon property to true or false depending on whether you would like to display the attachments as icons or text. Add an XmlPlaceholderDefinition to the template. Set the PlaceholderToBind property of the control to the XmlPlaceholderDefinition that you have just created, and you are ready to roll!

8.4 Summary

The Publishing API support for creating custom placeholder controls gives developers some powerful tools for tailoring Web sites to meet the demanding requirements of today's users. In Chapter 8: Useful Placeholder Controls (Part 1 of 2), we discussed how to create a placeholder control that enables users to enter dates from a calendar. In part 2 of this chapter, we covered a commonly requested placeholder control: a placeholder control that accepts multiple attachments. This allows template designers to specify the maximum number of attachments allowed in a control.

Read Chapter 8, Part 1

© 2009 Microsoft Corporation. All rights reserved.   Terms of Use | Trademarks | Privacy Statement
Page view tracker
Rate the Lightweight library
x
Lightweight builds on ScriptFree (loband) by adding features you've requested: a SearchBox and default code language selection.
Do you like the SearchBox?
Do you like the tabbed code blocks?
How useful is this topic?
Tell us more.
Thanks
x
You're helping to improve MSDN Online.
Feedback
Switch View
Classic
Lightweight Beta
ScriptFree
Switch View