Source Code

for file ~/controls/jcl.js

// jcl.js: JavaScript Common behaviours Library
// -----
// Behaviour loading and DataConnections for AJAX Controls
// Copyright (c) by Matthias Hertel, http://www.mathertel.de
// This work is licensed under a BSD style license. See http://www.mathertel.de/License.aspx
// More information on: http://ajaxaspects.blogspot.com/ and http://ajaxaspekte.blogspot.com/
// -----
// 12.08.2005 created
// 31.08.2005 jcl object used instead of global methods and objects
// 04.09.2005 GetPropValue added.
// 15.09.2005 CloneObject added.
// 27.09.2005 nosubmit attribute without forms bug fixed.
// 29.12.2005 FindBehaviourElement added.
// 02.04.2005 term method added to release bound html objects.
// 07.05.2006 controlsPath added.
// 09.05.2006 Raise only changed values.
// 09.05.2006 Load() and RaiseAll() added to support default-values on page start.
// 03.06.2006 binding to the document enabled for FF.
// 13.08.2006 classname functions added.
// 16.09.2006 bind() function added to the Function.prototype.
//            The "on___" functions that are automatically attached to events
//            are now always executed in the context of the bound object.
// 24.11.2006 DataConnections without a name are ignored now.
// 09.01.2007 allow Array.prototype.extensions (prototype.js)
// 06.06.2007 including an Open Ajax hub specification compatible implementation
// 11.07.2007 more robust classname functions.
// 15.08.2007 afterinit method support for JavaScript Behviors. This method is called after all components
//            on a page have been initialized.
// 22.08.2007 jcl.getCookie and jcl.setCookie added.
// 15.09.2007 BuildFullEventname added.
// 07.10.2007 Firefox bugs fixed.
// 11.11.2007 isIE removed: use jcl.isIE instead.
// 20.11.2007 DataConnections a.k.a. Page Properties finally removed to favour the OpenAjax.hub
// 12.12.2007 adding EventNameSpace
// 12.01.2007 initstate method support for JavaScript Behviors.
// 02.01.2009 adding new nls support
// 05.12.2009 fixed getCookie
// 01.10.2010 IE9 compatibility avoiding browser detection.

// initialization sequence: init(), initstate(), afterinit()

// -- OpenAjax hub --

if (typeof(window.OpenAjax) === "undefined") {
  // setup the OpenAjax framework - hub
  window.OpenAjax = {};
} // if

if (typeof(OpenAjax.hub) == "undefined") {

// a hub implementation
OpenAjax.hub = {
  implementer: "http://www.mathertel.de/OpenAjax",
  implVersion: "0.4",
  specVersion: "0.5",
  implExtraData: {},

  // ----- library management -----

  // the list of libraries that have registered
  libraries: {},

  // Registers an Ajax library with the OpenAjax Hub. 
  registerLibrary: function (p, u, v, e) {
    var entry = { prefix: p, namespaceURI: u, version: v, extraData:e };
    this.libraries[p] = entry;
    this.publish("org.openajax.hub.registerLibrary", entry);
  },

  // Unregisters an Ajax library with the OpenAjax Hub.
  unregisterLibrary: function (p) {
    var entry = this.libraries[p];
    this.publish("org.openajax.hub.unregisterLibrary", entry);
    if (entry)
      this.libraries[p] = null;
  },

  // ----- event management -----

  _regs: {},
  _regsId: 0,

  /// name, callback, scope, data, filter
  subscribe: function (n, c, s, d, f) {
    var h = this._regsId;

    s = s || window;

    // treating upper/lowercase equal is not clearly defined, but true with domain names.
    var rn = n.toLocaleLowerCase();

    // build a regexp pattern that will match the event names
    rn = rn.replace(/\*\*$/, "\S{0,}").replace(/\./g, "\\.").replace(/\*/g, "[^.]*");

    var entry = {id:h, n:rn, c:c, s:s, d:d, f:f};
    this._regs[h] = entry;

    this._regsId++;
    return(h);
  }, // subscribe


  unsubscribe: function (h) {
    this._regs[h] = null;
  }, // unsubscribe


  publish: function (n, data) {
    if ((n) && (n.length > 0)) {
      n = n.toLocaleLowerCase();
      for (var h in this._regs) {
        var r = this._regs[h];
        if (r && (n.match(r.n))) {
          var ff = r.f; if (typeof(ff) == "string") ff = r.s[ff];
          var fc = r.c; if (typeof(fc) == "string") fc = r.s[fc];
          if ((!ff) || (ff.call(r.s, n, data, r.d)))
            fc.call(r.s, n, data, r.d);
        } // if
      } // for
    } // if
  } // publish

}; // OpenAjax.hub
OpenAjax.hub.registerLibrary("aoa", "http://www.mathertel.de/OpenAjax", "0.4", {});

} // if (! hub)

