DotNetNuke

DotNetNuke Widgets Guide (Part 4 of 4)

January 12, 2010

author:

DotNetNuke Widgets Guide (Part 4 of 4)

This is the last post in my four-part series on DotNetNuke Widgets. Here’s a review of the other posts in this series:

  • Part 1 – Overview of DotNetNuke Widgets
  • Part 2 – DotNetNuke Widgets reference
  • Part 3 – Insights into how you can develop your own Widgets for DotNetNuke

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:

  • Passing data through Widget markup parameters
  • Using a Widget to integrate multiple jQuery plugins seamlessly
  • Using behavior injection to dynamically add event-handling to UI elements
  • Dynamically injecting stylesheets and scripts into the page
  • Using jQuery UI themes

Let’s start by looking at a mockup of the Widget (created using my favorite mockup tool – Balsamiq).

CatalogWidget Mockup

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.

[download id=”8″]

[smooth=id:1;]

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(&quot;TechBubble.Widgets&quot;);
TechBubble.Widgets.CatalogWidget = function(widget)
{
    TechBubble.Widgets.CatalogWidget.initializeBase(this, [widget]);
    this.catalogResourcesUrl =
                    $dnn.baseResourcesUrl +
                    &quot;Widgets/User/TechBubble/CatalogWidgetResources/&quot;;
    this.photoUrl = &quot;&quot;;
    this.theme = &quot;ui-lightness&quot;;
    this.carouselWidth = &quot;800&quot;;
    this.zoomWidth = &quot;250&quot;;
    this.zoomHeight = &quot;250&quot;;
    this.moreInfoHandler = &quot;&quot;;
    this.extension = &quot;jpg&quot;;
    this.products = [];
    this.hasProducts = false;

    if (!TechBubble.Widgets.CatalogWidget.Initialized)
    {
        $.getScript(this.catalogResourcesUrl +
                    &quot;scripts/jQuery-ui-1.7.2.custom.min.js&quot;);

        $.getScript(this.catalogResourcesUrl +
                    &quot;scripts/jquery.metadata.js&quot;);

        TechBubble.Widgets.CatalogWidget.Initialized = true;
    }
}
TechBubble.Widgets.CatalogWidget.inheritsFrom(
            DotNetNuke.UI.WebControls.Widgets.BaseWidget);
