Composite Controls

With CMS there are two opportunities to create custom composite controls. One opportunity is to create a custom server control. That is a control, very much akin to a user control, that uses existing server controls and creates a CMS-aware composite control. The other opportunity is to create a custom placeholder control. Both types of composite controls offer significant opportunities to extend the functionality of CMS. In the following sections, we'll examine both types of composite controls.

Creating Custom Server Controls

Very similarly to creating user controls, creating custom CMS-aware server controls is simply a matter of creating a composite control that is aware of CMS. In our user control example, we made the control "aware" of the Web Author context by examining the mode of the WebAuthor Context. Once we knew the mode, we conditionally set the visibility of the literal control. By creating this kind of CMS awareness, we removed the need to add any code to the template, but provided a way to allow a label to appear over the placeholder only when the Web Author was in an authoring state.

Now, we could do something very similar with a server control. The server control would operate in the same way that our user control did, but we would be able to add a design-time interface to the control, and you would be able to add to the global assembly cache (GAC). The advantage would be that not only would the design interface allow either a developer or visual designer potentially to have more control over the server control during design, but since the control is in the GAC, there's no need to add it to each project separately.

Ultimately the decision is up to you. There's a good article on Microsoft's MSDN site that discusses how to decide between a server or user control, listing the advantages and disadvantages of each; see

For examples of custom server controls, install the WoodgroveNet Bank sample site that ships with CMS. There are several custom server controls provided with the sample site. To download the sample site, you can follow the link to the download section for CMS at

Creating Custom CMS Placeholders

Up to this point, we've been dealing with a fixed set of CMS placeholders that your content contributors use to add content. However, CMS provides a base placeholder class that can be inherited, which allows you to create your own placeholders. This very powerful feature of CMS provides an almost unlimited set of possibilities when it comes to extending CMS, especially as it relates to content contribution.

NOTE: The term "placeholder" is used often in this book, particularly in this chapter. For clarity, we are referring to the placeholder control in this section. The control is the element that you can drag from the Toolbox in VS.NET to the design surface. Also, please be aware that there is a standard .NET Web control called a PlaceHolder (with a capital H). The two controls are not related in any way.

Essentially, the base placeholder class provides the core services with regard to content. In the broadest sense, content is anything that you want to store in the repository. Content could be text or images, as we've dealt with previously in this book. Content could also be settings or other, noneditorial data that your application may need to store. The base placeholder class allows you to inherit this basic storage and retrieval functionality while adding your own application-specific functionality. In addition, it relieves you (the developer) from having to handle the switch between Live, Edit, and authoring modes; you simply implement the appropriate overrides, and CMS takes care of the rest.

Now, you may be asking yourself why you might create a custom placeholder at all. One driving reason is to allow you to store data in the repository. Further, it provides content contributors with direct access to that content while they're authoring. You wouldn't need a custom placeholder, for example, to allow an administrator to change a property of a channel, since administrators can use Site Manager. This distinction is key when you're planning your CMS site.

To begin, let's examine the BasePlaceholderControl class. In Figure 29-3, we've looked up the class in the Object Browser.

Figure 29-3. The BasePlaceholderControl class in the Object Browser


BasePlaceholderControl is an inheritable class for building placeholders. In fact, all placeholders standard to CMS are based on this class. Within the class there are five primary methods that must be implemented when you are creating your custom placeholder.

  • CreateAuthoringChildControls: This method is used to create the controls that will represent the authoring view of your custom placeholder. This method and CreatePresentationChildControls replace CreateChildControls, typical of other .NET controls. The CreateChildControls method (taken care of by the BasePlaceholderControl class) calls this method and CreatePresentationChildControls at the appropriate time, based on the mode of the Web Author.

  • CreatePresentationChildControls: This method, like the Create AuthoringChildControls method, is responsible for one half of the traditional CreateChildControls method you've probably worked with in the past. CreatePresentationChildControls is called when the Web Author is in some sort of presentation mode those modes other than AuthoringNew and AuthoringReedit.

  • LoadPlaceholderContentForAuthoring: LoadPlaceholderContent ForAuthoring is called when the placeholder renders in authoring mode. This method makes the content stored in the repository available to your authoring controls.

  • LoadPlaceholderContentForPresentation: Like the LoadPlaceholderContentForAuthoring method, LoadPlaceholderContent ForPresentation allows you to retrieve content from the repository. Unlike the authoring method, this method is used to retrieve the content for presentation content displayed in modes other than AuthoringNew and AuthoringReedit.

  • SavePlaceholderContent: This method is responsible for saving content to the repository. In SavePlaceholderContent you can connect the bound placeholder definition to the controls in your custom placeholder.

