Steve Wellens

Programming in the .Net environment

Sponsors

Links

Log4net: Log to a JavaScript Console

While lurking and skulking in the shadows of various technical .Net sites, I've noticed many developers discussing log4net in their blogs and posts; log4net is an extremely popular tool for logging .Net Applications. So, I decided to try it out. After the initial complexities of getting it up and running, I was suitably impressed. Could it be…logging is fun? Well, I don't know if I'd go that far…at least I'd never admit it.

One of the great features of log4net is how easy it is to route the logs to multiple outputs. Here is an incomplete list of outputs…or 'Appenders' in the log4net vernacular:

AdoNetAppender Logs to databases
ConsoleAppender Logs to a console window
EventLogAppender Logs to Windows event log
FileAppender Logs to a file
RollingFileAppender Logs to multiple files
SmtpAppender Logs to an email address
TraceAppender Logs to the .Net trace system
AspNetTraceAppender Logs to the Asp.Net page trace
MemoryAppender Logs to a memory buffer
Etc., etc., etc.

Wow.

I've been doing a lot of work with jQuery/JavaScript and it dawned on me that seeing server side logging strings in a JavaScript Console could be useful.

So I wrote a log4net JavaScript Console Appender. Strings logged at the server will show up in the browser's console window. Note: For IE, you need to have the "Developer Tools" window active.

I'm not going to describe how to setup log4net in an Asp.Net web site; there are many step-by-step tutorials around. But I'll give you some hints:

  • Each step must be followed or it will not work (duh).
  • Putting the log4net settings in a separate configuration file is more complicated than having the settings in web.config: Start with the settings in web.config.
  • The name in this line of code: LogManager.GetLogger("MyLogger");
    ...refers to this section in the configuration file: <logger name="MyLogger">

I built the Appender and a test Asp.Net site in .Net Framework 4.0.  Here's the jsConsoleAppender.cs file:

using System;
using System.Collections.Generic;
using System.Text;
using log4net;
using log4net.Core;
using log4net.Appender;
using log4net.Layout;
using System.Web;
using System.Web.UI;
 
namespace log4net.Appender
{
    // log4net JSConsoleAppender
    // Writes log strings to client's javascript console if available
 
    public class JSConsoleAppender : AppenderSkeleton
    {
        // each JavaScript emitted requires a unique id, this counter provides it
        private int m_IDCounter = 0;
 
        // what to do if no HttpContext is found
        private bool m_ExceptionOnNoHttpContext = true;
        public bool ExceptionOnNoHttpContext
        {
            get { return m_ExceptionOnNoHttpContext; }
            set { m_ExceptionOnNoHttpContext = value; }
        }
 
        // The meat of the Appender
        override protected void Append(LoggingEvent loggingEvent)
        {
            // optional test for HttpContext, set in config file.
            // default is true
            if (ExceptionOnNoHttpContext == true)
            {
                if (HttpContext.Current == null)
                {
                    ErrorHandler.Error("JSConsoleAppender: No HttpContext to write javascript to.");
                    return;
                }
            }
 
            // newlines mess up JavaScript...check for them in the pattern
            PatternLayout Layout = this.Layout as PatternLayout;
 
            if (Layout.ConversionPattern.Contains("%newline"))
            {
                ErrorHandler.Error("JSConsoleAppender: Pattern may not contain %newline.");
                return;
            }
 
            // format the Log string
            String LogStr = this.RenderLoggingEvent(loggingEvent);
 
            // single quotes in the log message will mess up our JavaScript
            LogStr = LogStr.Replace("'", "\\'");
 
            // Check if console exists before writing to it
            String OutputScript = String.Format("if (window.console) console.log('{0}');", LogStr);
 
            // This sends the script to the bottom of the page
            Page page = HttpContext.Current.CurrentHandler as Page;
            page.ClientScript.RegisterStartupScript(page.GetType(), m_IDCounter++.ToString(), OutputScript, true);
        }
 
        // There is no default layout
        override protected bool RequiresLayout
        {
            get { return true; }
        }
    }
}

From the Asp.Net test application, here's the web.config file. In the pattern for the JSConsoleAppender, I added the word SERVER: to differentiate the lines from client logging. Note there are two other Appenders in the log…just for fun!

<?xml version="1.0"?>
<configuration>
 
  <!--BEGIN log4net configuration-->
  <configSections >
    <section name="log4net"
             type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
  </configSections>
  <log4net>
 
    <appender name="LogFileAppender"
              type="log4net.Appender.FileAppender">
      <param  name="File" value="C:\Log4Net.log"/>
      <layout type="log4net.Layout.PatternLayout">
        <param name="ConversionPattern"  value="%d %-5p %c %m%n"/>
      </layout>
    </appender>
 
    <appender name="TraceAppender"
              type="log4net.Appender.TraceAppender">
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date  %-5level [%property{NDC}] - %message%newline" />
      </layout>
    </appender>
 
    <appender name="JSConsoleAppender"
              type="log4net.Appender.JSConsoleAppender">
      <layout type="log4net.Layout.PatternLayout">
        <!--Note JSConsoleAppender cannot have %newline-->
        <conversionPattern value="SERVER: %date %-5level %logger:  %message  SRC: %location" />
      </layout>
    </appender>
 
    <logger name="MyLogger">
      <level value="ALL" />
      <appender-ref ref="LogFileAppender"  />
      <appender-ref ref="TraceAppender"  />
      <appender-ref ref="JSConsoleAppender"  />
    </logger>
  </log4net>
  <!--END log4net configuration-->
 
  <system.web>
    <compilation debug="true"
                 targetFramework="4.0"/>
  </system.web>
</configuration>

Here's the default.aspx file from the test program, I added a bit of JavaScript and jQuery:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title>Log4Net Test</title>
    <script src="Scripts/jquery-1.7.js" type="text/javascript"></script>
    <script type="text/javascript">
 
        $(document).ready(DocReady);
 
        function DocReady()
        {
            if (window.console) 
                console.log("CLIENT: Doc Ready!");
        }
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    <h5>Log4Net Test</h5>
        <asp:Button ID="ButtonLog" runat="server" Text="Do Some Logging!" 
            onclick="ButtonLog_Click" />   
    </div>
    </form>
</body>
</html>

Here's the default.cs file from the test program with some logging strings:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using log4net;
 
public partial class _Default : System.Web.UI.Page
{
    private static readonly ILog log = LogManager.GetLogger("MyLogger");
 
    protected void Page_Load(object sender, EventArgs e)
    {
        log.Info("Page_Load!");
    }
 
    protected void ButtonLog_Click(object sender, EventArgs e)
    {
        log.Info("Soft kitty, warm kitty");
        log.Warn("Little ball of fur");
        log.Error("Happy kitty, sleepy kitty");
        log.Fatal("Purr, purr, purr.");
    }
}

And finally, here are the three output logs:

From the JSConsoleAppender (IE Developer Tools), it includes a client log call with the server log calls:

From the TraceAppender, the Visual Studio Output window:

 

From the LogFileAppender (using Notepad):

You can download the solution/projects/code  here.

I hope someone finds this useful.

Steve Wellens

Goodby jQuery Templates, Hello JsRender

A funny thing happened on my way to the jQuery website, I blinked and a feature was dropped: jQuery Templates have been discontinued. The new pretender to the throne is JsRender.

jQuery Templates looked pretty useful when they first came out. Several articles were written about them but I stayed away because being on the bleeding edge of technology is not a productive place to be. I wanted to wait until it stabilized…in retrospect, it was a serendipitous decision.

This time however, I threw all caution to the wind and took a close look at JSRender. Why? Maybe I'm having a midlife crisis; I'll go motorcycle shopping tomorrow.

Caveat, here is a message from the site: Warning: JsRender is not yet Beta, and there may be frequent changes to APIs and features in the coming period.

Fair enough, we've been warned.

The first thing we need is some data to render. Below is some JSON formatted data. Typically this will come from an asynchronous call to a web service. For simplicity, I hard coded a variable:

    var Golfers = [
        { ID: "1", "Name": "Bobby Jones", "Birthday": "1902-03-17" },
        { ID: "2", "Name": "Sam Snead", "Birthday": "1912-05-27" },
        { ID: "3", "Name": "Tiger Woods", "Birthday": "1975-12-30" }
        ];

