设计有复杂客户端Script的服务器控件
    使用ASP.NET的服务器控件,可以极大的提高我们Web开发中组件的复用性。不过由于ASP.NET默认的__doPostBack传输机制,对于我们在Web页上开发DHMTL特性的功能没有什么实质的帮助,因为我们不可能每次HTML属性修改都doPostBack,这样的效率和用户体验会死人的哦~~
所以这时候使用客户端Script是一个很好的办法。那我们就用呗,可是问题就谁之而来了。使用Script来操作DHTML,需要获得被操作HTML元素对象的引用(废话,不然怎么操作?!)。我们最常用的方法是:使用HTML元素的ID来引用或者使用document对象的方法:getElementById、getElementsByName 和 getElementsByTagName。由于getElementsByName 和 getElementsByTagName获得的都是一个HTML元素对象集合,所以最常用的就是getElementById了。可是对于ASP.NET的服务器端控件,要在该控件PreRender的时候才能准确的获得其ClientID属性。而且就算我们取到了HTML元素的ID,我们还需要把这个ID hard-coding到被Register到页面的Script里去。这里的复杂程度就可大可小很难说了 。
。
这样一来每次修改Script都要很麻烦的到控件的RegisterClientScript方法里去修改Script,并处理好需要动态写入Script的参数。更麻烦的是如果页面允许多个这样的控件Render,还需要考虑会不会ID冲突等问题,虽然ASP.NET控件Render时候会自动保证控件和其子控件的ID是unique的,可是其它的HTML元素就比较的麻烦了。而且大量的ID使用,也会给Web的设计带来一些小麻烦,因为也存在需要避免ID重复的问题。
所以在我管理有复杂客户端Script的服务器控件时,我都不给其中的HTML元素设置ID,并且也不在客户端脚本中使用任何和HTML元素ID有关的方法去引用它们。那么怎么引用呢?使用HTML元素在DHTML对象树中的Hierarchy来查找它们就行了。当我们在IE中操作这个"控件"(已经就是HTML元素了)时,在该控件最外层作为container的HTML元素里俘获event,然后使用其event.srcElement,就可以以它为起点来查找需要的HTML元素了。比如:
 <table border="1" ondblclick="ShowOrHideSpan(this)" style="border-collapse: collapse;">
<table border="1" ondblclick="ShowOrHideSpan(this)" style="border-collapse: collapse;">
 <tr>
    <tr>
 <td name="title">
         <td name="title">
 Tilte</td>
             Tilte</td>
 </tr>
    </tr>
 <tr>
    <tr>
 <td>
         <td>
 <span>Content: asdf asdf asdf</span></td>
             <span>Content: asdf asdf asdf</span></td>
 </tr>
    </tr>
 </table>我们想实现double click表格中的Title来隐藏或显示Content,怎么弄呢?
</table>我们想实现double click表格中的Title来隐藏或显示Content,怎么弄呢?
使用如下JScript代码:
 <script language="javascript">
<script language="javascript">
 function ShowOrHideSpan(elmt)
function ShowOrHideSpan(elmt)
 {
{
 var srcElmt = event.srcElement;
    var srcElmt = event.srcElement;
 if ( srcElmt && srcElmt.tagName == 'TD' && srcElmt.name == 'title' )
    if ( srcElmt && srcElmt.tagName == 'TD' && srcElmt.name == 'title' )
 {
    {
 var span = FindChildElement(elmt, 'SPAN');
         var span = FindChildElement(elmt, 'SPAN');
 if ( span.style.display == 'none' )
         if ( span.style.display == 'none' )
 {
         {
 span.style.display = 'inline';
              span.style.display = 'inline';
 }
         }
 else
         else
 {
         {
 span.style.display = 'none';
              span.style.display = 'none';
 }
         }       
 }
    }   
 }
}
 </script>    这段Script中的FindChildElement(elmt, 'SPAN')就是在以elmt为起点遍历DHMTL对象树,一找出SPAN元素。当然这个示例只是一个demo,或许你会说,把事件监听放到第一个tr上多好,可那不是我们在这个demo中关心的东西。在实际的控件开发中可能会比这复杂的多,不过原理就是这样弄了,完全不依赖任何的ID。
