JQuery Tooltips with MVC3 and CSS 3.0 (Part 2)

by Admin 25. March 2012 02:56

So in my previous post I explained all of the steps required to create a simple but effective CSS 3 JQuery tooltip implementation.  This was a good start, I had all I needed to fulfill my requirement of a consistent tooltip in my application.  But I needed it to be a little easier to use throughout the application.  A custom Html Helper Extension to the rescue. Creating an HTML Helper is a pretty straight forward task this one, however, needed a bit of special sauce to make it work the way I wanted.

First I wanted to make sure I could bind it to my model, in case in the future I wanted to default it to the [Display] property on the model.  Second I wanted to be able to use the @<text></text> syntax for the contents of the tooltip.  It made sense to work backwards I wanted the control to work like this: 

@Html.InfoTooltipFor(model => model.ToolTipTitle,
	@<text>
	<h4>@Model.ToolTipTitle</h4>
	<ul>
   		<li>
			<b>Scott Guthrie</b> – 
			<a href="http://weblogs.asp.net/scottgu/">
				ScottGu's Blog
			</a> One of the best resources for all things Microsoft Web
                </li>
		<li>
			<b>Scott Hanselman</b> – 
				<a href="http://www.hanselman.com/blog/">
					Scott Hanselman's Computer Zen
				</a> Another great resource very current and very informative.
		</li>
		<li>
			<b>ASP.NET</b> – 
				<a href="http://www.asp.net/mvc">
					ASP.NET MVC
				</a> The mother of all things ASP.NET MVC
		</li>
     </ul>
</text>)

 

Seemed simple enough so at first I create the Helper as follows:

 

public static MvcHtmlString InfoTooltipFor<TModel, TValue>(
    this HtmlHelper<TModel> html, 
    Expression<Func<TModel, TValue>> expression, 
    string tooltip)
{
...
}

 

Nope that won't work... 

 

And once I thought about it makes sense that a string won't work.  It isn't anything remotely like a string it contains functions, delegates and properties. I know this type of implementation exists I have seen it in other controls.  So how is it done?  After an exhaustive google search I found this article by Phil Haack.  It was close to what I needed, so, after some trial and error I came up with this:

public static MvcHtmlString InfoTooltipFor<TModel, TValue>(this HtmlHelper<TModel> html, 
		Expression<Func<TModel, TValue>> expression, 
		Func<object, HelperResult> tooltip)
{
	//Get the Model metadata
	ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
	string htmlFieldName = ExpressionHelper.GetExpressionText(expression);
	string tooltipId = metadata.PropertyName ? ? htmlFieldName.Split('.').Last();

	if (String.IsNullOrEmpty(tooltipId))
	{
		return MvcHtmlString.Empty;
	}
	//creates the span tag for the image
	TagBuilder spanTag = new TagBuilder("span");
	spanTag.MergeAttribute("class", "tooltipItem");
	spanTag.MergeAttribute("id", tooltipId);
	spanTag.MergeAttribute("data-tooltip", "tt" + tooltipId);

	TagBuilder imgTag = new TagBuilder("img");
	imgTag.MergeAttributes(new RouteValueDictionary(
		new
		{
			@class = "infoimage",
			src = UrlHelper.GenerateContentUrl("~/Content/info.png", html.ViewContext.HttpContext),
			alt = "info"
		}
		));
	spanTag.InnerHtml = imgTag.ToString(TagRenderMode.Normal);

	//creates the tooltip contents
	TagBuilder tooltipTag = new TagBuilder("div");
	tooltipTag.MergeAttribute("class", "hidden");
	tooltipTag.MergeAttribute("id", "tt" + tooltipId);
	tooltipTag.MergeAttribute("name", "tt" + tooltipId);
	tooltipTag.MergeAttribute("data-tooltip", "tt" + tooltipId);
	tooltipTag.InnerHtml = tooltip(null).ToString();

	return MvcHtmlString.Create(spanTag.ToString(TagRenderMode.Normal) + tooltipTag.ToString(TagRenderMode.Normal));
}

The key is Func<object, HelperResult> this allows templated items to be passed into helper method.  This was exactly what I needed and poof like magic it worked.  MVC is awesome, creating a control like that in standard ASP.NET would have been a major pain. 

The entire solution is in the attached zip...

