3.3 Building the Required HTML Placeholder Validator
We are ready to build the RequiredHtmlPHValidator—a validator that ensures that authors do not leave an HtmlPlaceholderControl empty before the page is saved. To begin, add a class file named RequiredHtmlPHValidator.cs to the MCMSValidators project. Add the following namespaces above the namespace declaration.
using System;
using System.Text;
using System.Text.RegularExpressions;
using System.Web.UI;
using System.Web.UI.WebControls;
using Microsoft.ContentManagement.WebControls;
using Microsoft.ContentManagement.Publishing;
using Microsoft.ContentManagement.Publishing.Extensions.Placeholders;
namespace MCMSValidators
{
. . . code continues . . .
}
Instead of starting from scratch, our control will inherit the BaseValidator class of the System.Web.UI.WebControls library. BaseValidator already has the basic elements for web control validation such as the ControlToValidate and ErrorMessage properties. This will allow us to implement our control with less code.
. . . code continues . . .
public class RequiredHTMLPHValidator: BaseValidator
{
}
. . . code continues . . .
We don’t have to do anything within the RequiredHTMLPHValidator() constructor, so we will leave that empty.
To implement the RequiredHTMLPHValidator control, we will override the following methods of the BaseValidator class.
Method Name | What we will program it to do |
---|
ControlPropertiesValid() | Check to see if the control specified in the ControlToValidate property is an HtmlPlaceholderControl. If it isn’t, the validation control will not be generated when the page loads. |
OnPreRender() | Inject the client-side JavaScript that checks for empty placeholder controls. |
AddAttributesToRender() | Pass the name of the client-side JavaScript to be called to the built-in evaluationfunction() method used by all ASP.NET validation controls. Without this step, the script injected in the OnPreRender() method will not be fired when the page is validated. |
EvaluateIsValid() | We will write logic for performing server-side validation here. |
Overriding the ControlPropertiesValid() Method
Let’s start by overriding the ControlPropertiesValid() method of the base class, BaseValidator. The ControlPropertiesValid()
method returns a Boolean that indicates whether or not the control to
be validated exists and is of the correct object type. In this case, we
want it to return true only when the control specified can be found. If
the object exists, we proceed to check to see if it is indeed an HtmlPlaceholderControl.
When the control specified does not exist or isn’t a HtmlPlaceholderControl, the ControlPropertiesValid() property returns false and the validation control will not be generated when the page loads.
Add the ControlPropertiesValid() method directly below the RequiredHTMLPHValidator() constructor:
protected override bool ControlPropertiesValid()
{
Control ctrl = FindControl(ControlToValidate);
if (ctrl != null)
{
return (ctrl is HtmlPlaceholderControl);
}
else
{
return false;
}
}
Overriding the OnPreRender() Method
Next, we will make use of the client-side
JavaScript that we wrote in the previous section. Being a server-side
control, we can’t enter the script as it is. We need to inject it into
the page by calling the Page.RegisterClientScript() method. An appropriate place to do so would be in the overridden OnPreRender() method of our custom validator.
Is it possible to put the entire JavaScript in an external *.js file rather than injecting it into the page?
You could put the entire JavaScript into an external *.js
file. However, the script is dynamically generated based on the name of
the control to validate. For it to work from a static file, you will
have to fix the name of the validate() function and modify it to accept the name of the control to validate as an input parameter.
Another compelling reason to inject the script instead of maintaining it in a separate *.js file is to facilitate deployment. By injecting the code, we only need to install the single library file (in our case that’s MCMSValidators.dll) and not worry about the path or the existence of the *.js file as well. In addition, we can put aside any concerns about old cached versions of the file should the script be updated.
Add the OnPreRender() method directly below the ControlPropertiesValid() method:
// register the client-side validation script
protected override void OnPreRender(System.EventArgs e)
{
base.OnPreRender(e);
// Add the script only in authoring mode.
if (WebAuthorContext.Current.Mode == WebAuthorContextMode.AuthoringNew
|| WebAuthorContext.Current.Mode == WebAuthorContextMode.AuthoringReedit)
{
string phName = "NCPHRICH_";
if ((FindControl(this.ControlToValidate)) == null
|| !(FindControl(this.ControlToValidate) is HtmlPlaceholderControl))
return;
phName += ((HtmlPlaceholderControl)FindControl(this.ControlToValidate) )
.BoundPlaceholder.Name;
StringBuilder sb = new StringBuilder();
sb.Append("<script language=\"javascript\">");
sb.Append("function validate" + this.ControlToValidate + "() \n");
sb.Append("{ \n");
sb.Append(" var content = document.all." + phName + ".HTML; \n");
sb.Append(" if (ReqHtmlPhValid_isEmpty(content)) \n");
sb.Append(" { \n");
sb.Append(" return false; \n");
sb.Append(" } \n");
sb.Append(" else \n");
sb.Append(" { \n");
sb.Append(" return true; \n");
sb.Append(" } \n");
sb.Append("} \n");
sb.Append("</script>");
StringBuilder sb2 = new StringBuilder();
sb2.Append("\n<script language=\"javascript\"> \n");
sb2.Append("function ReqHtmlPhValid_isEmpty(content) \n");
sb2.Append("{ \n");
sb2.Append(" // Add more tags to ignore if you need to \n");
sb2.Append(" // Here, we ignore <hr> and <img> tags \n");
sb2.Append(" // additional tags need to be added and separated \n");
sb2.Append(" // with a '|' character. \n");
sb2.Append(" var tagsToKeep = \"img|hr\"; \n");
sb2.Append(" // This reg ex matches all <img> and <hr> tags \n");
sb2.Append(" var regExpTagsToKeep = \n");
sb2.Append(" \"<[\\\\s|/]*(\" + tagsToKeep + \")\\\\b[^>]*>\"; \n");
sb2.Append("var reTagsToKeep=new RegExp(regExpTagsToKeep,\"gim\"); \n");
sb2.Append(" // Check if a tag to keep is included & exit \n");
sb2.Append(" if (content.match(reTagsToKeep)) \n");
sb2.Append(" { \n");
sb2.Append(" // Placeholder is not empty. \n");
sb2.Append(" return false; \n");
sb2.Append(" } \n");
sb2.Append(" // This reg ex gets all tags in the content \n");
sb2.Append(" var regExpForAllTags = \"<[^>]*>\"; \n");
sb2.Append(" var reAllTags = new RegExp(regExpForAllTags,\"gim\"); \n");
sb2.Append(" // Remove all Tags by replacing with an empty string \n");
sb2.Append(" content = content.replace(reAllTags, \"\"); \n");
sb2.Append(" // Remove all spaces and non-breaking spaces ( ) \n");
sb2.Append(" content = content.replace(\" \",\"\"); \n");
sb2.Append(" content = content.replace(\" \",\"\"); \n");
sb2.Append(" if (content == \"\") \n");
sb2.Append(" { \n");
sb2.Append(" // All tags removed, leaving an empty string \n");
sb2.Append(" // Placeholder is empty. \n");
sb2.Append(" return true; \n");
sb2.Append(" } \n");
sb2.Append(" else \n");
sb2.Append(" { \n");
sb2.Append(" // After removing all tags, we still have content \n");
sb2.Append(" // Placeholder is not empty. \n");
sb2.Append(" return false; \n");
sb2.Append(" } \n");
sb2.Append("} \n");
sb2.Append("</script>");
if (this.RenderUplevel && this.EnableClientScript)
{
if (!Page.IsClientScriptBlockRegistered("ReqHtmlPhValid_" + phName
+ "_ClientScript"))
{
Page.RegisterClientScriptBlock("ReqHtmlPhValid_" + phName +
"_ClientScript", sb.ToString());
}
if (!Page.IsClientScriptBlockRegistered("ReqHtmlPhValid_IsEmpty"))
{
Page.RegisterClientScriptBlock("ReqHtmlPhValid_IsEmpty ",
sb2.ToString());
}
}
}
}
Notice that we registered the validateNameOfControlToValidate() and ReqHtmlPhValid_isEmpty() methods within two separate client script blocks. By registering the first client script block with a unique key, ReqHtmlPhValid_NameOfPlaceholder_ClientScript, each validation control will have its own version of the validateNameOfControlToValidate() method. The second client script block is registered with a fixed key named ReqHtmlPhValid_IsEmpty. By fixing the key, only one copy of the ReqHtmlPhValid_isEmpty() method will be generated even if there are multiple validation controls on the same page.
Overriding the AddAttributesToRender() Method
Now that we have defined the client-side validateNameOfControlToValidate()
method that performs the validation, we need figure out how to trigger
it when the page is validated. To do so we will borrow some
functionality found in the ASP.NET WebUIValidation.js script. The WebUIValidation.js
script file contains methods that support client-side validation, so we
can use it to save us re-writing all that functionality from scratch.
Before we do so, let’s take a look at how client-side validation works
with regular ASP.NET validation controls.
Consider the case of a simple web form that
contains several fields, including an ASP.NET button. When the button is
clicked, the fields are validated. How does the action of clicking the
button trigger validation? For the answer, take a look at the generated
HTML code for the button; here’s what you will see:
<input type="submit"
name="Button1"
value="Button"
onclick="if (typeof(Page_ClientValidate)=='function')
Page_ClientValidate();"
language="javascript"
id="Button1" />
Notice that embedded within the onclick event of the button, is a call to a method named Page_ClientValidate(). The Page_ClientValidate() function is defined within the WebUIValidation.js file and looks like this:
function Page_ClientValidate()
{
var i;
for (i = 0; i < Page_Validators.length; i++) {
ValidatorValidate(Page_Validators[i]);
}
. . . code continues . . .
}
The Page_ClientValidate() method in turn calls the ValidatorValidate() method (also found within the WebUIValidation.js file) for each validation control found on the page. The ValidatorValidate() method performs the crucial step of retrieving the name of the client-side validation routine from an attribute named evaluationfunction (highlighted below) and executes it.
function ValidatorValidate(val)
{
. . . code continues . . .
if (typeof(val.evaluationfunction) == "function")
{
val.isvalid = val.evaluationfunction(val);
}
. . . code continues . . .
}
Therefore, in order to trigger our client-side routine when the page validates, we will have to add an attribute named evaluationfunction to the validation control and assign the name of our function, validateNameOfControlToValidate, as its value.
The good new is that the BaseValidator class has just the method for adding attributes. It’s called AddAttributestoRender(). We will override this method in the base class to insert the evaluationfunction attribute to the control. Add the following code directly below the OnPreRender() method:
// Wiring the client-side javascript to the WebUIValidation.js script file
protected override void AddAttributesToRender(System.Web.UI.HtmlTextWriter
writer)
{
base.AddAttributesToRender(writer);
if (this.RenderUplevel && this.EnableClientScript)
{
// Perform validation only in authoring mode.
if (WebAuthorContext.Current.Mode == WebAuthorContextMode.AuthoringNew
|| WebAuthorContext.Current.Mode == WebAuthorContextMode.AuthoringReedit)
{
writer.AddAttribute("evaluationfunction", "validate"
+ this.ControlToValidate);
}
}
}
Overriding the EvaluateIsValid() Method
To complete the code, we need to implement the EvaluateIsValid()
method of the base class or the project will not compile. This is where
we implement server-side checks. For now, we will leave the EvaluateIsValid() empty and program it to always return true.
While this isn’t something you should do in a production environment,
don’t worry. We’ll be filling this method out later to provide
server-side validation in the section Implementing Server-Side Validation. Add the EvaluateIsValid() method directly below the AddAttributesToRender() method.
protected override bool EvaluateIsValid()
{
bool valid = true;
return valid;
}
The custom validation control is now complete. Save and compile the solution.
3.4 Adding the Custom Validator to the Template File
Before using our custom control, we first need to copy it to the bin
directory of our project file or add it as a reference to the MCMS web
application project. After you have done that, add the control to the
Visual Studio .NET Toolbox by choosing the Add/Remove Items option (or Customize Toolbox
option for VS.NET 2002). You can then drag and drop it onto template
files. For the control to work properly, you will have to set the ControlToValidate property to contain the value of the ID of the HtmlPlaceholderControl that you are validating and the ErrorMessage property to contain a friendly error message for the author.
The
ID of the HtmlPlaceholderControl does not appear in the
ControlToValidate dropdown list. Shouldn’t the dropdown list be
populated with placeholder IDs that match the type of control being
validated?
In order for the ID of the control to be included in the dropdown, we have to make the HtmlPlaceholderControl compatible with the ASP.NET validation control architecture. We will do just this in the section, The HtmlPlaceholderControl with Validation.