# Friday, 08 January 2010

In a couple of earlier posts I used jQuery to add a character counter to the ASP.Net AJAX HTML Editor.  I was contacted by a reader stating that when they modified the editor’s toolbar items the character counter stopped working.  After a small bit of troubleshooting I noticed the toolbar items drive the number of IFrames created.

The fix is small but requires some sleuthing that we can do with firebug to identify the correct IFrame we should be targeting.  With firebug console and our browser open to the url for our application we can type in $('#Editor1').contents().find('iframe').eq(0).  This grabs the first IFrame.  Be sure to change "Editor1" to the name of your control.

FirebugShot1

If we hover over the item in the brackets it will highlight the IFrame selected.  If the text area for the control isn’t highlighted then change the index value and try again until the text area is highlighted.

FirebugShot2

To be sure we have the right IFrame, click on the item in the brackets and it shows the HTML.  Look for the text you typed in to be sure.

FirebugShot3

Now that we’ve identified the correct index for the IFrame based on a customized editor we’ll need to change the GetEditor Function with this correct value.

function GetEditor() {
    return editor = $(Editor1).contents().find('iframe').eq(0);
}

You should be ready to go now! But wait! There’s more…

I was dissatisfied with having to hunt for the correct index value and thankfully there is a better way.  I noticed while trying this out on several scenarios that the IFrame we want is given the same ID.  At first, I didn’t think this would work since the ID is assigned by ASP.Net but it seems in this case we can trust it (could be my famous last words).  The IFrame is always given an ID of ControlName_ctl02_ctl00 or in this case Editor1_ctl02_ctl00.

Armed with this we can change the code to:

function GetEditor() {
    return editor = $(Editor1 + '_ctl02_ctl00')
}

That's a little better. We don't have to hunt for the correct index value for the IFrame used for the text.

Please download the code and give it a spin! AjaxHtmlEditorV3.zip (1.01 MB)

Happy Coding!

Read the earlier posts.

Posted 01.08.2010  #    Comments [2]  | 
# Tuesday, 22 December 2009

I made a small upgrade to an application that had a text box for date input to use the masked edit and calendar extender found in the asp.net control toolkit.  The two extenders are great together for allowing a user to select a date off the calendar or type it in without having to worry about the formatting.  The masked edit even has a nice auto complete feature that allows you to type the month and day without the year and the extender will add the year for you (if you want the current year of course).

Everything works well except for one small bug or feature when using these two extenders together.  If you set up your mask to be ''99/99/9999" but leave the calendar’s format to it’s default then on post backs with dates having one digit month or days the calendar control will reappear. 

In order to correct this, you have to set the format for the calendar to match your mask.  In the example above you would need to set the format to "MM/dd/yyyy" for the calendar extender.

Hopefully, someone will find this post before spending too long trying to figure out what is happening (like I did).

:-)

Posted 12.22.2009  #    Comments [0]  | 
# Saturday, 12 December 2009

Testing the routes in .Net MVC can be a bit tedious without some help.  MVCContrib’s TestHelper library eases the load and makes testing routes a breeze.

To demonstrate, I created an application to manage recipes with their ingredients and directions. Below is how I wanted the routing to work.

What I want the URL to look like What it will do
/recipe List all recipes
/recipe/blueberrypie Display blueberry pie recipe
/recipe/edit/blueberrypie Edit blueberry pie recipe
/recipe/blueberrypie/ingredients Show the ingredients for blueberry pie
/recipe/blueberrypie/directions Show directions for blueberry pie
/recipe/blueberrypie/ingredients/delete/1234 Delete ingredient with ID 1234 for blueberry pie & go back to the list of ingredients

I edited the RegisterRoutes method in the Global.asax.cs file to match the table:

// recipe/BlueberryPie/ingredients/edit/1234
routes.MapRoute(
    "Ingredients",
    "recipe/{nameKey}/ingredients/{action}/{id}",
    new { controller = "Ingredients", action = "Index" },
    new { id = @"\d+" }
    );

// recipe/BlueberryPie/ingredients/
routes.MapRoute(
    "IngredientsForRecipe",
    "recipe/{nameKey}/ingredients/{action}",
    new { controller = "Ingredients", action = "List" }
    );

// recipe/BlueberryPie/directions/edit/1234
routes.MapRoute(
    "Directions",
    "recipe/{nameKey}/directions/{action}/{id}",
    new { controller = "directions", action = "Index" },
    new { id = @"\d+" }
    );

// recipe/BlueberryPie/directions/
routes.MapRoute(
    "DirectionsForRecipe",
    "recipe/{nameKey}/directions/{action}",
    new { controller = "directions", action = "List" }
    );

// recipe/edit/BlueberryPie
routes.MapRoute(
    null,
    "recipe/{action}/{nameKey}",
    new { controller = "Recipe", action = "List", nameKey = "" },
    new { action = "create|edit|index|list" }
    );

// recipe/BlueberryPie
routes.MapRoute(
    "recipe",
    "recipe/{nameKey}",
     new { controller = "Recipe", action = "Index" }
    );

Now for the magic with MVCContrib’s TestHelper library and the extension ShouldMapTo:

[TestFixture]
public class RouteTests
{
    [TestFixtureSetUp]
    public void SetUp()
    {
        RouteTable.Routes.Clear();
        MvcApplication.RegisterRoutes(RouteTable.Routes); 
    }

    [Test]
    public void RootMatchesHome()
    {
        "~/".ShouldMapTo(x => x.Index());
    }

    [Test]
    public void RecipeRootMatchesList()
    {
        "~/recipe".ShouldMapTo(x => x.List());
    }

    [Test]
    public void RecipeNameShouldDisplayIndex()
    {
        "~/recipe/BlueberryPie".ShouldMapTo(x => x.Index("BlueberryPie")); 
    }

    [Test]
    public void CreateRecipeShouldDisplayCreateAction()
    {
        "~/recipe/create/".ShouldMapTo(x => x.Create());
    }

    [Test]
    public void EditRecipeShouldDisplayEditAction()
    {
        "~/recipe/edit/BlueberryPie".ShouldMapTo(x => x.Edit("BlueberryPie"));
    }
    [Test]
    public void DeleteRecipeShouldDisplayDeleteAction()
    {
        "~/recipe/delete/1".ShouldMapTo(x => x.Delete(1));
    }
    [Test]
    public void IngredientsForRecipeShouldDisplayIngredientsListAction()
    {
        "~/recipe/BlueberryPie/ingredients".ShouldMapTo(i => i.List("BlueberryPie")); 
    }
    [Test]
    public void DirectionsForRecipeShouldDisplayIngredientsListAction()
    {
        "~/recipe/BlueberryPie/directions".ShouldMapTo(i => i.List("BlueberryPie"));
    }
    [Test]
    public void DeleteIngredientShouldDisplayDeleteAction()
    {
        "~/recipe/BlueberryPie/ingredients/delete/1".ShouldMapTo(i => i.Delete(1));
    }
    [Test]
    public void DeleteDirectionShouldDisplayDeleteAction()
    {
        "~/recipe/BlueberryPie/directions/delete/1".ShouldMapTo(i => i.Delete(1));
    }
}

No need to mock/fake any context, setup route data, etc.  The code is short and sweet, easy to read, understand and matches the URL that would be in the browser making it very intuitive.

Please download the code.  The sample is using NUnit 2.5.

MVCContribTestRoutes.zip (603.49 KB)

Happy coding!

Posted 12.12.2009  #    Comments [0]  |