TechBubble.Widgets.CatalogWidget.registerClass(
            &quot;TechBubble.Widgets.CatalogWidget&quot;,
            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 &lt;object&gt; element with it by calling
            // the base render() method (can't use jQuery
            // shortcut to create element)
            var div =document.createElement(&quot;div&quot;);
            div.setAttribute(&quot;style&quot;,&quot;width:&quot; + this.carouselWidth + &quot;px&quot;);
            TechBubble.Widgets.CatalogWidget.callBaseMethod(this, &quot;render&quot;, [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(&quot;TechBubble.Widgets.CatalogWidget&quot;);

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.

Co-founder/CTO WhenHub; co-founder DNN Software; founder Edaptable; educator; Open Source proponent; Microsoft MVP; tech geek; creative thinker; husband; dad. Personal blog: http://www.kalyani.com. Twitter: @techbubble
16 Comments
  1. uberVU - social comments

    Social comments and analytics for this post... This post was mentioned on Twitter by techbubble: Blogged: DotNetNuke Widgets Guide (Part 4 of 4) http://bit.ly/8SCKuw...

  2. [...] This post was mentioned on Twitter by Joe Brinkman, Nik Kalyani, Nik Kalyani, Mark Allan, James Wallace and others. James Wallace said: RT @TechBubble Blogged: DotNetNuke Widgets Guide (Part 4 of 4) http://bit.ly/8SCKuw [...]

  3. caroig

    Excellent, a very useful series of articles.

    P.S. The links to the other parts are not working correctly in this page.

    • techbubble

      Thanks for the feedback and for pointing out the problem with the links which I have now corrected.

  4. caroig

    I'm use to working client side and don't really know a great deal about the internals of DNN so bear with me. How can I debug my javascript - I guess the widget js code is not going to be visible to Firebug? Can I log warnings/error to the console during the running of the widget code?
    Is there a way I can 'see' the widgets childnodes? It doesn't appear to be in the DOM so I'm guessing I can throw my current toolset away.

    Thanks

    • techbubble

      You can debug Widget javascript code just like any other JS code. You can use console.log() to log messages to show in FireBug or other add-ons that support the console object. Since Widgets are simply manipulating DOM elements, you can continue to see their effect on the DOM tree. While the HTML source you view in the browser's "View Source" listing will continue to display an <object> element for the Widget, the DOM will accurately reflect the changes made by your code, whether that's adding, removing or changing elements.

      If you add a div to the page with an ID of "DebugConsole" you can take advantage of two framework methods for debugging:

      $DEBUG(s, overwrite) which will cause the string passed as parameter "s" to be displayed in the DebugConsole div. The second parameter "overwrite" is optional and determines whether the console content should be pre-pended or replaced.

      $DEBUGLINE(s, overwrite) is identical except that it appends
      to the string you pass.

      • caroig

        Thanks for the pointers. Investigating a little further it looks like Firebug will do what I want soon - http://www.softwareishard.com/blog/firebug/fire...
        Unfortunately the beta doesn't seem to behave quite like the documentation, setting the brakpoint on the widget request jumps me into debugging the jQuery library .

  5. caroig

    Fantastic, I have a working widget!

    My widget isn't inserting content on the page, it's modfying content that has already been loaded by another module.
    I guess I still have to replace the widget object with an empty div - there's no way to just remove it with the render baseclass.

    • techbubble

      Unfortunately, with the present framework you have to specify a element to replace the <object> element. I use a <span> element in such cases because it is an inline element and it is highly unlikely that (unlike a <div>) there is a global CSS rule for it to have margins/padding or any other visual attribute that might mess with the page.

      Another option is to not call the base render() method and do the work of removing the <object> element yourself:

      this._widget.parentNode.removeChild(this._widget);

      I think this code will work, but have not tested it.

  6. caroig

    Nik, a couple of observations having hacked around with this for a while:-

    You can use something like

    var jParams=$(thisWidget._widget).children();

    to pull the parameters rather than the childnodes - this overcomes 'false' whitespace nodes.

    The $.getScript function does not cache which is a bit a drag for versioned libraries. There is nice article:- http://jamazon.co.uk/web/2008/07/21/jquerygetsc... that suggests an elegant overload.

    Saludos

    • techbubble

      @Saludos,

      Thanks! Those are two excellent tips. I will make these changes in my code and post updates where appropriate. Appreciate your insights.

  7. jkergosi

    This is great! I knew that widgets were built into DNN already. Glad to see documentation on it and working demos. I gave a presentation on this at the Dallas DNN Users Group recently and one question arose: How do you persist user setttings?

    For example, if a widget allows you to drag an object, how to you save its location? I noticed that Pageflakes.com has widget-specific settings that are persisted in one way or another. Being able to persist settings would be key in the personalization aspects of widgets. Any insights?

    Thanks as always.
    Jason

    • jkergosi

      Bump! :)

  8. Kevin McCusker

    Nik,

    Thank you for documenting the DotNetNuke Widgets. Your blog posts have been a big help in developing some widgets for our portals.

    I have a question though! Is it possible for a widget, in the render method, to call a function from a script that is added during the initialization? For example, during the render method, display a dialog window.

  9. Backlink Software

    Hei blog owner. I have a small request. I was just searching for some information on the topic you wrote and found this post. Some really awesome stuff you got here. Can I please share this post on my new website I'm currently workin on? Pretty please :) I will check back again later to see what you answered. Thank you, Simone .

  10. Wenhua Mei

    I'm looking to find this out as well! It seems like this is a great topic to discuss and I'm looking to see what comes from future research.

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.