|

Introduction
It is an unfortunate reality for web application users that web pages scroll. While this is "useful" for viewing documents, it can be annoying when you are forced to scroll down to save changes to a form. And it's really "really" annoying, when your job involves doing that maybe 1,000 times a day. Astutely, my clients have noticed this, and requested that I place the Save, Cancel, Help, etc. buttons at both the top and the bottom of every page.
Obviously, you can do this without much effort. Simply duplicate the controls at the top of the page, give them new ID's, and then reference the same event handlers that the other controls are using.
But we're programmers, right? And, genetically, we "loathe" duplicate code. Who really wants to call their controls btnSave1, and btnSave2? Totally uncool.
There must be "A Better Way".
A Better Way
What if you had a way to just duplicate a group of controls so that it appeared more than once on the same page? And to have the duplication performed completely at runtime, rather than at design time?
Enter... the Mirror control. The Mirror control is a very simple custom control, whose sole function is to re-render another control so that it appears in more than one location on the page.
It turns out that the .NET control-rendering model can be used to generate the HTML for a control more than once. Any WebControl, including custom controls, will have a RenderControl() method that the Page uses to generate the control's HTML. And you can use it too...
Using the Mirror Control
As with other WebControls, using the Mirror control is horribly complicated. It works like this;
<cc1:Mirror id="Mirror1" ControlID="ButtonPanel1" runat="server" />
- Give your control an
ID, like Mirror1. If you do not, it is not strictly necessary, Visual Studio will create one for you.
- Specify the
ID of the WebControl you are mirroring through the ControlID attribute.
- Yes, that's it.
At the top of your page, make certain to reference your custom control assembly, something like this;
<%@ Register TagPrefix="cc1" Namespace="MirrorControl"
Assembly="MirrorControl" %>
How It Works
Very simply, the Mirror control's Render() function;
- locates the control you have identified in the
ControlID attribute, using the Page.FindControl() method.
- forces the identified control to render itself by calling its
RenderControl() method.
The actual intelligence is just a few lines of code. This is the "entire" class definition for the Mirror control. Who said custom controls have to be complicated?
[ToolboxData("<{0}:Mirror runat="server"></{0}:Mirror>")]
public class Mirror : WebControl
{
public string ControlID = null;
protected override void Render (HtmlTextWriter writer)
{
if (ControlID == null)
return;
Control c = Parent.FindControl (ControlID);
if (c == null)
return;
c.RenderControl (writer);
}
}
Caveats and Limitations
- The control you specify will be duplicated "precisely" as rendered elsewhere. Again, "precisely". This includes things like the
ID attributes. If you have JavaScript that references those ID attributes, you will probably encounter issues.
- Avoid mirroring data-entry controls (e.g.
TextBox, ListBox, CheckBox, DropDownList, ...), as only the topmost one on the page will work properly.. During a postback, the ASP.NET postback handler will only pay attention to the first control matching the ID, and load its values from there. Thus, if you change the contents of a lower-in-the-page instance of the control, its value will be lost on postback.
- If you use absolute positioning in your layouts, make sure that you do not use them on the mirrored controls. Otherwise both sets of controls will render in the same location, one on top of the other. [Special thanks to UnderWing for pointing this out]
Tips and Tricks
- The
Mirror control is designed to duplicate one WebControl only. If you wish to duplicate a group of controls, you can use several Mirror controls, or, if they are adjacent, simply wrap your controls in a Panel, and reference the ID of the Panel instead.
- You can mirror the same control(s) more than once on the same page, if such a thing is useful to you.
- Avoid mirroring data-entry controls (e.g.
TextBox, ListBox, CheckBox, DropDownList, ...), as explained above.
- Avoid absolute positioning of the mirrored controls.
- If you need to access both instances of the control, the
FindControl() function will only locate one of them. However you may be able to iterate through the Page.Controls collection and locate both.
| You must Sign In to use this message board. |
|
| | Msgs 1 to 14 of 14 (Total in Forum: 14) (Refresh) | FirstPrevNext |
|
|
 |
