Search code examples
asp.netvb.netupdatepanelimagebutton

Series of ImageButtons not loading in time and causing issues if clicked quickly Asp.Net/VB.Net


I posted similar section of code earlier for a different issue which is now fixed, however I have another very annoying issue now: The code is for a Deal or no deal style game where the user opens boxes. The following code is in an update panel and ovbiously handles 22 boxes, but can anyone explain ths..

If you click the image buttons/boxes super fast, you get some that don't update the image to the 'openBox' image and some seem to set

session("BoxValue1")=0

before it loads and try to do:

btnBox2.imageUrl="~/files/images/icons/boxes/MONMOpenBox0.png"

which doesn't exist.

It seems to happen if you clear the image cache on your device, which is why i think the images aren't loading quick emough. Can anyone suggest a solution?

<asp:ImageButton ID="btnBox1" visible="false" cssclass="playboxsize" commandArgument="1" ImageUrl="~/files/images/icons/boxes/MONMClosedBox1.png" runat="server" onClick="btnBoxSelected_Click" />
<asp:ImageButton ID="btnBox2" visible="false" cssclass="playboxsize" commandArgument="2" ImageUrl="~/files/images/icons/boxes/MONMClosedBox2.png" runat="server" onClick="btnBoxSelected_Click" /> 
<asp:ImageButton ID="btnBox3" visible="false" cssclass="playboxsize" commandArgument="3" ImageUrl="~/files/images/icons/boxes/MONMClosedBox3.png" runat="server" onClick="btnBoxSelected_Click" />

VB.Net

Protected Sub btnBoxSelected_Click(sender As Object, e As System.EventArgs)
           
    Dim btn As ImageButton = CType(sender, ImageButton)
    Dim boxNo As Integer = btn.CommandArgument

    If session("round")="chooseBoxes" Then
        Select Case boxNo
            Case"1"
                btnBox1.imageUrl="~/files/images/icons/boxes/MONMOpenBox" & session("BoxValue1") & ".png"      
                btnBox1.enabled="false"
                session("ValueOpened")=session("BoxValue1")
                session("BoxValue1")=0
            Case"2"
                btnBox2.imageUrl="~/files/images/icons/boxes/MONMOpenBox" & session("BoxValue2") & ".png"
                btnBox2.enabled="false"
                session("ValueOpened")=session("BoxValue2")
                session("BoxValue2")=0
            Case"3"
                btnBox3.imageUrl="~/files/images/icons/boxes/MONMOpenBox" & session("BoxValue3") & ".png"
                btnBox3.enabled="false"
                session("ValueOpened")=session("BoxValue3")
                session("BoxValue3")=0
        End Select
    End If
End Sub

