This is the last post in my four-part series on DotNetNuke Widgets. Here’s a review of the other posts in this series:
In this post, I’ll walk you through the code of the TechBubble CatalogWidget. This Widget displays a carousel of Product thumbnail images. You can click on any thumbnail to see a larger version of the image. You can then move your cursor over any portion of the larger image to see a zoomed in image of the area below the cursor. I created this Widget to convey multiple concepts:
Let’s start by looking at a mockup of the Widget (created using my favorite mockup tool – Balsamiq).
In order to implement this UI, I decided to use two jQuery plugins:
(These are arbitrary choices…I am sure that there are other plugins that would work equally well or better.)
I have setup two live demos of the Widget so you can get a feel for the Widget’s UI:
You can download the Widget at the link below and follow along with the technical discussion that follows. I have also included a short slideshow that shows a screen grab of the installation process and the two demos.
Here are the properties that supported by the CatalogWidget:
photoUrl (required) – A URL to the location where the images for the Widget will be found. The Widget uses the following convention for image names: {label}.{extension} for hi-res image displayed in zoom area, {label}_thumb.{extension} for thumbnail image and {label}_small.{extension} for small image displayed when thumbnail is clicked. {label} and {extension} are explained in the description for other parameters.
theme (optional, default=ui-lightness) – Name of the jQuery UI theme that should be loaded from Google’s CDN for jQuery themes. The list of supported themes can be found on the jQuery Themeroller page. The theme name should match the defined name on the Themeroller page, with spaces in the name replaced with the hyphen character.
carouselWidth (optional, default=500) – Width of the carousel used to display the thumbnails in pixels. The number of thumbnails displayed will depend on the width of each thumbnail and the value of this parameter.
zoomWidth (optional, default=250) – Width of the zoomed image area in pixels.
zoomHeight (optional, default=250) – Height of the zoomed image area in pixels.
moreInfoHandler (optional) - Name of a function that will be called with a parameter of {label} when the information icon that appears to the right of the product name link is clicked. If this parameter is not specified, the information icon will not be displayed.
extension (optional, default=jpg) – The file extension for all images.
Product Data – In addition to these parameters, the Widget supports an unlimited number of additional product data parameters. Any parameter specified other than the above, is treated as product data. The value of the “name” attribute of the parameter will be used for the product {label} and the value of the “value” attribute will be used for the product’s descriptive name. Example: <param name=”ABC1000″ value=”ABC 1000 Super Duper Product” />.
OK, now we have that out of the way, let’s get started with building the Widget. The Widget is going to exist in the namespace “TechBubble.Widgets,” so by convention, the Widget file will be named “TechBubble.Widgets.CatalogWidget.js” and it will be located in ~/Resources/Widgets/User/TechBubble. All resources for this Widget will be in ~/Resources/Widgets/User/TechBubble/CatalogWidgetResources. Here’s the code for namespace registration and the Widget constructor:
Type.registerNamespace("TechBubble.Widgets");
TechBubble.Widgets.CatalogWidget = function(widget)
{
TechBubble.Widgets.CatalogWidget.initializeBase(this, [widget]);
this.catalogResourcesUrl =
$dnn.baseResourcesUrl +
"Widgets/User/TechBubble/CatalogWidgetResources/";
this.photoUrl = "";
this.theme = "ui-lightness";
this.carouselWidth = "800";
this.zoomWidth = "250";
this.zoomHeight = "250";
this.moreInfoHandler = "";
this.extension = "jpg";
this.products = [];
this.hasProducts = false;
if (!TechBubble.Widgets.CatalogWidget.Initialized)
{
$.getScript(this.catalogResourcesUrl +
"scripts/jQuery-ui-1.7.2.custom.min.js");
$.getScript(this.catalogResourcesUrl +
"scripts/jquery.metadata.js");
TechBubble.Widgets.CatalogWidget.Initialized = true;
}
}
TechBubble.Widgets.CatalogWidget.inheritsFrom(
DotNetNuke.UI.WebControls.Widgets.BaseWidget);
TechBubble.Widgets.CatalogWidget.registerClass(
"TechBubble.Widgets.CatalogWidget",
DotNetNuke.UI.WebControls.Widgets.BaseWidget);
In the constructor, we define default values for the various properties (parameters) that the Widget supports. In addition the variable TechBubble.Widgets.CatalogWidget.Initialized is used as a flag to prevent the jQuery UI and jQuery metadata plugin from being loaded. A nice optimization for this code would be to check for the actual existence of the plugin, instead of using a flag. In addition to the constructor, the calls to inheritsFrom() and registerClass() are standard calls for every Widget for setting inheritance and registering the class.
TechBubble.Widgets.CatalogWidget.prototype =
{
render:
function()
{
// Parse widget parameters
this._getParams();
// Load some sample data to display if none
// has been specified
if (!this.hasProducts)
this._getSampleData();
// Create a DIV element and swap out the Widget's
// defining <object> element with it by calling
// the base render() method (can't use jQuery
// shortcut to create element)
var div =document.createElement("div");
div.setAttribute("style","width:" + this.carouselWidth + "px");
TechBubble.Widgets.CatalogWidget.callBaseMethod(this, "render", [div]);
this._renderCarousel();
this._renderZoomer();
},
_getParams:
function()
{
// Enumerate and retrieve parameters
// . . . code omitted
},
_renderCarousel:
function()
{
// Render the Carousel plugin
// . . . code omitted
},
_renderZoomer:
function()
{
// Render the Zoomer plugin
// . . . code omitted
},
_injectStyleSheet:
function(name, isTheme)
{
// Inject a stylesheet into the page
// . . . code omitted
},
_getThumbnailList:
function(list, photoUrl)
{
return(productList);
},
_getSampleData:
function()
{
// Use and display Widget with sample data
// . . . code omitted
}
}
Next, let’s review the code for the Widget’s prototype where the required method render() is declared along with various private methods (prefixed with underscore). In the render() method, we start by getting the parameters. Then, if no products are defined, we use some sample product information. Next, we swap out the <object> element defining the Widget with a <div> element. This <div> element will become the parent container for the rest of the Widget which is rendered in the _renderCarousel() and the _renderZoomer() methods. Next in the code are a number of helper methods. I won’t go into a line-by-line explanation, but highlight a few things I think are noteworthy:
Script Injection: I use the jQuery method $.getScript(scriptUrl, anonymous function) in several places. This is a useful method when you want to inject a script into the DOM and want to ensure that any dependent code is not executed until the asynchronous loading of the script is completed. By putting all the dependent code in an anonymous function defined in the second parameter of this method I could achieve this goal.
Behavior Injection: In _renderZoomer(), I iterate through each thumbnail in the carousel and use the shortcut for the jQuery bind() method $(this).click() to inject a click handler for each of the thumbnail images. This is cleaner and more efficient than adding an onClick attribute when defining the HTML markup for each thumbnail.
DotNetNuke.UI.WebControls.Widgets.renderWidgetType("TechBubble.Widgets.CatalogWidget");
We wrap-up the Widget code by telling the Widget framework to render all Widgets of the type TechBubble.Widgets.CatalogWidget.
This also wraps-up my series on DotNetNuke Widgets. I hope you found the content and examples useful and are inspired to build your own DotNetNuke Widgets.