|
|
 |
|
|
I dynamically inject JavaScript into onclick by means of OriginalControl.Attributes.Add("onclick","myCode(); ") in the Page_Load of the aspx.
However, this doesn't get rendered in the mirrored control.
Ideas?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I'm not able to reproduce this, and I'm not seeing an easy way that such a thing could occur. The Mirror control doesn't do any caching, and its HTML is produced during Page.Render() -- essentially the very last thing that the page does. So anything you've done in your codebehind that affects the rendered HTML of your original control should also be picked up by Mirror.
The only exception I can think of is if you're doing something in the Page.Render() method that affects your original control; this could supercede your Control.Render() output, and therefore the final page render could potentially output different HTML for your original control than it does for the Mirror control (whose render reflects the actual OriginalControl.Render() output)
Here is my test case;
ASPX: <asp:Button ID="Button1" Runat="server" Text="Test Button" /> <smuCC:Mirror ControlID="Button1" runat="server" ID="Mirror1" />
CODEBEHIND (Page_Load):
if (!IsPostBack) Button1.Attributes.Add ("onclick", "alert('clicked');");
I've tried this with and without the IsPostBack test, and both on the original request and on subsequent postback requests. In all cases, the onclick is appearing in both controls on my system.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
Note to others, this will exactly duplicate the elements style attribute. As far as positioning, make sure to either wrap the mirror in a DIV/SPAN, or use FlowLayout.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
This must be one of the simplest solutions to this kind of problem that I have seen in a long while. Well done!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hey this is pretty innovative. I had been running into this problem quite frequently as a matter of fact - this will clean my code up nicely. Thanks
PS: This is what part of the alphabet would look like if the letters Q and R were removed.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I like your implementation. I think it's quite usefull in websites.
But on the other hand I'd rather use a Frame or IFrame when it comes to web applications, where the clients are using the application many times a day (as you say yourself).
You can then render the controls (i.e. buttons) outside of the iframe at the top or bottom of the main frame to prevent scrolling.
Ofcourse you need some technique for this, but maybe my codeproject article can help you with that 
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
The problem with using frames or Iframes is that they lead to the "data porthole" effect, where the top, bottom and sides are filled with immovable frou-frou, and the data --- the part the user is actually interested in -- is squeezed into a little box in the middle.
Truth, James
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Why should the top and bottom be immovable? When you have navigation bar, menu title or submit buttons, yes of course you must show them. But when a panel stays empty, you can just fold it, creating a bigger working area for the user. But yes, the sides will be immovable, but that is also true in a single page, unless you’re willing to let the user scroll horizontally.
Displaying submit buttons at the top AND the bottom (as discussed in this article), will make the working page even bigger, leading to even more scrolling.
Don’t forget the user is also very interested in the submit buttons, needing to scroll to them is often more annoying, then scroll the content itself.
Of course I agree with you, we as developers should try to make the working area of a web application as big as possible, stripping as much useless top and bottom space as possible.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Very nice article, DeKale. Like most web developers, I went through a phase where I favored heavily framed UI's with tons of cross-frame scripting to sync everything. It was... educational, but ultimately difficult to maintain and particularly unfriendly to keyboard users.
Your implementation seems to deal with the code-maintainability issue head-on, and now that IFRAME support is widespread and mousewheels are becomming more common... the approach clearly deserves some rethinking.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I myself am a keyboard user, and the more you use an application the more you notice how important it is to fully keyboard enable the application. In my article I deal with this issue, but it still needs some work though.
You are right about the maintainability. A solution must just work, no matter what. This is however not yet the case with my AnywherePlaceHolder implementation.
|
| Sign In·View Thread·PermaLink | 1.00/5 (1 vote) |
|
|
|
 |
|
|
 |
|
|
General News Question Answer Joke Rant Admin
|