TextBoxCounter Atlas Extender
At my User Group meeting last night, I presented on Atlas, and more specifically the control extenders.
Here is a control extender that I wrote for the example, and I think it's a nice little simple control, that is worthy of learning from. First I wrote the sample extender "Disabled Button" that is shown on the Atlas site. From that code, I wrote this control extender. The extender uses a label and a textbox, we watch the TextBox onkeyup event, and then write the length of the text entered in the textbox to the label.
The extender is built with three properties.
- TargetControlId (which is a default property, not one I added) that points to the textbox
- TargetLabelId is a custom property that I added, and it points to the label
- OutputFormat is the final property that asks the user for a string.format like approach to not hardcode the text output.
The finished asp.net code looks like this.
1: <asp:TextBox ID="TextBox1" runat="server" TextMode="MultiLine" Rows="5" Columns="50"></asp:TextBox><br />
2: <asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>
3:
4: <cc1:textboxcounterextender id="TextBoxCounterExtender1" runat="server">
5: <cc1:TextBoxCounterProperties
6: OutputFormat="{0} Chars" 7: TargetControlID="TextBox1" TargetLabelId="Label1" />
8: </cc1:textboxcounterextender>
The finished screen looks like this. See the 0 Chars? That's because nothing is typed in, as you type, the count updates itself.

Once you type, it'll look like this.

Here are the steps I followed.
First, you have to have the April CTP of Atlas installed, along with the Control Toolkit.
Once this is done, create a project that is of the type "Atlas Control Project" and Name it TextBoxCounter.

Once this is done, the Atlas Control Project template, will create 4 files for you.