EasyTooltips.zip (2.46 mb)

Tags: , , , ,

AJAX | css | jquery | MVC3

JQuery Tooltips with MVC3 and CSS 3.0 (Part 1)

by Admin 24. March 2012 17:58

I am a developer and no graphic artist by any strech of the imagination, but I am often thrown into that roll.   Seriously, how many small projects have the budget for a Graphic artist and a developer, not to mention the fact management rarely knows the difference.  

Regardless, I needed to create some tooltips to be used throughout an internal web application I was creating.  There are about 30 trillion jquery tooltip plugins on the internet, and most of them are good, I felt a little plugin overloaded, so I decide to roll my own.  The CSS 3.0 implementation was pretty simple (only 2 classes):

.tooltipwindow
{
   border-bottom-left-radius: 15px;
   border-top-left-radius: 0px;
   border-bottom-right-radius: 15px;
   border-top-right-radius: 15px;
   border: 1px solid #0099FF;
   position:absolute;
   padding:5px;
   background-color:#fff;
   z-index: 20000;
   box-shadow: 3px 3px 2px #808080;
   font-size:smaller;
}
 
.hidden
{
   display:none;
}

Only a few CSS 3.0 items to mention box-shadow (for the drop shadow effect) and border-radius (for the rounded corners on all but the top right) other than that pretty standard.  The JavaScript was pretty simple as well:

A few global variables:

//A 1/2 Second fade-in/out
var toolTipHideDelay = 500;
//A 1/4 second before the tooltip begins to fade
var toolTipMouseoutDelay = 250;
//The timer to determine when the fade should start
var toolTipHideTimer = null;
//The variable containing the tool tip contents
var tooltipWindow = null;
//The current Id of the tool tip
var tooltipCurrentId = null;

Then the actual tool event handlers:

//standard doc ready event
$(document).ready(function () {
 
    initializeTooltips();
})
 
function initializeTooltips() {
    //there is only one window for all tooltips on the page.
    tooltipWindow = $("#tooltipWindow");
    //all tooltips implement the following class
    var tooltipItem = $(".tooltipItem");
 
    //tooltip mouse events (not using hover because I want the tooltip to follow the cursor
    tooltipItem.mousemove(function (e) {
        //check to see if the tooltip is already opened
        if (toolTipHideTimer) {
            //clear the timeout since the user is hovering over the item again
            clearTimeout(toolTipHideTimer);
            //retrieve the tooltip data item from the current element
            if (tooltipCurrentId != $(this).data("tooltip")) {
                setToolTipPosition(this, e);
                //set the tooltip inner content
                tooltipWindow.html($("#" + $(this).data("tooltip")).html());
            }
        } else {
            //show the tooltip
            setToolTipPosition(this, e);
            //the tooltip is hidden do fade it in
            tooltipWindow.fadeIn(toolTipHideDelay);
            //set the tooltip inner content
            tooltipWindow.html($("#" + $(this).data("tooltip")).html());
        }
    });
    tooltipItem.mouseout(function (e) {
        //get ready to hide the tooltip
        toolTipHideTimer = setTimeout(function () {
            toolTipHideTimer = null;
            tooltipWindow.fadeOut();
        }, toolTipMouseoutDelay);
    });
 
    //tooltip window mouse events
    tooltipWindow.mouseover(function () {
        //keep the tooltip open if the user is over the tooltip window
        if (toolTipHideTimer) {
            clearTimeout(toolTipHideTimer);
        }
    });
    tooltipWindow.mouseout(function () {
        //get ready to hide the tooltip
        toolTipHideTimer = setTimeout(function () {
            toolTipHideTimer = null;
            tooltipWindow.fadeOut();
        }, toolTipMouseoutDelay);
    });
}

Then just a simple postion helper:

function setToolTipPosition(item, mouseevent) {
    var tooltipitem = $("#tooltipWindow");
    //set the tool tip to the current mouse position the 
    //2+ is so the user can still click the element under the 
    //tooltip
    tooltipitem.css({
        left: 2+(mouseevent.pageX) + "px",
        top: 2+(mouseevent.pageY) + "px"
    });
}

So now we have all of the client pieces we need, infact the following html will generate a tooltip:

