Image Maps and jQuery

Here is a simple example of using jQuery and image maps.  And using image maps to break up your large image into smaller pieces.

Example on JS Fiddle

<div>Hover over swatch</div>
<img src="http://markistaylor.files.wordpress.com/2012/05/swatches.jpg"
   alt="Swatches" usemap="#swatchmap" />

<map name="swatchmap">
 <area shape="rect" coords="0,0,20,20" alt="Graphite" />
 <area shape="rect" coords="24,0,44,20" alt="Snow" />
 <area shape="rect" coords="50,0,70,20" alt="Poppy" />
 <area shape="rect" coords="77,0,97,20" alt="Caynene" />
 <area shape="rect" coords="103,0,123,20" alt="Tan" />
</map>

<div>Hovered swatch: <span id="hoveredSwatch"></span></div>

<div>Selected swatch: <span id="selectedSwatch"></span></div>

 

var $hoveredSwatch = $("#hoveredSwatch");
var $selectedSwatch = $("#selectedSwatch");

$("area").hover(
   function() {
      var altText = $(this).attr("alt");
      $hoveredSwatch.html(altText);
   },
   function() {
      $hoveredSwatch.html("");
   }
).click(
   function() {
      var altText = $(this).attr("alt");
      $selectedSwatch.html(altText);
   }
);

Javascript Concatenating and Minifying

It is immensely important to minify your javascript/css files.  Just as Scott Hanselman has recently felt the pain points on the adverse affects and blogged about it here: http://bit.ly/pngcwC

Currently, at my job we employ a build process task to concatenate and minify our files.  While this is great and really helps our performance, it would be nice if we could just generate the “squished” files and then depend on output caching/client caching to hold on to the generated output.  The the process of hot-fixing our text assets could be simplified down to just sticking the files out in production and let the process handle the rest.

A bit of backstory to this, currently we use a methodology that assumes all the javascript and css files are grouped by folder and on the production servers we will render “<script src=’js/folderName_folderVersion.js’>” where folder version is essentially the date of the folder’s latest generated “squished” file.  So the following example assumes that a request will come in for a “js/folderName_folderVersion.js” and that we will need to strip out the “_folderVersion.js” part and then read through the javascript files in corresponding directory.

I used Microsoft’s Ajax minifier: http://ajaxmin.codeplex.com/ (that’s where you can find the “Minifier” and “CodeSettings” classes specified below)

Enjoy!

public class JavascriptHandler : IHttpHandler
{
    private readonly static bool enabledAutoMinified = ConfigurationManager.AppSettings["enableAutoMinified"] == "true";
    private readonly static Minifier minifier = new Minifier();
    private readonly static CodeSettings minifierSettings = new CodeSettings()
    {
        /* Minifier code settings here */
    };

    bool IHttpHandler.IsReusable
    {
        get { return false; }
    }

    void IHttpHandler.ProcessRequest(HttpContext context)
    {
        /*
         * logic for getting folder name and file info here
         *
         */

        // if the user sent us a "if-modified-since" header, compare the date to the cache date
        DateTime modifiedSince;
        var header = context.Request.Headers["If-Modified-Since"];
        if (header != null && DateTime.TryParse(header, out modifiedSince) && webInfo.DateTime == modifiedSince)
        {
            // send a 304 and exit out
            context.Response.StatusCode = 304;
            context.ApplicationInstance.CompleteRequest();
        }
        else
        {
            // set the cache headers
            var cacheHeader = context.Response.Cache;
            cacheHeader.SetValidUntilExpires(true); // cache this server-side
            cacheHeader.SetCacheability(HttpCacheability.Public); //tell the client to cache this file
            //cache for a long time, we are 100% dependant on the JS control
            // to give us a unique name everytime the underlying files change
            cacheHeader.SetExpires(DateTime.Today.AddDays(365));
            cacheHeader.SetMaxAge(TimeSpan.FromDays(365)); //cache for a long time
            cacheHeader.SetLastModified(webInfo.DateTime); //inform the client of what we know to be the lastmodified date

            // IIS won't gzip the request if the return content type isn't the following:
            context.Response.ContentType = "application/x-javascript";
            //get the folder/file that correspond to this request
            var javascriptFolder = context.Server.MapPath(folderPath);
            var javascriptFile = javascriptFolder + ".js";

            string[] files = new string[] { };
            if (Directory.Exists(javascriptFolder))
            {
                files = Directory.GetFiles(javascriptFolder);
            }
            else if (File.Exists(javascriptFile))
            {
                files = new[] { javascriptFile };
            }
            //run through all the files in the folder minify them and concate them
            foreach (var fileName in files)
            {
                var js = File.ReadAllText(fileName);
                if (enabledAutoMinified)
                {
                    var minifiedJs = minifier.MinifyJavaScript(js, minifierSettings);
                    // write the minified js directly into the output stream
                    context.Response.Output.Write(minifiedJs);
                    // Microsoft Ajax Minifier removes the last semi-colon
                    //  since we are concatenating then we should put it back
                    //  line break so that we can deciminate the different files
                    context.Response.Output.WriteLine(";");
                    foreach (var error in minifier.ErrorList)
                    {
                        // write any errors into the trace so we can debug later
                        context.Trace.Warn("minifier", error.ToString());
                    }
                }
                else
                {
                    // if we aren't minifying then just concatenate all the js files
                    context.Response.Output.Write(js);
                }
            }
        }
    }
}

MVC Pager Function