These fiels are already stubbed out for you, with great comments on what to do, to get your control extender built. The majority of the work is done in the javascript file, so we'll save that for last.
The designer file, is used for visual studio design support. Remember that an extender is just a server control. It's job is going to be to wire up the control you point it at, and create some javascript that outputs to the page. So my extender targets a textbox. Remember we're watching the textbox (with a client side event) so the textbox is our target control.
Here is my finished TextBoxCounterDesigner.cs file
1: using System.Web.UI.WebControls;
2: using System.Web.UI;
3: using Microsoft.AtlasControlExtender;
4: using Microsoft.AtlasControlExtender.Design;
5:
6: namespace LearnAtlasExtenders
7: { 8: class TextBoxCounterDesigner : ExtenderControlBaseDesigner<TextBoxCounterProperties, TextBox>
9: { 10:
11:
12: }
13: }
The only change I had to make to this file, is line 8, which originally targets a generic "control", and I changed it to TextBox.
I had to make the same/similar changes to the TextBoxCounterExtender.cs file
1: using System.Web.UI.WebControls;
2: using System.Web.UI;
3: using System.ComponentModel;
4: using System.ComponentModel.Design;
5: using Microsoft.AtlasControlExtender;
6:
7: #region Assembly Resource Attribute
8: [assembly: System.Web.UI.WebResource("LearnAtlasExtenders.TextBoxCounterBehavior.js", "text/javascript")] 9: #endregion
10:
11:
12: namespace LearnAtlasExtenders
13: { 14: [Designer(typeof(TextBoxCounterDesigner))]
15: [ClientScriptResource("TextBoxCounter", "TextBoxCounterBehavior", "LearnAtlasExtenders.TextBoxCounterBehavior.js")] 16: public class TextBoxCounterExtender : ExtenderControlBase<TextBoxCounterProperties, TextBox>
17: { 18: }
19: }
Next is to create our customer properties in TextBoxCounterProperties.cs
1: using System.Web.UI.WebControls;
2: using System.Web.UI;
3: using System.ComponentModel;
4: using Microsoft.AtlasControlExtender;
5:
6: namespace LearnAtlasExtenders
7: { 8: // TODO Set this to be your default extender property name
9: //
10: [DefaultProperty("TargetLabelId")] 11: public class TextBoxCounterProperties : TargetControlPropertiesBase<TextBox>
12: { 13: [IDReferenceProperty(typeof(Label))]
14: public string TargetLabelId
15: { 16: get { return GetPropertyStringValue("TargetLabelId"); } 17: set { SetPropertyStringValue("TargetLabelId", value); } 18: }
19:
20: public string OutputFormat
21: { 22: get { return GetPropertyStringValue("OutputFormat"); } 23: set { SetPropertyStringValue("OutputFormat", value); } 24: }
25: }
26: }
You can see here, we're creating two properties, each wtih a get and set routine. The SetPropertyStringValue and GetPropertyStringValue are methods built into the base class, we don't have to create them, just use them.
Finally, our .js file -- TextBoxCounterBehavior.js
1: Type.registerNamespace('LearnAtlasExtenders'); 2:
3: LearnAtlasExtenders.TextBoxCounterBehavior = function() { 4: LearnAtlasExtenders.TextBoxCounterBehavior.initializeBase(this);
5:
6: // TODO : (Step 1) Add your property variables here
7: //
8: var _TargetLabelId;
9: var _OutputFormat;
10:
11: this.initialize = function() { 12: LearnAtlasExtenders.TextBoxCounterBehavior.callBaseMethod(this, 'initialize');
13:
14: this.control.element.attachEvent('onkeyup', Function.createDelegate(this, this._onkeyup)); 15: this._onkeyup();
16: }
17:
18: this.dispose = function() { 19: LearnAtlasExtenders.TextBoxCounterBehavior.callBaseMethod(this, 'dispose');
20: }
21:
22: this.getDescriptor = function() { 23: var td = LearnAtlasExtenders.TextBoxCounterBehavior.callBaseMethod(this, 'getDescriptor');
24:
25: // TODO: (Step 2) Add your property declarations here.
26: //
27: td.addProperty('TargetLabelId', String); 28: td.addProperty('OutputFormat', String); 29: return td;
30: }
31:
32: this.get_TargetLabelId = function () { 33: return _TargetLabelId;
34: }
35:
36: this.set_TargetLabelId = function(value) { 37: _TargetLabelId = value;
38: }
39:
40: this.get_OutputFormat = function () { 41: return _OutputFormat;
42: }
43:
44: this.set_OutputFormat = function(value) { 45: _OutputFormat = value;
46: }
47:
48:
49: // These are helper functions for communicating state back to the extender on the
50: // server side. They take or return a custom string that is available in your initialize method
51: // and later.
52: //
53: this.getClientState = function() { 54: var value = LearnAtlasExtenders.TextBoxCounterBehavior.callBaseMethod(this, 'get_ClientState');
55: if (value == '') value = null;
56: return value;
57: }
58:
59: this.setClientState = function(value) { 60: return LearnAtlasExtenders.TextBoxCounterBehavior.callBaseMethod(this, 'set_ClientState',[value]);
61: }
62:
63: //custom behavior
64: this._onkeyup = function() { 65: var lbl = document.getElementById(_TargetLabelId);
66: var regex = /\{0\}/g; 67: lbl.innerHTML = _OutputFormat.replace(regex, this.control.element.value.length);
68: //lbl.innerHTML = _OutputFormat.split("{0}").join(this.control.element.value.length); 69: }
70:
71: }
72:
73: LearnAtlasExtenders.TextBoxCounterBehavior.registerSealedClass('LearnAtlasExtenders.TextBoxCounterBehavior', Microsoft.AtlasControlExtender.BehaviorBase); 74: Sys.TypeDescriptor.addType('TextBoxCounter', 'TextBoxCounterBehavior', LearnAtlasExtenders.TextBoxCounterBehavior); On lines 8,9 we define our javascript holder properties.
On lines 14-15 we wire up on onkeyup event to this.control, which is the TargetControl, which is the TextBox we point our extender at.
On lines 27-28 we add our properties to the base method (decalred on line 23
On lines 32-46 we write javascript get/set routines for our 2 properties
And finally on line 64-69 we write our onkeyup function, that changes the label text with a javascript routine. (Special thanks to my user group meeting, who helped my put this together with a regex replace as seen on lines 66-67, I originally had a javascript .split().join() routine that worked, but their solution was more elegant, so mine's commented out).
Save All.
Compile
Set a reference in your web project, to this control extender project
Add the extender to your tool box, and you'll forever have an extender that is easy to use, that will count the length of the text in a textbox.
Hope this helps as a good introduction to building custom control extenders.