Community Pick: Many members of our community have endorsed this article.
Editor's Choice: This article has been selected by our editors as an exceptional contribution.

GROX: A Simple DHTML Greybox For Confirmation Windows

Published:
I needed a confirmation window that was not an actual popup window. I wanted this window to be modal, like any of the LightBox clones, and to persist until the Poor User made her choice. I also wanted to specify the caption of the buttons. Most important, I did not want to have to use an external JavaScript library.

After investigating several possible candidates, I found none of them to my liking. So I did what I always do - I created my own, and I call it "GROX" (short for "GRaybOX").

Even if you have no need for something like this, tThere are aspects of the code behind GROX that might be of interest: the concept of a class; the act of binding methods to objects; closures.

GROX has been tested in:
Internet Explorer 8.0.6001.18702
Firefox 3.5.6
Opera 10.10, build 1893
Safari 4.0.4 (531.21.10)

1. Components

GROX generates DOM elements and stuffs then into a wrapper <div/> on the web page. It uses a tiny .PNG image to create the modal background, like Lightbox.

The popup "window" contains:
a Title section
a Message section
a Button section
I separated the JavaScript into two external files, the first of which is germane to GROX but not the focus of this article:
helper.js    Generic methods
                      grox.js       GROX-specific methods

Open in new window

GROX requires a place in which to build itself. This wrapper is an empty <div/> with a CSS style rule on "grox.htm" (or your own page):
CSS:
                      #div_modal_wrapper { display:block; }
                      
                      HTML:
                      <div id="div_modal_wrapper"></div>

Open in new window

Here is GROX in action with the light background:
 GROX in action (light background)
Here is GROX in action with the dark background:
 GROX in action (dark background)

2. Walkthrough

"grox.htm" is the test platform for GROX. It is a short HTML page that demonstrates the features of GROX:
 GROX test pageGROX allows you to specify the aspects of the popup "window":

Title
If you want a specific title, you may enter one. If left blank, "Attention" will be used

Message
You specify the message to display in the <textarea/>. Newlines are preserved, and HTML is allowed

Keyboard
If you wish to allow the window to be closed via the keyboard, check this

Hide HTML Elements
If you need to hide those pesky <select/>, <iframe/> and <object/> elements, check this

Light or dark background
The modal background can either be light or dark; light is less obtrusive, dark more prominent

Buttons
For each GROX button, enter the letter of the prompt (letters are not case-sensitive)For example, to offer a "Yes" and "No" button, you would enter "YN" (dyslexic you're if "NY" or).

Currently there are twelve active buttons:

A - Abort
C - Cancel
D - Dismiss
E - Exit
F - Fail
H - Help
I - Ignore
N - No
O - OK
Q - Quit
R - Retry
Y - Yes
Buttons for the remaining letters do not have a caption, so they are not be presented to the user (even if you specify them).

NOTE: You can edit "grox.js" and add a caption to an unused button to make it active. You can even add more buttons by using the numbers "0" through "9" and the shifted number characters "!" through ")". In fact, [b]any[/b] single character can be used for a button (but "a" through "z" are treaded as "A" through "Z").

Consider this nightmare: you specify only buttons that have no caption, then invoke GROX. With no active buttons, GROX will not allow the Poor User to make a selection! Worse, if you forget to enable keyboard input, GROX cannot be closed! This is Very Bad News for Betty User.

Fortunately, GROX provides a validation method for your button string, but it is up to you to use it. Since you might "forget", GROX examines your button string before it displays the modal popup, and if it is invalid, returns information to your web page about why GROX "isn't working":
    {
                              'id':'error', 
                              'value':'@', 
                              'text':'You must specify at least one button', 
                              'index':-7
                          }

Open in new window

NOTE: GROX will not display a popup without at least one button, even if the keyboard is
enabled.

You may specify as many buttons as you wish, but there is a practical limit: how many choices do you need to offer?

You may specify the same button more than once (GROX assigns a unique id attribute to each button), although your users may misunderstand your intentions with more than a single "Abort" button.

The "Show" button invokes the popup. Everything else is for testing purposes.

NOTE: For this discussion, the "Keyboard Is Active" and "Light BG" options are selected.

The most important part of the page is invoking GROX. Let's see how this is done:
<script type="text/javascript">
                      
                          function click_show(e) {
                              // Show the popup "window"
                              ev = resolve_event(e); // (see "helper.js" for code)
                              
                              var check = GROX.check( { 'button':$('txt_button').value } ), // Validate our buttons
                                  list = GROX.list( { 'format':'array' } ), // List all active buttons
                                  how = {}, // Filled-in later
                              //
                              exknob;
                              
                              $('txt_button').value = check; // Validated buttons
                      
                              if (check.length === 0) {
                                  alert('Please check your button selections'\n\nValid buttons are\n\n' + list);
                                  $('txt_button').focus();
                                  
                                  return false;
                              }
                              var how = {
                                  'wrapper':'div_modal_wrapper',    // Where the "popup" will be created
                                  'title':$('txt_title').value,     // "Window" title
                                  'text':$('txa_message').value,    // Message to display
                                  'keyboard':$('chk_kb').checked,   // "true" to accept "x || X" (abort) from keyboard
                                  'nasty':$('chk_nasty').checked,   // "true" to hide problematic HTML elements
                                  'button':check,                   // e.g. "ARF" for "Abort", "Retry" and "Fail" buttons
                                  'bg_style':$('rad_light').checked, // "true" = light background, "false" = dark background
                                  'callback':callback               // Callback method (invoked upon GROX completion)
                              };
                              // Show our popup
                              GROX.show(how);
                              
                              return false;
                          }
                          
                          
                          function page_load() {
                              $('btn_show').onclick = click_show.bindEventListener(this);
                              
                              $('txt_button').value = 'arf';
                              $('txa_message').value = base_text;
                              
                              $('lbl_button').innerHTML = 'Buttons (' + GROX.list({ 'format':'array' }) + '):';
                          }
                      
                      </script>

Open in new window

The function "page_load" is invoked when the page is rendered. This binds the "onclick" event to the "Show" button and sets the buttons and message to their default values. It also asks GROX for a list of active buttons and inserts this into the <label/> for the user's button selection.

NOTE: For more information on binding methods to objects, please refer to my article "KAFKA: A Simple CAPTCHA Implementation", or see Step #9 in this article.

You may wonder why there are no position, width or height values for the popup window? It is because GROX always sizes and centers itself dynamically (well, it tries real hard).

This apparent lack of positioning controls is not an oversight, I just didn't feel it was necessary for the initial
release of GROX.

The "callback" parameter is a JavaScript function you create and pass into GROX. When GROX completes, the function is invoked with the results from GROX (view the source for "grox.htm" for details).

Next, GROX is drawn and the user is put in charge.

3. User Interaction

If the window is resized or scrolled up or down, GROX will re-position itself in the center of the screen.

After GROX has drawn the popup "window", there are two things the user can do:
Click one of the buttons
Press the "x" key
When either of these events occur, GROX returns information about the event. Here, the "Fail" button was clicked:

 "Fail" button clickedAnd that's it. GROX is so simple, even a cave...well, you know what I mean.

4. The Code Behind GROX

GROX is a class with private properties and methods, and three exposed (public) methods:
show - Invoke GROX and display a modal popup "window"
list - Return a string or array of active buttons
check - Verifies (and returns) the list of buttons against the internal list
The structure of GROX is similar to this:
var HESSE = (function() {
                          var private_property_1 = 'You cannot see this from outside HESSE';
                          var private_property_2 = 'HESSE shares this with you';
                          
                          function private_method_1(parm) {
                              alert(private_property_1 + '\t\n' + arg); // Show the world one of my secrets and the passed parameter
                              
                              return private_property_2; // Make private property visible (NOT public)
                          }
                          // Exposed (public) methods
                          return { // Bracket must be on same line to avoid JavaScript misunderstanding our intent
                              'public_method_1' : function(parm) { return private_method_1(parm); }
                          };
                      })();

Open in new window

The only method HESSE exposes is "public_method_1". It alerts a secret and returns another. This is the only interaction allowed. You cannot access any of its internal (private) properties or methods. For example:
var result = HESSE.public_method_1('arlo'); // VALID
                      
                      var secret_1 = HESSE.private_property_1; // INVALID
                      
                      HESSE.private_method_1('fred'); // INVALID

Open in new window

Let's examine GROX's "show" method.
    function show_modal(how) {
                              // Expects
                              //    wrapper   DOM container
                              //    title     Popup title (optional)
                              //    text      Popup message
                              //    keyboard  "true" if keyboard is active
                              //    nasty     "true" to hide problematic HTML elements
                              //    button    String of button IDs
                              //    callback  Method invoked upon completion
                              
                              parm = how; // Save for later
                              
                              // Validate selected buttons
                              if (verify_options(how) === '') {
                                  alert('You must specify at least one button');
                                  return { 'id':'error', 'value':'@', 'text':'You must specify at least one button', 'index':-7 };
                              }
                              // Create event sinks
                              if (parm.keyboard) {
                                  // Allow for "x" and/or "X" to close popup
                                  document.onkeypress = key_press.bindEventListener(this);
                              }
                              window.onscroll = reposition.bindEventListener(this);
                              window.onresize = reposition.bindEventListener(this);
                              
                              // Hide pesky HTML elements?
                              if (parm.nasty) {
                                  show_hide_nasty('hidden');
                              }
                              // Build and show the popup "window"
                              var result = build_it(parm);
                          }

Open in new window

If the buttons selected do not validate, alert the Poor User and return error information to the caller.

NOTE: The "alert()" is optional; as long as you check the return value of a GROX invocation, you can handle errors silently.

Since we know there are buttons to display, we save the original arguments for later; if the keyboard is to be active, we bind the "key_press" method; we bind the "reposition" method to window events; we hide HTML elements (if requested); finally, we invoke the private method "build_it" with our parameters.

When the user has made her choice, GROX builds an object for sending back through the supplied callback method. Its structure is the same as the invalid button result, only the content differs:
BUTTON CLICK:
                      {
                          'id':'btn_modal_2_F',
                          'value':'F',
                          'text':'Fail',
                          'index':2 
                      } 
                      
                      KEY PRESS:
                      {
                          'id':'keyboard',
                          'value':'#',
                          'text':String.fromCharCode(key), // text is "x" or "X"
                          'index':-7 
                      }

Open in new window

This object is passed to a destructor method to clean up before we return to the calling page:
    function hide_modal(obj) {
                          // Tear down the popup structure
                          
                              // Un-bind button events
                              for (var i = 0; i < opt_btn.length; i += 1) {
                                  $(opt_btn[i].id).onclick = '';
                              }
                              // If keyboard is active, unbind "keypress"
                              if (parm.keyboard) {
                                  document.onkeypress = '';
                              }
                              // Un-bind window events
                              window.onscroll = '';
                              window.onresize = '';
                              
                              // Remove modal DOM elements (close the "window")
                              delete_children($(parm.wrapper));
                              
                              // Show pesky HTML elements?
                              if (parm.nasty) {
                                  show_hide_nasty('visible');
                              }
                              return parm.callback(obj);
                          }

Open in new window

5. The Modal Model

GROX creates an outer <div/> ("outer") for the modal background, paints it with a .PNG image and sets the z-index to 998 (an arbitrary value). Then it creates an inner <div/> ("inner") for the pseudo-window and stuffs the title, message and buttons within. "inner" has a z-index of 999 (to appear just above "outer").

NOTE: The z-index of "inner" must be greater then that of "outer".

CSS is applied to the DOM elements as they are created, then they are appended to "inner". Then "inner" is appended to "outer", and "outer" is appended to the supplied wrapper <div/>, presenting a modal confirmation window to the user.

NOTE: Rather than all of this DOM-manipulation, I could have created a framework HTML page and Ajaxed it up from the server. Since the buttons are dynamic, they would still have to be inserted into the page. I reasoned the DOM is the best fit, even with all of the code required.

There are a few things that could be done to improve GROX:

Provide CSS for the buttons (colors, font, etc.)
Provide text for the button title (as a new parameter for GROX)
Add an icon for window close (and the accompanying click event code)
Add a time-out mode, where inactivity after n seconds triggers an abort
The structure for the CSS is in place, and the "Abort", "Retry" and "Fail" buttons display with colors (these colors are not dynamic, i.e., you cannot specify them without editing "grox.js", so this is a "kind of" improvement).

Specifying the button title for each button might be overkill.

The ability to make GROX more flexible will make setting the parameters for GROX harder, perhaps even too unwieldly; if so, then I will not consider them further. My goal is to make GROX simple to use.

The window close and time-out features seem useful, and I might tackle them at a later date.

6. Objects As Parameters To Functions

Some of you may question my habit of passing objects as arguments to functions. To this I say, "Clarity above all else." I mean, eschew obfuscation isn't just a suggestion, eh?

In my experience, few things are as frustrating as a parameter list: is it in order; is it complete; did I forget
something; how do I not supply a parameter? The answer is, "Yes", "Yes", "Most likely" and "You can't".

All of this can be avoided by using objects for parameters to functions. Sure, they are a bit cumbersome, and
sometimes seem like overkill, but remember back when you had finished that perfect app, but Marketing asked for "just one more little change" and you had to adjust the parameter list and recompile umpteen dependent programs to comply with their moronic request? Well, adding a new parameter to an object is trivial, and it won't break anything. New code can use the parameter, and old code never expected it so it won't care. Or crash.

When a function receives an object instead of multiple arguments, there can be no doubt about what was sent. Missing entries are easily identified and set to a default value. All of the properties and/or methods can be iterated over, documented, displayed and acted upon.

This can be done, to a lesser degree, by using the arguments object available to every JavaScript function, but beware:
This object is like an array, but it is not a true array
You cannot retrieve an argument by name (except by its index number)
Missing or mis-ordered arguments cause havoc with your code
There are other reasons, but aren't those enough to at least consider this approach? I think so: what's not to love?

There are some cases where this approach does not make sense: the internal functions of a class may not need the flexibility an object provides because they are invoked within the class structure. They are not exposed to the world so the class can control what goes into and comes out of them.

7. Warranty

This software is provided "as-is". No effort will be made to diagnose and/or fix problems arising from the use of this code. Experts-Exchange is hereby granted permission to make this code available to others, but I retain the rights of sole ownership.

8. Installation

There are five files in the archive:
grox.htm - Testbed for GROX
grox.js - Code for implementing GROX
helper.js - Generic helper methods
grox_dark.png - Dark background for the modal <div/>
grox_light.png - Light background for the modal <div/>
Create a new virtual directory on your server - grox, for example - and unzip the archive into it. Then, enter

http://YOUR_SERVER_NAME/grox/grox.htm into your browser and try it.

9. Conclusion

Credit for binding methods to objects is due to entirely to Daniel Brockman; I simply use his idea. It is complex, but worth the time taken to grok its potential.

I welcome your comments and criticism, both good and bad.
  grox.zip
2
4,103 Views

Comments (1)

Hemanth KumarIT Analyst

Commented:
Hello Badotz,

I found this can be very helpful for one of my requirement, but unable to edit it.

I have one web page. while accessing the webpage I require this popup to load with Accept and Decline button with dark background.

On clicking accept, it will show the page normally.
On clicking decline button it should redirect to a page to show, "plz accept the agreement to navigate the page."


Plz will you be able to help me out on this ?

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.