OpenAjax.hub.registerLibrary("jcl", "http://www.mathertel.de/Behavior", "1.2", {});

// -- Javascript Control Library (behaviors) --

if (typeof (window.jcl) == "undefined") {
  // setup the jcl root object.
  window.jcl = {};
} // if

// Detect InternetExplorer for some specific implementation differences.
jcl.isIE = (window.navigator.userAgent.indexOf("MSIE") > 0);

/// A list with all objects that are attached to any behaviour
jcl.List = [];

// attach events, methods and default-values to a html object (using the english spelling)
jcl.LoadBehaviour = function (obj, behaviour) {
  if ((obj) && (obj.constructor == String))
    obj = document.getElementById(obj);

  if (obj == null) {
    alert("LoadBehaviour: obj argument is missing.");
  } else if (behaviour == null) {
    alert("LoadBehaviour: behaviour argument is missing.");

  } else {
    if (behaviour.inheritFrom) {
      jcl.LoadBehaviour(obj, behaviour.inheritFrom);
      jcl.List.pop();
    }

    if (obj.attributes) { // IE9 compatible
      // copy all new attributes to jcl.properties
      for (var n = 0; n < obj.attributes.length; n++)
        if (obj[obj.attributes[n].name] == null)
          obj[obj.attributes[n].name] = obj.attributes[n].value;
    } // if
    
    for (var p in behaviour) {
      if (p.substr(0, 2) == "on") {
        jcl.AttachEvent(obj, p, behaviour[p].bind(obj));
        
      } else if ((behaviour[p] == null) || (behaviour[p].constructor != Function)) {
        // set default-value
        if (obj[p] == null)
          obj[p] = behaviour[p];

      } else {
        // attach method
        obj[p] = behaviour[p];
      } // if
    } // for
    
    obj._attachedBehaviour = behaviour;
  } // if
  if (obj)
    jcl.List.push(obj);
}; // LoadBehaviour


/// Find the parent node of a given object that has the behavior attached.
jcl.FindBehaviourElement = function (obj, behaviourDef) {
  while ((obj) && (obj._attachedBehaviour != behaviourDef))
    obj = obj.parentNode;
  return(obj);
}; // FindBehaviourElement


/// Find the child elements with a given className contained by obj.
jcl.getElementsByClassName = function (obj, cName) {
  var ret = new Array();
  var allNodes = obj.getElementsByTagName("*");
  for (var n = 0; n < allNodes.length; n++) {
    if (allNodes[n].className == cName)
      ret.push(allNodes[n]);
  }
  return(ret);
}; // getElementsByClassName


/// Find the child elements with a given name contained by obj.
jcl.getElementsByName = function (obj, cName) {
  var ret = new Array();
  var allNodes = obj.getElementsByTagName("*");
  for (var n = 0; n < allNodes.length; n++) {
    if (allNodes[n].name == cName)
      ret.push(allNodes[n]);
  }
  return(ret);
}; // getElementsByName


// cross browser compatible helper to register for events
jcl.AttachEvent = function (obj, eventname, handler) {
  if (obj.addEventListener) { // IE9 compatible
    obj.addEventListener(eventname.substr(2), handler, false);
  } else {
    obj.attachEvent(eventname, handler);
  } // if
}; // AttachEvent


// cross browser compatible helper to register for events
jcl.DetachEvent = function (obj, eventname, handler) {
  if (obj.removeEventListener) { // IE9 compatible
    obj.removeEventListener(eventname.substr(2), handler, false);
  } else {
    obj.detachEvent(eventname, handler);
  } // if
}; // DetachEvent


/// Create a duplicate of a given JavaScript Object.
/// References are not duplicated !
jcl.CloneObject = function (srcObject) {
  var tarObject = new Object();
  for (var p in srcObject)
    tarObject[p] = srcObject[p];
  return(tarObject);
}; // CloneObject


