Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Javascript

Dynamically Updatable and AJAX Data Enabled JQuery Web Page Dialogs

4.85/5 (14 votes)
9 Apr 2014CPOL13 min read 50.7K   1.6K  
This is an article about dynamically updatable and AJAX data enabled JQuery web page dialogs.
In this article, we will create and dynamically update dialog windows for working on messages, confirmations, MVC partial view with AJAX data, and progress bars using a custom JQuery plugin.

Introduction

In my web application projects having many multiple level dialogs, I need to dynamically update components in an existing dialog box for subsequent tasks in the workflow. I also need to load ASP.NET MVC partial views and corresponding data obtained from AJAX calls to dialogs with minimal lines of code from callers. Since no library or commercial tool could be found to cover all my needs, I created my own JQuery plugin, the jqsDialog, based on these requirements and features.

  • Easy to use and customize.
  • Dialogs can be opened to any level deep and closed individually or with multiple levels together.
  • Any existing dialog component is dynamically updatable so that the same dialog instance can render different windows sequentially.
  • Built-in functions for loading any partial view and separated JSON data from AJAX calls.
  • Displaying a progress bar window for any processing delay using existing or new dialog instance.
  • Including options of modal, non-modal, and closing dialog by clicking outside.
  • Bootstrap for overall theme and styles that can be modified by custom style sheets.
  • Having all common dialog behaviors so that one tool can fit all needs across all pages in a website.

Running Demo Project

The downloaded source contains an ASP.NET MVC 5.0 website to demonstrate how to use the jqsDialog plugin in details. The project can directly be opened with the Visual Studio 2012/2013. During the re-build process, the project should automatically download referenced .NET related libraries. With all dependent JS and CSS libraries included in the project for the JQuery, JQuery UI, Bootstrap, and KnockoutJS, the modal dialog windows and data loading should work for the browsers IE 8.0 - 10.0, Mozilla Firefox 25.0, and Google Chrome 33.0, on Windows 7 to 8.1. Please see the list of JS and CSS files used by the demo application in the App_Start\BundleConfig.cs. If you run other browser types or versions, you may need to adjust the versions or contents of those dependent libraries.

Base Function for Creating Dialogs

The popupDialog function builds the core structures and components of a dialog. The function is generally called inside the plugin by other functions working on common, data loading, and progress bar dialog windows although it can also directly be called outside the plugin using the syntax:

JavaScript
[returned dialog instance] = $("[selector]").jqsDialog("popupDialog", [argObject]);

Below is an example to call the function with properties in the input argument object. Default values are shown in square brackets.

JavaScript
var popup = $("[selector]").jqsDialog("popupDialog", {
    message: "(text)",
    id: ["(GUID)"],
    width: [250],
    maxHeight: [$(window).height() - 120],
    title: ["Information"],
    titleTag: ["h4"],
    icon: [undefined],
    actionButtonLabel: [undefined],
    actionButtonType: [undefined or "button" if actionButtonLabel has value],
    closeButtonLabel: ["OK"],
    mainCssClass: ["jqs-dialog-main"],
    headerCssClass: ["jqs-dialog-header"],
    bodyCssClass: ["jqs-dialog-body"],
    footerCssClass: ["jqs-dialog-footer"],
    animation: [undefined],
    clickOutside: [false], 
    modal: [true],
    fadeIn: [true],
    parentResetClickOutside: [undefined],
    parentsToClose: [undefined],
    stopClose: [false] 
});

We can call this function with a message text as the only required input parameter item to create a simple alert type dialog box. Note that the base function can only create, not update, a dialog if it is directly called. The published dialog instance members or wrapper functions will be used to dynamically update existing dialog instances.

Virtual Dialog Container and Published Members

When calling the popupDialog function, an invisible wrapper called virtual dialog container (VDC) is created and appends the visible dialog box. It is bound to the top-level element, document.body, for a modal dialog and the JQuery selector for a non-modal dialog.

JavaScript
//Set virtual dialog container 
var vdc = $("<table><tr><td align='center'>");   

if (args.modal) {
    //Append virtual dialog container to document.body
    $("body").append(vdc);
}
else {
    //Append virtual dialog container to selector
    container.append(vdc);
}

The VDC also hosts elements, functions, and events that can be called from the code outside the plugin after the dialog has been created. These published members are fundamentals of implementing dynamic updating features. It's simple to call any dialog instance member. For example, use this line of code to change the closeButton label to "Cancel" for the existing dialog instance popup:

JavaScript
popup.setCloseBtn("Cancel");

There is a full specification section above each major function in the plugin. You can see the details of these published dialog instance members from the popupDialog function in the jqsDialog.js file.

Function for Common Dialogs

Although it's likely to dynamically update components of an existing dialog by directly calling published dialog instance members, it would result in more coding work from the caller's side. To further simplify the calling code, the plugin provides the showCommonPopup wrapper functions for creating and updating the alert/messaging and confirmation types of dialogs. The function has overloaded versions. We can specify limited individual arguments for a simple one-button modal dialog box or the object argument for any dialog box with more features.

The syntax of showCommonPopup function:

JavaScript
[returned vdc instance] = $("[selector]").jqsDialog("showCommonPopup", [argObject]);
[returned vdc instance] = $("[selector]").jqsDialog
("showCommonPopup", messageText, [title], [icon], [popup], [parentsToClose]);

The argObject contains all the same properties as the argObject of popupDialog function plus an additional property popup, which is used for receiving the existing VDC instance for updating processes. The following code snippet demonstrates a cascading show of confirmation and messaging dialog boxes using existing VDC instances (the first three dialog windows) and a new VDC instance (the last dialog window):

JavaScript
//Show a confirmation dialog
var popup = container.jqsDialog("showCommonPopup", {
        message: "This will overwrite your changes. Continue?",
        title: "Warning",
        icon: "warning",
        actionButtonLabel: "Yes",
        closeButtonLabel: "No"
});                
popup.actionButton.on("click", function () {
    //Dynamically switch to message dialog and set stopClose to true for keeping dialog open
    container.jqsDialog("showCommonPopup", {
        message: "Your request has been processed successfully",
        title: "Information",
        icon: "info",
        stopClose: true,
        popup: popup
    });
    popup.closeButton.on("click", function () {
        //Dynamically switch to another dialog
        container.jqsDialog("showCommonPopup", {
            message: "There is an error closing the dialog.",
            title: "Error",
            icon: "error",
            actionButtonLabel: "Ignore",
            closeButtonLabel: "Cancel",                        
            popup: popup
        });
        popup.actionButton.on("click", function () {                        
            //Show a new dialog using everything else by default and 
            //close this and parent dialogs together
            container.jqsDialog("showCommonPopup", {
                message: "You will ignore the error and close all dialogs",
                title: "Message",
                icon: "info",                            
                parentsToClose: popup                
            });
        });
    });
});

When running the code, the confirmation dialog box is firstly shown:

Image 1

Clicking the Yes button, the existing dialog is updated to the messaging dialog box:

Image 2

Clicking the OK button, the existing dialog is further updated to an error confirmation dialog box:

Image 3

Clicking the Ignore button, a new messaging dialog box is created on the previous dialog. Clicking the OK button will close the active dialog and its parent together.

Image 4

Loading Partial View and Data

The showDataPopup function is designed to load an MVC partial view with the data obtained from AJAX calls onto the dialog window. Here is the syntax to call the function:

JavaScript
[returned vdc instance] = $("[selector]").jqsDialog("showDataPopup", [argObject]);

The properties of the argObject for the dialog are the same as those for popupDialog function as listed previously. The properties for AJAX calling plus some additional items are added to the argument object. Below is the calling code example:

JavaScript
var popup = container.jqsDialog("showDataPopup", {
    //For Ajax call
    url: ["(URL string)"],
    data: [undefined], 
    type: ["GET"],            
    contentType: ["application/x-www-form-urlencoded; charset=utf-8"], 
    cache: [true - Ajax() default],
    async: [true - Ajax() default],
    beforeSend: [undefined]         
    success: [undefined],
    error: [undefined],
    complete: [undefined],
    //Additional
    args.stopProgress: [undefined or false], //Set true to disable built-in progress bar. 
    popup: [undefined] //existing vdc instance   
    
    //Remaining properties are the same as those for popupDialog function
    . . . 
});

The input parameter properties and default values for AJAX calls are the same as the native JQuery Ajax() function but only frequently used properties of the argument object are included. The dataType property is not exposed to consumers. It is hard-coded to the "json" since the function always returns the data in the JSON type. Although the success, error, and complete properties for callbacks are published, in most cases we use the jsonViewSuccess, jsonViewError, and jsonViewComplete events raised in the callForJsonView function. The jsonViewSuccess event is what we use to get any partial view and the data back from an AJAX call:

In the callForJsonView function:

JavaScript
container.trigger("JsonViewSuccess", { html: result.html, data: result.data });

In the consumer code:

JavaScript
container.on("JsonViewSuccess", function (html, data) {
  //Do something on html and data...
}

In an ASP.NET MVC application, a controller usually returns the partial view with the data model to the location of the parent view where the @Html.Partial or @Html.RenderPartial helper function is called. This approach cannot practically be used for loading any partial view and data into dynamically generated or updated dialogs. We need the logic exclusively in the JQuery plugins to inject the partial view HTML text into the dialog content and also bind the data returned from the AJAX call to the HTML elements. Thus, the MVC controller should return these items to the AJAX caller:

  1. An empty data model used for field type mapping in the partial view.
  2. A data array containing:
    • the generated partial view HTML text
    • the JSON object instance with all requested data records

In the method of page controller:

C#
//The product parameter contains data obtained from data source
return PartialViewJson("../Demo/_Product", new Product(), product);

In BaseController.cs:

C#
protected JsonViewResult PartialViewJson
(string viewName, object model = null, params object[] data)
{
    //viewName is used for getting the partial view for streaming process
    return new JsonViewResult 
    {
        Data = new
        {
            html = RenderPartialViewToString(viewName, model),
            data = Json(data, "application/json; charset=utf-8", 
            System.Text.Encoding.UTF8, JsonRequestBehavior.AllowGet).Data
        } 
    };
}

protected string RenderPartialViewToString(string viewName, object model)
{
    if (string.IsNullOrEmpty(viewName))
        viewName = ControllerContext.RouteData.GetRequiredString("action");
            
    ViewData.Model = model;
    using (StringWriter sw = new StringWriter(CultureInfo.CurrentCulture))
    {
        //Streaming the partial view to HTML text
        ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView
                                      (ControllerContext, viewName);
        ViewContext viewContext = new ViewContext
        (ControllerContext, viewResult.View, ViewData, TempData, sw);
        viewResult.View.Render(viewContext, sw);
        return sw.GetStringBuilder().ToString();
    }
}

The JsonViewResult is a custom type derived from the System.Web.Mvc.JsonResult class. You can see details from the Classes\JsonViewResult.cs file in the demo project if interested.

Finally, the consumer code in the dataDialog.js page plugin calls the showDataPopup function in the dialog plugin. These tasks are then performed in the order:

  • Obtaining the partial view HTML and JSON object data from the server
  • Injecting the HTML into the content of the dialog and rendering the dialog structure with data fields
  • Processing the JSON data from the jsonViewSuccess event with KnockoutJS
  • Binding the data to HTML elements by KnockoutJS and showing a complete dialog
JavaScript
loadProductPopup: function (container, rowid) {            
    var popup = container.jqsDialog("showDataPopup", {
        url: "/Products/LoadProductById/" + rowid,                
        width: 450,             
        title: "Product",
        titleTag: "h3",
        actionButtonLabel: "Save",
        closeButtonLabel: "Cancel",                
        animation: "top,200"
    }); 
            
    container.on("jsonViewSuccess", function (html, data) {                
        //html already injected to dialog inside showDataPopup
        var viewModel = ko.mapping.fromJS(data.data[0]); 
        var bindNode = document.getElementById("divPupContent");
        if (bindNode) {
            ko.cleanNode(bindNode);
            ko.applyBindings(viewModel, bindNode);
        }                                             
        container.off();
    });
                        
    popup.actionButton.on("click", function () {        
        //Pass dialog as parent for closing together later
        methods.saveProduct(container, popup);
    });
}

You can see how easy coding and less lines of code from the consumer side. With default values used for anything else, only the url parameter value is specified in the properties of the argument object for the AJAX call part. If passing the complex data to the AJAX call, you just need to add another two properties, the data: [object] and contentType: "application/json", into the argument object. The function will automatically use the POST method to process the request. The code in the partial view file, _Product.cshtml, is also very neat and kept minimal (about ten lines for this case. See the file for details). The generated dialog window is shown below:

Image 5

When clicking the Save button, a new confirmation dialog is shown. Both original page and data dialog window now become faded backgrounds.

Image 6

When clicking the Yes button, the submission request will be sent to the server. After getting back the status response, the existing VDC instance is then updated to a messaging dialog for the status notification.

Image 7

Between the confirmation and the status notification dialogs, there could be another dynamically loaded window using the same VDC instance for showing the progress status if there is any delay (see the next section).

The above sample just shows a simple form of the data on the dialog window. The data and display structures on the dialogs can be more complex ones, such as hierarchical data sets with multiple JqGrid or KnockoutJS grids. You can implement these structures using the data loading features of the jqsDialog plugin.

Progress Bar Windows

The progress bar window uses the base dialog structure but loads an animation image to the dialog content with or without the title and footer. Here is the syntax to call for a progress bar display:

JavaScript
[returned vdc instance] = $("[selector]").jqsDialog
("showProgressBar", [existingDialog], [keepParts])

Thanks to the dynamic updating feature, the progress bar can be switched from and to any other types of dialogs using the same VDC instance. If the existingDialog parameter has a value, the progress bar will be in the updated VDC mode. Otherwise, a separate new instance of the VDC will be created for displaying the progress bar. The appearance of progress bar windows are also based on the boolean value of the keepParts parameter.

  1. When passing true for the keepParts parameter, the title and footer with buttons are maintained, only the content is replaced by the animation image. The example is the progress bar appearing before successfully loading the data from the AJAX call in the demo project. In this case, the full structure of the dialog is rendered but the progress bar image is injected into the content until it is replaced by the partial view and data results. During the waiting, the Save button on the footer is disabled. This type of the progress bar display is the default behavior for the data loading dialog, which can be turned off by set the argument object property as "stopProgress: true" when calling the showDataPopup function.

    Image 8
  2. When the keepParts parameter is undefined or set to false, the progress bar is shown on the popup window with the title "Please wait..." and no button in the footer. This scenario can be seen in the demo project in which there is a simulated processing delay after clicking the Save button on the confirmation dialog for submitting the data. Normally, this progress bar window should be switched to a messaging dialog box for either successful or error status notification after receiving any feedback from the server.

    Image 9

Note that I still keep the x button on the right corner of the dialog when showing the progress bar. Users can click it to cancel the process if it takes too long or hangs. If you would not like the x button on the progress bar window, you can certainly remove it by calling the dialog instance function setHeader:

JavaScript
//Remove x button for progress bar (pass anything with not-null value)
popup.setHeader("no-x");

But don't forget to add the x button back to the next dialog to which it is switched:

JavaScript
//Reset header with x button
popup.setHeader();

It's not a good practice for a progress bar, when it is appearing, to be closed by any click-outside action. Thus the click-outside option should always be disabled when loading the progress bar by using these lines of code in the showProgressBar function:

JavaScript
//Set type status used for re-setting click-outside for next updated dialog
popup.typeStatus = "progress";

//Always disable click-outside but doesn't change isClickOutside value 
//when calling VDC's clickOutside function. Thus isClickOutside can be used to 
//resume setting after progress bar dialog is switched to other type.
popup.clickOutside(false);

The click-outside option should automatically be resumed for the subsequent updated dialog, if the option is originally enabled for the VDC instance, by using the code in the showCommonPopup and showDataPopup functions:

JavaScript
//Reset click-outside if updated from progress bar 
if (args.popup.typeStatus == "progress" && args.clickOutside == undefined) {
    args.popup.clickOutside(args.popup.isClickOutside);
}

Setting More Buttons on Dialog Footer

By design, the dialog plugin has two predefined buttons. Additional buttons, if needed, can be added into the dialog footer by calling the VDC instance function, setOtherBtn, after the dialog is created or updated.

JavaScript
[returned button instance] = containerInstance.setOtherBtn(label, [type])

Below is an example for adding a third button to the dialog.

JavaScript
//Call base popupDialog for demo purpose. Could call showCommonPopup.
var popup = container.jqsDialog("popupDialog", {
    message: "<br/><br/>(Content could be anything injected)<br/>",
    title: "Selection",
    actionButtonLabel: "Upload",
    closeButtonLabel: "Cancel",
    width: 320
});
//Add a new button with function for click event.
var emailButton = popup.setOtherBtn("Email");
emailButton.on("click", function () {                
    container.jqsDialog("showCommonPopup", "You selected sending out an email", 
                        "Message", "info", popup);
});

The screenshot for the dialog window is like this:

Image 10

Non-modal Dialogs

When used as non-modal type, some considerations may be needed for the jqsDialog plugin.

  • The modal property of the input argument is set to true by default. Setting it to false for rendering the dialog of a non-modal type.
  • The fadeIn property of the input argument is set to true by default. Setting the modal to false will automatically set the fadeIn to false.
  • Non-modal dialog can only be opened on its base parent window. Opening any non-modal child dialog from its non-modal parent dialog is not supported. To open such a child dialog from a non-modal parent dialog, you may set the fadeIn property of the input argument to false so that no gray background is shown.
  • The click-outside option should not be set for a child dialog that is opened from a non-modal parent dialog with the click-outside option enabled.
  • If a non-modal parent dialog is set with the click-outside option, the option needs to be disabled when you open a child dialog from the parent dialog and don't want to close the parent dialog together. You need then to set the parentResetClickOutside property value in the child dialog input argument object for resuming the parent's click-outside option after closing the child dialog.
  • The id property value of the non-modal dialog needs to be explicitly defined to avoid opening multiple same dialogs when sending the same command from the parent window.

Below is the code snippet for creating a non-modal dialog and its child dialog with parent click-outside disabled and re-enabled scenarios.

JavaScript
//Needs explicit Id for non-modal dialog to prevent opening the same dialog again.
var popup = container.jqsDialog("showCommonPopup", {
    message: "It's the parent non-modal dialog window.",
    title: "Non-modal Dialog",                
    actionButtonLabel: "Open Child",
    closeButtonLabel: "Cancel",
    clickOutside: true,
    modal: false,                
    id: "non-modal-1"
});
//Needs to check button existence for IE 8 
if (popup.actionButton != undefined) {
    popup.actionButton.on("click", function (event) {
        //Need to disable non-modal parent click-outside when opening child dialog from it
        popup.clickOutside(false);
    var popupChild = container.jqsDialog("showCommonPopup", {
            message: "It's the child dialog opened from the non-modal parent.",
            title: "Child Dialog",
        fadeIn: false,
            //Re-enable parent's click-outside option
            parentResetClickOutside: popup
        });
    }); 
}

The non-modal dialog example is shown in the below screenshot. Feel free to use the sample application or your own to test various other scenarios.

Image 11

Files for Themes and Styles

The jqsDialog plugin in the demo project uses the CSS from these source files:

  • bootstrap_spacelab.css: sets overall themes. The classes modal-header, modal-body, and modal-footer, and btn are set by default for the dialog header, body, footer, and buttons. Changing to other bootstrap themes may also change the look and feel of the dialogs.
  • jqsDialog.css: provides custom specific styles for dialog structures. The custom styles may need to be adjusted when you change the overall bootstrap theme.
  • site.css: holds the general purpose styles applying to all pages across the site. For example, the btn class is added into this file to overwrite some settings for buttons in the bootstrap CSS file. The updated styles apply to buttons on all pages including dialogs.

Summary

The JQuery dialogs presented here are implemented with all requirements and features listed at the beginning of the article. All JavaScript functions in the plugin can be consumed by applications of any platform although the demo project is built as an ASP.NET MVC website application. With the dynamically updatable features, you can inject any forms of the data or media into the dialog content or change any dialog components, such as the header, icons, footer, and buttons. You may also freely modify anything in the plugin and associated supporting files for the functionality, themes, and styles based on what you need, making these dialogs more rich and robust. Happy coding!

History

  • 8th April, 2014: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)