A jQuery Alternative for ACT CalendarExtender

April 6, 2014

As I extend an ASP.Net application with MVC functionality, I also need to replace some of the components that the AjaxControlToolkit provides. Case in point, the CalendarExtender.

The CE is declared in markup like this:

<ajax:CalendarExtender id=”ce” runat=”server” TargetControlID=”edDate” Format=”dd.MM.yyyy”>

edDate being the ASP.Net TextBox control containing the selected date.

The simplest replacement is, of course, the Datepicker widget found in the jQuery UI library.

Integration the Datepicker is a bit more effort as compared to the other controls in this series, but not much.

First we need to reference the required .js and .css files of jQuery and jQuery UI:

<script type="text/javascript" src="@Url.Content("~/script/jquery-1.8.3.js")"></script>
<script type="text/javascript" src="@Url.Content("~/script/jquery-ui-1.9.2.custom.min.js")"></script>
<link href="@Url.Content("~/css/jquery-ui-1.9.2.custom.css")" rel="stylesheet" type="text/css" />

Depending on the options you used for the CalendarExtender, the initialization for the Datepicker may look like this:

$("#edDate").datepicker({
  changeMonth: true,
  changeYear: true,
  showButtonPanel: true
});
$("#edDate").datepicker("option", "dateFormat", "dd.mm.yy");
$("#edDate").datepicker("option", $.datepicker.regional["de"]);

For more options, see the sample page and press View Source, or browse the documentation.

Note that while the CE requires “yyyy” for 4-digit years, the Datepicker interprets “yy” as 4-digit years, and will insert the year twice if you use “yyyy”.

I’m not sure whether the localization files are part of the default jQuery UI downloads – if not, you can find the localized strings for Datepicker in the GitHub repository.

Lastly, the CE provides a “Today” button which sets the current date and closes the calendar, whereas Datepicker just navigates to the current date in the calendar control, but does not select it.

To change the Datepicker behavior, I use the code found in an answers on SO:

$.datepicker._gotoToday = function (id) {
    var target = jQuery(id);
    var inst = this._getInst(target[0]);
    if (this._get(inst, 'gotoCurrent') && inst.currentDay) {
        inst.selectedDay = inst.currentDay;
        inst.drawMonth = inst.selectedMonth = inst.currentMonth;
        inst.drawYear = inst.selectedYear = inst.currentYear;
    }
    else {
        var date = new Date();
        inst.selectedDay = date.getDate();
        inst.drawMonth = inst.selectedMonth = date.getMonth();
        inst.drawYear = inst.selectedYear = date.getFullYear();
        this._setDateDatepicker(target, date);
        this._selectDate(id, this._getDateDatepicker(target));
    }
    this._notifyChange(inst);
    this._adjustDate(target);
};

See also here for further discussion.

Finally, if the jQuery UI skins do not fit in the design of your application, open the developer tools of your browser (F12) and find out which jQuery UI CSS classes define the look of the Datepicker control. In my case, I added a bit of CSS to adjust the control:

.ui-datepicker table.ui-datepicker-calendar {
    font-size: 11px;    
}
.ui-datepicker table.ui-datepicker-calendar td
{
    width: 17px;
}
.ui-datepicker table.ui-datepicker-calendar td a 
{
    padding: 0px;
}
.ui-datepicker .ui-datepicker-title select {
    font-size: 0.9em;
    margin: 1px 0;
}
.ui-widget button {
    font-size: 0.8em;
}

Note that the overriding style definition selectors must be more specific than the original jQuery UI declarations.

Aaaand we are done! 😉

Advertisements

A jQuery Alternative for ACT HoverMenuExtender

April 1, 2014

As I extend an ASP.Net application with MVC functionality, I also need to replace some of the components that the AjaxControlToolkit provides. Case in point, the HoverMenuExtender.

The HME is declared in markup like this:

<ajax:HoverMenuExtender ID=”hme” runat=”server” TargetControlID=”tdTable” PopupControlID=”paTable” PopupPosition=”Bottom” OffsetX=”0″ OffsetY=”2″>
</ajax:HoverMenuExtender>

tdTable being, in my case, a TD table cell in a menu bar, and paTable the submenu that is meant to dropdown when hovering over the table cell.

As replacement, I found the Simple jQuery Dropdowns library on CSS-Tricks, and the sample looked similar to the solution I wanted to replace.

Adding the Simple Dropdowns was straight forward, as it consists only of a couple of Javascript lines and some CSS classes. Integration was easy and quickly done.

But then…

As I mentioned, the menu bar was defined as single-row HTML table, and the cells are fixed-width for all drop-down menus, except the one displaying a record identifier – this cell uses a table’s (built-in!) capability to expand so that the table extends over the defined width, no matter how many other fixed-width cells there are and how wide the browser window is.

So I added some display:table* styles like this

<ul class="dropdown" style="width: 99%; display:table">
    <li style="display:table-cell; ">
        <a href="#">Menu 1</a>
        <ul class="sub_menu">
            <li><a href="#">Command 1</a></li>
            <li><a href="#">Command 2</a></li>
        </ul>
    </li>
    <li style="display:table-cell; width: auto" >
        <a href="#">ID</a>
        <ul class="sub_menu">
            <li><a href="#">Command 1</a></li>
            <li><a href="#">Command 2</a></li>
        </ul>
    </li>
      <li style="display:table-cell; ">
        <a href="#">Menu 2</a>
        <ul class="sub_menu">
            <li><a href="#">Command 1</a></li>
            <li><a href="#">Command 2</a></li>
        </ul>
    </li>
    <li style="display:table-cell">&nbsp;</li>
</ul>

Chrome and IE9 displayed the menu as expected, but Firefox insisted on displaying the dropdown left-aligned below to <ul> table, no matter how I changed the individual styles and classes.

Fortunately I found this answer on SO that illustrated a solution that works for Firefox (and the other browsers!): add a <div style=”position:relative”> inside the <td>.

So now my menu bar looks like this:

<ul class="dropdown" style="width: 99%; display:table">
    <li style="display:table-cell; " >
        <div style="position: relative">
            <a href="#">Menu 1</a>
            <ul class="sub_menu">
                <li><a href="#">Command 1</a></li>
                <li><a href="#">Command 2</a></li>
            </ul>
        </div>
    </li>
    <li style="display:table-cell; width: auto" >
        <div style="position: relative">
            <a href="#">ID</a>
            <ul class="sub_menu">
                <li><a href="#">Command 1</a></li>
                <li><a href="#">Command 2</a></li>
            </ul>
        </div>
    </li>
    <li style="display:table-cell; " >
        <div style="position: relative">
            <a href="#">Menu 2</a>
            <ul class="sub_menu">
                <li><a href="#">Command 1</a></li>
                <li><a href="#">Command 2</a></li>
            </ul>
        </div>
    </li>
    <li style="display:table-cell" class="command">&nbsp;</li>
</ul>

A jQuery Alternative for ACT CollapsiblePanelExtender

March 30, 2014

As I extend an ASP.Net application with MVC functionality, I also need to replace some of the components that the AjaxControlToolkit provides. Case in point, the CollapsiblePanelExtender.

The CPE is declared in markup like this:

<ajax:CollapsiblePanelExtender ID=”cpe” runat=”server” CollapseControlID=”paHeader” ExpandControlID=”paHeader” AutoCollapse=”false” AutoExpand=”false” Collapsed=”true” TargetControlID=”paDetail” ExpandedImage=”~/images/collapse_blue.jpg” CollapsedImage=”~/images/expand_blue.jpg” SuppressPostBack=”true” ImageControlID=”imgHeader”>
</ajax:CollapsiblePanelExtender>

paHeader and paDetail are both asp:Panels – clicking on the header toggles the display of the detail panel. The image control is toggled accordingly.

My goal was to create a JavaScript function which uses parameters similar to the markup, omitting those that do not matter in my application.

In MVC, of course, the equivalent of an asp:Panel is a simple HTML <div>.

I started out with a function declaration like this (please forgive non-standard casing):

function CollapsiblePanelExtender(ID, CollapseControlID,
    TargetControlID, ImageControlID) { }

but then noticed that I needed to distinguish between initial states expanded and collapsed, as well as an additional callback after expanding/collapsing.

