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

Custom JavaScript Event Manager (CJEM)

4.93/5 (32 votes)
3 Aug 2011GPL39 min read 90K   3.7K  
A Custom JavaScript Event Manager Class Designed to Manage Window, Document and Control Events on a Webpage

Download Source Files

Main Files

Control Examples

Example CJEM Advanced Control

screenshot.png

Introduction

This is a script I developed many years back, around 2002. It is an event manager for JavaScript that fixes many issues the browser wars created and makes developing controls with events on web pages, fun and easy. I couldn't come up with a catchy name for it, so I just called it (CJEM) Custom JavaScript Event Manager. I decided to release it to the public because development with JavaScript has been increasing especially over the past two years. Maybe someone will find this article beneficial for their web development. I hope you enjoy it.

Why Did I Create It?

I was tired of using the browser event manager, only to find out my code stopped working with different versions and browsers. The managers were also very buggy and did not implement everything that was reported in the online documentation. Lack of naming conventions between browser companies with objects in JavaScript, HTML, CSS and DOM has caused many developers to go crazy, writing cross compatible code. Is it so hard to stick to a standard naming structure?

Why Should You Use It?

It is a very lightweight, simple, highly organized and thought out script that has been in production for many years. It makes life easy when creating web page control events with JavaScript and DOM. I also tried to follow the standard documentation online and create what was missing or not uniform. Using this script can make you more organized with your JavaScript source files. The script also works in most if not all browsers and versions like Internet Explorer 5+, FireFox, Chrome, Opera, Safari, Konqueror and Netscape if you dare.

Class Properties and Methods

Title: Custom JavaScript Event Manager (CJEM) - Class
Namespace: Global
Description: The main event manager class for creating window, document and control events.
PropertiesDescriptionReturn Value
EventsThis property variable contains an array collection of events, class names, control ids, event types, callbacks and other custom settings. The property is mostly used for internal information for the event manager, but has been left open to allow developers to extend it.Array Collection
Method(s)DescriptionReturn Value
Add (ClassObject, ControlID, EventType, EventCallBack, TotalInstance, Wait, UserObject)Adds a window, document or control event to the manager for monitoring. When the event fires, the manager will call the event callback method. The Add method also supports adding more than one EventCallBack by using the method again.Nothing
Parameters (Marked with * are required)

* ClassObject (string) - The name of window, document, class or variable that holds the method to call.

* ControlID (string) - The id for the window, document or control to attach the event to.

* EventType (string) - The event type name you want to monitor, onmousedown, onmouseup, onmousemove, onblur, etc.

* EventCallBack (function) - The function() to call back when the event occurs.

TotalInstance (integer) - The total times you want the event to occur. 0 = Complete, null or undefined is never ending.

Wait (integer) - The total milliseconds to wait before calling the EventCallBack method. Null or undefined is instant.

UserObject (object) - A custom parameter for adding anything you want that will be transfered to the EventCallBack.

 
Remove (ControlID, EventType, EventCallBack)Removes a window, document or control event from the manager for monitoring. This will stop the event from being broadcasted to the EventCallBack.Nothing
Parameters (Marked with * is Required)

* ControlID (string) - The id for the window, document or control that the event was attach to.

* EventType (string) - The event type name onmousedown, onmouseup, onmousemove, onblur, etc., that you would like to stop monitoring.

EventCallBack (function) - The function() that was used for callback. If you specify a unique one, it will only remove that one. Null or undefined will result in all similar event types to be removed.

Implementation

To implement CJEM is rather easy, just include the external 'cjem.js' file in a web page header. Then use the add or remove methods of the CJEM class to create or remove event handlers. CJEM allows for complete separation of JS from HTML. No more adding in-line JS to HTML tags to create DOM events. Everything is handled nice and easy in the JS file.

Example 1 - Capture Document Events

The first example captures the document onmousedown event. Relatively simple example, but created in less than 10 seconds. When you click anywhere on the document, it will display an alert message telling you what occurred. 

JavaScript
// Captured the document mouse down event.
function DocumentMouseDownHandler(src)
{
  alert("You captured the '" + src.EventType + 
	"' from the control: '" + src.ControlID + "'");
}

// Capture the document mouse down
$CJEM.Add('document','document', 'onmousedown', DocumentMouseDownHandler);

Example 2 - Capture Window Event + Control Event

This example captures the window load event only once. It then adds two events to the span, onmousemove and onmouseout. When you mouse over the span, it will change the innerHTML of the span, then it will change it back on mouse out. In order to capture a control event, the document window must be loaded first. Also the window load might fire more than once in different browsers, so the best thing to do is set the TotalInstance to 1. This makes sure it only fires once.

JavaScript
// Captured the Window Load Handler.
function WindowLoadHandler(src)
{
  // Capture the document example mouse move.
  $CJEM.Add('document','example', 'onmousemove', DocumentExampleMouseMoveHandler);

  // Capture the document example mouse move.
  $CJEM.Add('document','example', 'onmouseout', DocumentExampleMouseOutHandler);
}

// Captured the document example mouse move event.
function DocumentExampleMouseMoveHandler(src)
{
  $O(src.ControlID).innerHTML = 'Mouse Over This! - Mouse is Over';
}

// Captured the document example mouse out event.
function DocumentExampleMouseOutHandler(src)
{
  $O(src.ControlID).innerHTML = 'Mouse Over This!';
}

// Capture the Window Load Event one time.
$CJEM.Add('window','window','onload', WindowLoadHandler, 1);

Example 3 - Event Manager Total Instances

I felt the need to add a total instance parameter to the add method. You may find the need to use it, like the previous window load method. In this example, we will create a span that will onmousedown only 3 times, then stop. It will display an alert and tell you how many instances left.

JavaScript
// Captured the Window Load Handler.
function WindowLoadHandler(src)
{
  // Capture the document example mouse move.
  $CJEM.Add('document','example', 'onmousedown', DocumentExampleMouseDownHandler, 3);
}

// Captured the document example mouse down event.
function DocumentExampleMouseDownHandler(src)
{
  alert("You captured the '" + src.EventType + "' from the control: '" + 
	src.ControlID + "'. Total Instances Left: '" + src.TotalInstance + "'");
}

// Capture the Window Load Event one time.
$CJEM.Add('window','window','onload', WindowLoadHandler, 1);

Example 4 - Event Manager Wait

I also felt the need to add a wait parameter to the add method. The wait parameter allows you to specify an integer of milliseconds to wait until the event should fire the EventCallBack method. This gives you the freedom to create some unique controls that fire later even though the event already occurred. With the example below, you can set the textbox to how many milliseconds, then click on the span to wait. Remember 1000 milliseconds is 1 second.

JavaScript
// Captured the Window Load Handler.
function WindowLoadHandler(src)
{
  // Get milliseconds to wait
  var msec = $O('mseconds').value;

  // Capture the document example mouse down.
  $CJEM.Add('document','example', 'onmousedown', 
	DocumentExampleMouseDownHandler, null, msec);
}

// Captured the document example mouse down event.
function DocumentExampleMouseDownHandler(src)
{
  alert("You captured the '" + src.EventType + 
	"' from the control: '" + src.ControlID + "'");
}

// Capture the Window Load Event one time.
$CJEM.Add('window','window','onload', WindowLoadHandler, 1);

Example 5 - Attaching More EventCallBacks

You can also attach more than one EventCallBacks to the same event. This allows the handler to call separate methods on one event, for example like below. When the span is clicked, it will fire the DocumentMouseDownHandler1 and DocumentMouseDownHandler2 methods. The neat thing is you can set delays also like the previous example. So DocumentMouseDownHandler2 will fire 2 seconds after the first alert. This allows for major power, especially with AJAX.

JavaScript
// Captured the Window Load Handler.
function WindowLoadHandler(src)
{
  // Capture the document example mouse down.
  $CJEM.Add('document','example', 'onmousedown', DocumentMouseDownHandler1);

  // Capture the document example mouse down.
  $CJEM.Add('document','example', 'onmousedown', DocumentMouseDownHandler2, null, 2000);
}

// Captured the document mouse down event.
function DocumentMouseDownHandler1(src)
{
  alert("You captured the first '" + src.EventType + 
	"' from the control: '" + src.ControlID + "'");
}

// Captured the document mouse down event.
function DocumentMouseDownHandler2(src)
{
  alert("You captured the second '" + src.EventType + 
	"' from the control: '" + src.ControlID + "'");
}

// Capture the Window Load Event one time.
$CJEM.Add('window','window','onload', WindowLoadHandler, 1);

Example 6 - Bubbling Events

When an event fires, it will bubble through all parent containers. This is a great feature implemented by most major browsers. The problem is browsers have different ways to handle it. Handling the scope and naming conventions of methods for cancellation can be a real pain. So I decided to fix these issues and make it simple and easy, the way it should have been done. To stop event bubbling, just 'return true' in your EventCallBack method. If you want event bubbling to occur, just return false or don't add a return. This not only makes it easy to stop bubbling, but fixes the scope issues.

JavaScript
// Captured the Window Load Handler.
function WindowLoadHandler(src)
{
  // Capture the document mouse down.
  $CJEM.Add('document','document', 'onmousedown', DocumentMouseDownHandler);

  // Capture the span1 mouse down with bubbling.
  $CJEM.Add('document','span1', 'onmousedown', Span1MouseDownHandler);

  // Capture the span2 mouse down without bubbling.
  $CJEM.Add('document','span2', 'onmousedown', Span2MouseDownHandler);
}

// Captured the document mouse down event.
function DocumentMouseDownHandler(src)
{
  alert("You captured the '" + src.EventType + 
	"' from the control: '" + src.ControlID + "'");
}

// Captured the span1 mouse down event and bubble to document.
function Span1MouseDownHandler(src)
{
  alert("You captured the '" + src.EventType + 
	"' from the control: '" + src.ControlID + "'");
}

// Captured the span2 mouse down event and stop bubbling.
function Span2MouseDownHandler(src)
{
  alert("You captured the '" + src.EventType + 
	"' from the control: '" + src.ControlID + "'");

  // Cause the event to stop bubbling.
  return true;
}

// Capture the Window Load Event one time.
$CJEM.Add('window','window','onload', WindowLoadHandler, 1);

Creating Your EventCallBack Method

Once you have created your EventCallBack method, CJEM will pass your method one parameter object. It will contain the following:

  1. ClassObject - The name of window, document, class or variable that holds the method to call.
  2. ControlID - The id for the window, document or control to attach the event to.
  3. EventType - The event type name, onmousedown, onmouseup, onmousemove, onblur, etc.
  4. TotalInstance - The total instances left before the event stops handling.
  5. Wait - The total milliseconds to wait before calling your EventCallBack method.
  6. UserObject - A custom parameter for adding anything you want that will be transfered to the EventCallBack.
  7. event - This is the window event object.

The parameter object above will give you tons of information helping you to create remarkable controls. Scoping will no longer be an issue to access information like the class name, control id, parent id, event type, window event, etc.

Prevent Default Event Behavior

Someone requested me to show an example of how to prevent the default behavior of an element. Adding the code below to a callback handler will stop the event from firing its default behavior. The example shows a standard link element that will not work on single click or mouse down, but only on double click. Using the code below will enable this to work.

JavaScript
// Captured the Window Load Handler.
function WindowLoadHandler(src)
{
  // Setup events
  $CJEM.Add('document','link', 'onmousedown', mousedownhandler);
  $CJEM.Add('document','link', 'onclick', mousedownhandler);
  $CJEM.Add('document','link', 'ondblclick', doubleclickhandler);
}

// Handles the link mouse down and click.
function mousedownhandler(src)
{
    if(src.event.preventDefault){ src.event.preventDefault()}
    else{src.event.stop()};

    src.event.returnValue = false;
    src.event.stopPropagation();   
    return true;
}

// Handles the link double click.
function doubleclickhandler(src)
{
  window.location = $O(src.ControlID).href;
}

// Capture the Window Load Event one time.
$CJEM.Add('window','window','onload', WindowLoadHandler, 1);

Custom JavaScript Event Manager - Source Code

Below is the Custom JavaScript Event Manager (CJEM) source code. I will also upload a compressed version later for use on the web.

JavaScript
// Registers a Namespace.
var $N={R:function(N){var o=window;var x=false;
	for(var a=N.split('.');a.length>0;){var s=a.shift();
	if(a.length==0){if(o[s]){x=true;}}if(!o[s]){o[s]={};}o=o[s];}if(x){return 1;}}};

// Finds an object by id.
function $O(S){return document.getElementById(S);}

// Check if object is undefined, null or string is empty.
function $NU(O){if(O==undefined||O==null){return true;}
	else{if(typeof(O)=='string'&&O==''){return true;}else{return false;}}}

// Custom Javascript Event Manager.
var $CJEM = {

    // The events array collection.
    Events: null,

    // Add an event to the manager.
    Add: function(ClassObject, ControlID, EventType, 
	EventCallBack, TotalInstance, Wait, UserObject) {

      // Check if events collection is null and create it or reuse it.
      if ($NU(this.Events)) { this.Events = new Array(); }

      // Check if that id exists already as object or create it.
      if ($NU(this.Events[ControlID])) { this.Events[ControlID] = {}; }

      // Get the event item for the specific control id.
      var EventItem = this.Events[ControlID];

      // Check if handlers exist or create new array for holding multiple same events.
      if ($NU(EventItem[EventType])) { EventItem[EventType] = new Array(); }

      // Get the handlers for the event item.
      var Handlers = EventItem[EventType];

      // Add new event handler.
      Handlers.push({'ClassObject': ClassObject, 'ControlID': ControlID, 
	'EventType': EventType, 'EventCallBack': EventCallBack, 
	'TotalInstance': TotalInstance, 'Wait': Wait, 'UserObject': 
	UserObject, event: null});

      // Register the event with the manager.
      $CJEM.Register(ControlID, EventType);
    },

    // Remove an event from the manager.
    Remove: function(ControlID, EventType, EventCallBack) {

        // Check if collection contains events.
        if ($NU(this.Events)) { return; }

        // Get the Event by control id.
        var EventItem = this.Events[ControlID];

        // Check if Event Item has handlers.
        if ($NU(EventItem)) { return; }

        // Get the handlers for the event item.
        var Handlers = EventItem[EventType];

        // Check if event item contains handlers.
        if ($NU(Handlers)) { return; }

        if(!$NU(EventCallBack))
        {
          // Find specific handler with eventcallback.
          for (var i = 0; i < Handlers.length; i++) {

            if (EventCallBack == Handlers[i].EventCallBack) 
		{ Handlers[i] = null; Handlers.sort(); Handlers.pop(); }
          }
        }
        else
        {
          // Remove all event handlers.
          Handlers = [];
        }

        // Reset if no more handlers.
        if(Handlers.length==0)
        {  
          Handlers = null;
          EventItem[EventType] = null;
          delete EventItem[EventType];
        
          // UnRegister from method handlers.
          $CJEM.UnRegister(ControlID, EventType); 
        }      
    },

    // Unregister event in the manager.
    UnRegister: function(ControlID, EventType) {

        // Check for window, document or control event to unregister.
        switch (ControlID) {

            case 'window': window[EventType] = null; break;
            case 'document': document[EventType] = null; break;
            default: 
              
              var control = $O(ControlID);
              if($NU(control)){ return; }
              control[EventType] = null; break;
        }
    },

    // Register event in the manager.
    Register: function(ControlID, EventType) {

        // Check for window, document or control event to register.
        switch (ControlID) {

            case 'window': window[EventType] = function(e) 
		{ $CJEM.Caller(e, ControlID, EventType); }; break;
            case 'document': document[EventType] = 
		function(e) { $CJEM.Caller(e, ControlID, EventType); }; break;
            default: 

              var control = $O(ControlID);
              if($NU(control)){ return; }
              control[EventType] = function(e) 
		{ $CJEM.Caller(e, ControlID, EventType); }; break;
        }
    },

    // Calls the EventCallBack for each handler.
    Caller: function(e, ControlID, EventType) {

        // Get the event object from the window.
        if (!e) { e = window.event; }

        // Get the event item from the collection.
        var EventItem = this.Events[ControlID];

        // Get the event item handlers.
        var Handlers = EventItem[EventType];

        if($NU(Handlers)){ return; }

        for (var i = 0; i < Handlers.length; i++) {

            // Get the EventObject.
            var EventObject = Handlers[i];

            // Set the window event into the event object.
            EventObject.event = e;

            // Check for TotalInstance.
	    if(!$NU(EventObject.TotalInstance))
            {
              if(EventObject.TotalInstance>0)
              { 
                EventObject.TotalInstance -= 1; 
              }
            }

            // Output object
            var handlerobject = {'ClassObject': Handlers[i].ClassObject, 
		'ControlID': Handlers[i].ControlID, 'EventType': Handlers[i].EventType, 
               	'TotalInstance': Handlers[i].TotalInstance, 
		'Wait': Handlers[i].Wait, 'UserObject': 
		Handlers[i].UserObject, event: e};

            // Check for Wait Timer
            if(!$NU(EventObject.Wait))
            {
              // Execute callback on wait timer.
              var t=setTimeout(function(){ if(EventObject.EventCallBack(handlerobject))
		{ if (navigator.appVersion.indexOf('MSIE') != -1) 
		{ e.cancelBubble = true; } else { e.stopPropagation(); }}}, 
		EventObject.Wait);
            }
            else
            {
              // No wait just fire.
              if (EventObject.EventCallBack(handlerobject)) {

                // Execute callback handler for event.
                if (navigator.appVersion.indexOf('MSIE') != -1)
		{ e.cancelBubble = true; } else { e.stopPropagation(); }
              }
            }

            // Remove event if total instances equals 0.
            if(EventObject.TotalInstance==0)
            { 
              $CJEM.Remove(ControlID, EventType, EventObject.EventCallBack); 
            }  
        }
    }
};

Compressed Version

I created a compressed version of CJEM. It weighs in at a whopping 1.9 KB. I stripped out the comments and long named variables and spacing. It doesn't change the output naming, so you can substitute it for the original.

i.e., It won't break your working code.

Control Examples

I will be adding many control examples through the months to this article. Also, if you have an example to share, please add it below and if it's good I will add it to the main article with your credits.

Tab Control

tabcontrol.png

Drop Down Select Control

dropdown.png

Checkbox Control

checkbox.png

movableitem.png

Conclusion

That concludes my article. If you find any bugs or want to suggest improvements, please give a comment below. Also just to be aware, there might be bugs from me changing everything to a long naming convention. I did that to improve readability and learning. I hope you all enjoyed my new article.

History

  • August 3, 2011 - Creating a custom Datagrid
  • July 5, 2011 - Making a new version
  • June 13, 2011 - Creating some new controls
  • May 19, 2011 - Prevent default example upon request
  • May 7, 2011 - Found a small issue with multi events and one instance
  • Apr 28, 2011 - Movable item example created
  • Apr 20, 2011 - Extending some code to include CSS class support
  • Apr 12, 2011 - Building more advanced control examples
  • Apr 8, 2011 - Checkbox example created
  • Apr 3, 2011 - Drop down select control example created
  • Mar 31, 2011 - Tab control example created
  • Mar 30, 2011 - Creating some example control libraries
  • Mar 10, 2011 - Created a compressed CJEM, file size 1.9 KB
  • Mar 9, 2011 - Added date calendar textbox example
  • Mar 6, 2011 - Released CJEM for General Public License (GPL3)

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)