<h3 style="display:inline-block">For More info hover over the icon</h3>
<span class="tooltipItem" data-tooltip="ttToolTipTitle" id="ToolTipTitle">
  <img alt="info" class="infoimage" src="/Content/info.png" />
</span>
 
<div class="hidden" data-tooltip="ttToolTipTitle" id="ttToolTipTitle" name="ttToolTipTitle">        
            <h4>Learn More from these sites</h4>
            <ul>
                <li><b>Scott Guthrie</b> – <a href="http://weblogs.asp.net/scottgu/">ScottGu's Blog</a> One of the best resources for all things Microsoft Web</li>
                <li><b>Scott Hanselman</b> – <a href="http://www.hanselman.com/blog/">Scott Hanselman's Computer Zen</a> Another great resource very current and very informative.</li>
                <li><b>ASP.NET</b> – <a href="http://www.asp.net/mvc">ASP.NET MVC</a> The mother of all things ASP.NET MVC</li>
            </ul>
        </div>
</div>
 
<div id="tooltipWindow" class="hidden tooltipwindow">
</div>

 

That looks like this:

 

So we are good to go, I just didnt want to have to copy all of this HTML for every tooltip I created in the system.  In Part 2 I will talk about how to integrate this with an MVC Html Helper.

 

Tags: , , ,

css | jquery

MVC Makes AJAX easier

by Admin 12. February 2012 19:07

MVC is a pretty new paridigm to me, I just started using it about 5 months ago, but one of the first things I noticed is how much easier it has made AJAX.  This is coming from someone who has had a very long career in ASP.NET.  There were a few different ways to provide Ajax functionality in ASP.NET the evil update panel being the easiest to implement.  My preference and, IMO, the more correct approach was to use the AJAX framework in conjunction with AJAX Aware WCF/Web Services.  The low framework overhead (no Eventing Model) in conjuction with the relatively small request/response package made these fairly easy to implement and perform very well.  The problem with these services is they were still tied up in all sorts of framework gook.

With MVC, all of that framework gook is gone, it is a much simpler PHP style implementation of AJAX.  There are several ways to utilize AJAX in the MVC world, there are the AJAX MVC Helper Class which is very easy to work with, but I perfer a pure JQuery ajax approach.

Enough talk here is an example of a simple implementation of ajax using 4 commonly used methods:

First the Controller (in this case I am using the Products table of the Northwind Database (kickin' it old school)):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
 
namespace JqueryAjax.Controllers
{
    public class ProductsController : Controller
    {
        //
        // GET: /Products/
 
        public ActionResult Index()
        {
 
            List<Product> prodList = null;
            using (NorthwindEntities ctx = new NorthwindEntities())
            {
                prodList = ctx.Products.OrderBy(p => p.ProductName).ToList();
 
            }
            return View(prodList);
        }
 
        public ActionResult _productInfoJSON(int? id)
        {
            Product prod = null;
            using (NorthwindEntities ctx = new NorthwindEntities())
            {
                prod = ctx.Products.Single(p => p.ProductID == id);
 
            }
            return Json(prod, JsonRequestBehavior.AllowGet);
        }
 
        public ActionResult _productInfo(int? id)
        {
            Product prod = null;
            using (NorthwindEntities ctx = new NorthwindEntities())
            {
                prod = ctx.Products.Single(p => p.ProductID == id);
 
            }
            return PartialView(prod);
        }
 
        [HttpPost]
        public ActionResult _productInfoJSONParams()
        {
            Product prod = null;
            int prodId = int.Parse(this.ValueProvider.GetValue("id").AttemptedValue);
            using (NorthwindEntities ctx = new NorthwindEntities())
            {
                prod = ctx.Products.Single(p => p.ProductID == prodId);
 
            }
            return Json(new { prod.ProductName, prod.UnitPrice });
        }
 
    }
}

I use the "_" syntax to denote partial actions and views.  So looking at the above code you will notice there is only one full action "Index", so lets look at its view:

@model IEnumerable<JqueryAjax.Product>
 
@{
    ViewBag.Title = "Products List";
}
 
@section script{
    <script src="@Url.Content("~/Scripts/Info.js")" type="text/javascript"></script>
}
<h2>Index</h2>
<table>
    <tr>
        <th>
 
        </th>
        <th>
            ProductName
        </th>
    </tr>
 
@foreach (var item in Model) {
    <tr>
        <td>
            <a class="prodInfo" href="#" data-product-id="@item.ProductID">
                <img src="../../Content/Images/info.jpg" width="20" height="20" alt="info" border="0" />
            </a>
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.ProductName)
            <div id="product-@item.ProductID" style="display:none;">
            </div>
        </td>
    </tr>
}
 
</table>
So in this view I am simply building a Table of product names with an info image, easy peasy.  The Ajax parts of the html are housed in the:
 
<div id="product-@item.ProductID" style="display:none;">
And the:
<a class="prodInfo" href="#" data-product-id="@item.ProductID">

The former is the place holder for our dynamic content and the latter is the link to our dynamic content.  Now that we have all of the page scaffolding in place we need to hook up the pieces.  There are several ways we can retrive the content.  We could retrieve a standard JSON object from a controller _productInfoJSON and _productInfoJSONParams or you can return a partial view.  This is where the power of MVC really hits home.  You can craft a fully functional page and return it to your AJAX container.  That is pretty sweet...  But first lets look at the JSON example:

I created 2 a JSON Get (useful for RESTful services) and a JSON Post (prefered for internal web site calls):

Here is the Java script for calling the JSON get in conjuntion with the previously crafted view and controller:

/// <reference path="jquery-1.7.1.min.js" />
$(document).ready(function () {
    $(".prodInfo").click(function (e) {
        //stop the href="#" from firing
        e.preventDefault();
 
        var prodId = $(this).data("productId");
        jsonGet(prodId);
 
    });
});

function jsonGet(prodId) {
    $.ajax({
        url: "/Products/_productInfoJSON/" + prodId,
        success: function (data) {
            $("#product-" + prodId).toggle();
            $("#product-" + prodId).html("<span> price:"+ data.UnitPrice +"</span>");
        }
    });
}
and the JSON parameterized post:

jsonPost(prodId);

function jsonPost(prodId) {
    $.ajax({
        url: "/Products/_productInfoJSONParams",
        type: "POST",
        data: { id: prodId },
        success: function (data) {
            $("#product-" + prodId).toggle();
            $("#product-" + prodId).html("<span> price:" + data.UnitPrice + "</span>");
        }
    });
}

This will get you a simple return value that you can format in JQuery.

Pretty simple right?  Well its about to get easier.  Now lets say we want to return a significant amount of data to the client and we don't want to do that messy JavaScript HTML formatting.  Partial Views to the rescue.  Create the following partial view from the _productInfo action:

@model JqueryAjax.Product
 
<fieldset>
    <legend>Product Info</legend>
 
    <div class="display-label"></div>
    <div class="display-field">
        QuantityPerUnit: @Html.DisplayFor(model => model.QuantityPerUnit)
    </div>
 
    <div class="display-field">
        UnitPrice: @Html.DisplayFor(model => model.UnitPrice)
    </div>
 
    <div class="display-field">
        UnitsInStock: @Html.DisplayFor(model => model.UnitsInStock)
    </div>
 
    <div class="display-field">
        UnitsOnOrder: @Html.DisplayFor(model => model.UnitsOnOrder)
    </div>
 
    <div class="display-field">
        ReorderLevel: @Html.DisplayFor(model => model.ReorderLevel)
    </div>
 
    <div class="display-field">
        Discontinued: @Html.DisplayFor(model => model.Discontinued)
    </div>
</fieldset>

Now change the JS click event to fire:

partialJqueryGet(prodId);

with the function definition of:

function partialJqueryGet(prodId) {
    $.get("/Products/_productInfo/" + prodId,
    function (data) {
        $("#product-" + prodId).toggle();
        $("#product-" + prodId).html(data);
    });
}

BA BAM done.  Now you are returning a partial view from an ajax call.  You can also use a pure jquery ajax implementation as opposed to the Get helper class:

function partialAjaxGet(prodId) 
{
    $.ajax({
        url: "/Products/_productInfo/" + prodId,
        success: function (data) {
            $("#product-" + prodId).toggle();
            $("#product-" + prodId).html(data);
        }
    });
 
}

 

All of the code and the northwind database can be found here:

JqueryAjax.zip (2.67 mb)

http://www.microsoft.com/download/en/details.aspx?id=23654

 

Tags: , , , , , ,

AJAX | ASP.NET | jquery | JSON | MVC3 | Razor

FAJAX Image Buttons

by Admin 17. November 2010 15:40

I had an interesting question the other day.  "How can I use AJAX to stream images back from a database to the web browser?"  The answer is, you dont need to. This functionality has been around since .net 1.0.  You can create an HTTP Handler.  Http Handlers give you control over any request recieved from the client.  In the case of an Image streamed from... whatever, you can do it quite simply...  Here is all of the code you need:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="SlideShow.aspx.cs" Inherits="SlideShow" %>
<!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 runat="server">
    <title>Slide Show</title>
    <script>
        var index = 1;
        function getImage() {
            //increment counter
            if(index == 5){
                index = 1;
            }
            else{
                index ++
            }

            document.getElementById("sldImage").src = index + ".ipg";
        }
    </script>
</head>

<body>
    <form id="form1" runat="server">
    <div id="slidePanel">
        <img id="sldImage" src="1.ipg" alt="slideImg" />
    </div>
    <input type="button" value="Next" onclick="getImage();return false;" />
    </form>
</body>
</html>

In the code above I am I am using an "IPG" extension as a replacement for jpegs.  This code will also support gif as "IIF".  The Image Handler would look as follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
/// <summary>
/// Summary description for Class1
/// </summary>
public class HttpImageHandler : IHttpHandler
{
    public void ProcessRequest(System.Web.HttpContext context)
    {
        long fileID = 0;
        long.TryParse(Path.GetFileNameWithoutExtension(context.Request.Path), out fileID);

        context.Response.ContentType = getContentType(context.Request.Path);
        FileInfo fi = null;
        switch (fileID)
        {
            case 1:
                fi = new FileInfo("C:\\Pics\\Chrysanthemum.jpg");
                break;
            case 2:
                fi = new FileInfo("C:\\Pics\\Desert.jpg");
                break;
            case 3:
                fi = new FileInfo("C:\\Pics\\Hydrangeas.jpg");
                break;
            case 4:
                fi = new FileInfo("C:\\Pics\\Jellyfish.jpg");
                break;
            case 5:
                fi = new FileInfo("C:\\Pics\\Koala.jpg");
                break;
        }
        context.Response.BinaryWrite(getImage(fi.OpenRead(), Path.GetExtension(context.Request.Path)));
        
        context.Response.End();
    }
    public bool IsReusable
    {
        get
        {
            return false;
        }
    }

    /// <summary>
    /// Gets the image steram as a byte array
    /// </summary>
    /// <param name="path">The </param>
    /// <returns></returns>
    private byte[] getImage(Stream imageData, string ImageType)
    {
        Bitmap imgIn = new Bitmap(imageData);
        System.IO.MemoryStream outStream = new System.IO.MemoryStream();

        imgIn.Save(outStream, getImageFormat(ImageType));

        return outStream.ToArray();
    }
    private string getContentType(String path)
    {
        switch (Path.GetExtension(path))
        {
            case ".ipg": return "Image/jpeg";
            case ".iif": return "Image/gif";
            default: break;
        }
        return "";
    }
    private ImageFormat getImageFormat(String ImageType)
    {
        switch (ImageType)
        {
            case ".ipg": return ImageFormat.Jpeg;
            case ".iif": return ImageFormat.Gif;

            default: break;
        }
        return ImageFormat.Jpeg;
    }
}

In this example I am just using the default images that ship with windows.  You will need to make a few changes to your web config, and if you are using IIS 6.0 or below, you will need to add the filter to your list of served content.

<!-- in system.web -->
    <httpHandlers>
      <add verb="*" path="*.ipg"
        type="HttpImageHandler"/>
      <add verb="*" path="*.iif"
        type="HttpImageHandler"/>
    </httpHandlers>
<!-- system.webServer -->
    <handlers>
      <add  verb="*" path="*.ipg"
            name="HttpImageHandler"
            type="HttpImageHandler"/>
      <add  verb="*" path="*.iif"
            name="HttpImageHandler"
            type="HttpImageHandler"/>        
    </handlers>

That is pretty much it...  pressing the next button will go to the server and retrieve the corresponding image file, however it is stored.

Tags: ,

ASP.NET

Calendar

<<  November 2014  >>
MoTuWeThFrSaSu
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567

View posts in large calendar

Page List

RecentComments

None