</script>    这段Script中的FindChildElement(elmt, 'SPAN')就是在以elmt为起点遍历DHMTL对象树,一找出SPAN元素。当然这个示例只是一个demo,或许你会说,把事件监听放到第一个tr上多好,可那不是我们在这个demo中关心的东西。在实际的控件开发中可能会比这复杂的多,不过原理就是这样弄了,完全不依赖任何的ID。
不过这种方式也还是有些不足,因为有时我们会发现FindChildElement相对于FindParentElement不是那么容易确定,一个起点元素的parent肯定是唯一的,而它的child显然可能是很多个。不过在我觉得在控件开发中注意一下HTML元素的Hierarchy比管理它们ID要容易得多,而且也不会对Script产生太大的影响。
欢迎您的讨论和建议~~
附一、FindChildElement(element, tagName)源代码(先深遍历):
 <script lanuage="JavaScript">
<script lanuage="JavaScript">
 function FindChildElement(element, tagName)
function FindChildElement(element, tagName)
 {
{
 var isFounded = false;
    var isFounded = false;
 var elements = element;
    var elements = element;
 var result = element;
    var result = element;
 if ( element.tagName == tagName )
    if ( element.tagName == tagName )
 {
    {
 return element;
        return element;
 }
    }
 while(!isFounded && elements != null && result != null && result.tagName != tagName)
    while(!isFounded && elements != null && result != null && result.tagName != tagName)
 {
    {
 elements = elements.childNodes;
        elements = elements.childNodes;
 for( var i=0 ; elements != null && i < elements.length ; i++ )
        for( var i=0 ; elements != null && i < elements.length ; i++ )
 {
        {
 result = elements.item(i);
            result = elements.item(i);
 var result2 = FindChildElement(result, tagName);
            var result2 = FindChildElement(result, tagName);
 if ( result == null || result2 == null )
            if ( result == null || result2 == null )
 {
            {
 continue;
                continue;
 }
            }
 if ( result.tagName == tagName || result2.tagName == tagName )
            if ( result.tagName == tagName || result2.tagName == tagName )
 {
            {
 if ( result2.tagName == tagName )
                if ( result2.tagName == tagName )
 {
                {
 result = result2;
                    result = result2;
 }
                }
 isFounded = true;
                isFounded = true;
 break;
                break;
 }
            }
 }
        }
 }
    }
 if ( isFounded )
    if ( isFounded )
 {
    {
 return result;
        return result;
 }
    }
 else
    else
 {
    {
 return null;
        return null;
 }
    }
 }
}
 </script>    在某些时候我们可以不用这个遍历算法,而使用element.all.tags(tagName)来获取Child Element,这个依赖于该页面的HTML元素之间的Hierarchy。
</script>    在某些时候我们可以不用这个遍历算法,而使用element.all.tags(tagName)来获取Child Element,这个依赖于该页面的HTML元素之间的Hierarchy。
附二、FindParentElement(element, tagName)源代码:
 <script lanuage="JavaScript">
<script lanuage="JavaScript"> </script>
</script>
所以这时候使用客户端Script是一个很好的办法。那我们就用呗,可是问题就谁之而来了。使用Script来操作DHTML,需要获得被操作HTML元素对象的引用(废话,不然怎么操作?!)。我们最常用的方法是:使用HTML元素的ID来引用或者使用document对象的方法:getElementById、getElementsByName 和 getElementsByTagName。由于getElementsByName 和 getElementsByTagName获得的都是一个HTML元素对象集合,所以最常用的就是getElementById了。可是对于ASP.NET的服务器端控件,要在该控件PreRender的时候才能准确的获得其ClientID属性。而且就算我们取到了HTML元素的ID,我们还需要把这个ID hard-coding到被Register到页面的Script里去。这里的复杂程度就可大可小很难说了
 。
。这样一来每次修改Script都要很麻烦的到控件的RegisterClientScript方法里去修改Script,并处理好需要动态写入Script的参数。更麻烦的是如果页面允许多个这样的控件Render,还需要考虑会不会ID冲突等问题,虽然ASP.NET控件Render时候会自动保证控件和其子控件的ID是unique的,可是其它的HTML元素就比较的麻烦了。而且大量的ID使用,也会给Web的设计带来一些小麻烦,因为也存在需要避免ID重复的问题。
所以在我管理有复杂客户端Script的服务器控件时,我都不给其中的HTML元素设置ID,并且也不在客户端脚本中使用任何和HTML元素ID有关的方法去引用它们。那么怎么引用呢?使用HTML元素在DHTML对象树中的Hierarchy来查找它们就行了。当我们在IE中操作这个"控件"(已经就是HTML元素了)时,在该控件最外层作为container的HTML元素里俘获event,然后使用其event.srcElement,就可以以它为起点来查找需要的HTML元素了。比如:
 <table border="1" ondblclick="ShowOrHideSpan(this)" style="border-collapse: collapse;">
<table border="1" ondblclick="ShowOrHideSpan(this)" style="border-collapse: collapse;"> <tr>
    <tr> <td name="title">
         <td name="title"> Tilte</td>
             Tilte</td> </tr>
    </tr> <tr>
    <tr> <td>
         <td> <span>Content: asdf asdf asdf</span></td>
             <span>Content: asdf asdf asdf</span></td> </tr>
    </tr> </table>
</table>使用如下JScript代码:
 <script language="javascript">
<script language="javascript"> function ShowOrHideSpan(elmt)
function ShowOrHideSpan(elmt) {
{ var srcElmt = event.srcElement;
    var srcElmt = event.srcElement; if ( srcElmt && srcElmt.tagName == 'TD' && srcElmt.name == 'title' )
    if ( srcElmt && srcElmt.tagName == 'TD' && srcElmt.name == 'title' ) {
    { var span = FindChildElement(elmt, 'SPAN');
         var span = FindChildElement(elmt, 'SPAN'); if ( span.style.display == 'none' )
         if ( span.style.display == 'none' ) {
         { span.style.display = 'inline';
              span.style.display = 'inline'; }
         } else
         else {
         { span.style.display = 'none';
              span.style.display = 'none'; }
         }        }
    }    }
} </script>
</script>不过这种方式也还是有些不足,因为有时我们会发现FindChildElement相对于FindParentElement不是那么容易确定,一个起点元素的parent肯定是唯一的,而它的child显然可能是很多个。不过在我觉得在控件开发中注意一下HTML元素的Hierarchy比管理它们ID要容易得多,而且也不会对Script产生太大的影响。
欢迎您的讨论和建议~~
附一、FindChildElement(element, tagName)源代码(先深遍历):
 <script lanuage="JavaScript">
<script lanuage="JavaScript"> function FindChildElement(element, tagName)
function FindChildElement(element, tagName) {
{ var isFounded = false;
    var isFounded = false; var elements = element;
    var elements = element; var result = element;
    var result = element; if ( element.tagName == tagName )
    if ( element.tagName == tagName ) {
    { return element;
        return element; }
    } while(!isFounded && elements != null && result != null && result.tagName != tagName)
    while(!isFounded && elements != null && result != null && result.tagName != tagName) {
    { elements = elements.childNodes;
        elements = elements.childNodes; for( var i=0 ; elements != null && i < elements.length ; i++ )
        for( var i=0 ; elements != null && i < elements.length ; i++ ) {
        { result = elements.item(i);
            result = elements.item(i); var result2 = FindChildElement(result, tagName);
            var result2 = FindChildElement(result, tagName); if ( result == null || result2 == null )
            if ( result == null || result2 == null ) {
            { continue;
                continue; }
            } if ( result.tagName == tagName || result2.tagName == tagName )
            if ( result.tagName == tagName || result2.tagName == tagName ) {
            { if ( result2.tagName == tagName )
                if ( result2.tagName == tagName ) {
                { result = result2;
                    result = result2; }
                } isFounded = true;
                isFounded = true; break;
                break; }
            } }
        } }
    } if ( isFounded )
    if ( isFounded ) {
    { return result;
        return result; }
    } else
    else {
    { return null;
        return null; }
    } }
} </script>
</script>附二、FindParentElement(element, tagName)源代码:
 <script lanuage="JavaScript">
