Sunday, January 27, 2013

How to Fully Disable Page Content using jQuery

You may think that disabling elements on an HTML page is as simple as assigning disabled=”true” to every element or by invoking a jQuery selector such as $(“input”).attr(“disabled”,”true”);, but it’s not that simple. What about links or click events assigned to elements inside the content being disabled? When disabling content on a page, you may not want those links or clickable elements to function anymore. This post will review what is needed to disable all input elements within a container (such as a DIV) so that nothing can be entered or clicked.

Selecting Elements to Disable

In order to disable every element contained within a container, including events, you need to recursively selected all elements inside the container, iterate through each selected element, and disable each element. To do so, you will need to use the jQuery find() method to select all elements. Taken from the jQuery API page, “Given a jQuery object that represents a set of DOM elements, the .find() method allows us to search through the descendants of these elements in the DOM tree and construct a new jQuery object from the matching elements.”. Use the find() method with caution. Using the find() method to recursively traverse very busy DOM’s can result in poor performance.
Assuming you are disabling all elements inside a DIV identified by “myData”, the following jQuery code will select all elements in the DIV. If you are not interested in disabling everything, you can limit your search by providing the appropriate selector to the find() method. For example, find(“:input,:button”) selects only INPUT type elements and button elements.

// selects all elements inside the DIV 
$(“div#myData”).find(“*”).each(function(index) {
    // each iteration in the loop will find 
    // one element to disable
    var inp  = $(this);
    …
});

Disabling Each Element within the Loop

Not all elements are disabled in the same manner. Some elements are disabled by simply adding the attribute, disabled=”true”, and others require additional logic to prevent any event assigned to the element from firing.
The first thing to do is determine what the type of element that is being disabled. To do so, the type attribute is retrieved from the element. If the element has a value for the type element, then use the tagName value on the type element as the type of element.

// for each iteration in the loop will find one 
// element to disable
var inp  = $(this);

// determine the type of element being disabled.
var type = inp.attr("type");
if (type == undefined) {
  if (inp[0] == undefined) {
    type = "";
  } else {
    type = inp[0].tagName.toLowerCase();
  }
}
Anchors
For anchors, we need to remove all events and prevent anything from happening when the user clicks the link. In order to enable the element successfully in a future action, we need to save the events before removing them. Finally, we style the anchor to look disabled. For this article, I’ll use the CSS class offered by the jQuery UI themes. The contents of the ui-state-disabled CSS class can be found at the bottom of this article.

// disable all events (see function below)
disableEvents(inp);
 
var href = inp.attr("href");
if (href != undefined) {
  // save the HREF attribute value and remove the value
  inp.data(“disabler-anchor-href”, href);
  inp.attr("href", "#");
}
   
// a click event which performs no action 
inp.on("click", function(e) {
  e.preventDefault();
});

// make the anchor look disabled
inp.addClass(“ui-state-disabled”);
Disabling the events requires us to retrieve the array of events associated to the element. jQuery adds an “events” data map attribute on the element when the first event is registered. One event can have many handlers. For each event defined on the element, loop through each handler and save the event and handler in order to be re-applied if the element is enabled in a future action. Finally, remove the event by unbinding it.
In order to facilitate reuse, I made this into a function.

function disableEvents(inp) {
  // jQuery adds an "events" data attribute on the 
  // element when events are registered
  var events = inp.data("events");
  if (events != undefined) {  
    var savedEvents = [];
    // loop through each event found on the element...
    $.each(events, function(eventName, handlers) {
      $.each(handlers, function(index) {
        var handler = handlers[index];
        if (handler != undefined) {
          // save the event and handler
          var eventObj = {
            'eventName' : eventName, 
            'handler' : handler
          };
          // save the event
          savedEvents.push(eventObj);
          // remove the event from the element
          inp.unbind(eventName);
        }
      });
    });
    // store the saved events as a data attribute 
    // on the element
    inp.data(“disabler-saved-events”, savedEvents);
  }   
}
Submit buttons and Button Elements
For input elements of type button and button elements, simply adding the attribute disabled=”true” will disable the element and prevent the element from being clicked.

if (type == "submit" || type=="button") {
  inp.attr("disabled", "true");
}
Labels
Since label elements is just text, simply assigning a CSS class to make the label look disabled will suffice.

else if (type == "label") {
  inp.addClass("ui-state-disabled");
}
Select Element, Check Boxes, and Radio Buttons
For HTML select boxes, check boxes, and radio buttons, all events need to be removed (like was done with anchors) and we need to add the disabled=”true” attribute.

else if (type == "select" || type == "checkbox" 
    || type == "radio") {
  disableEvents(inp);
  inp.attr("disabled", "true");
}
Input (type=”text”) and TextArea boxes
For input and textarea elements, add the disabled=”true” and readonly=”readonly” attributes.

else if (type == "text" || type == "textarea") {
  inp.attr("disabled", "true");
  inp.attr("readonly", "readonly");
}
All Other Elements
For all other elements, simply remove all events.

else {
  disableEvents(inp);
}

How to Enable the Content that was disabled

To enable content that was previously disabled by the method described in this article, select all elements in the same manner that was applied when the elements were disabled and essentially reverse what we did when we disabled the element.

$(“div#myData”).find(“*”).each(function(index) {
    var inp  = $(this);
    …
});

Anchors

Unbind the event put on the element when the element was disabled (which prevented any action when clicked).

// unbind event put on by disable function
inp.unbind();
Restore the events that were saved off when disabled. In order to facilitate reuse, I made this into a function.

// put back all events removed when disabled
enableEvents(inp);
Restore the HREF removed when disabled.

// put back the HREF removed when disabled
var href = inp.data(“disabler-anchor-href”);
if (href != undefined) {
  inp.attr("href", href);
  inp.removeData(“disabler-anchor-href”);
}
Remove the CSS class which makes the anchor look disabled.

inp.removeClass(“ui-state-disabled”);
Loop through all events saved on the element before disabled. For each saved event, add the event back to the element.

function enableEvents(inp) {
  var savedEvents = inp.data(“disabler-saved-events”);
  if (savedEvents != undefined) { 
    // loop through each saved event and register 
    // events on the element.
    $.each(savedEvents, function(index) {
      var savedEvent = savedEvents[index];
      var eventName = savedEvent.eventName;
      var handler = savedEvent.handler;
      inp.on(eventName, handler); 
    });
  }
}
Submit buttons and Buttons
For input elements of type button and button elements, remove the disabled attribute.

if (type == "submit" || type=="button") {
  inp.removeAttr("disabled");
}
Labels
Remove the CSS class which makes the label look disabled.

else if (type == "label") {
  inp.removeClass("ui-state-disabled");
}
Select Element, Check Boxes, and Radio Button Elements
For HTML select boxes, check boxes, and radio buttons, restore all events that were removed and remove the CSS class which makes the element look disabled.

else if (type == "select" || type == "checkbox" 
    || type == "radio") {
  enableEvents(inp);
  inp.removeAttr("disabled”);
}
Input (type=”text”) and textarea boxes
For HTML select boxes, check boxes, and radio buttons, restore all events that were removed and remove the CSS class which makes the element look disabled.

else if (type == "text" || type == "textarea") {
  inp.removeAttr("disabled");
  inp.removeAttr("readonly");
}
All Other Elements
For all other elements, restore all events that were removed when disabled.

else {
  enableEvents(inp);
}

CSS

You can use the CSS values for making an element looked disabled.

.ui-state-disabled { 
  cursor: default !important; 
  opacity: .35; 
  filter:Alpha(Opacity=35); 
  background-image: none;
}

jQuery Disabler

If this seems like a lot, you’re in luck. I created a jQuery plugin that can completely disable or present a read-only view of the content on your page. The plugin uses the code outlined and described in this article. You are free to use it as-is or take the code and use the code in your scripts.
With Disabler, your code will be as simple as this…

$("div#myData").disabler({
  disable : true
});
You can find Disabler on the jQuery plugins site
( http://plugins.jquery.com/disabler ) or directly on my site at
( http://dougestep.com/dme/jquery-disabler-widget ).