NOTE: Make sure that you call EnsureChildControls before you try to set the values of either the authoring or presentation controls. This will ensure that the authoring and presentation controls exist before you try to assign a value to them. If you don't, an exception will be raised. Also, the BasePlaceholderControl class does not call either LoadPlaceholderContentForPresentation or LoadPlaceholderContentForAuthoring on a postback.

Now that you have a good understanding of the five mandatory methods to implement, let's look at how you might implement each in an example within the BOTS Consulting site.

In the BOTS site, the developers wanted to provide a way for the content contributors to indicate the industry that a specific case study addressed. Although it's possible to accomplish this with a standard placeholder, the regular HTML placeholder allowed a little too much contribution flexibility (translation: It allowed the content contributors to enter any value). So, in an effort to provide a little more guided contribution, the BOTS Consulting developers created a drop-down list box placeholder. This placeholder allowed content contributors to choose the target industry for a case study from a set list of choices defined by the developers.

In order to create this control in your project, we'll first need to create a control library project within our current solution. This new project will hold all our new composite controls for the BOTS Consulting site. In our solution, we've chosen to call the new project botsconsultingwebcontrols. To create your own Web control library, use the following instructions:

  1. Make sure you have an open solution. In our case, we have our BOTS Consulting solution open.

  2. Right-click your solution and pick Add from the context menu.

  3. Now, choose Add New Project from the second context menu that appears.

  4. From the dialog that appears, pick Visual C# Projects from the left column (if you're using VB.NET, you should choose Visual Basic Projects).

  5. Then, pick Web Control Library from the right column. Do not choose the Windows Control Library; that type of project is for Windows form projects.

  6. Once you've chosen the correct project type, you should enter the name you wish to give your new project. In our case, we've called our new control library botsconsultingwebcontrols. In Figure 29-4 you'll see a screen shot of what you should be seeing at this point.

    Figure 29-4. Creating a new Web control library in VS.NET


  7. Click OK to create the new project.

Now that we have our new project, you'll need to add references to the appropriate CMS assemblies. To add the references, right-click the References folder in your new project and choose Add Reference from the context menu. When the Add Reference dialog appears, click the Browse button and browse to the CMS assembly directory <Install Drive>: \Program Files\Microsoft Content Management Server\Server\bin. At minimum, you'll need to add references to the following assemblies:

  • Microsoft.ContentManagement.Common.dll

  • Microsoft.ContentManagement.Publishing.dll

  • Microsoft.ContentManagement.Publishing.Extensions. Placeholders.dll

  • Microsoft.ContentManagement.Web.dll

  • Microsoft.ContentManagement.WebControls.dll

After you've added the appropriate assembly references, we need to add the new control. Right-click the new project and pick Add from the context menu. Choose Add New Item. In the left column of the Add New Item dialog, choose Local Project Items, and in the right column, choose Web Custom Control. Once you've done that, enter the name of the new control in the name property. Our control will be aptly named DropDownPlaceholderControl. Now, simply click Open. What should appear next is the basic code that VS.NET supplies.

We now have to set up this new control to be a custom placeholder control. The first thing is to add the following namespaces to our control:

  • Microsoft.ContentManagement.WebControls

  • Microsoft.ContentManagement.Publishingnamespace

Next, change the "class" statement to inherit from the BasePlaceholderControl class instead of the more generic WebControl class. In Listing 29-2, you can see the C# code we've added to our control up to this point.

Listing 29-2 The initial code for our DropDownPlaceholderControl

