Wednesday, September 4, 2013

Handling Twitter Bootstrap Modals and UpdatePanels with PostBacks

In the process of updating a web application from an extremely rigid design to a responsive design using the Twitter Bootstrap framework. The goal is to keep the changes to the application to the HTML layout as possible. There are some situations where server side code can be removed or updated for display purposes.

This works great until you have situations which are more complicated like Bootstrap modal dialogs which need to display after a UpdatePanels and UpdatePanels which need to update in modal dialogs.

Searching the internet did not provide very fruitful results. Some involved registering script blocks, but I was not able to get them to work. This was not my preferred implementation because it deviated from my initial goal, but I was looking for solutions.

I remembered that I could execute code on an UpdatePanel PostBack and that led me to the following solution. I think there should be a better way to handle it, but it will do for a first pass. In my situation, I had a master page with a script tag using the PageRequestManager like the example below. I added the highlighted lines to call a function (RequestCallback) if it had been declared.

var prm = Sys.WebForms.PageRequestManager.getInstance();

prm.add_initializeRequest(initializeRequest);
prm.add_endRequest(endRequest);

function initializeRequest(sender, args) {
    if (prm.get_isInAsyncPostBack()) {
        args.set_cancel(true);
    }
}

function endRequest(sender, args) {
    ToggleProgressMsg('Please Wait While Your Content Loads...');
    if (RequestCallback) {
        RequestCallback(sender, args);
    }
}

Now we can declare the RequestCallback function on any page to execute when a call back occurs. This leaves us in an situation where we need to know what panel was updating or what control initiated the request. I found the following variable referenced in several places on the web.

sender._postBackSettings.panelID

I found that in several situations, this variable is not reliable. I found that the following variable was always what I expected.

sender._postBackSettings.asyncTarget

In a situation where a grid needs to populate fields on a modal dialog and display the dialog. The grid and modal dialogs were already wrapped in an UpdatePanel so I can leverage the RequestCallback function to clean up the backdrop (the modal closes, but leaves the backdrop) and then open or reopen the dialog based on the control that executed the request.

function RequestCallback(sender, args) {
    var editLink = "lnkEditCar",
        altAddr = "lnkbtnAlternativeAddress",
        trigger = sender ? sender._postBackSettings.asyncTarget : "";

    console.log("RequestCallback", sender, args, trigger, "END");
    
    // Clean up the backdrop
    $('body').removeClass('modal-open');
    $('.modal-backdrop').remove();
    
    // Handle Alternate Address request callback
    if (trigger.indexOf(altAddr, trigger.length - altAddr.length) !== -1) {
        console.log('Reopening Dialog', altAddr);
        $('#pManageCar').modal();
    }
    // Handle Edit Card request callback
    if ($('span[id$="tabpCars_tab"]').hasClass("ajax__tab_active") && trigger.indexOf(editLink , trigger.length - editLink .length) !== -1) {
        console.log("tabpCars_tab");
        $('#pManageCars').modal('show');
    } else if ($('span[id$="tabpTrucks_tab"]').hasClass("ajax__tab_active") && trigger.indexOf(up, trigger.length - up.length) !== -1) {
        console.log("tabpTrucks_tab");
        $('#pManageTrucks').modal('show');
    }
}

The result is that the modal behaves pretty much as expected. I am sure I will revisit this code, but that is how refactoring works.

No comments: