smithvoice.com
 Y'herd thisun? 

“Essentially nobody understands calculus the first time they take it. I didn't understand calculus the first time I took it. In fact, for most of us who teach calculus, the time we understood calculus was the time we taught it... and I recommend this as a method for learning anything.”

from Change And Motion: Calculus Made Clear by Professor Michael Starbird

ASP.Net limited textarea for Ajax

TaggedCoding, CSharp, DotNet, ASP.Net

Huh?  What?

Oh sorry, been heads down the past few ... months?  Sheesh.

Ok.  There is one thing I did the other day that I'll forget if I don't put it here.  I'm working on a few new sites and all of them at one place or another need a textarea with a character limit.

I know, we've all done these since before the beginning in one way or another.  It's no big deal to just check the length in the postback or add a key event javascript method with an alert or whatever, and these days even adding a countdown span means just grabbing someone's freebie jquery sample ;-). 

Those all work fine mostly.  But for this project there was a twist that threw me a fail.  I had to sometimes load the control (usually as a part of editor usercontrol) dynamically to a Ajaxified page OR let the parent control be added normally to the Ajax area at runtime with its visibility set to false for later toggling. 

BTW: the javascript is dynamically generated because there may be more than one instance of the control on a page or ajax area and so the unique control Ids would be embedded in the javascript when the controls are being rendered.  The knee jerk is that inverting the problem with a general routine having controlIDs being passed to it would take care of things... but in the case of most simple jquery routines this can just be moving the pointer along because where you register the control with the listener remains a question.  At least it still was with me, but I admit that I am no jquery master. 

The first specbullet is still relatively workable... RegisterStartupScript will set up the customized JS link script in the page when the control is loaded dynamically.  But the second requirement was the niggle because the control is on the page and so its RegisterStartupScript event occurs... followed by an immediate javascript error when the script can't find the textbox and counter controls; They aren't on the page because 'Visible = false' means that they don't get put in the HTML.

I toyed with setting the ascx to display hidden via css so the textarea and span will be rendered but not displayed in up to date browsers, but it got overly complicated when the control was used as a component of one or more higher level containers.  I want this control to work no matter how many levels of containment it may be wrapped into and detecting the startmodes of each level and logically branching the javascript registration wasn't simple.

And I wanted this to be simple.  Drop and go.  There were too many pages that would conceivably host the control alone or in composite to make it hand-code-intensive.

First thing though... I wanted a better js library than the one I'd been using for years.  A while of searches and tests gave me Jan Jarfalk's jquery.limit-1.2 .  Old as my previous self-made script but cleaner, and it doesn't move the caret to the end of the text when the limit is hit the way most of them do.  To get around the loading issue I modified it slightly to bail out if the textarea was undefined due to it not being rendered.  Here's the modified version (watch the line breaks when copying):

 


 //ORIGINAL from: Jan Jarfalk
//              http://code.google.com/p/jquery-limit/downloads/detail?name=jquery.limit-1.2.source.js&can=2&q=
//MODIFIED:  by smith to check for undefined $(self).val [if(val == undefined)return; added] so that
//               the js can be used in a control that will be used in AJAX and have its visible property
//               toggled.  If the check isn't done then when the control is made !visible and the code runs
//               there will be a JS error because !visible causes the control to not be rendered in the HTML.

//MODIFIED:  by smith to put a more full english counter value, original commented out. 
 

(function ($) {
     $.fn.extend({  
        limit: function(limit,element) {

            var interval, f;
            var self = $(this);

            $(this).focus(function(){    
            interval = window.setInterval(substring,100);
            });

            $(this).blur(function(){    
                clearInterval(interval);
                substring();   
            });

        substringFunction = "function substring(){
            var val = $(self).val();  
            if(val == undefined)return; var length = val.length;  
            if(length > limit){$(self).val($(self).val().substring(0,limit));}";

        if(typeof element != 'undefined')
            substringFunction += "if($(element).html() != limit-length){$(element).html((limit-length<=0)?'0 of '  
           + limit + ' characters remain':limit-length + ' of ' + limit + ' characters remain');}"
    

        //if (typeof element != 'undefined')
        //    substringFunction += "if($(element).html() != limit-length){
        //$(element).html((limit-length<=0)?'0':limit-length);}"

        substringFunction += "}";

        eval(substringFunction);
 
        substring();
   
        } 
    
    });

})(jQuery);

 


