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






4.75/5 (14投票s)
一篇关于构建类似 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
类名,该类名将包含 TextBox
和 Button
,以及一个 <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
函数将容器面板放置在单击的点上,并获取该点的标签名称。

以下是 _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;
}
},
一旦我抓取了浮动面板,我就捕获了单击点的鼠标单击事件。我还减去了 scrollX
和 scrollY
以精确到原始单击点的图像位置。目前,此代码仅支持 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
)。它接受三个参数
- 方法参数数组
- 函数成功回调
- 函数失败回调
如果 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 日:初次发布