We also need some templates, I created two. Note: The script blocks have the id property set. They are needed so JsRender can locate them.

    <script id="GolferTemplate1" type="text/html">
        {{=ID}}: <b>{{=Name}}</b> <i>{{=Birthday}}</i> <br />
    </script>
 
    <script id="GolferTemplate2" type="text/html">
        <tr>
            <td>{{=ID}}</td> 
            <td><b>{{=Name}}</b></td> 
            <td><i>{{=Birthday}}</i> </td>
        </tr>
    </script>

Including the correct JavaScript files is trivial:

    <script src="Scripts/jquery-1.7.js" type="text/javascript"></script>
    <script src="Scripts/jsrender.js" type="text/javascript"></script>

Of course we need some place to render the output:

    <div id="GolferDiv"></div><br />
    <table id="GolferTable"></table>

The code is also trivial:

    function Test()
    {
        $("#GolferDiv").html($("#GolferTemplate1").render(Golfers));
        $("#GolferTable").html($("#GolferTemplate2").render(Golfers));
 
        // you can inspect the rendered html if there are poblems.
        // var html = $("#GolferTemplate2").render(Golfers);
    }

And here's what it looks like with some random CSS formatting that I had laying around.

 

 Not bad, I hope JsRender lasts longer than jQuery Templates.

One final warning, a lot of jQuery code is ugly, butt-ugly. If you do look inside the jQuery files, you may want to cover your keyboard with some plastic in case you get vertigo and blow chunks.

[Edit]

Someone asked me if it was possible to include the templates in an external file.  This seemed to work: 

<!--#include virtual="Scripts/RenderTemplates.js" -->

I hope someone finds this useful.

Steve Wellens

HTML 5 and jQuery – A Match Made in Heaven

I recently attended an ASP.NET MVC seminar hosted by the Twin Cities .Net User Group. The speaker was K. Scott Allen. Scott is a charismatic, gifted speaker and did a great job. You can always pick up useful items when watching an expert write a program from scratch.

When I saw what he did with jQuery and HTML 5 Custom Data Attributes, I got so excited I ran around the room squealing like a little girl and wetting my pants until someone threw an unopened can of pop at my head and knocked me out cold. Well, that didn't happen, but what Scott did was interesting enough to compel me to write this post.

What are HTML 5 Custom Data Attributes?

They are attributes that YOU create for an HTML element that starts with "data-"

Here's an example:

    <asp:TextBox 
        ID="TextBox1" 
        CssClass="TextEntry" 
        data-entryType="Date" 
        runat="server">
    </asp:TextBox>

data-entryType="Date" is a Custom Data Attribute. Note that asp:TextBox will render as an input element in the browser.

What is incredibly useful, is that jQuery can select elements by Custom Data Attributes. This is better than using CSS class names because CSS is for visual appearance and the CSS classes may be applied to elements you do NOT want to select with jQuery.

In the following example, I have three text boxes for entering data. They all share the same CSS class. Two of the textboxes are marked with a Custom Data Attribute to specify a date type of input.

Page Includes:

    <script src="Scripts/jquery-1.6.1.js" type="text/javascript"></script>
    <script src="Scripts/jquery-ui-1.8.15.custom/development-bundle/ui/jquery-ui-1.8.15.custom.js"
        type="text/javascript"></script>
    <link href="Scripts/jquery-ui-1.8.15.custom/css/smoothness/jquery-ui-1.8.15.custom.css"
        rel="stylesheet" type="text/css" />

CSS:

    .TextEntry
    {
        background-color: Cyan;
    }

jQuery:

<script type="text/javascript">
 
    $(document).ready(DocReady);
 
    function DocReady()
    {
        $("input[data-entryType = 'Date']").datepicker();
    }

HTML:

<table>
    <tr>
        <td>
            Company Name:
        </td>
        <td>
            <asp:TextBox ID="TextBoxCompanyName" CssClass="TextEntry" runat="server"></asp:TextBox>
        </td>
    </tr>
    <tr>
        <td>
            Start Date:
        </td>
        <td>
            <asp:TextBox ID="TextBoxStartDate" CssClass="TextEntry" data-entryType="Date" runat="server"></asp:TextBox>
        </td>
    </tr>
    <tr>
        <td>
            End Date:
        </td>
        <td>
            <asp:TextBox ID="TextBoxEndDate" CssClass="TextEntry" data-entryType="Date" runat="server"></asp:TextBox>
        </td>
    </tr>