Of course using this means JQuery so you'll have to add it to your project.  I used jquery-2.0.3.min.js via NuGet. 

The JQuery requires JSON and that is normally taken care of by the user browser but not always - like if your users have IE compatibility turned on, which is the default for intranet connections.  At first I added JSON2 from http://www.JSON.org/js.html, just dropping it in the scripts folder and adding the registration to the others in the control.  But then I decided to instead set X-UA-Compatibility to IE-Edge mode during the control's Init event.  It's cleaner and it's as much/as little a hack as most everything else in web coding.  If you don't like it then use the JSON2.js and add the reference to your pages/controls.  It takes care of IE9 and 10 which covers my requirements (till someone yells <g>).

Aside from those outband needs, the rest is pretty standard ascx coding.  Because I'm using it in a usercontrol within my projects you'll see that the property sheets doesn't offer custom Editors for Font groupings, URLs (the javascript paths) and colors so you have to type those in as strings. 

If after a while this control seems solid in the wild I'd convert it to a dll and add the Editor attributes, embed the jquery and limiting script, and maybe put in some logic for notfounds and other exceptions.  Maybe ;-).

Using it is just dropping it on a form and setting the props.  Or you can do it dynamically..

For me, so far anyway, it works plopped on a page or runtime loaded or either way in or out of an ajax panel and - thank god - loaded in either fashion but with its .Visible property set to false.

Here's the ascx code files  Change the namespaces and paths to fit your needs.  Hope it helps.

OH!  before I forget.  I'm using the RADAJAX system from Telerik.  If you're using it too then remember that by default adding any Telerik Ajax components to your pages or master pages will likely add this line:

<asp:ScriptReference Assembly="Telerik.Web.UI" Name="Telerik.Web.UI.Common.jQueryInclude.js"></asp:ScriptReference>

That's Telerik's own embedded jQuery and if it's on the page or masterpage then it will cancel out all calls to your own choice of JQuery library.  They just won't work. You can try to tap into their library but they made modifications that you might have to deal with.  The good thing is that if you just comment it out then 99% of the time you will be totally fine with your runtime jquery and theirs so long as your own library is relatively up to date and stable. Bang on it to make sure you aren't in the 1%. 

Also on the subject of Telerix RadAjax, if you use their RadDatePicker look closely in IE.  It tends to cut off the initial textarea display; I've seen it every time on IE10 and NOT in compatibility mode.  There are a bunch of esoteric attempts to fix this problem but there is a fast and simple way to do it that doesn't require editing the css.  Just type a few spaces in the DateFormat property after your format string.  I had to write that somewhere to make myself remember, sorry about hijacking this page ;-).

ucMulti.ascx:


<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="ucMulti.ascx.cs" Inherits="sv.controls.ucMulti" %>
<asp:Panel ID="pnlMultiBorder" runat="server">
<asp:TextBox ID="txtInput" runat="server" Width="100%"></asp:TextBox>
<asp:Label ID="lblCounter" runat="server" Width="100%" style="text-align:left;position:relative;top:-12px"></asp:Label>
</asp:Panel>


 

uMulti.ascx.cs:


using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;

namespace sv.controls
{
    [PersistChildren(false), ParseChildren(true)]
    public partial class ucMulti : System.Web.UI.UserControl
    {
        #region constants
        const TextAlign DEFAULT_LABELTEXTALIGN = TextAlign.Left;
        const bool DEFAULT_ALLOWUSERRESIZE = false;
        const int DEFAULT_MAXTEXTLENGTH = 100;
        const string DEFAULT_JS_JQUERY_PATH = "~/scripts/jquery-2.0.3.min.js";
        const string DEFAULT_JS_LIMITPATH = "~/scripts/jquery.limit-1.2.source.js";
        const string DEFAULT_LABELFONTCOLOR = "#000";
       
