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

单击一次按钮

starIconstarIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIcon

2.38/5 (14投票s)

2006 年 8 月 4 日

5分钟阅读

viewsIcon

74303

downloadIcon

637

单击按钮/图像按钮时;禁用页面上的所有按钮,这可以确保该按钮只单击一次,而不是不小心单击两次。

Sample Image - ClickOnceButton.gif

引言

这是一个自定义的 Button/ImgBtn,它可以禁用页面上的所有其他按钮(Image Button、<button>、类型为 SubmitResetButton 的 input 元素),一旦按钮被禁用,它们将无法被单击,直到被控件本身重新启用。此功能可确保按钮只单击一次,而不是连续单击多次,而且由于它禁用了页面上的所有其他按钮,因此在一个按钮的处理过程中,不会出现单击其他按钮的情况。

背后的原理

每当单击此 Custom Button/ImgBtn 时,我都会搜索页面上的所有按钮,创建这些按钮的克隆,禁用它们并将它们置于原始按钮之上,这就产生了按钮被禁用的错觉。我避免修改现有按钮的原因是,我不想中断按钮禁用它们时的处理过程,而且我还想保留 document.activeElementbtn.Click() 等功能。

对于 ImageButton,在创建 ImageButton 的克隆时,我将图像的 URL 更改为禁用的,所以必须在同一图像路径中有一个禁用的图像。

特点

  • 您无需编写任何额外的代码,只需将此控件包含在页面中即可,如果您使用的是 ImageButton
  • 按钮的禁用发生在 JavaScript 中,因此按钮在服务器进行任何长时间处理之前就会被禁用。
  • 页面上可以有任何类型的按钮,所有按钮都将被禁用。
  • 按钮被禁用是为了一个特定的时间间隔,这个时间间隔可以为不同的自定义按钮增加或减少。
  • 在控件重新启用所有按钮之前,如果页面提交到服务器并重新加载,您可以看到所有按钮都已重新启用,因为所有操作都在客户端进行,因此状态不会被保留。
  • JavaScript 函数(EnableCustomButton())是公开的,通过这些函数可以重新启用所有按钮,甚至在达到启用间隔之前,例如,如果验证失败,您可以向最终用户发出警报并立即重新启用按钮。
  • 存在一个 JavaScript 函数,您可以利用该函数禁用所有按钮,甚至无需使用提供的自定义按钮,当使用您自己的控件或 HTML 按钮但仍希望在单击按钮时禁用页面时,此 JS 方法可能会很有用。

必备组件

  • 为了显示禁用的图像,必须在同一图像路径中存在“XXX_disable.XXX”,即,如果您的 Image 按钮 URL 是“add.gif”或“btnXLS.jpg”,您还必须在同一图像路径中拥有“add_disable.gif”和“btnXLS_disable.jpg”。
  • 每个按钮输入类型(submitresetbutton)、BUTTON 标签和 Image Button 都必须定义 ID。
  • 如果您不使用此 CustomImageButton,但仍希望其他普通按钮在单击时禁用整个页面,则需要在按钮的“onclick”事件中调用 JavaScript 函数“CustomButtonClicked(this)”。
    例如,在上例中,“Final Save”按钮是一个普通的 ASP Button,但单击它时,它也会禁用整个页面,代码如下:
    btnFinalSave.Attributes.Add("onclick","CustomButtonClicked(this)");

属性

  • DisableTimeOut:单击 CustomImageButton 后,页面上的所有按钮将禁用多长时间。默认值:8000
  • PutDisableScriptAtEnd:按钮的禁用发生在“JavaScript onclick 事件”之后。默认值:false

    示例,当 PutDisableScriptAtEnd = false

    <input onclick="CustomButtonClicked(this);
     if ( !SomeClientSideValidation() ) return false;" ../>

    PutDisableScriptAtEnd = true

    <input onclick="if ( !SomeClientSideValidation() ) return false; 
     CustomButtonClicked(this);" ../>

