Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Building an ImageTag Extender like Facebook Image Tagger - Part 1

0.00/5 (No votes)
4 Jun 2008 2  
An article about building an ImageTag Extender like Facebook Image Tagger

Introduction

A few days ago, one of my clients asked for an Image tagging control which looks like the Facebook Photo Tagging application. That one was built on PHP but my application was based on ASP.NET, so I needed to find some solution with ASP.NET and Ajax. The result is this ImageTag Extender that allows to tag an image with respective name tags and that can be stored through Ajax enabled Web service. Before we jump in-depth to understand the extender, we will discuss the basics of Ajax Control Toolkit Extenders.

ASP.NET Ajax Extender

Atlas brought the concept of extenders to provide client side behaviors to controls. Extenders attach JavaScript, DHTML and stylesheets to the control they takes as the target control.

The ImageTag Extender

My ImageTag Extender takes an ASP Image control as the target Control. It also takes few properties such as another image to act as a Save button, a text box which takes the tag name, and a Container class name for a ASP Panel control which will contain the TextBox and the Button and a <div> which will target the tag area and be visible when the user clicks on a point to tag.

The ImageTagExtender declaration will be as follows:

<cc1:ImageTagExtender ID="ImageTagExtender1" runat="server"
    TargetButtonID="Image2"
    TargetControlID="Image1" TargetTextBox="TextBox1" TextContainerClass="Float">
</cc1:ImageTagExtender>

The code for the Container Panel is given below:

<asp:Panel ID="Panel1" runat="server" CssClass="Float">
    <Div style="border-style:dashed;border-width:medium;
        height:50px;width:50px;border-color:Black;z-index:10">
    </Div>

    <asp:Image ID="Image2" runat="server" Height="32px" ImageUrl="~/BlueHills.jpg"
        Width="46px" />

    <asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
</asp:Panel>

The idea of the tag extender is to grab the point where the user clicks on the picture and save that point along with the tag name through a Web service method.

And while loading the image in view mode, load the tagged points through another Web method and place transparent <div> objects there and also please tag names which will be visible when the user moves the mouse over the <div> area.

I created the extender using Visual Studio 2008 ASP.NET Ajax Extender Project template because it prepares your web.config with the required declarations. You can also create the extender in Visual Studio 2005.