// calculate the absolute position of an html element
jcl.absolutePosition = function(obj) {
  var pos = null;
  
  if (obj) {
    pos = new Object();
    pos.top = obj.offsetTop;
    pos.left = obj.offsetLeft;
    pos.width = obj.offsetWidth;
    pos.height= obj.offsetHeight;

    obj = obj.offsetParent;
    while (obj) {
      pos.top += obj.offsetTop;
      pos.left += obj.offsetLeft;
      obj = obj.offsetParent;
    } // while
  }
  return(pos);
}; // _absolutePosition


/// When an object publishes or subscribes events it is possible to define the complete eventname
/// by a local eventname and a eventnamespace of a surrounding object.
jcl.BuildFullEventname = function (obj) {
  var en = null;
  
  // find the local event name on the object itself.
  if (! obj) {
    return(null);
  } else if ((obj.eventname) && (obj.eventname.length >0)) {
    en = obj.eventname;
  } else if ((obj.attributes["eventname"]) && (obj.attributes["eventname"].value.length > 0)) {
    en = obj.attributes["eventname"].value;
  } // if
  
  // search the event namespace if not present in the local eventname.
  if ((en) && (en.indexOf('.') < 0)) {
    while ((obj) && (! obj.eventnamespace) && ((obj.attributes) && (! obj.attributes["eventnamespace"])))
      obj = obj.parentNode;
    if (obj == document) {
      en = "jcl." + en; // default namespace, if none is specified.
    } else if ((obj) && (obj.eventnamespace)) {
      en = obj.eventnamespace + "." + en;
    } else if ((obj) && (obj.attributes["eventnamespace"])) {
      en = obj.attributes["eventnamespace"].value + "." + en;
    } // if
  } // if
  return(en);
}; // BuildFullEventname


/// Return the local part of a full qualified eventname.
jcl.LocalEventName = function (evn) {
  var idx;
  if (evn) {
    idx = evn.lastIndexOf('.');
    if (idx >= 0)
      evn = evn.substr(idx+1);
  } // if
  return(evn);
}; // LocalEventName


/// Return the eventnamesapce of a full qualified eventname.
jcl.EventNameSpace = function (evn) {
  var idx;
  if (evn) {
    idx = evn.lastIndexOf('.');
    if (idx >= 0)
      evn = evn.substr(0, idx);
    else
      evn = null;
  } // if
  return(evn);
}; // EventNameSpace


// find a relative link to the controls folder containing jcl.js
jcl.GetControlsPath = function () {
  var path = "../controls/";
  var s;
  for (var n in document.scripts) {
    s = String(document.scripts[n].src);
    if ((s) && (s.length >= 6) && (s.substr(s.length -6).toLowerCase() == "jcl.js"))
      path = s.substr(0,s.length -6);
  } // for
  return(path);
}; // GetControlsPath


// init all objects when the page is loaded
jcl.onload = function(evt) {
  var obj, c;
  evt = evt || window.event;

  // initialize all 
  for (c in jcl.List) {
    obj = jcl.List[c];
    if ((obj) && (obj.init))
      obj.init();
  } // for
  
  for (c in jcl.List) {
    obj = jcl.List[c];
    if ((obj) && (obj.initstate))
      obj.initstate();
  } // for

  for (c in jcl.List) {
    obj = jcl.List[c];
    if ((obj) && (obj.afterinit))
      obj.afterinit();
  } // for
}; // onload


// init all objects when the page is loaded
jcl.onunload = function(evt) {
  evt = evt || window.event;

  for (var n in jcl.List) {
    var obj = jcl.List[n];
    if ((obj) && (obj.term))
      obj.term();
  } // for
}; // onunload


// allow non-submitting input elements
jcl.onkeypress = function(evt) {
  evt = evt || window.event;
  
  if (evt.keyCode == 13) {
    var obj = document.activeElement;

    while ((obj) && (obj.nosubmit == null))
      obj = obj.parentNode;

    if ((obj) && ((obj.nosubmit == true) || (obj.nosubmit.toLowerCase() == "true"))) {
      // cancle ENTER / RETURN
      evt.cancelBubble = true;
      evt.returnValue = false;
    } // if
  } // if              
}; // onkeypress


// --- cookie helper methods ---
// from http: //www.elated.com/articles/javascript-and-cookies/
jcl.getCookie = function (aName) {
  var results = document.cookie.match('(^;) ?' + aName + '=([^;]*)(;$)');
  if (results)
    return (unescape(results[2]));
  else
    return (null);
}; // _getCookie


