65.9K
CodeProject 正在变化。 阅读更多。
Home

构建一个类似于 Facebook 图片标签的 ImageTag Extender - 第一部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (14投票s)

2008年6月4日

CPOL

5分钟阅读

viewsIcon

52105

downloadIcon

1265

一篇关于构建类似 Facebook 图片标签的 ImageTag Extender 的文章

引言

几天前,我的一位客户要求一个图片标注控件,看起来像 Facebook 的照片标注应用程序。那个是基于 PHP 构建的,但我的应用程序是基于 ASP.NET 的,所以我需要用 ASP.NET 和 Ajax 找到一些解决方案。结果就是这个 ImageTag 扩展器,它允许用相应的名字标签来标注图片,并通过 Ajax 启用的 Web 服务进行存储。在深入了解这个扩展器之前,我们将讨论 Ajax 控件工具包扩展器的基础知识。

ASP.NET Ajax 扩展器

Atlas 带来了扩展器的概念,为控件提供客户端行为。扩展器将 JavaScript、DHTML 和样式表附加到它们作为目标控件的控件上。

ImageTag 扩展器

我的 ImageTag 扩展器以一个 ASP Image 控件作为目标控件。它还接受几个属性,例如另一个用作保存按钮的图像、一个接受标签名称的文本框,以及一个 ASP Panel 控件的 Container 类名,该类名将包含 TextBoxButton,以及一个 <div>,它将定位标注区域,并在用户单击某个点进行标注时可见。

ImageTagExtender 的声明如下

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

Container Panel 的代码如下

<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>

标签扩展器的想法是捕获用户单击图片的点,并通过 Web 服务方法将该点与标签名称一起保存。

在以查看模式加载图像时,通过另一个 Web 方法加载已标注的点,并在那里放置透明的 <div> 对象,以及标签名称,当用户将鼠标悬停在 <div> 区域上时可见。

我使用 Visual Studio 2008 ASP.NET Ajax 扩展器项目模板创建了扩展器,因为它会为你的 web.config 文件准备所需的声明。你也可以在 Visual Studio 2005 中创建扩展器。

我的 Extender 类如前所述,需要三个属性,并且它以图像对象作为目标,因此你必须在 ImageTagExtender 类中声明这些属性,并定义扩展器的设计器类型和行为文件。

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

我的属性声明如下

[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);
        }
    }

行为类

在 ASP.NET Ajax 库中,你可以使用 registerNamespace 方法为你的脚本类定义 namespace

Type.registerNamespace('ImageTagExtender');

行为文件包含所有客户端方法和属性。

行为文件的第一个方法是初始化器,它初始化所有 JavaScript 对象。

 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');
    },

这里 $addHandler 是一个 JavaScript 快捷方式,用于动态地将处理程序添加到对象。它接受对象、事件名称和处理程序作为参数。

接下来,你必须在脚本中声明属性。这里有一个强制要求,就是严格遵循以下属性约定

(get + _ + PropertyName)

这是我的属性声明

    // 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;
    },

onclick 函数将容器面板放置在单击的点上,并获取该点的标签名称。

AjaxControlExtender1

以下是 _onclick 函数的代码

 _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);

  }

这里 _findChildByClass 函数返回所有具有指定类名的对象。我从 Omar Al Zabir 的 Drop Things 项目中获得了从对象中抓取这个想法。

 _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;
        }
    },

一旦我抓取了浮动面板,我就捕获了单击点的鼠标单击事件。我还减去了 scrollXscrollY 以精确到原始单击点的图像位置。目前,此代码仅支持 Internet Explorer 4+,我正在努力实现跨浏览器兼容。然后我将浮动面板放置在单击点上。

保存标签

一旦浮动面板放置好,用户就在文本框中输入标签名称并单击“保存”按钮。现在我的 _onbuttonclick 函数被触发,以调用 Web 服务来保存位置和标签名称。

_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);
 },

这里我抓取浮动面板的位置和文本框的值,并将它们传递给 Web 服务方法 TaggReciever.TaggReciever.SetTag

Ajax 库使用服务的完全限定名称(即 Namespace.ClassName.MethodName)。它接受三个参数

  1. 方法参数数组
  2. 函数成功回调
  3. 函数失败回调

如果 Web 方法接受单个参数,则可以直接传递;如果它接受多个参数,则必须以数组形式传递。接下来,成功回调函数和失败回调函数分别是成功和失败的异步回调时调用的函数名称。

在这里,在成功回调函数中,你将获得服务方法的返回值。在这个例子中,我们的服务返回一个 string ,其中包含标签的描述。

这是我的成功回调函数 _setPosition 的代码

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

我只是用结果值弹出一个提示

这是 Web 方法的代码

[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]);
       }

这里有一个重要的事项,就是你必须通过向 Web 服务类添加 [System.Web.Script.Services.ScriptService] 属性来使服务可以通过 Ajax 调用进行访问。在这里,你可以看到 set tag 方法只是返回参数。你可以保存这些值或做任何你想做的事情。

在失败的回调函数中,你可以处理从服务返回的错误。

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

结论

在本文中,我讨论了扩展器以及如何通过服务调用保存标签。在本文的下一部分,我将讨论一个用于显示标签的扩展器,以及一些关于解析 XML 或 JSON 序列化器的 Web 服务返回值的技巧。

历史

  • 2008 年 6 月 2 日:初次发布
© . All rights reserved.