My Extender class expects three properties as I mentioned before and it takes an image object as its target, so you have to declare the properties in the ImageTagExtender class and also define the type of designer and the behavior file for the extender.

 [Designer(typeof(ImageTagExtenderDesigner))]
  [ClientScriptResource("ImageTagExtender.ImageTagExtenderBehavior",
      "ImageTagExtender.ImageTagExtenderBehavior.js")]
  [TargetControlType(typeof(Image))]
  public class ImageTagExtender : ExtenderControlBase {

The declaration for my properties is as follows:

[ExtenderControlProperty]
    [DefaultValue("")]
    [IDReferenceProperty(typeof(Image))]
    public string TargetButtonID
    {
      get
      {
        return GetPropertyValue("TargetButtonID", "");
      }
      set
      {
        SetPropertyValue("TargetButtonID", value);
      }
    }

    [ExtenderControlProperty]
    [DefaultValue("")]
    [IDReferenceProperty(typeof(TextBox))]
    public string TargetTextBox
    {
      get
      {
          return GetPropertyValue("TargetTextBox", "");
      }
      set
      {
          SetPropertyValue("TargetTextBox", value);
      }
    }

    [ExtenderControlProperty]
    public string TextContainerClass
    {
        get
        {
            return GetPropertyValue("TextContainerClass", "");
        }
        set
        {
            SetPropertyValue("TextContainerClass", value);
        }
    }

The Behavior Class

In the ASP.NET Ajax library, you can define the namespace for your script class with the registerNamespace method.

Type.registerNamespace('ImageTagExtender');

The behavior file contains all the client side methods and properties.

The first method of the behavior file is the initializer which intializes all JavaScript objects.

 ImageTagExtender.ImageTagExtenderBehavior = function(element) {
    ImageTagExtender.ImageTagExtenderBehavior.initializeBase(this, [element]);
    // TODO : (Step 1) Add your property variables here
    //
    this._targetButtonIDValue = null;
    this._targetTextBox = null;
    this._textContainerClass = null;
}

ImageTagExtender.ImageTagExtenderBehavior.prototype = {
    initialize : function() {
        ImageTagExtender.ImageTagExtenderBehavior.callBaseMethod(this, 'initialize');
        // TODO: add your initialization code here
        $addHandler(this.get_element(), 'click',
        Function.createDelegate(this, this._onclick));
        var sButton = $get(this._targetButtonIDValue) ;
        $addHandler(sButton,'click',Function.createDelegate(this, this._onbuttonclick));
//        this._onclick();
    },

    dispose : function() {
        // TODO: add your cleanup code here
        ImageTagExtender.ImageTagExtenderBehavior.callBaseMethod(this, 'dispose');
    },

Here $addHandler is a JavaScript shortcut to add handlers to objects dynamically. It takes the object, event name and a handler as parameters.

Next, you have to declare the properties in your script. Here one thing is mandatory, that is to strictly follow the property convention as follows:

(get + _ + PropertyName)

Here are my property declarations:

    // TODO: (Step 2) Add your property accessors here
    //
    get_TargetButtonID : function() {
        return this._targetButtonIDValue;
    },
    set_TargetButtonID : function(value) {
        this._targetButtonIDValue = value;
    },
    get_TargetTextBox : function() {
        return this._targetTextBox;
    },
    set_TargetTextBox : function(value) {
        this._targetTextBox = value;
    },

    get_TextContainerClass: function() {
        return this._textContainerClass;
    },

    set_TextContainerClass : function(value) {
        this._textContainerClass = value;
    },

The onclick function places the container panel on the point clicked and gets the tag name for that point.

AjaxControlExtender1

Here is the code for the _onclick function:

 _onclick : function() {

  var panel = this._findChildByClass(document,this._textContainerClass);
  var e = $get(this._targetButtonIDValue);
  var ev = window.event;
  var pos = Sys.UI.DomElement.getLocation(panel);
  var scrollX = document.body.scrollLeft ||
       document.documentElement.scrollLeft;

  var scrollY = document.body.scrollTop ||
       document.documentElement.scrollTop;
     var clickX = window.event.x-pos.x+scrollX;
     var clickY = window.event.y- pos.y+scrollY;
     Sys.UI.DomElement.setLocation(panel,clickX+pos.x-25,clickY+pos.y-25);

  }

Here the function _findChildByClass returns all the objects with a specified class name. I got this idea of grabbing objects from Omar Al Zabir's Drop Things project.

 _findChildByClass : function(item, className)
    {
        // First check all immediate child items
        var child = item.firstChild;
        while( child != null )
        {
            if( child.className == className ) return child;
            child = child.nextSibling;
        }

        // Not found, recursively check all child items
        child = item.firstChild;
        while( child != null )
        {
            var found = this._findChildByClass( child, className );
            if( found != null ) return found;
            child = child.nextSibling;
        }
    },

Once I grabbed the floating panel, then I captured the mouse click event for the clicked points. I also deducted the scrollX and scrollY in order to be precise about the original point clicked on the image. Currently, this code only supports Internet Explorer 4+, I am working on cross browser aspects. Then I just placed the floating panel on the clicked point.

Saving the Tag

Once the floating panel is placed, then the user provides a tag name in the text box and clicks the Save button. Now my _onbuttonclick function gets fired to call a Web service to save the positions and the tag name.

_onbuttonclick : function()  {

    var panel = this._findChildByClass(document,this._textContainerClass);
    var pos = Sys.UI.DomElement.getLocation(panel);
    var text = $get(this._targetTextBox).value;

    var args = new Array(pos.x.toString() ,pos.y.toString(),text);

    TaggReciever.TaggReciever.SetTag(args,this._setPosition,this._onerror);
 },

Here I grab the position for the floating panel and the text value of the text box and pass them to the Web service method TaggReciever.TaggReciever.SetTag.

The Ajax library takes a fully qualified name for the service method (i.e. Namespace.ClassName.MethodName). It takes three parameters:

  1. Method Parameters array
  2. Function Succeeded callback
  3. Function Failed Callback

If the Web method takes a single parameter, then it can be directly passed but if it consumes multiple parameters, then those have to be passed in the form of an array. Next, the Function Succeeded callback and Function Failed callback are the function names that are called at successful and failed asynchronous callbacks respectively.

Here, in success callback function, you will get the return value of the service method. In this example, our service returns back a string with a description of the tag.

Here is the code for my success callback function _setPosition:

 _setPosition : function(result){
alert(result);
  },

I just put an alert with the result value:

Here is the code for the Web method:

[WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [ToolboxItem(false)]
    [System.Web.Script.Services.ScriptService]
    [Serializable]
    public class TaggReciever : System.Web.Services.WebService
    {
        [WebMethod]
    public string SetTag(string[] args)
    {
      //...Save Values
    return string.Format("x:{0},y:{1},Text:{2}", args[0], args[1], args[2]);
       }

One important thing over here is that you have to tag the service consumable through Ajax calls by adding a [System.Web.Script.Services.ScriptService] attribute to the Web service class. Here you can see that the set tag method is just returning the arguments. You can save the values or do whatever you want.

In the failed call back function, you can handle the error returned from the service.

 _onerror : function(error){
  alert('error');
  },

Conclusion

In this article, I discussed about the extender and how to save the tags through service calls. In the next part of this article, I'll discuss about an extender for showing the tags and few more tips about parsing Web service returns from XML or JSON serializer.

History

  • 2nd June, 2008: Initial release

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here