[View full width]

 using System; using System.Web.UI; using System.Web.UI.WebControls; using System.ComponentModel; using Microsoft.ContentManagement.Publishing.Extensions.Placeholders; using Microsoft.ContentManagement.WebControls; namespace botsconsultingwebcontrols {       /// <summary>       /// Summary description for DropDownPlaceholderControl.       /// </summary>       [DefaultProperty("Text"),             ToolboxData("<{0}:DropDownPlaceholderControl runat=server></{0} graphics/ccc.gif:DropDownPlaceholderControl>")]       public class DropDownPlaceholderControl : Microsoft.ContentManagement.WebControls graphics/ccc.gif.BasePlaceholderControl 

Once we have this basic structure in place, we'll need to add code to and remove code from our control to finalize our custom placeholder control. The first step is to remove the standard Text property VS.NET places in new controls. This property may be appropriate in some cases. However, in our control, we don't need the property, so we're going to remove it. Next, we need to add our mandatory functions. These were the functions we listed earlier in the chapter. In Listing 29-3, you can see the functions we've placed in our control.

Listing 29-3 The mandatory functions that need to be included in custom placeholders

[View full width]

 protected override void CreateAuthoringChildControls(BaseModeContainer authoringContainer) { } protected override void CreatePresentationChildControls(BaseModeContainer graphics/ccc.gif presentationContainer) { } protected override void LoadPlaceholderContentForAuthoring(PlaceholderControlEventArgs e) { } protected override void LoadPlaceholderContentForPresentation(PlaceholderControlEventArgs e) { } protected override void SavePlaceholderContent(PlaceholderControlSaveEventArgs e) { } 

After you've removed the Text property and added the functions in Listing 29-3, you should be able to successfully build your solution without errors.

To begin the actual coding of our custom placeholder control, let's start by creating the authoring environment. As we mentioned, we want our authors to be able to pick the appropriate industry focus for a given case study from a drop-down list. This list will have the following items in it by default: Pharmaceutical, Construction, Manufacturing, and Technology. First, we define a new private member variable in our class to hold a reference to our drop-down list (we'll need this later). In our example, we're going to call that new variable IndustryList. Once we've created our member variable, we'll need to add code to our Create AuthoringChildControls function to physically create the drop-down list control and add the list items. To see what this looks like, refer to Listing 29-4.

Listing 29-4 The new member variable and an implemented CreateAuthoringChildControls function
 protected System.Web.UI.WebControls.DropDownList IndustryList; protected override void CreateAuthoringChildControls(BaseModeContainer authoringContainer) { this.IndustryList = new DropDownList(); this.IndustryList.Items.Add(new ListItem("Construction","<value>Construction</value>")); this.IndustryList.Items.Add(new ListItem("Manufacturing","<value>Manufacturing</value>")); this.IndustryList.Items.Add(new ListItem("Pharmaceutical","<value>Pharmaceutical</value>")); this.IndustryList.Items.Add(new ListItem("Technology","<value>Technology</value>")); this.IndustryList.EnableViewState = false; authoringContainer.Controls.Add(IndustryList); } 

In Listing 29-4 we're creating the authoring view of the placeholder. The first few lines of the function simply create the drop-down list. In this example, BOTS only had four industry options, so there are only four "manual" add statements. We're turning off the view state for the drop-down list control, because when switching between Edit and authoring modes, controls sometimes become "confused" and throw an exception. In the last statement, we're adding our drop-down list control to the authoring environment container. Without this statement, your control(s) won't render (as we found out the first time we created a custom placeholder).