</table>

With this code, the jQuery DatePicker is effortlessly (in a manner of speaking) attached to the correct HTML elements.

 

There were other slick things demonstrated at the seminar and although it was a seminar on MVC, most of the features demonstrated were applicable to ASP.NET forms.

I have an open mind about MVC…if it allows me to satisfy my client's needs in a more timely fashion than ASP.NET forms, then bring it on.

However, when I see source code mixed in with HTML markup, it looks like a giant step backwards towards classic ASP. I understand the frustration of dealing with the HTML output from the ASP.NET controls. Oh, yes, I do. But you don't have to use every control and there are controls that do give you 100% control over the HTML. So, I will continue to proceed with caution.

I hope someone finds this helpful.

Steve Wellens

Posted: Aug 19 2011, 09:00 PM by SGWellens | with 5 comment(s) |
Filed under: , ,
Five Phases of Developer Maturity

I generally don't post stuff like this but…what the hell…

Are you a good developer? A great developer? A World Class developer? Perhaps you only think you are a good developer. Maybe you are a terrible developer and don't even know it. The only way to know for sure is to get feedback from other developers and the users of your software.

I believe developers go through several learning phases on their way to becoming World Class developers. There are no short cuts…the dues must be paid. Here are five phases I have observed…as they relate to the code produced:

Phase I: It Works. The first time you get a program to work is immensely rewarding: You have mastered the computer. It was tough…but patience and persistence paid off. Not everyone can do what you just did. Not everyone can make a program work. You are special. You have experienced the magic of bending a machine to your will.

However, sometimes your program crashes. Sometimes it gives unexpected results. How were you to know those pesky users would type in letters when digits were expected? Sadly, some developers never get beyond this phase. Some begin to see themselves as 'creative visionaries': their code is 'proof of concept' and wasn't designed for actual use (so they say). Implementation details are left to others. Often these people are arrogant and fool managers into believing they are gifted. It is a sad programming shop where this occurs.

Phase II: It Works Good. At this phase, the developer has learned to really test the code. They test every input and account for every output. They check the return value of every function. They installed exception handlers and deal with the exceptions raised in testing. The program is now bullet-proof, tsunami-proof and even pointy-haired manager proof. It took extra work but now it is rock solid. No more embarrassing crashes.

But there is a slight problem; the code is a pile of convoluted spaghetti with deeply nested if-else clauses and case statements. It's disgusting to look at and it's embarrassing to admit you can't maintain your own code. When someone asks for a new feature, you take a sick day and update your resume.

Phase III: It is Good. The code works well and it's solid. But, you rewrite it anyway—sometimes several times. You re-organize it so it is clear to read and easy to understand. The code is logically separated into functions, classes and modules. There are concise helpful comments in the code. An intern may view the code and sneer, "What's the big deal, that's simple, I could have written that". No, he couldn't. He has no idea what it took to get the code to that state. Simplifying complexity is an art that cannot be taught.

Phase IV: It has Added Value. While working on the code, you created several functions and classes that can be reused in other projects. It takes more work to make a module reusable...three times as much work is the general consensus. The reusable modules help other projects get a head start. Other developers trust you and, more importantly, they trust your code. You recognize when to consider 'outside' code and understand the risk/reward of doing so. Leveraging the work of skilled developers is not a short cut, it's smart.

Phase V: It is Delivered Early. After mastering code, design and system architecture, there is only one frontier left to keep the job interesting…beat the schedule. Statistically, 30 to 50 percent of software projects fail. Even more are late. When you can deliver on-time solid code that is easy to maintain, you are gold. Caveat: Schedules are calculated guesses. You can plan all you want but as Mike Tyson succinctly put it: "Everyone has a plan, until they get hit."

So, there it is—my two ¢ents.

Steve Wellens

Note to Professional Editors: I'm aware the text vacillates between second and third person form…but it seemed to work that way so I broke the rules.

Learning to Like Linq or, Loving the Linq Loquacious

Once upon a time, long, long ago, I was working at a company that gave their engineers some shiny, state-of-the-art, new-fangled HP calculators. They were awesome; instead of battery-draining LEDs for display, they used something called an LCD. And, they had buttons galore: more buttons than any other calculator at that time. We positively drooled at their appearance. They looked something like this:

      

But when it came time to do something useful, like adding two numbers, instead of entering:

    3 + 5

You had to enter:

    3 5 +

What the heck? Crazy! It's all mixed up! It was using something called: Reverse Polish Notation (postfix instead of infix notation).

A small number of people used their calculators for a few weeks but eventually everyone retired them to a bottom desk drawer. After spending our entire lives with infix notation, no one wanted to change: Learning something new is hard; unlearning something old is harder.

Years later, while I was becoming a software engineer by going to school at night, we studied designing calculator software. Then it made sense: Pushing and popping operators and operands on and off a stack is very efficient for the developer and the computer. Sadly, it is unfriendly for the user.

I believe Linq suffers from the same malady.

Here is a pseudo SQL statement:

    select MyNumber from MyTable where MyNumber > 1 

 Here is a similar Linq query:

    from MyNumber in MyList where MyNumber > 1 select MyNumber

What the heck? Crazy! It's all mixed up!

It's Reverse Polish SQL!

How many developers have looked at Linq the first time and said, "No thanks, please go away"? A plethora, I'm sure.

Recently however, I took on a contract that required Linq knowledge. Guess what? You can use the Linq library functions, which are extension methods, without having to delve into the abomination of Reverse Polish SQL.

Here's an example, create a new array that contains the elements common to two other arrays:

    int[] MyList1 = new int[] { 1, 2, 3, 4 };
    int[] MyList2 = new int[] { 1, 7, 3, 12 };
 
    int[] MyList3 = MyList1.Intersect(MyList2).ToArray();

One line of code! The Intersect method is a Linq extension method. Being able to accomplish a complex task with one line of code is exhilarating and empowering. Did I really say exhilarating? I've got to get out more.

Here are a few more Linq extension method examples:

    double Average = MyList1.Average();
    int Sum = MyList1.Sum();
    MyList3 = MyList1.Where(x => x > 2).ToArray();

 OK, Linq is awesome. I am sold. To get a list of available functions, right-click on a Linq Extension method and select "Go To Definition". There are too many functions and overloads to list here. The functions above are in the System.Linq namespace in the static Enumerable class. There are other classes in the namespace but Enumerable is the easiest one to jump into. Here are the others: Linq Namespace.

I hope someone finds this useful.

Steve Wellens

BTW, I have gotten used to the abomination of Reverse Polish SQL. Hmmm, I wonder where that old HP calculator went to.

 

Posted: May 28 2011, 11:05 AM by SGWellens | with 7 comment(s)
Filed under: , ,
Five Bucks says you’ll Bookmark this Site: jsFiddle.net

In my never-ending wandering of technical web sites, I've been encountering links to jsFiddle.net more and more. Why? Because it is an incredibly useful site:

  1. It is a great 'sandbox' to play in. You can test, modify and retest HTML, CSS, and JavaScript code.
  2. It is a great way to communicate technical issues and share code samples.

There are four screen areas: Three inputs* and one output:

The three inputs are:

  • HTML
  • CSS
  • JavaScript

The output is:

  • The rendered result

Here's a cropped screen shot:

What am I thinking? Here's the actual page: Demo

*There are other inputs. You can select the level of HTML you want to run against (HTM5, HTML4.01 Strict, etc). You can add various versions of JavaScript libraries (jQuery, MooTools, YUI, etc.). Many other options are available.

If I wanted to share this code with someone manually, they would have to copy and paste three separate code chunks into their development environment. And maybe load some external libraries. Not many people are willing to make such an effort. Instead, with jsFiddler, they can just go to the link and click Run. Awesome.

I hope someone finds this useful (and I was kidding about the five bucks).

Steve Wellens

jQuery Selector Tester and Cheat Sheet

I've always appreciated these tools: Expresso and XPath Builder. They make designing regular expressions and XPath selectors almost fun! Did I say fun? I meant less painful. Being able to paste/load text and then interactively play with the search criteria is infinitely better than the code/compile/run/test cycle. It's faster and you get a much better feel for how the expressions work.

So, I decided to make my own interactive tool to test jQuery selectors:  jQuery Selector Tester 

Here's a sneak peek:

Note: There are some existing tools you may like better:

http://www.woods.iki.fi/interactive-jquery-tester.html

http://www.w3schools.com/jquery/trysel.asp?filename=trysel_basic&jqsel=p.intro,%23choose

My tool is different:

  • It is one page. You can save it and run it locally without a Web Server.
  • It shows the results as a list of iterated objects instead of highlighted html.
  • A cheat sheet is on the same page as the tester which is handy.

I couldn't upload an .htm or .html file to this site so I hosted it on my personal site here: jQuery Selector Tester.

Design Highlights:

To make the interactive search work, I added a hidden div to the page:

<!--Hidden div holds DOM elements for jQuery to search-->
<div id="HiddenDiv" style="display: none">
</div> 

When ready to search, the searchable html text is copied into the hidden div…this renders the DOM tree in the hidden div:

// get the html to search, insert it to the hidden div
var Html = $("#TextAreaHTML").val();
$("#HiddenDiv").html(Html);

When doing a search, I modify the search pattern to look only in the HiddenDiv. To do that, I put a space between the patterns.  The space is the Ancestor operator (see the Cheat Sheet):

// modify search string to only search in our
// hidden div and do the search
var SearchString = "#HiddenDiv " + SearchPattern;
try
{
    var $FoundItems = $(SearchString);
}
 
 

Big Fat Stinking Faux Pas:

I was about to publish this article when I made a big mistake: I tested the tool with Mozilla FireFox. It blowed up…it blowed up real good. In the past I’ve only had to target IE so this was quite a revelation.

When I started to learn JavaScript, I was disgusted to see all the browser dependent code. Who wants to spend their time testing against different browsers and versions of browsers? Adding a bunch of ‘if-else’ code is a tedious and thankless task. I avoided client code as much as I could.

Then jQuery came along and all was good. It was browser independent and freed us from the tedium of worrying about version N of the Acme browser.

Right? Wrong!

I had used outerHTML to display the selected elements. The problem is Mozilla FireFox doesn’t implement outerHTML.

I replaced this:

// encode the html markup
var OuterHtml = $('<div/>').text(this.outerHTML).html();

With this:

// encode the html markup
var Html = $('<div>').append(this).html();
var OuterHtml = $('<div>').text(Html).html();
Another problem was that Mozilla FireFox doesn’t implement srcElement.
I replaced this:
var Row = e.srcElement.parentNode; 
With this:
var Row = e.target.parentNode;
Another problem was the indexing. The browsers have different ways of indexing.
I replaced this:
// this cell has the search pattern  
var Cell = Row.childNodes[1];  
 
// put the pattern in the search box and search                    
$("#TextSearchPattern").val(Cell.innerText);
 With this:
// get the correct cell and the text in the cell
// place the text in the seach box and serach
var Cell = $(Row).find("TD:nth-child(2)");
var CellText = Cell.text();
$("#TextSearchPattern").val(CellText);
 
So much for the myth of browser independence. Was I overly optimistic and gullible? I don’t think so. And when I get my millions from the deposed Nigerian prince I sent money to, you’ll see that having faith is not futile.

Notes:

My goal was to have a single standalone file. I tried to keep the features and CSS to a minimum–adding only enough to make it useful and visually pleasing.

When testing, I often thought there was a problem with the jQuery selector. Invariable it was invalid html code. If your results aren't what you expect, don't assume it's the jQuery selector pattern: The html may be invalid.

To help in development and testing, I added a double-click handler to the rows in the Cheat Sheet table. If you double-click a row, the search pattern is put in the search box, a search is performed and the page is scrolled so you can see the results. I left the test html and code in the page.

If you are using a CDN (non-local) version of the jQuery libraray, the designer in Visual Studio becomes extremely slow.  That's why there are two version of the library in the header and one is commented out.

For reference, here is the jQuery documentation on selectors:

http://api.jquery.com/category/selectors/

Here is a much more comprehensive list of CSS selectors (which jQuery uses):

http://www.w3.org/TR/CSS2/selector.html

I hope someone finds this useful.

Steve Wellens

Posted: Feb 13 2011, 01:00 PM by SGWellens | with 4 comment(s)
Filed under: ,
Create Image Maps with GIMP