        #endregion

        #region instance variables

        private TextAlign _labelTextAlign = DEFAULT_LABELTEXTALIGN;
        private bool _allowTBResize = DEFAULT_ALLOWUSERRESIZE;
        private int _maxLength = DEFAULT_MAXTEXTLENGTH;
        private string _jqueryJSFilePath = DEFAULT_JS_JQUERY_PATH;
        private string _textLimitJSFilePath = DEFAULT_JS_LIMITPATH;
        private string _labelFontColor = DEFAULT_LABELFONTCOLOR;
        #endregion

        #region base methods

        protected void Page_Init(object sender, EventArgs e)
        {
            //helps when IE compatibility is enabled (the default on inTRAnet a darned annoying policy)
            Response.AddHeader("X-UA-Compatible", "IE=Edge");

        }

        protected void Page_Load(object sender, EventArgs e)
        {
            //don't use IsPostback logic if you want it to work in an ajaxifed area

            AddJQueryCores();
            AddUniqueJavascriptToPage();

            if (!IsPostBack)
            {
                SetlabelAlignment();
                AllowUserResizeTextbox = _allowTBResize;
            }

        }
        #endregion

        #region private methods

        private void AddUniqueJavascriptToPage()
        {
            string scriptID = Guid.NewGuid().ToString();
            string scriptCode = "<script>if(typeof " + txtInput.ClientID + " != undefined){ $('#" + txtInput.ClientID + "').limit('" +
                    _maxLength.ToString() + "', '#" + lblCounter.ClientID + "');};</script>";

            //the following is left in for testing only... it works when your control instance is not used
            //in Ajax, as in added at design time or added dynamically on a non-ajaxified page area
            //if (!Page.ClientScript.IsClientScriptBlockRegistered(scriptID))
            //{
            //    Page.ClientScript.RegisterStartupScript(this.Page.GetType(), scriptID, scriptCode);
            //}

            //Script Manager works when the location area is ajaxified

            ScriptManager.RegisterStartupScript(this.Page, this.Page.GetType(), scriptID, scriptCode, false);

        }

        private void AddJQueryCores()
        {

            //hardcoding the js paths isn't great but this is a quick example
            //alter the path setting methodology however you desire ... if you have to ;-)

            ClientScriptManager csm = Page.ClientScript;

            if (!csm.IsClientScriptIncludeRegistered("keyName"))
            {
                csm.RegisterClientScriptInclude("keyName",
                    Page.ResolveUrl(_jqueryJSFilePath));
            }

            if (!csm.IsClientScriptIncludeRegistered("keyName2"))
            {
                csm.RegisterClientScriptInclude("keyName2",
                    Page.ResolveUrl(_textLimitJSFilePath));
            }

        }

        private void SetlabelAlignment()
        {
            lblCounter.Style.Remove("text-align");
            lblCounter.Style.Add("text-align", _labelTextAlign.ToString());
        }

        private void SetUserResize()
        {
            if (_allowTBResize)
            {
                txtInput.Style.Remove("resize");
                txtInput.Style.Add("resize", "both");
            }
            else
            {
                txtInput.Style.Remove("resize");
                txtInput.Style.Add("resize", "none");
            }
        }

        #endregion

        #region public properties

        [Browsable(true), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), Category("Resources")]
        [DefaultValue(DEFAULT_JS_JQUERY_PATH)]
        public string JSJqueryFilePath
        {
            get { return _jqueryJSFilePath; }
            set { _jqueryJSFilePath = value; }
        }