Now that we have this much of the control, we should be able to test the basic control by adding it to a template. To add this new placeholder control to your template, you first should add it to your Toolbox in VS.NET. Adding this new placeholder to your Toolbox is pretty easy; just use the following instructions:

  1. Open a template and make sure you're in Design view.

  2. Once the template appears, click the Content Management Server tab in the VS.NET Toolbox. You should see the existing placeholder objects that are standard to CMS.

  3. Right-click the Toolbox and pick Customize Toolbox from the context menu.

  4. When the Customize Toolbox dialog appears, click the .NET Framework Components tab.

  5. Click the Browse button in the lower right corner of the dialog box; the button will be above the OK/Cancel/Reset/Help buttons.

  6. You'll have to navigate through your file system to find your Web control library project. Once you've located the project, select the "bin" directory and then the assembly that corresponds to your new control library. Depending on how you've set up your VS.NET environment, you may have "debug" and "release" folders within your "bin" folder.

  7. Click the assembly and click OK.

  8. Now, just select the specific controls you want to appear in the Toolbox. At this point, it's likely you only have one control, but you may have been very productive and gotten a few more done while you've been reading this. In Figure 29-5 you can see what our dialog looks like after we have selected our assembly and checked our DropDownListPlaceholder class.

    Figure 29-5. The Customize Toolbox dialog with our new placeholder class selected


  9. Once you've selected your new placeholder, click OK.

Your new placeholder will be shown in the Toolbox. In Figure 29-6, you can see our new placeholder in the Toolbox.

Figure 29-6. The new DropDownPlaceholderControl in our VS.NET Toolbox


Now that you have the new placeholder in your Toolbox, just drag it onto your template. In the BOTS Consulting example, we've positioned the new placeholder just above the body copy. In this way, we can allow the content contributor to choose the industry focus before they begin to write the copy. Further, when the page is fully rendered, the industry focus will be the first element displayed on the page (after the title), so the reader instantly knows to what industry this case study applies.

Before we can truly "test drive" our new control, we need to add a placeholder definition to our TGI. Since this is a new placeholder type, there won't be a corresponding placeholder definition. However, we can use one of the existing placeholder definitions for our placeholder. In this example, we're going to use an XML placeholder definition, since the XML placeholder type makes it fairly easy to store data versus editorial text (which is probably better with an HTML placeholder).

As we learned in our earlier chapters, you'll want to open the Template Explorer window and find the TGI associated with your template. In our case, we're using the Case Study Detail TGI. You'll need to check out your template and click the ellipsis to call up the Placeholder Definition Collection Editor. Go ahead and create a new XML placeholder definition for the new placeholder you just added to your template. In our case, we've called this new placeholder definition CaseStudyIndustry. In Figure 29-7 you can see the new definition we've added.

Figure 29-7. The Placeholder Definition Collection Editor with the new XML placeholder definition


After you've added your definition, you'll need to bind the new definition to your placeholder. Go back to your template and click the placeholder control you added earlier. In the properties list, select the new placeholder definition in the PlaceholderToBind property. Notice that this placeholder doesn't discriminate among the various definitions that exist in the definition collection for this TGI. All the definitions that are in the TGI are listed in the drop-down list. We'll address this issue later, but be careful to pick the definition you just created; the placeholder will allow you to bind it to any of the definitions in the TGI, whether or not they're XML definitions.

With the new placeholder control added to the template and bound to our new placeholder definition, we're ready to test the new control. In Figure 29-8, you can see what our new placeholder control looks like in a case study. Keep in mind that the control will only render in authoring mode, and it doesn't "understand" how to save content as of yet. However, this is a good way to making sure you've achieved the right results so far.

Figure 29-8. A case study with the new drop-down list placeholder control


If all has gone well, your test drive should have been successful. We still need to wire up the rest of the functions so that our new placeholder is fully functional. Let's continue our construction by adding the code to SavePlaceholderContent. Again, this function is where we take the values that have been assigned to our child controls, in this case a drop-down list, and assign them to the bound placeholder in our TGI. You must make sure you call EnsureChildControls so that an exception isn't thrown at runtime. In Listing 29-5, you can see the code we've added to our SavePlaceholderContent function (the line breaks are not intentional).

Listing 29-5 SavePlaceholderContent code in our drop-down list placeholder control

[View full width]

 protected override void SavePlaceholderContent(PlaceholderControlSaveEventArgs e) {       EnsureChildControls();       try       {             ((XmlPlaceholder)this.BoundPlaceholder).XmlAsString  = this.IndustryList graphics/ccc.gif.SelectedItem.Text;       }       catch (Exception ex)       {             string saveExceptionMessage = "Error saving placeholder contents: " + this graphics/ccc.gif.GetType().Name + " :: " + this.ID + " :: " + ex.Message;             Exception saveException = new Exception(saveExceptionMessage, ex);             throw saveException;       } } 