Having a clickable image in a web page is not a big deal. Having an image in a web page with clickable hotspots is a big deal. The powerful GIMP editor has a tool to make creating clickable hotspots much easier.

GIMP stands for GNU Image Manipulation Program. Its home page and download links are here: http://www.gimp.org/ (it is completely free).

Beware: GIMP is an extraordinarily advanced and powerful image editor. If you wish to use it for general image editing tasks, you have a steep learning curve to climb. FYI: I used it to create the shadows you see on the images below. Fortunately, the tool to make Image Maps is separate from the main program.

To start, open an image with GIMP or, drag and drop an image onto the GIMP main window. I'm using the image of a bar graph.

Next, we have to find the Image Map tool and launch it (Filters->Web->Image Map…):

Why is the Image Map tool under Filters and not Tools? I don't know. It's mystery—much like the Loch Ness Monster, the Bermuda Triangle, or why my socks keep disappearing when I do laundry. I swear I've got twenty single unmatched socks. But I digress…

Here is what the Image Map tool looks like:

If we click the blue 'I' button, we can add information to the Image Map:

Now we'll use the rectangle tool to create some clickable hotspots. Select the Blue Rectangle tool, drag a rectangle, click when done and you'll get something like this:

You can also make circle/oval and polygon areas. You can edit all the parameters of an image map area after drawing it.

Rectangle settings (for fine tweaking):