Solution

  • Like any web page, if you click on a button (even without an update panel), then the current post back cycle is halted, and a new post-back starts.

    And the same holds true when using an update panel. In other words, if you click on a button, and the post-back cycle starts (and as I stated, even with an update panel, then a page post back DOES occur, but it called a "partial page post back"). In that partial page post-back, we still have a post back occurring, and the page load event still fires first, and then your button code stub fires.

    Hence, in either case (with update panel, or not), then if a user clicks a button again while waiting for that round trip, then the current page life cycle is halted, and a new page post back starts. This can halt, and stop the existing code from completing.

    In other words, if you click a button before the current post-back is done, then it is terminated, and a new round trip and page cycle starts.

    In near all cases, when using a web site, users often can click another button again, or even the same button twice. And in most cases, this does not occur very often, but remains an issue.

    To prevent the button from being clicked on two times, then often I will add a client-side JavaScript function which will either hide the button, or better yet hide a div and part of the page to give great feedback to the user that the web page has realized their button click.

    In fact, I OFTEN will use some client-side JavaScript to hide "part" of the page, since then it gives the user the impression that the web site responds MUCH faster than it really is.

    Hence this effect:

    enter image description here

    In above, I am using network throttling, since I wanted to see how the page works with a slow internet connection. But, note how when you click the button, I actually hide a whole "div" area of the page (not just the button you clicked on). This means the user can't click 2 times on the button, but more important is the page seems to respond instant to the user with that client side click event that hides part of the page. Thus, the application "appears" to run and respond faster, and the bonus part is the user can't click on the button two times.

    So, one simple way is to add a client-side event in which you hide the button. When the page life cycle (the so-called post back and round trip occurs), the markup of course is sent again from the server, and the button will re-display (the style setting will not persist, and with a fresh new page or update panel, then the markup is re-loaded).

    Hence you can add this to your image button:

    jQuery:

                    <asp:ImageButton ID="ImageButton1" runat="server" ClientIDMode="Static"
                        ImageUrl="~/Content/Balls/b0.png"
                        CssClass="myball"
                        OnClick="ImageButton1_Click"
                        OnClientClick="$(this).hide()"                        
                        />
    

    With pure JavaScript, then this:

                    <asp:ImageButton ID="ImageButton1" runat="server" ClientIDMode="Static"
                        ImageUrl="~/Content/Balls/b0.png"
                        CssClass="myball"
                        OnClick="ImageButton1_Click"
                        OnClientClick="myhide('ImageButton1')"                        
                        />
    
    
            <script>
    
                function myhide(btn) {
                    document.getElementById(btn).style.display = "none"
                    
                }
    
            </script>
    

    Note the introduction of ClientIdMode="static", and this was used to allow the JavaScript (or jQuery) to use the actual control id, since asp.net has a habit of changing the id of the control when rendering the page.

    Some caution and testing are required when using an update panel, since you are running both the browser and the web server on the same computer. I can't stress how important this issue becomes. The result during development is both the web server and browser network connection is on the one and same computer (and is VERY fast).

    In production, the web site is running on a server, and there is an internet connection between your browser and that web server, and thus things will run much slower. So much slower, that in some cases I had to drop the use of the update panel.

    The solution is to start writing JavaScript to run in the client-side browser. And in fact, for updating things like graphics, and a highly interactive type of web page, then update panels are often not sufficient speed wise.

    So, during testing, make it a habit to hit f12 (browser debug tools), and set the network throttle to say fast 3g. This will then give you an idea of how the site "really" going to respond for those with a less then ideal network connection.

    enter image description here.

    So, when browser launches after hitting f5 to run the application, hit f12, and the on-network tab, choose the above network throttle setting.

    So, keep in mind while an Update Panel seems like magic, and can often avoid the need to start writing client-side JavaScript code?

    For a highly interactive page, Update Panels can fall short, since they are still doing a post back (partial page post back). While this is better than a full-page post-back, an update panel can still be too slow for graphical updates and a highly interactive web page.

    As such, then to obtain good performance, you often have to dump the use of the update panel, and start writing client-side JavaScript.

    Edit: Disable button click until update panel is done

    Ok, so assume we have 4 Image buttons. To same time, I'm using the same click event for each button.

    So, say this markup:

        <style>
            .myball {
                width: 64px;
                height: 64px;
                margin-right:20px;
            }
        </style>
    
    
        <asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Conditional">
            <ContentTemplate>
    
    
                <asp:ImageButton ID="BallBut_0" runat="server" ClientIDMode="Static"
                    ImageUrl="~/Content/Balls/b0.png"
                    CssClass="myball"
                    OnClick="BallBut_0_Click"
                    />
    
                <asp:ImageButton ID="BallBut_1" runat="server" ClientIDMode="Static"
                    ImageUrl="~/Content/Balls/b0.png"
                    CssClass="myball"
                    OnClick="BallBut_0_Click"
                    />
    
                <asp:ImageButton ID="BallBut_2" runat="server" ClientIDMode="Static"
                    ImageUrl="~/Content/Balls/b0.png"
                    CssClass="myball"
                    OnClick="BallBut_0_Click"
                    />
    
                <asp:ImageButton ID="BallBut_3" runat="server" ClientIDMode="Static"
                    ImageUrl="~/Content/Balls/b0.png"
                    CssClass="myball"
                    OnClick="BallBut_0_Click"
                    />
    
            </ContentTemplate>
        </asp:UpdatePanel>
    

    And our code behind is this:

    Dim Balls(3) As Integer   ' 0 to 3 = 4 balls
    
    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    
        If Not IsPostBack Then
            ' first page load
    
            For i = 0 To UBound(Balls)
                Balls(i) = i
            Next
    
            Call UpDateBalls()
    
            Session("Balls") = Balls
    
        Else
            ' this is a post back, load the ball array
            Balls = Session("Balls")
        End If
    
    
    End Sub
    
    Sub UpDateBalls()
    
        Dim sBtn As String = ""
        Dim Btn As ImageButton
        For i = 0 To UBound(Balls)
    
            sBtn = $"BallBut_{i}"
            Btn = Page.FindControl(sBtn)
    
            Btn.ImageUrl = $"~/Content/Balls/b{i}.png"
    
        Next
    
    
    End Sub
    
    Protected Sub BallBut_0_Click(sender As Object, e As ImageClickEventArgs)
    
        Dim Btn As ImageButton = sender
    
        Dim BtnIndex = Split(Btn.ID, "_")(1)
    
        Balls(BtnIndex) += 1
        Debug.Print($"Btn index = {BtnIndex} value = {Balls(BtnIndex)}")
        If Balls(BtnIndex) > 10 Then
            Balls(BtnIndex) = 0
        End If
    
        Btn.ImageUrl = $"~/Content/Balls/b{Balls(BtnIndex)}.png"
    
        System.Threading.Thread.Sleep(500)   ' fake some more processing and delay
    
    End Sub
    

    Note how I used a array into session, and I use a "made up" naming convention for the buttons with _0, then _1 etc. This allows me to use that one button click, and then with a split() command convert the value (number) after the _ into a index number.

    As I suggested in this post, often hiding an area of the web page is a great way to give user's feedback that you clicked on a button, and as noted also solves the issue of a double click.

    However, in your case, it would not make sense to hide all of the buttons, as that would look poor.

    So, we need code to disable the buttons and not hide the button. However, one big issue is that if we attach some JavaScript to a given button, and disable the button, then the server side click does not work anymore (since the button been disabled).

    So, what we really need?

    We want some client side JavaScript to run WHEN the post-back of the update panel starts.

    Turns out you can do this.

    Hence, I'm introducing (assuming) that you have jQuery installed for that page.

    Hence, right after the update panel, we have this client side JavaScript code.

        <script>
    
            var prm = Sys.WebForms.PageRequestManager.getInstance();
            prm.add_initializeRequest(StartPostBack);
            prm.add_endRequest(AfterPostBack);
    
    
            function StartPostBack(sender, arg) {
    
                $('[id^="BallBut"]').prop('disabled', true)
            }
    
            function AfterPostBack(sender, arg) {
    
                console.log("after")
    
            }
    
    
        </script>
    

    So, note how I setup 2 JavaScript functions. However, the second function that runs after the update panel has completed its round trip is not required here. Remember, when the post-back is done, then a fresh new copy of the markup comes down from the server. Since the controls are being disabled by client side code (and not server side), then the setting of the disabled value does NOT persist. So, as a FYI, I'm pointing out that using client side code to change a attribute of a control in most cases will not survive a round trip, but such buttons changed by server side code has automatic view state, and thus the change will persist. In this example, that is ideal, since then when the update panel has done all it's work, and sends back a fresh copy of the controls, they will all be re-enabled, and this will occur without additional code on your part.

    Since I have a "fake" processing delay in that button, then note VERY close that when I click a button, the mouse pointer changes from a "hand" to that of a simple pointer - the button click been disabled, and note how then when the panel is ready to receive another click, the "hand" pointer comes back.

    enter image description here