jcl.setCookie = function (aName, value, path, expire) {
  if ((path == null) || (path == "")) {
    // use the current folder from the url for the cookie to avoid conflicts
    path = String(window.location.href).split('/');
    path = '/' + path.slice(3, path.length-1).join('/');
  } // if

  if (expire) {
    var today = new Date();
    expire = parseInt(expire, 10) * 1000 * 60 * 60 * 24;
	expire = new Date(today.getTime() + expire);
  } else {
    expire = null;
  }// if

  window.document.cookie = aName + "=" + escape(value)
    + ((path) ? ';path=' + path : "")
    + ((expire) ? ";expires=" + expire.toGMTString() : "");
}; // setCookie


/// Call a function before next repaint. The function is triggered only once.
/// See http://www.w3.org/TR/animation-timing/
function throttle(func) {
  var isStarted = false;

  if (typeof func != 'function')
    throw new Error("throttle: not a function");

  return function () {
    var context = this;

    if (!isStarted) {
      isStarted = true;
      window.requestAnimationFrame(function () {
        func.call(context);
        isStarted = false;
      });
    } // if
  }; // function()
} // throttle


jcl.init = function () {
  jcl.AttachEvent(window, "onload", jcl.onload);
  jcl.AttachEvent(window, "onunload", jcl.onunload);
  jcl.AttachEvent(document, "onkeypress", jcl.onkeypress);
}; // init

// document.jcl_isinit (is not declared!) will be set to true to detect multiple jcl includes.
if (document.jcl_isinit)
  alert("multiple jcl includes detected !");
document.jcl_isinit = true;

jcl.init();

// ----- make FF more IE compatible -----
if (! jcl.isIE) {

  // ----- HTML objects -----

  HTMLElement.prototype.__defineGetter__("innerText", function () { return(this.textContent); });
  HTMLElement.prototype.__defineSetter__("innerText", function (txt) { this.textContent = txt; });

  HTMLElement.prototype.__defineGetter__("XMLDocument", function () { 
    return ((new DOMParser()).parseFromString(this.innerHTML, "text/xml"));
  });


  // ----- Event objects -----

  // enable using evt.cancelBubble=true in Mozilla/Firefox
  Event.prototype.__defineSetter__("cancelBubble", function (b) {
    if (b) this.stopPropagation();
  });

  // enable using evt.returnValue=false in Mozilla/Firefox
  Event.prototype.__defineSetter__("returnValue", function (b) {
    if (!b) this.preventDefault();
  });


  // ----- XML objects -----
  
  // select the first node that matches the XPath expression
  // xPath: the XPath expression to use
  if (!XMLDocument.selectSingleNode) {
    XMLDocument.prototype.selectSingleNode = function(xPath) {
      var doc = this;
      if (doc.nodeType != 9)
        doc = doc.ownerDocument;
      if (doc.nsResolver == null) doc.nsResolver = function(prefix) { return (null); };
      var node = doc.evaluate(xPath, this, doc.nsResolver, XPathResult.ANY_UNORDERED_NODE_TYPE, null);
      if (node) node = node.singleNodeValue;
      return (node);
    }; // selectSingleNode
  } // if

  // select the first node that matches the XPath expression
  // xPath: the XPath expression to use
  if (!Node.selectSingleNode) {
    Node.prototype.selectSingleNode = function(xPath) {
      var doc = this;
      if (doc.nodeType != 9)
        doc = doc.ownerDocument;
      if (doc.nsResolver == null) doc.nsResolver = function(prefix) { return(null); };
      var node = doc.evaluate(xPath, this, doc.nsResolver, XPathResult.ANY_UNORDERED_NODE_TYPE, null);
      if (node) node = node.singleNodeValue;
      return(node);
    }; // selectSingleNode
  } // if


  Node.prototype.__defineGetter__("text", function () {
    return(this.textContent);
  }); // text

}


// ----- Enable an easy attaching methods to events -----
// see http://digital-web.com/articles/scope_in_javascript/

Function.prototype.bind = function (obj) {
  var method = this, temp = function () {
    return method.apply(obj, arguments);
  };
  return (temp);
}; // Function.prototype.bind

// ----- End -----


This page is part of the http://www.mathertel.de/ web site.

For updates and discussions see http://ajaxaspects.blogspot.com/.