JavaScript functions (it's up to you to write them):

Here is a setup with two rectangles and one polygon area:

When you hit save a map file is generated that looks something like this:

<img src="BarGraphImage.png" width="326" height="306" border="0" usemap="#BarGraphImageMap">

<map name="BarGraphImageMap">
  <!-- #$-:Image map file created by GIMP Image Map plug-in -->
  <!-- #$-:GIMP Image Map plug-in by Maurits Rijk -->
  <!-- #$-:Please do not edit lines starting with "#$" -->
  <!-- #$VERSION:2.3 -->
  <!-- #$AUTHOR:Steve Wellens -->
  <!-- #$DESCRIPTION:A bar graph with  no instrinsic value -->
  <area shape="rect" coords="213,62,253,269" onmouseover="ImageMapMouseHover(&apos;Biggest Bar&apos;)" onmouseout="ImageMapMouseHover(&apos;&apos;)"  nohref="nohref"></area>
  <area shape="rect" coords="114,131,158,267" href="~/Details2.aspx"></area>
  <area shape="poly" coords="57,44,76,100,181,102,183,62,142,34" href="http://www.microsoft.com"></area>
</map>

Paste the contents into a web page and you are almost there. I made some tweaks before it became usable:

  • Replaced &apos; with apostrophes in the javascript functions.
  • Changed the image path so it would find the image in my images directory
  • Tweaked the href urls.
  • Added Title="Some Text" to get tool tips.
  • Cleaned out the comments.

Result:

The final markup (with JavaScript function):

<script type="text/javascript" language="javascript">

    function ImageMapMouseHover(Msg)
    {
         $("#Label1").html(Msg);
    }

<img src="Images\BarGraphImage.png" width="326" height="306" border="0" usemap="#BarGraphImageMap">

<map name="BarGraphImageMap">
  <area shape="rect" coords="213,62,253,269" onmouseover="ImageMapMouseHover('Biggest Bar')" onmouseout="ImageMapMouseHover('')"  nohref="nohref"></area>
  <area shape="rect" coords="114,131,158,267" href="Details2.aspx" title="Details 2"></area>
  <area shape="poly" coords="57,44,76,100,181,102,183,62,142,34" href="http://www.microsoft.com"></area>
</map>

It may seem like a lot of bother but, the tool does the heavy lifting: i.e. the coordinates. Getting the regions positioned and sized is easy using a visual tool…much better than doing it by hand.

This, of course, isn't a full treatise on the tool but it should give you enough information to decide if it's helpful.

I hope someone finds this useful.

Steve Wellens

The jQuery ‘live(…)’ Function

On the Asp.Net forums where I am a moderator, a developer was having a problem hooking up click events to the rows of a dynamically created html table. He didn't want to embed the onclick handler in the table rows as each row was created so he tried using jQuery. The problem was he was trying to hook up the click event before the table was created. How to help him?

The jQuery live(…) function comes to the rescue!

What can I say about jQuery and the live(…) function? It's like falling in love with a beautiful woman, who is kind, can cook and keeps a tidy house. So you marry her and then you find out that she's heiress to a trust fund worth millions!

The jQuery live(…) function is an esoteric function; in fact, you may never find cause to use it in your entire career. But if you do use it, it can solve some problems very elegantly.

To put it succinctly, instead of this:

    $('#MyTable tr').click(function()
    {
        alert("Table row clicked");
    })

Use this:

    $('#MyTable tr').live('click', function()
    {
        alert("Table row clicked");
    })

The live(…) function causes the event handler to be automatically assigned to elements matching the selector…including elements that haven't been created yet. You heard that correctly. It assigns events to elements that don't exist.

As rows are created and added to the table element in question, they automatically get the event handler assigned.

While working on this I became curious and looked at the source code for the jQuery live(…) function. That was a mistake. A big mistake. There is a quote from Otto von Bismark that goes something like, "…two things you don't want to see made are sausage and legislation." I think we can add the jQuery source code to that list.  To be fair, I understand that jquery code runs on millions of browsers daily.  Sacrificing some readability and maintainability for performance is perfectly reasonable under these circumstances. 

Fortunately, the online documentation provides a good description of how the function works: http://api.jquery.com/live/

Bonus Information: While I was poking around in the jQuery source code (with a safety rope, hard hat and air-sickness bag) I happened across some lines like these:

    if ( name === "find" ) 
    if ( arguments.length === 2 )

I'd never seen this before and it's not even mentioned in a book called The JavaScript Bible. It's called the Identity Equal Operator (there is also an Identity Not Equal operator !==). It checks both type and value. In other words, it does not do a type conversion before checking for equality. Here's an illustration of how it works compared to the equality operator:

    var x = 3;

    // equality
    x == 3 is true
    x == '3' is true
    x == "3" is true

    // identity equality
    x === 3 is true
    x === '3' is false
    x === "3" is false

[Edit/Update]

It has been pointed out to me that jQuery 1.4 introduced the delegate(...) function which does the same thing as the live(...) function but inserts the event handler closer to the selected elements rather than at the root of the DOM so it's performance is better.  However, another person pointed out if you are attaching events to dozens of elements (say anchors) it's better to have a single handler at the top of the DOM.  Here is the best article I found discussing the differences: http://test.kingdesk.com/jquery/bind_live_delegate.php

 

I hope someone finds this useful.

Steve Wellens.

Posted: Jan 24 2011, 12:40 PM by SGWellens | with 4 comment(s)
Filed under: ,
Web.Config is Cached

There was a question from a student over on the Asp.Net forums about improving site performance. The concern was that every time an app setting was read from the Web.Config file, the disk would be accessed. With many app settings and many users, it was believed performance would suffer.

Their intent was to create a class to hold all the settings, instantiate it and fill it from the Web.Config file on startup. Then, all the settings would be in RAM. I knew this was not correct and didn't want to just say so without any corroboration, so I did some searching.

Surprisingly, this is a common misconception. I found other code postings that cached the app settings from Web.Config. Many people even thanked the posters for the code.

In a later post, the student said their text book recommended caching the Web.Config file.

OK, here's the deal. The Web.Config file is already cached. You do not need to re-cache it.

From this article http://msdn.microsoft.com/en-us/library/aa478432.aspx

It is important to realize that the entire <appSettings> section is read, parsed, and cached the first time we retrieve a setting value. From that point forward, all requests for setting values come from an in-memory cache, so access is quite fast and doesn't incur any subsequent overhead for accessing the file or parsing the XML.

The reason the misconception is prevalent may be because it's hard to search for Web.Config and cache without getting a lot of hits on how to setup caching in the Web.Config file.

So here's a string for search engines to index on: "Is the Web.Config file Cached?"

A follow up question was, are the connection strings cached?

Yes. http://msdn.microsoft.com/en-us/library/ms178683.aspx

At run time, ASP.NET uses the Web.Config files to hierarchically compute a unique collection of configuration settings for each incoming URL request. These settings are calculated only once and then cached on the server.

And, as everyone should know, if you modify the Web.Config file, the web application will restart.

I hope this helps people to NOT write code!  

Steve Wellens

More Posts Next page »