Welcome to Atalasoft Community Sign in | Join | Help

Overriding SharePoint WebControls: DateTimeField

Abstract

Rendering Microsoft’s SharePoint WebControls can be a great way to write customized field editor.  However, when their controls fall short or malfunction how can you avoid writing your own to do the job?  This article shows some powerful tools you can use to find the issue and hopefully fix it with minimal code writing.

 

Required Tools

  • .NET Reflector - If you don’t already have this one in your developer toolbox, then get it.
  • FireBug – An excellent plug-in for Firefox web browser, this tool will help you isolate elements, step through js, see network traffic, and more!
  • User Agent Switcher – Another Firefox plug-in, when something won’t go in Firefox (often for SharePoint controls) you need to fool it
  • A sense of humor – Lots of times you find exactly what you want and there’s nothing you can do about it (private, protected, internal, oh my!)

 

Introduction

I’m working on the Property Editor as part of our product VizitSP viewer.  The property editor is a panel that shows the metadata about a document.  It displays all the fields for that document’s content type, and allows you to edit them.  We do this through some simple code that gets the document, gets its content type, and iterates through that content type’s fields and adds the field’s RenderingControl to the page.  An issue comes up in a few spots, one which I’ll tell you about in this article.  Really, the information I want to convey in this article is how much knowledge you can gain with a little poking around.

 

The Problem

Microsoft’s DateTimeField already takes care of a lot of heavy lifting.  Our problem arose with the frame when it is rendered and its onload event tries to do some positioning.  It looks like the calendar doesn’t like being so close to the right edge of the screen and moves over when it is.  Here’s how it looks in our Property Editor:DTF

Here you can see the dateTime field, its Calendar icon and the rendered calendar that’s been pushed to the left.  Ack!  It’s under the scroll bar for the neighboring frame.  Hmm…

 

DTF2 

This is what the dateTime field’s DatePicker should look like.

 

Let’s start by figuring out how this tool is rendered.  Fire up Firefox and go to your page with the DateTime field and…AWE!  It’s not there?  The DateTime field is an example of a control that Microsoft chooses not to render on ‘non type-1 browsers.’  What does that mean?  No IE?  No workie!  Let’s fool ‘em with the user agent switcher and get that DateTime icon to come up…Once you do, open up Firebug and let’s figure out what this thing is doing.

fb Click the Inspect button on Firebug’s bar, and mouse-over the calendar until it has a blue border, then left-click the calendar to lock the inspector on that element.  The calendar icon is inside an anchor tag <a> whose onclick event calls clickDatePicker() function.  We can find the clickDatePicker() function by clicking Firebug’s Script tab, and then just above that there’s a button with the name of the page you’re on.  Clicking that will bring up a menu of all the javascript-containing elements (including pages with embedded js).  Going through the list one will definitely stick out: datepicker.js.  Click it and the Firebug window will now contain the javascript complete with line numbers (that turn green when they’re hit) where you can put break points, a Watch/Stack/Breakpoints window to the right, this is all very familiar stuff!

Scroll through the javascript until you find the clickDatePicker() function.  A little, light reading and you come to discover the DatePicker is an iframe, and at around line 346, its source attribute is changed to iframesrc + datestr.  iframesrc is the 5th argument, moving again up the stack to clickDatePicker() that variable is src, which comes from its second argument.  Moving further up the stack is the calendar icon’s anchor’s onclick event that passes as the second argument “/_layouts/iframe.aspx? …”.  We have all the pieces to this puzzle, first is datepicker.js (which we know) and second is iframe.aspx.

iframe.aspx is a a simple aspx page that includes the datepicker.js and whose onload is

   <BODY onload="PositionFrame('DatePickerDiv');" onkeydown="OnKeyDown(this);" style="margin:0;">

Find PositionFrame() in datpicker.js and you’ll see, around line 190:

   1: var cxBody=document.parentWindow.parent.document.documentElement.offsetWidth;
   2: if (ifrm.style.pixelRight+elt.offsetWidth > cxBody)
   3: {
   4:     ifrm.style.pixelRight=ifrm.style.pixelLeft;
   5:     ifrm.style.pixelLeft -=elt.offsetWidth;
   6: }

Which is a bit strange; this code runs if the calendar’s right edge plus the calendar’s width are off the right edge of its parent’s right edge.  If you’re window is sized down enough so that its right edge is within one calendar-width of the calendar’s right edge, this will move the calendar over one calendar-width to the left!  Go figure.  In RTL (Right-to-Left) layout, this might make sense, but in our Property Editor, that code causes the issue shown above.

If this is just your SharePoint installation, then you can just go ahead and comment lines 190~195 of datepicker.js and refresh your browser (after a cache clean) and you should be good to go.  However, we deploy VizitSP to customers and can’t just go overwriting files in their SharePoint installation.  Not to mention it’s unsupported!

Well, let’s go see how the DateTimeControl is rendered to get a sense of how this is strung together.  We already know the iframe.aspx and datepicker.js files play a role, we know it’s DateTimeField, so open up the Microsoft.SharePoint.dll in Reflector and go into the Microsoft.SharePoint.WebControls.DateTimeField.  If you just start poking around, you’ll find what you’re looking for, and probably other things, too!  Let’s just cut to the chase…

In the instance fields you’ll find m_urlDatePickerFrame and m_urlDatePickerJavaScript, curious.  Right-click on one those fields to select Analyze

 

murldatepickeralt

 

The Analyzer window will open (if it isn’t already) and will be populated with a new entry: Microsoft.SharePoint.WebControls.DateTimeControl.m_urlDatePickerJavaScript : String

Expand that, and expand Used By to tell you what code uses this field.  There are 4, the ctor (constructor), the getter and setter (these will be interesting later), and, ahh: OnPreRender().  Right-click the OnPreRender() method in the analyzer window, select Go To Member and start reading.  Eventually, you’ll hit this (ignore the line breaks):

this.Page.ClientScript.RegisterClientScriptBlock(typeof(DateTimeControl), "datepicker_js", "<SCRIPT language='javascript' src='"
SPHttpUtility.HtmlUrlAttributeEncode(this.m_urlDatePickerJavaScript)
"'></SCRIPT>");
 
Ahha!  We might be able to override the js and iframe!  The question is how to set this field so we can control it.  If you Go back to the Analyzer window and go to the set_DatePickerJavaScriptUrl(String) method you see it’s public, and it does nothing special.  Expanding that method in the analyzer and expand the Used By…nothin’.  Bummer!  Hey, that doesn’t matter that much, it’s public and we can set it before the control is rendered.  The next biggest hurdle to get over is the fact that DateTimeField has its DateTimeControl as private.  I want to mess with it as little as possible, so a little poking around the m_dateTimeControl field in DateTimeField and you’ll end up in CreateChildControls().  There you’ll find:
 
this.m_dateTimeControl = (DateTimeControl) this.TemplateContainer.FindControl("DateTimeField");

Well, heck, we can do that!  Let’s get to it.

 

The Solution

Open up your solution and add a new class.  In the class begin by extending the DateTimeField:

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Text;
   4: using Microsoft.SharePoint.WebControls;
   5: using Microsoft.SharePoint;
   6: using System.Security.Permissions;
   7:  
   8: namespace Vizit.SharePoint
   9: {
  10:     public class VSPDateTimeField : DateTimeField
  11:     {
  12:         private DateTimeControl m_dateTimeControl;
  13:  
  14:         protected override void CreateChildControls()
  15:         {
  16:             if (base.IsFieldValueCached)
  17:             {
  18:                 base.CreateChildControls();
  19:                 return;
  20:             }
  21:  
  22:             SPFieldDateTime field = (SPFieldDateTime)base.Field;
  23:             if (field == null) return;
  24:             base.CreateChildControls();
  25:             if (base.ControlMode == SPControlMode.Display) return;

Boilerplate, we’ve inherited DateTimeField and followed its conventions with our own m_dateTimeControl, and overriding the CreateChildControls() method.  Next, we get a reference to the DateTimeControl:

            this.m_dateTimeControl = (DateTimeControl)this.TemplateContainer.FindControl("DateTimeField");
            if (this.m_dateTimeControl == null)
                throw new ArgumentException(SPResource.GetString("InvalidControlTemplate", new object[] { "DateTimeField" }));

Up to now, we’ve just been reiterating the base type’s code.  But now we get to override the JavaScript path, and iframe path:

if (base.ItemContext.Web != null)
{
    this.m_dateTimeControl.DatePickerFrameUrl = base.ItemContext.Web.ServerRelativeUrl + "/_layouts/myDatePicker.aspx";
    this.m_dateTimeControl.DatePickerJavaScriptUrl = base.ItemContext.Web.ServerRelativeUrl + "/_layouts/myDatePicker.js";
}
 
The last step to this is making the iframe use your js in its onload event, simple.  In the aspx page, change the script line to point to your js instead:
 
<script src="./myDatePicker.js"></script>

Sweet!  Now you can copy those to files to myDatePicker.aspx from iframe.aspx and myDatePicker.js from datepicker.js in the Layouts folder and make any changes you want.  When you need to render a DateTimeField you can render your control instead where your aspx and js will be used, giving you a good amount of freedom with Microsoft’s code. 

 
 

Conclusion

This is just one example of how you can reflect through SharePoint dlls, read through aspx pages, and javascript code to arrive at a very quick answer to what could be a nightmare of a problem (who wants to rewrite an entire date picker when there’s already a good one available, DRY anyone?).  Taking what you know here, you could extend this to render the DateTimeField in Firefox or other non type-1 browsers, but it’s a lot more work.  Hopefully I’ve given a good outline of the massive power .NET Reflector lends to a SharePoint developer, along with the tools of any web developer, Firebug and the User Agent Switcher.

Published Friday, May 01, 2009 11:29 AM by dterrell

Comments

No Comments
Anonymous comments are disabled