As you can see in Listing 29-5, the code isn't terribly complex. We first ensure that our child controls are created; we then read the value from our drop-down list control and set the XmlasString property of the bound placeholder (again, in this case we're using an XML placeholder definition). CMS takes care of the rest. You'll notice that we're performing some light exception handling. Although we're using the standard exception message, you could create your own custom exception.

Next, we need to fill the LoadContentForAuthoring function. In this function, we're going to retrieve the content we saved previously. This function is the opposite of the prior function we talked about. Instead of assigning a value to the bound placeholder, based on the drop-down list, we're retrieving the value from the bound placeholder and finding the corresponding value in our drop-down list. In Listing 29-6 you can see how this function is constructed.

Listing 29-6 The LoadContentForAuthoring function in our custom placeholder control

[View full width]

 protected override void LoadPlaceholderContentForAuthoring(PlaceholderControlEventArgs e)             {                   EnsureChildControls();                   try                   {                         ListItem savedSelectedIndustry = this.IndustryList.Items graphics/ccc.gif.FindByValue(((XmlPlaceholder)this.BoundPlaceholder). XmlAsString);                         this.IndustryList.SelectedIndex = this.IndustryList.Items.IndexOf graphics/ccc.gif(savedSelectedIndustry);                   }                   catch (Exception ex)                   {                         string saveExceptionMessage = "Error loading placeholder contents: " +                               this.GetType().Name + " :: " + this.ID + " :: " + ex.Message;                         Exception saveException = new Exception(saveExceptionMessage, ex);                         throw saveException;                   }             } 

Again, as with SavePlaceholderContent, we start by ensuring that our child controls are created with the EnsureChildControls call. After we've done that, we can retrieve the value from the placeholder. Because we're dealing with an XML placeholder and we're presenting the values in a drop-down list, setting up the control for the author takes a couple of extra steps. Namely, once we get the value back from the repository, we need to search the drop-down list control to find the value that matches what we've gotten back from the repository. Once we find the right index, we set the SelectedIndex property of the drop-down list to match. In this way, the content contributor is presented with the same value they entered earlier.

So now what? We've created an authoring environment, and we've saved the content to the repository and retrieved it again. What's next? Well, now we have to create the presentation controls. As you're probably aware by now, CMS always "lives" in two worlds: the authoring world and the presentation world. Although we've created the authoring world, we haven't addressed the presentation world. To create the presentation view of our placeholder, we have to implement two functions, which correspond closely to the authoring functions we've already covered. The two functions are CreatePresentationChildControls and LoadPlaceholderContentForPresentation.

We'll begin with CreatePresentationChildControls. This function is where you'll place the code to create the controls you want to appear when the Web Author is in modes other than AuthoringNew and AuthoringReedit. In our custom placeholder control, we're going to use the literal control to display the value chosen. In Listing 29-7 you can see how we've constructed our function.

Listing 29-7 The CreatePresentationChildControls function in our DropDownListPlaceholder

[View full width]

 private System.Web.UI.WebControls.Literal SelectedIndustry; protected override void CreatePresentationChildControls(BaseModeContainer graphics/ccc.gif presentationContainer) {             SelectedIndustry = new Literal();             presentationContainer.Controls.Add(SelectedIndustry); } 

Kind of unexciting, huh? Since we're simply creating the presentation control environment, there isn't much to do. Again, we created a private member variable for our literal control, and in the function we physically created it. Once it existed, we added it to our presentation container. That's it.

Now that we have our presentation environment, we can fill it with content through the LoadPlaceholderContentForPresentation function (see Listing 29-8). In this function, we need to retrieve the content from the repository and assign the value to our literal control's Text property.

