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:
[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.
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.
var vdc = $("<table><tr><td align='center'>");
if (args.modal) {
$("body").append(vdc);
}
else {
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
:
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:
[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):
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 () {
container.jqsDialog("showCommonPopup", {
message: "Your request has been processed successfully",
title: "Information",
icon: "info",
stopClose: true,
popup: popup
});
popup.closeButton.on("click", function () {
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 () {
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:
Clicking the Yes button, the existing dialog is updated to the messaging dialog box:
Clicking the OK button, the existing dialog is further updated to an error confirmation dialog box:
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.
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:
[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:
var popup = container.jqsDialog("showDataPopup", {
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],
args.stopProgress: [undefined or false],
popup: [undefined]
. . .
});
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:
container.trigger("JsonViewSuccess", { html: result.html, data: result.data });
In the consumer code:
container.on("JsonViewSuccess", function (html, 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:
- An empty data model used for field type mapping in the partial view.
- 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:
return PartialViewJson("../Demo/_Product", new Product(), product);
In BaseController.cs:
protected JsonViewResult PartialViewJson
(string viewName, object model = null, params object[] data)
{
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))
{
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
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) {
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 () {
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:
When clicking the Save button, a new confirmation dialog is shown. Both original page and data dialog window now become faded backgrounds.
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.
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:
[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.
-
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.
-
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.
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
:
popup.setHeader("no-x");
But don't forget to add the x button back to the next dialog to which it is switched:
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:
popup.typeStatus = "progress";
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:
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.
[returned button instance] = containerInstance.setOtherBtn(label, [type])
Below is an example for adding a third button to the dialog.
var popup = container.jqsDialog("popupDialog", {
message: "<br/><br/>(Content could be anything injected)<br/>",
title: "Selection",
actionButtonLabel: "Upload",
closeButtonLabel: "Cancel",
width: 320
});
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:
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.
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"
});
if (popup.actionButton != undefined) {
popup.actionButton.on("click", function (event) {
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,
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.
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