        [Browsable(true), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), Category("Resources")]
       [DefaultValue(DEFAULT_JS_LIMITPATH)]
        public string JSTextLimitFilePath
        {
            get { return _textLimitJSFilePath; }
            set { _textLimitJSFilePath = value; }
        }

        [Browsable(true), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), Category("Appearance")]
        public System.Drawing.Color BackColor
        {
            get { return pnlMultiBorder.BackColor; }
            set { pnlMultiBorder.BackColor = value; }
        }

        [Browsable(true), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), Category("Appearance")]
        public string LabelFontColor
        {
            get
            {
                return _labelFontColor;

            }
            set
            {
                _labelFontColor = value;
                lblCounter.Style.Remove("color");
                lblCounter.Style.Add("color", _labelFontColor);
            }
        }

        [Browsable(true), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), Category("Appearance")]
        [DefaultValue(TextBoxMode.MultiLine)]
        public TextBoxMode TextBoxMode
        {
            get { return txtInput.TextMode; }
            set { txtInput.TextMode = value; }
        }

        [Browsable(true), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), Category("Appearance"), Description("ere is a")]
        [DefaultValue("300px")]
        public Unit TextBoxHeight
        {
            get { return txtInput.Height; }
            set { txtInput.Height = value; }
        }

        [Browsable(true), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), Category("Appearance")]
        [DefaultValue("300px")]
        public Unit Width
        {
            get { return pnlMultiBorder.Width; }
            set { pnlMultiBorder.Width = value; }
        }

        [Browsable(true), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), Category("Appearance")]
        [DefaultValue(DEFAULT_LABELTEXTALIGN)]
        public TextAlign LabelTextAlign
        {
            get { return _labelTextAlign; }
            set
            {
                _labelTextAlign = value;
                SetlabelAlignment();
            }
        }

        [Browsable(true), Category("Appearance"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        [DefaultValue(DEFAULT_MAXTEXTLENGTH)]
        public int MaxLength
        {
            get { return _maxLength; }
            set { _maxLength = value; }
        }

        [Browsable(true), Category("Appearance"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public FontInfo LabelTextFont
        {
            get { return lblCounter.Font; }

        }

        [Browsable(true), Category("Appearance"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public FontInfo TextFont
        {
            get { return txtInput.Font; }

        }

        [Browsable(true), Category("Appearance"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public string Text
        {
            get { return txtInput.Text; }
            set { txtInput.Text = value; }

        }

        [Browsable(true), Category("Appearance"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        [DefaultValue(DEFAULT_ALLOWUSERRESIZE)]
        public bool AllowUserResizeTextbox
        {
            get { return _allowTBResize; }
            set
            {
                _allowTBResize = value;
                SetUserResize();
            }
        }

        #endregion
       
    }
}


 ucMulti.ascx.designer.cs


//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace sv.controls {
   
   
    public partial class ucMulti {
       
        /// <summary>
        /// pnlMultiBorder control.
        /// </summary>
        /// <remarks>
        /// Auto-generated field.
        /// To modify move field declaration from designer file to code-behind file.
        /// </remarks>
        protected global::System.Web.UI.WebControls.Panel pnlMultiBorder;
       
        /// <summary>
        /// txtInput control.
        /// </summary>
        /// <remarks>
        /// Auto-generated field.
        /// To modify move field declaration from designer file to code-behind file.
        /// </remarks>
        protected global::System.Web.UI.WebControls.TextBox txtInput;
       
        /// <summary>
        /// lblCounter control.
        /// </summary>
        /// <remarks>
        /// Auto-generated field.
        /// To modify move field declaration from designer file to code-behind file.
        /// </remarks>
        protected global::System.Web.UI.WebControls.Label lblCounter;
    }
}

 


K.  Gotta get back to work. I'm making a new ad-hoc query system to work with my object model that does NOT require direct access to the stinking database.  LINQ2SQL is fun but it creeps me out to have a solid OM then have to go all the way around it and put the db so close to the users.  2 days... honest, 2 days!  Or maybe three.

See ya!



home     who is smith    contact smith     rss feed π
Since 1997 a place for my stuff, and it if helps you too then all the better smithvoice.com