Listing 29-8 The code in the LoadPlaceholderContentForPresentation function
 protected override void LoadPlaceholderContentForPresentation(PlaceholderControlEventArgs e) {       EnsureChildControls();       try       {             this.SelectedIndustry.Text = ((XmlPlaceholder)this.BoundPlaceholder).XmlAsString;       }       catch (Exception ex)       {             string saveExceptionMessage = "Error loading placeholder contents: " +                   this.GetType().Name + " :: " + this.ID + " :: " + ex.Message;             Exception saveException = new Exception(saveExceptionMessage, ex);             throw saveException;       } } 

Notice in this function that you simply have to retrieve the value from the repository and assign the value to the literal control. That's it.

If you've followed along so far, you should have a functional drop-down list custom placeholder.

Authoring-Only Custom Placeholders

Up to this point, we've focused on a fairly basic placeholder control. However, there are almost limitless variations. One specific variation is creating an authoring-only placeholder. There may be some business requirements that require content contributors to contribute content when authoring but not have the content show up in the live site. A good example of this is a placeholder that allows a content contributor to manipulate posting settings. In the BOTS site, there's an authoring-only placeholder that allows a contributor to change the start date of the posting while they're authoring.

A common challenge that some template designers have is providing a contribution environment that makes "sense" to a broad audience. For example, CMS provides some basic page properties that content contributors and developers can use. Unfortunately, depending on the skill level of the content contributor, the Page Properties dialog may be forgotten ("Oh, I didn't know that was there") or prove difficult to use ("I don't understand what all this means"). As a result, some of the productivity that could be gained by CMS is lost. There is a way, however, to get that back through using custom placeholders.

One of the elements of the BOTS Consulting Web site in the press release section is the date of the release. This date is taken from the Start Date property of a posting. By using the start date, contributors have the ability to create the release well in advance of the actual public release and have CMS publish the content when it's appropriate. Further, the release will always reflect the date it was released to the public. Unfortunately, the Start Date property is in the Page Properties dialog. As a result, it requires content contributors to go back and reset that property once they've saved the page. Often, contributors will forget to set this property, and it may even be missed during the workflow process. To combat this problem, the BOTS Consulting site has a custom placeholder control that's used only during authoring to allow the content contributor to set the date of the posting while they're authoring. In Figure 29-9 you can see what this placeholder looks like.

Figure 29-9. The press release template with the calendar custom placeholder


Now, this is not revolutionary, but it does go a long way toward making contribution easier for a broader audience. Not only is the control available during authoring, but it should be relatively intuitive you use a calendar to set the release date of the posting.

In the press release template, we are already using the StartDate property of the posting to display the release date, so we don't need the placeholder to display during any mode other than AuthoringNew or AuthoringReedit. In essence, we need an authoring-only placeholder. In order to create an authoring-only placeholder, we simply don't fully implement the two presentation-focused functions in the control, LoadContentForPresentation and CreatePresentationChildControls. We still have to have them listed, but we won't add any code to them.

The next element of our control is the need to change the posting start date. For this we're just going to use a standard call to the PAPI to affect the posting's StartDate property. We want the StartDate property to be affected when the content contributor chooses a date on the calendar and saves the posting. Keep in mind, however, that the role of the person who changes the date may affect the workflow. Since page properties are the domain of the moderator, an author who changes the release date will force the need for moderator approval, and that posting will disappear from the site if it's already been published.

The complete code for the CalendarPlaceholder is shown in Listing 29-9.

Listing 29-9 The CalendarPlaceholder control code