<script lanuage="JavaScript"> </script>
</script>
posted on 2005-02-20 11:02 birdshome 阅读(7444) 评论(19) 编辑 收藏 引用 收藏至365Key 所属分类: Jscript&Dhtml开发 、Asp.net控件开发
 
                    
                     
                    
                 
                    
                 

 
        
 
                
             浙公网安备 33010602011771号
浙公网安备 33010602011771号
评论
# re: 设计有复杂客户端Script的服务器控件 回复
服了,你的script造诣# re: 设计有复杂客户端Script的服务器控件 回复
问一下,你每篇文章的标题下面有个google tracker,怎么弄的?连研究一下都懒得去做了:)# re: 设计有复杂客户端Script的服务器控件 回复
在'用脚本为本blog增加了几项自定义功能'那片文章里有说怎么弄得# re: 设计有复杂客户端Script的服务器控件 回复
记性越来越差了,以前看过的~~~麻烦你了,呵呵# re: 设计有复杂客户端Script的服务器控件 回复
佩服# re: 设计有复杂客户端Script的服务器控件 回复
如果一个页面中存在两个datagrid的服务器控件,而这两个datagrid都含有一列是checkbox(header栏也有checkbox--全选的作用),那这时候用你的方法不能起到一选择某个datagrid中的header栏里的checkbox就能实现全选或者全部不选!# re: 设计有复杂客户端Script的服务器控件 回复
怎么不能呢?header里checkbox的onclick='SynchCheck(this)';# re: 设计有复杂客户端Script的服务器控件 回复
如果层次结构变了,或者节点类型变了(都是有可能的需求),相应代码就会变动,对吧?而使用ClientID则不存在这个问题,而且每个需要在Client端操作的DOM对象的ID完全可以使用既定的规则生成(比如统一的前缀 + 本元素类型 + 本元素用途作为名字).
对于DOM对象的控制既可以使用ID管理的方法,也可以用对象树遍历的方法管理,各有千秋.
另:国内开发WebControl的程序员还真不多,很幸运能看到你的Blog。
# re: 设计有复杂客户端Script的服务器控件 回复
如果做页面相关较强的组件,我比较赞同使用ClientID的方式,那样简单,直接,不怕元素的层次变动。不过既然做成了控件,对于使用的人来说改动就少了(基本没有),如果实在要改,就是对控件本身不足的提高,重新整理元素层次和代码是不可避免的。BTW: 也非常希望能听到你的经验之谈:)
# re: 设计有复杂客户端Script的服务器控件 回复
能不能提供一个完整控件操作例子,小人看不明白啊# re: 设计有复杂客户端Script的服务器控件 回复
使用srcElement就不能浏览器兼容了,还是使用dom来定位好些,不过要麻烦点# re: 设计有复杂客户端Script的服务器控件 回复
dom是规范嘛!# re: 设计有复杂客户端Script的服务器控件 回复
其实在ASP.Net控件中继承 INamingContainer 这个接口,子控件的ID就不会出现重复了# re: 设计有复杂客户端Script的服务器控件 回复
@风格你未免太信任M$了,即使ASP.NET的控件都实现了INamingContainer interface,Framework解析ASP.NET页面还是有控件id重名的bug存在。
我在上文中所说的避免ID重名,主要还考虑了手工输入的ID和非ASP.NET控件产生的ID这些混合情况,我的一个观点是,尽量减少ID在同一页面中的数量,是避免潜在ID重名的一个可行方法。
# re: 设计有复杂客户端Script的服务器控件 回复
看看学学!# re: 设计有复杂客户端Script的服务器控件 回复
好,顶# re: 设计有复杂客户端Script的服务器控件 回复
我一直都用elment.getElementsByTagName()来实现那个FindChildrenElement的方法。# re: 设计有复杂客户端Script的服务器控件 回复
@putor还可以用element.all.tags('TagName'),不过我不能确认这样拿到的数组的第一个元素是不是child hierarchy中的第一个我想要找的那个元素。
# 不需要自己写JS,微软已经提供了 回复
微软不太厚道,把JS藏在system.web.dll中了,叫WebForm.js,用WebResource可以访问得到WebForm_GetParentByTagName(element, tagName)
WebForm_GetElementsByTagName(element, tagName)