I am in the process of creating a site that needs to show a basic grid of data to a user, allow the user to select certain rows and then post the IDs of those rows to a web service.  It is a fairly straight forward UI.

When presenting a user with a grid there are always at least two basic requirements to a good grid UI.  The first requirement is sorting and the second one that I am going to examine farther into is paging.  Before starting this project I had come across two projects that integrate well with MVC and implement paging:

The MVC Contrib project is great and has added a lot of useful functionality to ASP.NET MVC projects.  However the paging presentation that they implement wasn’t quite what I wanted.

I really liked John’s idea and how his pager method presented the links.  So I took his method and used it in my project.  Thanks John!  But, lately I have been on a replace all for loops with linq kick.  And I thought I could enhance John’s method further with linq.  The result is the code you see at the bottom of the page.

To explain what is going on in the method below, the first step in the method is to collect the controller, action name and route values, these are all things that are needed for constructing action links.  Part of collecting the route values is to make sure that we have the appropriate route value for the url parameter that holds the current page.  The next step was to create a range list from the first page to the last page and filter the list down to just the pages we wanted to present.  Then the last step is simply just building the html string to return to the MVC view.

Enjoy!

public static class ExtensionMethods
{
    /// <summary>
    /// Creates paginated links
    /// </summary>
    /// <param name="helper">HtmlHelper class that can provide access to helpful functionality</param>
    /// <param name="startPage">Starting page (most likely 1)</param>
    /// <param name="currentPage">Number of the current page</param>
    /// <param name="totalPages">Number of total pages</param>
    /// <param name="pagesToShow">Number of pages to show forward and backward</param>
    /// <param name="currentPageUrlParameter">URL Parameter that sets the current page</param>
    public static MvcHtmlString Pager(this HtmlHelper helper, int startPage, int currentPage, int totalPages, int pagesToShow, string currentPageUrlParameter)
    {
        System.Web.Routing.RouteData routeData = helper.ViewContext.RouteData;
        string actionName = routeData.GetRequiredString("action");
        string controller = routeData.GetRequiredString("controller");

        System.Web.Routing.RouteValueDictionary values = routeData.Values;
        if (!values.ContainsKey(currentPageUrlParameter))
            values.Add(currentPageUrlParameter, currentPage);
        bool started = false;
        StringBuilder html =
        Enumerable.Range(startPage, totalPages)  /* Create a list from the first page to the last page */
        .Where(i => (currentPage - pagesToShow) < i & i < (currentPage + pagesToShow))  /* filter the list to just the pages we want to show */
        .Aggregate(new StringBuilder(@"<div class=""pager"">"), (seed, page) =>
            {

               /* run through the list of pages and create the string of page numbers */
                values["page"] = page;
                string style = "pagerPage";
                if (page == startPage)
                    style += " firstPage";
                if (page == totalPages)
                    style += " lastPage";
                var htmlDic = new Dictionary<string, object>();
                htmlDic.Add("style", style);

                if (started)
                    seed.Append("&nbsp;|&nbsp;");
                else
                    started = true;

                if (page == currentPage)
                    seed.AppendFormat("<span style=\"pagerPage currentPage\">{0}</span>", page);
                else
                    seed.Append(helper.ActionLink(page.ToString(), actionName, controller, values, htmlDic).ToHtmlString());
                return seed;
            });
        html.Append(@"</div>");

        return MvcHtmlString.Create(html.ToString());
    }
}

LINQPad beyond Linq

LINQPad is a great way to learn Linq, but there is plenty of information on their site about that: http://linqpad.net/.  And I don’t need to rehash all the information on their site.  Instead, I would like to talk about the ways LINQPad can be used beyond that.  I am also going to assume you know what about the Dump function native to LINQPad and general understanding of LINQPad.

Using LINQPad to Test simple .NET Functions

LINQPad can be used to test any .NET function.  Simply type in the .NET

Using LINQPad to test ASMX Web Services

Generate the SOAP client – open “Visual Studio Command Prompt” and run “WSDL <url>”

Compile the SOAP client – run “csc /t:library <filename generated in previous step>”

Add the correct references in LINQPad, one to the library we just created and one to System.Web.Services.dll

Type the necessary code to call the web service, just as you would have normally in Visual Studio

Using LINQPad to call a WCF Service

Generate the client – open “Visual Studio Command Prompt” and run “SVCUTIL /config:<path to LINQPad’s config file> /mergeConfig <url>”
Note that this will generate the client and will add the necessary settings in LINQPad’s config file

Compile the client – run “csc /t:library <code file generated in previous step>”

Add the correct references in LINQPad, one to the library we just created and one to System.ServiceModel.dll

Add the follow code and make adjustments as necessary

var endpoint = new System.ServiceModel.EndpointAddress("http://www.ecubicle.net/gsearch_rss.asmx");
var binding = new System.ServiceModel.BasicHttpBinding();
var oProxy = System.ServiceModel.ChannelFactory.CreateChannel(binding, endpoint);
/* Code specific to service */
var request = new GetSearchResultsRequest() { Body = new GetSearchResultsRequestBody() };
request.Body.searchPage = "";
request.Body.gQuery = "Test";
request.Body.numOfResults = "50";

bool successfulCall = false;
try{
   /* Service Call */
   oProxy.GetSearchResults(request).Dump();
   oProxy.Close();
   successfulCall = true;
}finally{
   if (!successfulCall)
   {
      oProxy.Abort();
   }
}