Wesley Bakker

Interesting things I encounter doing my job...

Sponsors

News

Wesley Bakker
motion10
Rivium Quadrant 151
2909 LC Capelle aan den IJssel
Region of Rotterdam
The Netherlands
Phone: +31 10 2351035

(feel free to chat with me)
 

Add to Technorati Favorites

SharePoint 2010 BCS model deploy errors

Today I again faced an annoying SharePoint BCS deployment bug. I encountered it before in the beta but figured it would be solved in the release version so I forgot about it. Today it bit me in the rear again.

What's the issue?

As soon as you deploy a BCS solution from VS2010 and changed the model to much from the previous version you deployed, you get all sorts of errors. If you look closely to them, you'll notice they are caused by SharePoint trying to compile previous versions. If you however retract your solution and view Central Admin there are no models defined at all! Somehow these previous versions are stored somewhere we can't view or delete them!

The solution

Sometimes the solution can be very simple. Don't trust the UI, don't trust VS2010 but try the Object Model. After I ran the following code I could find a lot of left over models and entities.

 

using(var site = new SPSite("http://siteurl")){
    var parentFarm = site.WebApplication.Farm;
    var businessDataServices = parentFarm.Services.GetValue();

    var context = SPServiceContext.GetContext(site);
    var catalog = businessDataServices.GetAdministrationMetadataCatalog(context);

    Console.WriteLine("Models:");
    var models = catalog.GetModels("*");
    foreach (var model in models) {
        Console.WriteLine("\t{0}", model.DefaultDisplayName);
    }

    Console.WriteLine("\nEntities:");
    var entities = catalog.GetEntities("*", "*", false);
    foreach (var entity in entities) {
        Console.WriteLine("\t{0}", entity.DefaultDisplayName);
    }               
}
            
Console.ReadLine();

After verrifying all models and entities were indeed left overs I adjusted the code a little bit to delete them.

 

using(var site = new SPSite("http://siteurl")){
    var parentFarm = site.WebApplication.Farm;
    var businessDataServices = parentFarm.Services.GetValue();

    var context = SPServiceContext.GetContext(site);
    var catalog = businessDataServices.GetAdministrationMetadataCatalog(context);

    Console.WriteLine("Models:");
    var models = catalog.GetModels("*");
    foreach (var model in models) {
        Console.WriteLine("\t{0}", model.DefaultDisplayName);
        model.Delete();
    }

    Console.WriteLine("\nEntities:");
    var entities = catalog.GetEntities("*", "*", false);
    foreach (var entity in entities) {
        Console.WriteLine("\t{0}", entity.DefaultDisplayName);
        entity.Delete();
    }               
}
            
Console.ReadLine();

After running the code I could deploy my solution again and all worked fine!

Cheers,

Wes

Posted: Oct 20 2010, 04:40 PM by webbes | with 3 comment(s)
Filed under:
Configure hMailServer for SharePoint

There is this great free mailserver called hMailServer which can be found here. It is very stable and works definately like a charm. There is some awesome documentation and thus a breeze to configure. I wanted to use this free mail server on my SharePoint 2010 development / demo machine but got stuck with two problems to resolve.

Authentication

SharePoint can not connect to a mail server that requires authentication. hMailServer is secure by default so you have to add ip-range and define that this ip-range does not require authentication. In my case I defined a range from 0.0.0.0 till 255.255.255.255 and allow message to and from @development.com only. Have a look at the screenshot for were to find these settings and what their value should be.

image

Drop folder

This one was a bit more complicated. SharePoint requires a path to a ‘drop folder’ where it can find the incoming emails. hMailServer however does not work with drop folders. It creates a folder hierarchy for each account. The dropfolder in the screenshot below is created by me. Just note the folder hierarchy.

image

bad sender or receiver

Even if you do direct SharePoint to one of those folders inside the hierarchy, it results in a ‘bad sender or receiver’ error when it tries to process the email. But why? It works with the default IIS smtp server right? This error occurs because the default Microsoft SMTP server adds two headers to an email: the x-sender and the x-receiver. SharePoint uses these headers when it processes the emails.

solution

The solution to all this is the use of VBScript. hMailServer can be extended by writing some event handlers.

image

If we could write a script, that copies an incoming message to a ‘drop folder’ AND add the two headers, we are done. Luckily we can!

First we create a dropfolder inside the existing hierarchy.(Have a look at the screenshot above). Be sure to set the permissions for this folder to allow the SharePoint Timer Service account to make modifications!

Then - after clicking the ‘Show scripts’ button(see screenshot above) - simply open the script file ‘EventHandlers.vbs’ and add the following script.

Sub OnDeliverMessage(oMessage)
        Dim path, filename, fso, original, copy

    path = Split(oMessage.Filename, "\", -1, 1)

    filename = "C:\Program Files (x86)\hMailServer\Data\development.com\dropfolder\" & _
           path(UBound(path))

    Set fso = CreateObject("Scripting.FileSystemObject")

    Set copy = fso.CreateTextFile(filename, True)
        copy.WriteLine("x-sender: " & oMessage.FromAddress)
        copy.WriteLine("x-receiver: " & oMessage.To)

    Set original = fso.OpenTextFile(oMessage.Filename, 1)
        copy.WriteLine(original.ReadAll)
        
    copy.Close
    original.Close
End Sub

This should replace the commented Sub OnDeliverMessage(oMessage). Save the script., ‘Check syntax’ to be sure and click ‘Reload scripts’ to enable this freshly added script. The script simply copies ALL incoming mail messages to the ‘dropfolder’ and prepends the two mentioned headers. Now simply point SharePoint to the dropfolder and that’s it!

Conclusion

With this script in place you can very well use hMailServer in combination with SharePoint!

Cheers and have fun,

Wesley

tip: create a catch all address for all your SharePoint incoming mail. You do not want to manually add a new address for each list you want to mail enable.

image

SharePoint Calculated Field from string to number

Every now and then you run into unexpected SharePoint behavior. The Title field of an item is a 'single line of text' field. In my specific case however this Title field contained a file number. Not a problem at all, BUT you get a really strange sort order when you start to sort numbers as strings. File number 1000 comes before file number 9. This is however expected behavior.

The client wanted to be able to sort the items in a numeric way so I simply created a calculated columns of type number with this expression "=[Title]". Easy peasy right? And that's where the unexpected behavior came along. The field indeed got the value of the Title field, but if I sorted the list by this field, it behaved exactly as if the field contained a single line of text. So again, 1000 comes before 9. Checked if I indeed set the calculated field to be a number and well, I did. The value simply wasn't treated as a number but as text. Strange...

So I started digging in my development experience and I decided to make a little change to the expression. I changed the expression to "=[Title] + 0". And what do you know? The calculated field now indeed returned a number and was treated like a number. Sorting by the field now produced the expected behavior. File number 9 now comes before file number 10000.

Cheers,

Wes

Google Analytics and jQuery, happy together

Google Analytics is great out of the box already, but you can do much more than just registering your page loads. Especially with all these “Web 2.0” sites it can be convenient to not register page loads, but events! In this blog post I’ll show you how you can use jQuery in combination with Google Analytics to get a great insight on what actually happens on your website while you’re not looking!

Tracking Events

With Google Analytics you can track custom events if you like. This gives you the power to register f.e.:

  • Who’s been hovering over my “Buy” button?
  • Who’s been clicking the “Download” link?
  • Who’s been clicking the jQuery.UI.Tab?
  • Who’s been clicking the advertisements?
  • Who’s been clicking on which external links?

The Google Analytics method to register these events is:

pageTracker._trackEvent(category, eventType, optional_label, optional_value) 

optional_label is useful for filtering and optional_value is somewhat special. If you pass an integer to the value parameter, Google analytics will aggregate the value for you. So if you have an ad campaign, you can keep track of how many people clicked the advertisements AND you can see right away how much money you’ve earned with that.

There’s a nice explanation on the _trackEvent method over here: http://code.google.com/apis/analytics/docs/tracking/eventTrackerGuide.html

jQuery Plugin

It’s quit cumbersome however to attach an event handler to all these items on your page. So I’ve created a jQuery plugin to make life easier for you and in it’s simplest form you can use it like this:

$("selector").trackEvent(optional_options);

What this does is that it will call _trackEvent if someone clicks the selected elements once per element. It will use the element nodeName as category variable and the href, value, id or text (first non empty in that order) as label variable. For the value variable it uses 1.

So if my html looks like this:

<script type="text/javascript">
$(function(){
  $("a").trackEvent();
});
</script>

<a href="http://www.virtuelekassa.net">
     ASP.Net iDeal library</a>
<a href="http://www.virtuelekassa.net/downloads/library.zip">
     Download Library</a>

Than if someone clicks the first hyperlink, the event gets tracked like this:

pageTracker._trackEvent("A",
                        "click",
                        "http://www.virtuelekassa.net",
                         1);

If someone clicks the second link, it will be tracked like this:

pageTracker._trackEvent("A",
                        "click",
                        "http://www.virtuelekassa.net/downloads/library.zip",
                         1);

These events will be tracked only once per element click per page load.

External links

This of course can be very helpful in registering how many people navigate away from your web site through external links. So I’ve added an extra filter expression to select all external links on the page. You can use it like this:

$(function(){
    $("a:external").trackEvent();
});

With this in place, all hyperlinks on your web site that point to external web sites get the event tracking behavior.

Flexible

Although the defaults are fine for me, they might not be for you, so the plugin is flexible enough to adjust it to your likings. The optional options variable looks like this:

var settings = {
    eventType : string,
    once      : bool,
    category  : string or function,
    action    : string or function,
    label     : string or function,
    value     : int or function,
};
Example 1

Lets say you would like to track every click on your jQuery.UI.Tab, with:

  • “Tab” as the category value
  • The text of the tab as label value

Your jQuery load function would look somewhat like this:

var eventTrackingOptions = {
    once    : false,
    category: "Tab",
    label   : function(event){ return $(this).text(); }
};

$(function(){
    $("#tabContainer li").trackEvent(eventTrackingOptions)
});
Example 2

Lets say you have a nice picture of your boy- or girlfriend on your page and would like to track every mouseenter AND click on this lovely picture.

Your jQuery load function would look somewhat like this:

var eventTrackingOptions = {
    eventType: "mouseenter click",
    once     : false,
    category : "Predators",
    label    : "My sweet hearth"
};

$(function(){
    $("img.myLoverPic").trackEvent(eventTrackingOptions)
});
Example 3

Lets say you have some advertisements on your page, and you get payed 2 cents on each click. You can now keep track yourself on how much much money you are making like this.

<script type="text/javascript">
var eventTrackingOptions = {
    category: "Advertisements",
    label   : function(event){ return $(this).attr("rel"); },
    value   : 0.02
};

$(function(){
    $(".advertisementLink").trackEvent(eventTrackingOptions)
});
</script>

<!-- some html -->
<ul>
    <li>
        <a class="advertisementLink"
           rel="CompanyOne"
           href="http://companyone.com">Buy here!</a></li>
    <li>
        <a class="advertisementLink"
           rel="CompanyTwo"
           href="http://companytwo.com">Or here!</a></li>
</ul>

The Source

I guess you’re all bored by now and curious about the source… so here it is:

 /** 
  * Version 1.0
  * March 27, 2010
  *
  * Copyright (c) 2010 Wesley Bakker
  * Licensed under the GPL licenses.
  * http://www.gnu.org/licenses/gpl.txt
  **/
(function($) {
    var methods = {
        getOptionValue: function(value, elem, event) {
            if ($.isFunction(value)) {
                value = value.call(elem, event);
            }

            return value;
        },
        getCategory: function() {
            return this.nodeName;
        },
        getAction: function(event) {
            return event.type;
        },
        getLabel: function() {
            var self = $(this);
            if (self.is("a")) {
                return self.attr("href");
            }
            else if (self.is("input")) {
                return self.val();
            }
            else if (self.attr("id")) {
                return self.attr("id");
            }
            else {
                return self.text();
            }
        }
    };

    $.expr[':'].external = function(elem) {
        return (elem.host && elem.host !== location.host) === true;
    };

    $.fn.trackEvent = function(options) {
        var settings = {
            eventType : "click",
            once      : true,
            category  : methods.getCategory,
            action    : methods.getAction,
            label     : methods.getLabel,
            value     : 1
        };

        if (options) $.extend(settings, options);

        this.each(function(i) {
            var eventHandler = function(event) {
                var category = methods.getOptionValue(settings.category, this, event);
                var action   = methods.getOptionValue(settings.action  , this, event);
                var label    = methods.getOptionValue(settings.label   , this, event);
                var value    = methods.getOptionValue(settings.value   , this, event);

                //alert(category + "||" + action + "||" + label + "||" + value);
                pageTracker._trackEvent(category, action, label, value);
            };

            if (settings.once) {
                $(this).one(settings.eventType, eventHandler);
            }
            else {
                $(this).bind(settings.eventType, eventHandler);
            }
        });

        return this;
    };
})(jQuery);

Conclusion

With this straight forward jQuery plugin it’s a piece of cake to keep track of what’s happening on your page while you’re not there.

Cheers,

Wes

Get rid of the style="border-width:0px" from the Image tag.

I do have pet projects in which I try to get every single nitty gritty detail right. And then it bothers me that by default the ASP.NET Image control adds a style="border-width:0px" to the rendered image tag even though I never asked for it. Not even does it add the style attribe without asking, it doesn't offer a way to get rid of it! You can get rid of it though!

ControlAdapters

Fortunately ASP.NET does come with control adapters, so we can do something about it. Lets first create a new .browser file in the App_Browser directory to tie up our control adapter to the rendering of the Image control. The contents should like lik this:

<?xml version="1.0" encoding="utf-8" ?>
<browsers>
  <browser refID="Default">
    <controlAdapters>
      <adapter
        controlType="System.Web.UI.WebControls.Image"
        adapterType="WMB.VirtueleKassa.WebControls.ImageAdapter" />
    </controlAdapters>
  </browser>
</browsers>

Next thing to do is to create the WMB.VirtueleKassa.WebControls.ImageAdapter. Create a new class file in the App_Code directory. The code for the ImageAdapter should look like this:

!! have a look at the comment of RichardD! He proposed a better solution !!

using System;
using System.IO;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.Adapters;

namespace WMB.VirtueleKassa.WebControls { 
    public class ImageAdapter : WebControlAdapter {
        public ImageAdapter() {
        }

        protected override void RenderBeginTag(HtmlTextWriter writer) {
            Image img = this.Control as Image;

            if (img.BorderWidth.IsEmpty) {
                string origTag = string.Empty;

                using (StringWriter sw = new StringWriter())
                using (HtmlTextWriter hw = new HtmlTextWriter(sw)) {
                    base.RenderBeginTag(hw);
                    hw.Flush();
                    hw.Close();

                    origTag = sw.ToString();
                }

                string newTag = origTag.Replace("border-width:0px;", "");
                newTag = newTag.Replace(" style=\"\"", "");
                writer.Write(newTag);
            }
            else {
                base.RenderBeginTag(writer);
            }
        }

        protected override void RenderEndTag(HtmlTextWriter writer) {
        }

        protected override void RenderContents(HtmlTextWriter writer) {
        }
    }
}

And thats it! If you do apply a width yourself, the tag will be saved, and so will any other style tags. Only if, after the deletion of the border-width:0px; the style attribute remains empty, the complete style attribute will be removed as well. Have a look at the source of this page to see the result.

Regards,

Wesley

Posted: Mar 22 2010, 11:23 AM by webbes | with 7 comment(s)
Filed under:
Remove page flicker in IE8

In this pet project of mine I have two large background images. Unfortunately this means that IE will display a nasty page flicker on each request. I used to get rid of this with the a metatag that looks like this:

    <!--[if IE]>
        <meta http-equiv="Page-Exit" content="blendTrans(Duration=0.0)" />
    <![endif]-->

For some reason however, this doesn't work in IE8 with cached pages. Use this tag instead and it all works fine again.

    <!--[if IE]>
        <meta http-equiv="Page-Exit" content="Alpha(opacity=100)" />
    <![endif]-->

Cheers,

Wes

Asynchronous DataContext

Triggered by a blog post of Scott Hanselman I was wondering if I could create a generic extension method to asynchronously retrieve objects from a DataContext. And well I could. I ended up with two classes.

The ExecuteAsyncState:

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Data.Linq;
   4:  using System.Data.SqlClient;
   5:  using System.Threading;
   6:   
   7:  namespace AsyncDataAccess {
   8:      public class ExecuteAsyncState<T> {
   9:          public ExecuteAsyncState(DataContext dataContext, SqlCommand sqlCommand, Action<IEnumerable<T>> onReady) {
  10:              this.DataContext = dataContext;
  11:              this.SqlCommand = sqlCommand;
  12:              this.OnReady = onReady;
  13:              this.WaitHandle = new AutoResetEvent(false);
  14:          }
  15:   
  16:          public DataContext DataContext { get; private set; }
  17:          public SqlCommand SqlCommand { get; private set; }
  18:          public Action<IEnumerable<T>> OnReady { get; private set; }
  19:          public AutoResetEvent WaitHandle { get; private set; }
  20:      }
  21:  }

And the DataContextUtility:

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Data.Linq;
   4:  using System.Data.SqlClient;
   5:  using System.Linq;
   6:   
   7:  namespace AsyncDataAccess {
   8:      public static class DataContextUtility {
   9:          public static IAsyncResult ExecuteAsync<T>(this DataContext dataContext, IQueryable<T> query, Action<IEnumerable<T>> onReady) {
  10:              SqlCommand sqlCommand = dataContext.GetCommand(query) as SqlCommand;
  11:   
  12:              ExecuteAsyncState<T> asynchState = new ExecuteAsyncState<T>(dataContext, sqlCommand, onReady);
  13:   
  14:              AsyncCallback callback = new AsyncCallback(EndExecuteAsync<T>);
  15:              return sqlCommand.BeginExecuteReader(callback, asynchState);
  16:          }
  17:   
  18:          private static void EndExecuteAsync<T>(IAsyncResult result) {
  19:              ExecuteAsyncState<T> asynchState = result.AsyncState as ExecuteAsyncState<T>;
  20:   
  21:              DataContext dataContext = asynchState.DataContext;
  22:              SqlCommand sqlCommand = asynchState.SqlCommand;
  23:              Action<IEnumerable<T>> onReady = asynchState.OnReady;
  24:   
  25:              SqlDataReader reader = sqlCommand.EndExecuteReader(result);
  26:              var resultData = from item in dataContext.Translate<T>(reader)
  27:                               select item;
  28:   
  29:              try {
  30:                  onReady.Invoke(resultData);
  31:              }
  32:              finally {
  33:                  reader.Close();
  34:                  asynchState.WaitHandle.Set();
  35:              }
  36:          }
  37:      }
  38:  }

These two classes enable you to retrieve your object asynchronously. Like so:

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text;
   5:  using System.Threading;
   6:   
   7:  namespace AsyncDataAccess {
   8:      class Program {
   9:          static void Main(string[] args) {
  10:              using (SampleDbContextDataContext context = new SampleDbContextDataContext()) {
  11:                  context.Connection.Open();
  12:                  
  13:                  var customerQuery = from Customer c in context.Customers
  14:                                      select c;
  15:   
  16:                  IAsyncResult customerResult =
  17:                      context.ExecuteAsync<Customer>(customerQuery, (customers) => {
  18:                                                                         foreach (var c in customers) {
  19:                                                                             Console.WriteLine(c.ToString());
  20:                                                                         }
  21:                                                                     });
  22:   
  23:                  var productQuery = from Product p in context.Products
  24:                                     select p;
  25:   
  26:                  IAsyncResult productResult =
  27:                      context.ExecuteAsync<Product>(productQuery, (products) => {
  28:                                                                       foreach (var p in products) {
  29:                                                                           Console.WriteLine(p.ToString());
  30:                                                                       }
  31:                                                                   });
  32:   
  33:                  Console.WriteLine("Before the queries are returned:");
  34:   
  35:                  ExecuteAsyncState<Customer> customerState = customerResult.AsyncState as ExecuteAsyncState<Customer>;
  36:                  ExecuteAsyncState<Product> productState = productResult.AsyncState as ExecuteAsyncState<Product>;
  37:   
  38:                  WaitHandle[] waitHandles = new[] { customerState.WaitHandle, productState.WaitHandle };
  39:                  WaitHandle.WaitAll(waitHandles);
  40:   
  41:                  Console.WriteLine("After the queries are returned:");
  42:   
  43:                  Console.ReadLine();
  44:              }
  45:   
  46:          }
  47:      }
  48:  }

And that results in a screen like this:

image

Where you’ll see it prints my name first, then a product name, then the rest of the customer names to finish of with the rest of the product names.

Conclusion

It was very easy to get this result and you could of course add some more methods that simply return a single object instead of an IEnumerable<T>.

The code is attached so you can try it yourself.

Cheers and have fun,

Wesley

Sandboxed Solution Screencast

Last Januari I was presenting at the SharePoint Connections 2010 in Amsterdam. As a non Microsoft speaker they do not record your presentation so I decided to create a screencast on the SharePoint 2010 Sandboxed Solutions subject myself. I'm not very good at screencasts but it does contain some useful information and some nice code samples.

Goto Screencast

The code samples and slidedeck are attached.

Cheers,

Wesley Bakker

BetterImageProcessor is on codeplex

Finally Released to the open!

For quit some time now, I have had the BetterImageProcessor on-line at http://www.waterwijkers.nl/bip. I received an awful lot of feedback on it and it was downloaded thousands of times. About half a year ago I decided I should port it to .Net 3.5 and maybe place it on CodePlex. Well today is the day. The Aplha version is released on CodePlex! Have a look at http://bip.codeplex.com/ and just let me know if you digg it.

Cheers,

Wes

jQuery hoverImage plugin

At the project I’m currently reviewing they’ve used a lot of hover effects for images and links containing images. You can use CSS’s :hover for it, but that means you’ll have to add a new CSS line for each hover image and that it doesn’t work in IE6 for images. So they’ve decided to use a different approach. On all these images that needed a swap effect, they’ve added some JavaScript in the “onmouseover” and “onmouseout” and that’s something I wasn’t pleased with cause it was all cluttering the html, it asked for typo’s and they were registering handlers on the wrong events(they should have used "onmouseenter" and "onmouseleave"). And then I noticed they have jQuery registered on that same page.

Writing a plugin

I really love the cross-browser compatibility jQuery gives me out of the box and the ease of use. So it was time to write a jQuery plug-in. Let’s not bother you any longer with an introduction and show you the code for such a simple plug-in.

(function($) {
    $.fn.extend({
        hoverImage: function(options) {
            var defaults = { src: "-hover", preload: true, replaceEnd: "" };
            options = $.extend(defaults, options);

            var append = options.src.indexOf(".") == -1;

            var splitter;
            if (append) {
                splitter = options.replaceEnd + ".";
            }

            return this.each(function() {
                var obj = $(this);
                var img = obj.is("[src]") ? obj : obj.children("[src]:first").eq(0);

                if (!img.is("[src]")) {
                    return true;
                }

                var oSrc = img.attr("src");
                img.data("oSrc", oSrc);

                var hSrc = options.src;
                if (append) {
                    hSrc = oSrc.split(splitter).join(hSrc + ".");
                }
                
                img.data("hSrc", hSrc);

                if (options.preload) {
                    new Image().src = hSrc;
                }

                obj.hover(function() { img.attr("src", img.data("hSrc")); },
                          function() { img.attr("src", img.data("oSrc")); });
            });
        }
    });
})(jQuery);
 

So what does this code do?

It is a default plug-in with some settings.

  • src
    You can define a complete URL to an image to use on hover OR the string to append to the default src of the image. The default value for this is “-hover” which means that if the default src of your image is “mybutton.gif” on hover that will be replaced by “mybutton-hover.gif”.
  • preload
    Determines if you would like to preload the hover image. The default is true so your hover effect will show almost instantly.
  • replaceEnd
    This is an extra option to replace a value at the end of the default image src on hover. If your default image url is “mybutton-default.gif” and your hover image url is “mybutton-hover.gif” you need to replace “-default” at the end of the image url.

If the object on which this plug-in is called does not have a “src” attribute, it wil try to get the first child that has. The hover effect will still take place on hovering the parent, but the child its image src will be changed.

Here’s a sample of html in which the plug-in is used.

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>hoverImage test page</title>
    <script src="/ClientScript/jquery-1.3.2.min.js" type="text/javascript"></script>
    <script src="/ClientScript/jquery.hoverImage.js" type="text/javascript"></script>

    <script type="text/javascript">
        $(function() {
            $(".standardImage").hoverImage({replaceEnd: "-standard"});
            $(".hoverImage").hoverImage();
        });
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <img class="standardImage" src="Imgs/button-standard.gif" />
        <img class="hoverImage" src="Imgs/button.gif" />
        <a class="hoverImage" href="#" style="display:block; width:100%;border:solid 1px black;">
            <img src="Imgs/button.gif" />
        </a>
    </div>
    </form>
</body>
</html>

Works like a charm!

Cheers and have fun!

Wesley

Posted: Dec 25 2009, 02:27 PM by webbes | with 3 comment(s)
Filed under: ,
More Posts « Previous page - Next page »