var CollapsiblePanelExtenders = {};
function CollapsiblePanelExtender(ID, CollapseControlID, 
    TargetControlID, ImageControlID, expanded, fn) {
  var ExpandedImage = '@Url.Content("~/images/collapse_blue.jpg")';
  var CollapsedImage = '@Url.Content("~/images/expand_blue.jpg")';

CollapsiblePanelExtenders is our array of all CPEs on the page. Parameters expanded and fn are optional.

First, we register the CPE in the array, and initialize the image and the detail’s overview setting:

  CollapsiblePanelExtenders[ID] = { collapsed: !(expanded || false) };
  $("#" + ImageControlID).attr("src", (expanded || false) ? ExpandedImage : CollapsedImage);
  $("#" + TargetControlID).attr("overflow", "hidden");

Finally, we add a click handler on the header panel:

  $("#" + CollapseControlID).click(function (pa) {
    if (CollapsiblePanelExtenders[ID].collapsed) {
      $("#" + TargetControlID).css("height", "");
      $("#" + ImageControlID).attr("src", ExpandedImage);
      CollapsiblePanelExtenders[ID].collapsed = false;
    } else {
      $("#" + TargetControlID).css("height", "0px");
      $("#" + ImageControlID).attr("src", CollapsedImage);
      CollapsiblePanelExtenders[ID].collapsed = true;
    }
    if (fn) {
      fn(CollapsiblePanelExtenders[ID].collapsed);
    }
  });
};

The click handler adjusts the detail’s height and sets the expand/collapse image. If a callback function is defined, the function is executed with the collapsed status as parameter.

I was surprised that it only takes a couple of JavaScript lines to implement an ACT control. Note, however, that the presented solution does not invoke any animations – have a look at jQuery Effects, if you need them…


Workaround for “Could not load control AjaxControlToolkit”

March 6, 2014

I wanted to update an ASP.Net application I maintain to the latest version of Ajax Control Toolkit, only to get an exception raised by the toolkit

Could not load control AjaxControlToolkit.[ControlName]. The script reference(s) of this control was not loaded correctly. If AjaxControlToolkit.config is used, probably this control is not registered properly.

See also posts on SO such as here and here.

The internets suggest not to use the latest version, as not versions are created equal, and point to the January 2013 edition as reliable.


JavaScript Love Hate

November 8, 2012

I have to admit: in all those years of web development, I was quite reluctant to write any JavaScript code.

In the early days of ASP.Net, JS was either not required or not (easily) possible to implement the functionality we know today. This changed with the arrival of the Ajax Control Toolkit that simply injected pre-built pieces of JavaScript code into a page using server-side controls, or by manually typing a couple lines of code.

The main reason for me, though, was that you cannot compile JavaScript.

You have no idea whether your piece of code actually works – you need to run it to see whether it’s ok. Combine this with the typical behavior of IE on a dev machine, popping up a message box whenever a JS error occurs, or different parsers with different sensitivities to well- and mal-formedness of code in different browsers (missing a semicolon, adding a comma too much), and it’s easy to see that for people used to compiling code there was a lot to dislike.

Also, until quite recently, there were simply too many JavaScript libraries available to make a sensible choice between jQuery, prototype, ext-js, and several others. Fortunately (or unfortunately, depending on your preferences), this has changed, as illustrated in this chart.

But the times, they are a-changing. Today’s web applications are inconceivable without proper client-side functionality and asynchronous server communication, all implemented in Javascript. As Scott Hanselman cited more than a year ago, “JavaScript is Assembly Language for the Web“.

Why do I write all this? Well, I finally brought myself to write my first JavaScript application. The ASP.Net MVC project is essentially just the container for JS code, and also implements the Ajax backend using Controllers that return JsonResults.

While programming in JavaScript “for real”, I got quite fascinated about the things that are possible in JavaScript, and the project soon reached 2000 lines of code. However, as it goes with first projects, the code soon became unmanageable and needed a cleanup. The code implemented various dialogs with partially dynamic fields and validation, a hierarchical database browser and a web directory browser, but it was a mess.

The first restructuring was to avoid global variables as much as possible, encapsulate the Ajax calls in a static object, and each dialog in one function, with the event handlers implemented as local function variables.