[View full width]

 using System; using System.Web.UI; using System.Web.UI.WebControls; using System.ComponentModel; using Microsoft.ContentManagement.Publishing; using Microsoft.ContentManagement.Publishing.Extensions.Placeholders; using Microsoft.ContentManagement.WebControls.Design; using Microsoft.ContentManagement.WebControls; namespace botsconsultingwebcontrols {       /// <summary>       /// Summary description for CalendarPlaceholder control.       /// </summary>       [ SupportedPlaceholderDefinitionType( typeof(HtmlPlaceholderDefinition)) graphics/ccc.gif,DefaultProperty("CurrentDate"),       ToolboxData("<{0}:CalendarPlaceholder runat=server></{0}:CalendarPlaceholder>"),       Description("Allows authors to change the start date of a posting while adding graphics/ccc.gif content")]       public class CalendarPlaceholder : Microsoft.ContentManagement.WebControls graphics/ccc.gif.BasePlaceholderControl       {             private System.DateTime currentDate = System.DateTime.Now;             private bool canSetPostingStartDate = false;             private Literal alternateAuthoringControl;             [Bindable(false),             Category("Settings")]             public System.DateTime CurrentDate             {                   get                   {                         return currentDate;                   }             }             protected System.Web.UI.WebControls.Calendar newCalendar;             protected override void CreateAuthoringChildControls(BaseModeContainer graphics/ccc.gif authoringContainer)             {                   if (CmsHttpContext.Current.Posting.State.ToString() == "New")                   {                         // If this is a new posting and it hasn't been saved at least once                         // just display a message to the author indicating that they                         // can't set the startpublishdate until the posting has been                         // saved at least once.                         this.alternateAuthoringControl = new Literal();                         this.alternateAuthoringControl.EnableViewState = true;                         // Set this boolean to false to ensure none of the other operations                         // will try and affect the startpublishdate or placeholder value                         canSetPostingStartDate = false;                         authoringContainer.Controls.Add (this.alternateAuthoringControl);                   }                   else                   {                         // Assign the basic properties of the child control                         this.newCalendar = new Calendar();                         this.newCalendar.ID = "StartPublishCalendar";                         // Explicitly set the EnableViewState property to true                         // If this isn't done, you'll occasionally receive an                         // exception about improper casting                         this.newCalendar.EnableViewState = true;                         // Set the canSetPostingDate property to true so the rest                         // of the operations can occur                         canSetPostingStartDate = true;                         // Add the calendar control to the authoring container                         authoringContainer.Controls.Add(this.newCalendar);                   }             }             protected override void CreatePresentationChildControls(BaseModeContainer graphics/ccc.gif presentationContainer)             {                   // This function isn't implemented since this is an authoring                   // only placeholder             }             protected override void LoadPlaceholderContentForAuthoring graphics/ccc.gif(PlaceholderControlEventArgs e)             {                   EnsureChildControls();                   try                   {                         // Only load content if this isn't a new posting, since we can't graphics/ccc.gif set the start                         // date unless the posting has already been saved once.                         if (canSetPostingStartDate)                         {                               // If this isn't a postback and the current placeholder doesn't                               // have any value                               if((!Page.IsPostBack) && ((HtmlPlaceholder)this graphics/ccc.gif.BoundPlaceholder).Text == "")                               {                                     this.newCalendar.SelectedDate = currentDate;                                     this.newCalendar.TodaysDate = currentDate;                                     this.newCalendar.VisibleDate = currentDate;                               }                               // Assign the appropriate values to the calendar control                               // based on the placeholder value                               else                               {                                     System.DateTime placeholderDate =  System.DateTime graphics/ccc.gif.Parse(((HtmlPlaceholder)this.BoundPlaceholder).Text);                                     this.newCalendar.SelectedDate = placeholderDate;                                     this.newCalendar.TodaysDate = currentDate;                                     this.newCalendar.VisibleDate = placeholderDate;                               }                         }                   }                   catch (Exception exp)                   {                         // Show error conditions in the console                         string myExceptionMessage = "Error loading placeholder contents: " +                               this.GetType().Name + " :: " + this.ID + " :: " + exp.Message;                         Exception myException = new Exception(myExceptionMessage, exp);                         throw myException;                   }             }             protected override void LoadPlaceholderContentForPresentation  graphics/ccc.gif(PlaceholderControlEventArgs e)             {                   // This function isn't implemented since this is an authoring                   // only placeholder             }             protected override void SavePlaceholderContent (PlaceholderControlSaveEventArgs e)             {                   EnsureChildControls();                   try                   {                         // Make sure we can set the startpublishdate of the posting                         if (canSetPostingStartDate)                         {                               // Make sure that the date selected on the calendar is after                               // the startpublish date of the channel.  Child object start graphics/ccc.gif dates                               // can't parent startpublish dates.                               if (this.newCalendar.SelectedDate.ToUniversalTime() < graphics/ccc.gif CmsHttpContext.Current. Channel.StartDate)                               {                                     // Throw an exception to alert the content contributor                                     // that they've broken the rules                                     Exception myException = new Exception("Posting start graphics/ccc.gif date prior to channel start date");                                     throw myException;                               }                               else                               {                                     // Set the bound placeholder value equal to the graphics/ccc.gif calendar selected date                                     ((HtmlPlaceholder)this.BoundPlaceholder).Html  = this graphics/ccc.gif.newCalendar.SelectedDate.ToString();                                     CmsHttpContext.Current.Posting.StartDate = this graphics/ccc.gif.newCalendar.SelectedDate.ToUniversalTime();                               }                         }                         else                         {                               // Store an empty string in the placeholder if we can't affect                               // the startpublishdate property of the posting                               ((HtmlPlaceholder)this.BoundPlaceholder).Html  = "";                         }                   }                   catch (Exception exp)                   {                         // Show error conditions in the console                         string myExceptionMessage = "Error loading placeholder contents: " +                               this.GetType().Name + " :: " + this.ID + " :: " + exp.Message;                         Exception myException = new Exception(myExceptionMessage, exp);                         throw myException;                   }             }             protected override void OnPopulatingDefaultContent  graphics/ccc.gif(PlaceholderControlCancelEventArgs e)             {                   // This function allows us to fill the placeholder with a default                   // value when the posting is new; since this control will not allow                   // authors to change the value until after the posting has been saved                   // once, we simply fill in a default value for the literal control.                         try                         {                               this.alternateAuthoringControl.Text = "You cannot set the graphics/ccc.gif release date until the release has been saved once.";                         }                         catch (Exception exp)                         {                               string myExceptionMessage = "Error loading placeholder graphics/ccc.gif contents: " +                                     this.GetType().Name + " :: " + this.ID + " :: " + exp graphics/ccc.gif.Message;                               Exception myException = new Exception(myExceptionMessage, exp);                               throw myException;                         }             }       } } 

As you walk through the code, you'll probably notice a few differences between this placeholder control and the earlier example we used. There are some unique characteristics of this particular control that need to be noted.

  • It's not possible to change the start date of a new posting. A posting doesn't truly exist until after it's saved once. As a result, we had to create a special condition that handled new postings (Authoring New) differently from postings that are being reedited (AuthoringReedit). In the case of a new posting that has never been saved, we simply show a literal control with a basic text message. Further, we don't change the placeholder value of the posting. In all other cases, the control displays a calendar that allows the content contributor to change the date.

  • In connection with our earlier point, the BasePlaceholderClass has an event that's automatically registered and called OnPopulated DefaultContent. This event allows you to control the initial content that a placeholder might be assigned in the case of a new posting. Because we're explicitly preventing any authoring when the posting is brand new, we use this event to change the Text property of the literal control. It's in this event where we specify the message to the author about not being able to change the release date.

  • The calendar control depends on a postback behavior as the user changes the selected date. This is normally fine. However, since we're authoring, the console will normally warn a content contributor if they try to navigate away from a posting while in authoring mode. Again, this is normally a good thing. However, when we select a different date in the calendar control, it looks to the console as if we're trying to navigate away. So, we made a small change to the default console. In the DefaultConsole user control, we check for the existence of the calendar placeholder. If this kind of placeholder exists on a page, we set EnableLeave AuthoringWarning to "false". In this way, the message about navigating away from the authoring environment will not appear as we change the selected value. The code we added to the console is provided in Listing 29-10.

Listing 29-10 The code in the DefaultConsole to turn off the leave-authoring warning
 System.Web.UI.Control myControl = this.Page.FindControl("CalendarPlaceholder1"); if (myControl != null) {       this.Console1.EnableLeaveAuthoringWarning = false; } else {       this.Console1.EnableLeaveAuthoringWarning = true; } 

Microsoft Content Management Server 2002. A Complete Guide
Microsoft Content Management Server 2002: A Complete Guide
ISBN: 0321194446
EAN: 2147483647
Year: 2003
Pages: 298 © 2008-2017.
If you may any questions please contact us: