Multiline textbox (textarea) and maxLength

In ASP.NET, you can create a textarea by adding a multiline textbox to your markup:

<asp:textbox id="textboxDescription" runat="server" textmode="MultiLine" />

Intuitively, we started adding the MaxLength property to this, in order to let the content of the textarea match the length of our database column:

<asp:textbox id="textboxDescription" runat="server" textmode="MultiLine" maxlength="2000" />

If you’re reading this, you know that doesn’t work. We, however didn’t. This is easily fixed with some javascript. But if you have a large site with many of these mistakes, you’d want to do it in one go. Use a control adapter for this:

Public Class TextBoxAdapter

    Inherits WebControlAdapter

    ' If you need to access the adapted control, the framework advises to expose

    ' a private method encapsulating the Adapter's Control property.
    ' We can cast it without doing any test, as by naming convention,
    ' this adapter shall only be used for adapting Textboxes (or derived types)
    Private ReadOnly Property TextBoxControl() As TextBox
        Get
            Return DirectCast(MyBase.Control, TextBox)
        End Get
    End Property 

    Protected Overrides Sub RenderBeginTag(ByVal writer As System.Web.UI.HtmlTextWriter)
        If TextBoxControl.TextMode = TextBoxMode.MultiLine AndAlso TextBoxControl.MaxLength > 0 Then
            writer.AddAttribute("maxlength", TextBoxControl.MaxLength.ToString)
        End If 
        MyBase.RenderBeginTag(writer)
    End Sub 

    Protected Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter)
        RenderBeginTag(writer)
        If TextBoxControl.TextMode = TextBoxMode.MultiLine Then
            HttpUtility.HtmlEncode(TextBoxControl.Text, writer)
        Else
            MyBase.RenderContents(writer)
        End If
        MyBase.RenderEndTag(writer)
    End Sub

End Class

Due to a bug in the WebControlAdapter class, you have to override the Render method.

Now, add an App_Browsers ASP.NET folder to your project, and add an Allbrowsers.browser file to the folder:

<browsers>
    <browser refID="Default">
        <controlAdapters>
            <adapter controlType="System.Web.UI.WebControls.TextBox" adapterType="Ihc.Cpd.Presentation.Globals.TextBoxAdapter" />
        </controlAdapters>
    </browser>
</browsers>

Finally, include some javascript to your project. We’ve put it in a seperate file, which is always loaded (this needs jQuery, and was based on other peoples code, but I’ve lost the links):

// ignore these keys
var ignore = [8, 9, 13, 33, 34, 35, 36, 37, 38, 39, 40, 46];

// use keypress instead of keydown as that's the only place keystrokes could be canceled in Opera
// handle textareas with maxlength attribute
$('textarea[maxlength]').live('keypress',
    function(event) {
        // this is where the magic happens
        var self = $(this),
        maxlength = self.attr('maxlength'),
        code = $.data(this, 'keycode');

        // check if maxlength has a value.
        // The value must be greater than 0
        if (maxlength && maxlength > 0) {
            // continue with this keystroke if maxlength
            // not reached or one of the ignored keys were pressed.
            if (self.val().length >= maxlength || $.inArray(code, ignore) !== -1) {
                alert('Too long!');
                return false;
            }
            else
                return true;
        }
    })
    .live('keydown',
        function(event) {
            // store keyCode from keydown event for later use
            $.data(this, 'keycode', event.keyCode || event.which);
        })
    .live('keyup',
        function(event) {
            // cut long text if pasted in the textarea
            var self = $(this),
            maxlength = self.attr('maxlength'),
            text = self.val();
            if (text.length > maxlength)
            {
                alert('Too long!');
                self.val(text.substr(0, maxlength));
            }
        });

Now, what does that learn us? First, that we didn’t test this good enough. Second, a schemaless database like RavenDb has the advantage over relational databases. That’s why I’m switching from NHibernate to RavenDb for my own project.