But all this code with its anonymous parameters and asynchronous callbacks passing untyped (in JavaScript, but typed in C#) parameters to the success() functions is still difficult to understand (“write-only”).

The latest innovation that gave me a prospect of typed, compilable, and modular programming is called TypeScript, an extension to the JavaScript language that compiles to JavaScript (CodePlex). It installs as new project type and item type in Visual Studio, and its support for typical IDE functionality such as Intellisense, Go to Definition, Find References, etc. is quite good, judging from first glance.

Finally, a couple of links to introductory news articles that drew my interest for TypeScript:

Give it a try!


Lessons Learned Developing DNN Modules

May 2, 2012

Jumping into DNN development, here’s a couple of things I learned from developing my first modules.

Module Path

There are a couple of variables a module can derive its file system location (and thus relative URL paths) from:

ControlPath /dnn/DesktopModules/MyModule/
ModulePath /dnn/DesktopModules/MyModule/
Request.ApplicationPath /dnn
Request.CurrentExecutionFilePath /dnn/Default.aspx
ModuleConfiguration.ControlSrc DesktopModules/MyModule/View.ascx

If you need to reference files in the file system, or URLs relative to the module’s installation path, this statement

var IncludePath = ModuleConfiguration.ControlSrc
  .Replace("/View.ascx", "/");

gives you the module’s base directory.

Packaging

You can freely edit the module’s .dnn file to edit the components of the installation.

For example, if the module has no Edit or Settings dialog, remove Edit.ascx* and/or Settings.ascx* from the .dnn file under

component/
  desktopModule/
    moduleDefinitions/
      moduleDefinition/
        moduleControls

as you remove or exclude them from the DNN project (.csproj file).

If you do not have an Edit form, disable the registration of the Edit form in the ModuleActions getter of View.ascx.cs.

For layout definition and CSS classes in your Edit.ascx, see the HTML\EditHtml.ascx that comes with DNN.

View/Edit Mode

If your View.ascx should behave differently depending on whether it is displaying in View mode or Edit mode, use the following markup to distinguish the two modes:

<%  if (!DotNetNuke.Common.Globals.IsEditMode())  {   %>
      <!-- markup for edit mode -->
<%  } else { %>
      <!-- markup for view mode -->
<%  } %>

Closing Forms

To close a dialog, simply redirect to the current page:

protected void cmdUpdate_Click(object sender, EventArgs e)
{
  try
  {
    UpdateSettings();
    Response.Redirect(Globals.NavigateURL(), true);
  }
  catch (Exception exc)
  {
    Exceptions.ProcessModuleLoadException(this, exc);
  }
}

JavaScript

DNN uses a combination of JavaScript libraries

Embedding DNN pages in iframes

A typical DNN page contains menu, header, content panes, and a footer. The default skin of DNN6 is called DarkKnight and provides a page skin called „Host: DarkKnight – popUpSkin“ with just a single content pane and no headers, footers, borders, etc., and is thus perfect for embedding DNN pages inside an <iframe> either in the same DNN installation or from outside.

Display Time Range

You can set the period to display a page using the Start Date and End Date settings in the Advanced Settings section of the Module Settings.
Start and End Dates are only settable on a day level, there is no built-in way to display modules based on time of day.

Automatic Refresh

Automatic Refresh is supported on page level, but not on module level.

A little warning: the refresh interval is also active in Edit mode, so if you set the interval too short, you may not be able to set it to a longer interval directly in the page. You need to navigate to Host/Page Management and reset the refresh interval.


Fixing the AjaxControlToolkit ColorPicker

March 2, 2012

The Ajax Control Toolkit ColorPicker adds a dynamic color palette to an asp:TextBox and sets the textbox’s value to the hex value of the selected color.

There are two problems with the control (.Net 3.5 build 51116 of Nov 2011):

  • First, the OnClientColorSelectionChanged JS event is not fired if the user edits the textbox manually.

To fix this problem, you need to add two properties to the asp:TextBox declaration

onchange="javascript: colorTextChanged(this);" 
onkeyup="javascript: colorTextChanged(this);"

and implement a JavaScript function

function colorTextChanged(sender) {
    // sender is the textbox, sender.value is the edited color
}
  • Second, the color palette does not show if you set the SelectedColor property from code

The bug report I found on this topic is quite dated (Sep 2009), so nobody seems to care. The solution provided certainly worked for older versions of ACT, but needs to be adjusted (AjaxControlToolkit vs. Sys.Extended.UI) to look like this:

Sys.Application.add_init(function() {
    // Store the color validation Regex in a "static" object off of   
    // Sys.Extended.UI.ColorPickerBehavior.  If this _colorRegex object hasn't been   
    // created yet, initialize it for the first time.   
    if (!Sys.Extended.UI.ColorPickerBehavior._colorRegex) {
        Sys.Extended.UI.ColorPickerBehavior._colorRegex = 
            new RegExp('^[A-Fa-f0-9]{6}$');
    }
});