Using the Code

使用 CustomImageButton

<Tittle:CustomImageButton id=btnXLS onclick=btnExport_Click Runat="server" 
 ImageUrl="Images/btnExportToExcel.gif">

其 HTML 代码如下:

<input onclick="CustomButtonClicked(this);" type="image" name="btnXLS" id="btnXLS" 
        onclick src="Images/btnExportToExcel.gif" alt="" border="0" />

上面的代码将渲染为,单击时,禁用页面上的所有按钮,即使其他任何按钮上都没有编写任何内容,也不一定需要其他按钮是同一种类型 CustomImageButton

单击普通按钮时禁用所有按钮

X.aspx
<asp:button id="btnFinalSave" Text="Final Save" Runat="server" onclick="btnFinalSave_Click" />

X.aspx.cs
btnFinalSave.Attributes.Add("onclick","CustomButtonClicked(this)");

上面的代码将渲染为如下所示,单击时,禁用页面上的所有按钮。

即使在实际间隔之前也启用按钮,例如,在验证失败时;立即重新启用按钮

X.aspx
<Tittle:CustomImageButton id="btnSave" ImageUrl="Images/btSave.gif" 
 Runat="server" onclick="btnSave_Click" />
<script language="javascript">
function SomeClientSideValidation()
{
    if ( !confirm('Validation not met.\n\nYes - will still save 
         the page\nNo - will not save the page and re-Enable all buttons') )
    {
        EnableCustomButton('btnSave');
        return false;
    }
    
    return true;
}                
</script>
X.aspx.cs
btnSave.Attributes.Add("onclick","if ( !SomeClientSideValidation() ) return false;");

技术实现解析

CustomImageButton.cs

..
public class CustomImageButton : ImageButton
{
    public int DisableTimeOut ..
    public int PutDisableScriptAtEnd ..
    
    ..
    protected override void OnPreRender(EventArgs e)
    {
        #region On Pre Render Javascript
        //CustomButtonJavascript
        if (!Page.IsClientScriptBlockRegistered("CustomButtonScript") )
        {
            string CustomButtonScript = @"
            <script language="javascript">
            //    TittleJoseph@yahoo.com
            //  https://codeproject.org.cn/script/articles/list_articles.asp?userid=1654009
            var idOfImageButtonsToDisable = '';
            var idOfButtonsToDisable = '';

            //Custom Image Button Clicked.
            function CustomButtonClicked(obj)
            {
                idOfImageButtonsToDisable = '';

                if ( obj.disabled == false )
                {
                    var pgForm = obj.form;
                    
                    //note: unfortunately type=image never comes up in 
                    //Form.Elements therefore searching them by tag name INPUT
                    //search all type=image, button, submit, reset and disabling them.
                    var inputs = pgForm.getElementsByTagName('input');
                    
                    for ( var i=inputs.length-1; i>=0; i--)
                    {
                    try
                    {
                var currentButton = inputs[i];
            if ( (currentButton.type == 'submit' || currentButton.type == 'button' 
            || currentButton.type == 'reset') && currentButton.disabled == false )
            {
                //storing ids of all buttons, will require when enabled back later.
                idOfButtonsToDisable += (idOfButtonsToDisable==''?'':',') + currentButton.id
                
                CreateDuplicateButtonOverSame(currentButton);
            }

            //Image Buttons and not if already disabled 
            //(duplicate button will always be disabled.)
            if ( currentButton.type == 'image' && currentButton.disabled == false )
            {
                //If image button is already disabled, do not touch it.
                if ( currentButton.src.toLowerCase().indexOf('_disable.') >= 0 )
                    continue;

                //storing ids of all image buttons, will require when enabled back later.
                idOfImageButtonsToDisable += (idOfImageButtonsToDisable==''?'':',') + 
                                              currentButton.id ;

                CreateDuplicateButtonOverSame(currentButton);
            }
                    }
                    catch(e)
                        {
                            //alert(e.description);
                        }
                    }

                    //Searching all <BUTTON> tags and disabling them as well.
                    var buttons = pgForm.getElementsByTagName('button');
                    
                for ( var i=buttons.length-1; i>=0; i--)
                {
                    try
                    {
                        var currentButton = buttons[i];
                        
                        //storing ids of all buttons, will require when enabled back later.
                        idOfButtonsToDisable += (idOfButtonsToDisable==''?'':',') + 
                                                currentButton.id

                        CreateDuplicateButtonOverSame(currentButton);
                    }
                    catch(e)
                    {
                        //alert(e.description);
                    }
                }

                    setTimeout('ReEnableAllButtons(\''+obj.id+'\')',"+
                                this.DisableTimeOut.ToString() +@");
                }
            }
            function CreateDuplicateButtonOverSame(currentButton)
            {
                //Create a duplicate button same as this 1 and disabling that.
                var dummyBtn = currentButton.cloneNode();
                dummyBtn.id = dummyBtn.id+'_clone';
                dummyBtn.disabled = true;
                //ERROR: don't know why, but name attribute is not resetting here, 
                //still removing 'name' as it is conflicting
                var nm = dummyBtn.getAttribute('name');
                dummyBtn.removeAttribute('name');
                dummyBtn.setAttribute('name',nm+'_clone');
                dummyBtn.setAttribute('onclick','');
                dummyBtn.style.display = 'none';

                //Inserting duplicate buttons before end of body
                document.body.insertAdjacentElement('beforeEnd',dummyBtn);
                
                //Getting co-ordinate of original image buttons.
                var mouseY = document.body.scrollTop + 
                             parseFloat(currentButton.getBoundingClientRect().top);
                var mouseX = document.body.scrollLeft + 
                             parseFloat(currentButton.getBoundingClientRect().left);

                //co-ordinates which are received are 2 pixel extra and 
                //do not fully cover the actual object
                mouseX = (mouseX >= 2)?mouseX-2:mouseX;
                mouseY = (mouseY >= 2)?mouseY-2:mouseY;
                
                //I'm not hiding actual button because document.activeElement 
                //will not be available then,
                //just placing disable image/button on top of actual button/image button.
                currentButton.style.position = 'static';
                dummyBtn.style.position = 'absolute';
                dummyBtn.style.left = mouseX;
                dummyBtn.style.top = mouseY;

            if ( dummyBtn.tagName == 'BUTTON' )
            {
                dummyBtn.innerHTML = currentButton.innerHTML;
            }
            else //INPUT
            {
                if ( dummyBtn.type == 'image' )
                {
                    //Changing Image to Disable image.
                    //must have file 'add_disable.gif','deleteImage_disable.jpg' 
                    //in directory structure 
                    //if actual image button url is 'add.gif', 'deleteImage.jpg' respectively.
                    var extensiondot = currentButton.src.lastIndexOf('.');
                    dummyBtn.src = currentButton.src.substr(0,extensiondot) + '_disable'+
                    currentButton.src.substr(extensiondot);
                }
            }
            dummyBtn.style.display = '';
            }

            function ReEnableAllButtons(objId)
            {
                //Image Button Object which fired the event.
                var obj = document.getElementById(objId);
                
                if ( idOfButtonsToDisable != '' )
                {
                    var buttonIds = idOfButtonsToDisable.split(',');
                    
                    for (var i=0; i< buttonIds.length; i++)
                    {
                        var btnClone = document.getElementById(buttonIds[i]+'_clone');
                        if ( btnClone != null )
                            btnClone.removeNode(true);
                        document.getElementById(buttonIds[i]).style.position='';
                    }
                    idOfButtonsToDisable = '';
                }

                if ( idOfImageButtonsToDisable != '' )
                {
                    var imageButtonIds = idOfImageButtonsToDisable.split(',');
                    
                    for (var i=0; i< imageButtonIds.length; i++)
                    {
                        var imgbtnClone = document.getElementById(imageButtonIds[i]+'_clone');
                        if ( imgbtnClone != null )
                            imgbtnClone.removeNode(true);
                        document.getElementById(imageButtonIds[i]).style.position='';
                    }
                    idOfImageButtonsToDisable = '';
                }
            }
            
            //Call this function, if you want Enable the image before the time.
            function EnableCustomButton(objId)
            {
                ReEnableAllButtons(objId);
            }                
            </script>
            ";
            Page.RegisterClientScriptBlock("CustomButtonScript",CustomButtonScript);
        }
        
        base.OnPreRender (e);
        #endregion
    }

    protected override void AddAttributesToRender(HtmlTextWriter writer) 
    {
        #region Add disabling script on onclick event
        string existingOnClick = "";
        if ( this.Attributes["onclick"] != null )
            existingOnClick = this.Attributes["onclick"];

        //script which disables all buttons on the page.
        string disableScript = "CustomButtonClicked(this);";
        string concatwith = (existingOnClick.TrimEnd(' ').EndsWith
        (";")==true||existingOnClick=="")?"":";";
        if ( existingOnClick != "" )
        {
            existingOnClick = existingOnClick.Trim();
            existingOnClick = existingOnClick.Replace("javascript:","");

            if ( PutDisableScriptAtEnd == true ) 
                disableScript = existingOnClick + concatwith + disableScript;
            else
                disableScript = disableScript + existingOnClick;
        }

        //Remove already being used "onclick", otherwise this attribute comes twice.
        this.Attributes["onclick"]=null;

        writer.AddAttribute(HtmlTextWriterAttribute.Onclick, disableScript);

        base.AddAttributesToRender(writer);
        #endregion
    }
}

常见问题解答

您是否强迫我在页面上只使用这个控件,而不能使用任何其他自定义控件或 HTML 控件?

完全不会。最终起作用的是 JavaScript。因此,您甚至可以将其复制到您的Global.js文件中,并调用其方法进行禁用。但我仍然认为最好将所有内容都放在控件内部,您可能需要创建类似的 ImageButton,您可以按照下面的“未来增强”部分中的说明复制代码,这样您就不必在每个地方专门调用 JavaScript 方法了,例如,除了 CustomImageButton,还可以使用 CustomButton

您能建议我可以在哪里使用它吗?

  • 当您只想让用户单击一次按钮时,单击两次按钮可能会导致问题。
  • 当您认为服务器上正在进行一些耗时的逻辑,并且系统看起来很慢或挂起,并且您不希望用户不耐烦地单击同一个按钮两次时。

此代码在 .NET 2.0 或更高版本中有效吗?

我不知道,因为我没有 .NET 2.0 可以测试。顺便说一句,是 JavaScript 在起作用,所以您可以复制 JavaScript 代码。

可能的未来增强

  • 当前的自定义控件是用于 ImageButton 的。同样的代码也可以用于自定义 Button,即 CustomButton,它继承自 System.Web.UI.WebControls.Button。您需要将这里所有公开的属性复制到那里,并覆盖 OnPreRenderAddAttributesToRender 方法。
  • 我们可以添加一个额外的属性 StatusMessage,它可以在按钮被单击并禁用后,在处理过程中显示一些消息在状态栏中。

参考文献

没有参考文献,所有代码都是我从零开始编写的!:)

结论

我非常想知道此代码是否以任何方式帮助了您,请不要犹豫或懒惰地留下您的评论,告诉您对这次提交的感受以及它帮助了您多少。如果您在使用此控件的代码中保留此页面的 URL,我将不胜感激。

历史

  • 2006年8月4日:初始版本

许可证

本文没有明确的许可附加,但可能包含文章文本或下载文件中的使用条款。如有疑问,请通过下方的讨论区联系作者。作者可能使用的许可证列表可以在此处找到。

© . All rights reserved.