Centering a div with a "Loading..." spinner.gif using jQuery and Ajax.BeginForm

25,942

Solution 1

Hack 1:

I have "solved" the problem in that I have moved the #PleaseWait div to the very top of the site's master view. That way, #PageContanier, with its relative position, is not interacting with the set up. It now works.

The downside is that now EVERY page in the app has this div (with style="display:none;"). Only some of them will ever need it.

Hack 2:

An improvement on Hack 1 is to create a Content placeholder just under the body tag. That way, from the pages where you need it, you can inject whatever div you need to have a centered panel on your pages.

This eliminates the redundancy in pages that don't use the panels, and gives you a lot flexibility. However, the downside is that each time you have to write the code. Not exactly DRY, so...

Look Mum, no Hacks!:

Thanks to Jim's suggestion (see accepted answer), I have cracked the issue. No more hacks required.

I can use a pure jQuery solution, as follows (if javascript is disabled, nothing happens, which is good).

So:

I created a Utilities.js file and put in there the following function:

    function initSpinnerFunction() {
        $("body").prepend("<div id ='PleaseWait' style='display:none;'>Please wait...</div>");.
    }
    // Add the jQuery center() method...
    jQuery.fn.center = function () {
                this.css("position", "absolute");
                this.css("top", ($(window).height() - this.height())/ 2 + $(window).scrollTop() + "px");
                this.css("left", ($(window).width() - this.width()) / 2 + $(window).scrollLeft() + "px");
                return this;
            }
            });

In my wizard.aspx view I put the following jQuery code:

    $(document).ready(function () {
                initSpinnerFunction();            
            });

            function isValid() {
                $('#PleaseWait').center();
                if (true) // In case I want to return true
                {
                    return true;
                }
                else // I want to return false
                {
                    $('#PleaseWait').hide(); // Manually hide the LoadingElementId
                    return false;
                }
            }

The isValid() function is used, as before, in my MVC AJAX form (see below).

The MVC Ajax Form does not change. Ie:

@using (Ajax.BeginForm(Model.Step.ActionName, 
             null, 
             new AjaxOptions { UpdateTargetId = "wizardStep", OnBegin="isValid", LoadingElementId="PleaseWait" }, 
             new {@name="wizardForm", @id="wizardForm" } ))
        { 
        @* do ajax stuff, see my "Loading... panel below *@
        }

If javascript is disabled, the solution still works. The spinner gif doesn't show which is the correct behaviour, as there is no asynch request, just a page refresh.

And with this little framework, the hack detailed above is no longer needed: only the pages that explicitly request it have the PleaseWait div.

To style the PleaseWait div, I have used:

<style type="text/css">
        #PleaseWait
        {
            z-index: 2000;
            padding:6px;
            padding-left:40px;
            background:#F2E8BC url("../Images/Shared/spinner.gif") no-repeat 6px center;
            width:200px;
            height:80px;
            -webkit-border-radius: 10px;
            -moz-border-radius: 10px;
            border:4px solid #666;
            font-weight:bold;
        }
    </style>

This rule will produce a dull square box in IE, but in Mozilla browsers, it will produce a natty wee box with rounded corners, making it look very pro.

Job done, thanks to Jim.

Solution 2

Have you thought of using BlockUI, seems a good candidate for this. I used it on one of our projects before and it's very very good.

Solution 3

aw,

don't know why this didn't occur to me sooner. You could overcome the entire issue of the additional div in your master template by using jquery. a la:

function initSpinnerFunction(){
    // this would be you first line (i.e. prepend to body)
    $("body").prepend("<div id ='PleaseWait' style='display:none;'></div>");
    // OR if you want a jquery reference to the newly 
    // created div for whatever reason
    var newDiv = $("<div id ='PleaseWait' style='display:none;'></div>")
                 .prependTo('body');
    // then do rest of normal jquery stuff such as making div visible etc...
}

function exitSpinnerFunction(){
    // normal spinner exit stuff followed by final line 
    // which removes the pleasewait div from the DOM
    $("body").remove("#PleaseWait");
    // OR
    $("#PleaseWait").remove();
}

it may or may not be appropriate, but would certainly limit it to only the pages that actively invoked the spinner....

Share:
25,942
awrigley
Author by

awrigley

ASP.NET MVC and CMS

Updated on December 22, 2020

Comments

  • awrigley
    awrigley over 3 years

    The Setup

    I have designed a very easy to use MVC wizard. The cherry on the cake is a spinner gif that will appear when I change wizard step. I want it to center on the page, regardless of the size of the browser window.

    You can see an example of this at:

    Sample Wizard

    PLEASE DON'T COMPLETE THE WIZARD, UNLESS YOU HAVE ENCEPHALITIS...

    The Problem

    Problem is, I have a centered CSS layout, controlled with the following rule:

    #PageContainer
    {
        position:relative;
        width:800px;
        left:50%;   
        margin:0px 0px 0px -400px;
        text-align:left;
    }
    

    And this rule seems to interact badly with the jquery code that centers the div with the spinner gif. Ie, it doesn't center.

    The Code

    My MVC form code is:

        @using (Ajax.BeginForm(Model.Step.ActionName, 
                 null, 
                 new AjaxOptions { UpdateTargetId = "wizardStep", OnBegin="isValid", LoadingElementId="PleaseWait" }, 
                 new {@name="wizardForm", @id="wizardForm" } ))
            { 
            @* do ajax stuff, see my "Loading... panel below *@
    
            <div id="PleaseWait" style="display:none;">
                Please Wait...
            </div>
            } 
    

    jQuery code is:

    <script type="text/javascript">
            jQuery.fn.center = function () {
                this.css("position", "absolute");
                this.css("top", ($(window).height() - this.height())/ 2 + $(window).scrollTop() + "px");
                this.css("left", ($(window).width() - this.width()) / 2 + $(window).scrollLeft() + "px");
                return this;
            }
            });
            function isValid() {
                $('#PleaseWait').center();
                if (true) // In case i whant to return true
                {
                    return true;
                }
                else // I whant to return false
                {
                    $('#PleaseWait').hide(); // Manually hide the LoadingElementId
                    return false;
                }
            }   
    </script>
    

    My Analysis

    As the jQuery code sets up the div panel to have an absolute position, it therefore has an absolute position relative to the first parent item that does not have position static (the default).

    Therefore, as a result of the margin hack used to center the page, there is something creeping into the jQuery calculation that upsets the apple cart.

    Ie:

    The jQuery calculation is based on the size of the window, but the position of the #PleaseWait panel is positioned with respect to the #PageContainer div (this div is part of the layout). Obviously, these are two very different things. I therefore thought I would get away with figuring out the constant values to subtract from the top and left calculations for the #PleaseWait div.

    However, the results are unpredictable and I am now too befuddled to figure it out. Help!

    Any help much appreciated.

    JOB DONE!:

    Problem sorted. See Jim's accepted answer for the ideas, my follow up answer for full the implementation.