 /**
 *  Interface for cross-browser Drag'n'Drop
 *
 *  @version 1.5.2
 *  @title Drag'n'Drop Interface
 *  @author Ilya Lebedev (ilya@lebedev.net), (c) 2005, 2006
 *  @license GNU LGPL
 *  @depends DOMextensions, http://forum.dklab.ru/js/advises/RasshireniyaIe-domDlyaDrugihBrauzerovDomextensionsJs.html
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *  See http://www.gnu.org/copyleft/lesser.html
 *
 *
 *  Legend:
 *  ! attention, it's important
 *  ? not approved bug / no clear view how to fix a bug
 *  + added feature
 *  % changed/bug fixed
 *  - removed
 *
 *  Revision history:
 *
 *  v1.5.2
 *  % __handleDrop method fixed, now it drops properly
 *
 *  v1.5.1
 *  + cleanup of __DDI__.currenTarget property on drag end and unsuccessful drag start
 *  % handleOnDrop optimizations (use bitmasks instead of strings)
 *  % __onDrop handler is called only if effectAllowed and dropEffect has compatible values
 *  % __dropEffect reset on drag leave
 *  % drag init optimization (library initializes only if it has something to drag, not on each node)
 *  - some junk code
 *  % onDragEnd 2nd time is called if current target is not equal to dragged element
 *
 *  v1.5
 *  % execution of dragstart.after plugins moved to onclick event
 *  % initialization of drag moved back to 'onclick' mouse event due to Mozilla/FF bugs with document selection, input controls
 *    and moving of cetrain nodes.
 *  % MZ/FF behaviour fixing code is moved to fixDragInMz plugin
 *  - code cleanup
 *  ! to receive dropped elements node SHOULD implement __onDrop handler, process onDrop or/and return 'true'.
 *    in DDI prior to 1.5 setting dropEffect was enough to receive dropped element.
 *  % geometry had to calc __layerX/__layerY in wrong way (used to MZ relative layer coordinates, instead of page coordinates)
 *  + plugin methods sorting on install to execute them in pluginGroups ascending order 
 *    (allows to disable less important plugins and plugin groups)
 *  % handleDragEvent did not return false, if no such event handler exists
 *  + DDI now checks parent nodes for __onDragStart handler, if main target node would not be dragged
 *  % __onDragEnter, __onDrag, __onDragLeave, __onDrop are executed only if dragged node set effectAllowed other than 'none'
 *  + __onDragEnd is called even if there was no drag (like shutdown plugin function)
 *  + __onDragEnd finishes drag on target node too
 *  ! all plugins implementing drag.before, dragend.before method should return definitely true or false, 
 *    because 'undefined' is threated as false and execution of __onDrag, __onDragEnter, __onDragOver, __onDragLeave, __onDrop
 *    will be blocked
 *  + geometry calculates mouse position over layer
 *  + geometry stores layer offsets as el.__offsetX, el.__offsetY and mouse pointer position as el.__layerX, el.__layerY
 *  + plugin could block execution of lower-order plugin groups
 *  + plugin groups: DDI_CORE, DDI_SYSYEM, DDI_VISUAL
 *  + plugin could block execution of __onDrag, __onDragEnter, __onDragOver, __onDragLeave
 *    by returning false from drag.before handler
 *  + plugin could block execution of __onDrop by returning false from dragend.before handler
 *  % updated bugfix with position:static, instead of position:absolute, position:relative is used, 
 *    safe for table-cell elements
 *
 *  v1.4.3
 *  % Mozilla/FireFox bug: it does not allow to drop nodes with position:static on DOM levels below 
 *    element with position:relative
 *  % dropping element on window.document caused to invoke __onDrop from last node, dragged over
 *  % Mozilla/FireFox bug: drag beginned on element with position:absolute caused to stick later even.target to that element
 *  % Mozilla/FireFox bug: element.cloneNode() clones not only the node contents, 
 *    but also ID, which should be unique in the document
 *
 *  v1.4.2
 *  % project structure: plugins are moved to Plugins folder
 *  + geometry object for cross-browser page geometry calculations
 *    calculates mouse pointer position and difference beetween last and current coordinates
 *  + evt.__dX, evt.__dY properties - difference beetween last and current mouse pointer coordinates
 *  + evt.__currentTargetOffsetX, evt.__currentTargetOffsetY offsets for evt.__currentTarget object
 *  + evt.__targetOffsetX, evt.__targetOffsetY offsets for evt.__target object
 *  % all geometry calculations are done with geometry object
 *
 *  v1.4.1
 *  % dragstart.before plugin method execution moved to __DDI__.initDrag handler
 *
 *  v1.4
 *  + runtime.always plugin callback. Runs continuously during the drag.
 *  % bug with object prototypes, for-in now skips values assigned by Object.prototype
 *
 *  v1.3
 *  + __onDragStart bubbling (the topmost element with __onDragStart handler will be used)
 *  // be careful - IE has a major bug with elements under parent with "position:relative"
 *  // such elemets are 'transparent' for event handlers (except areas filled with content)
 *  + __onDragStart can return 'false' in order to disable current drag behaviour
 *  + __onDragEnter bubbling (performing lookup for __onDragOver handler)
 *  + __currentTarget event property, pointing on node, wich executes handler
 *  % __onDrop handling behaviour, now we do the drop on the node, which did __onDragOver handling
 *  % bug with wrong number of arguments in setPlugin and removePlugin wrappers
 *  % __onDragOver detection routine, now it's possible to dynamicaly set __onDragOver handler
 *  + pluginManager.disablePlugin method
 *  + pluginManager.enablePlugin method
 *  + __DDI__.disablePlugin wrapper for pluginManager.disablePlugin
 *  + __DDI__.enablePlugin wrapper for pluginManager.enablePlugin
 *
 *  v1.2
 *  % plugin interface
 *  + executing <plugin>init callback (if exists) on plugin setup (setPlugin)
 *  + executing <plugin>shutdown callback (if exists) on plugin remove (removePlugin)
 *  % setPlugin, removePlugin methods now receive plugin name only
 *  - pluginManager.getMethodFromName method
 *  % bug with missing 'dragstart.after' plugin executing
 *  % bug with wrong dragState evaluation
 *
 *  v1.1.01
 *  + __dataTransfer.getClonedElement method to retrieve dragged element clone
 *
 *  v1.1
 *  + getDragState method in dataTransfer object
 *  + plugins interface
 *  - some 'helpers' (drag state, cursor lock, etc)
 *  % binding __target element property to the event object, pointing on topmost node
 *  + plugin fixNoMouseSelect
 *  + plugin dragStateAsCursor
 *  + plugin lockCursorAsDefault
 *
 *  v1.0.07
 *  - cleaned up code
 *  % moved "no mouse select" fix from __showCursor to __handleDragStart, it's enough to call fix once on mousedown event
 *
 *  v1.0.06
 *  % bug when it was impossible to drop elements in MZ/FF
 *  % __onDragStart now fires within mousemove event, instead of mousedown
 *
 *  v1.0.05
 *  % bug with missed evevt.__dataTransfer property in __onDrop handlers
 *
 *  v1.0.04
 *  + DOMExtensions.js library
 *  % fixed selection reset
 *  + added event.__target property - reference to current DOM node
 *  - some junk code
 *
 *  v1.0.03
 *  % bug with blocked text selection in MZ/FF
 *  + script drops document.selection on drag begin
 *
 *  v1.0.02
 *  % bug with text selection during Drag
 *  + document.createElementExt function
 *  % all elements creation is moved to document.createElementExt
 *  % cleaned up the code
 *
 *  v1.0.01
 *  % bug with IE5.x .style.cursor property
 *
 *  v1.0 
 *  + initial release
 **/

(__DDI__ = {
  /*
  *  Object that implements Drag'n'Drop interface
  *  It is based on MS dataTransfer interface
  */
  __dataTransfer__ : function (el) {
    var self = this;
    /*
    *  Stores textual info, if any
    *
    *  @type string
    *  @access private
    */
    var __text__ = null;
    /*
    *  Stores HTML info, if any
    *
    *  @type string
    *  @access private
    */
    var __html__ = null;
    /*
    *  Stores DOM tree, if any
    *
    *  @type object
    *  @access private
    */
    var __dom__ = null;
    /*
    *  Stores currently dragging node
    *
    *  @type object
    *  @access private
    */
    var __element__ = el;
    
    /*
    *  Public properties
    *
    *  effectAllowed
    *  dropEffect
    */
    var __________________________Publics__________________________;
    /*
    *  Property specifying allowed effect from caller
    *
    *  @type string
    *  @access public
    */
    this.effectAllowed = 0;
    var __effectAllowed = null;
    /*
    *  Property specifying allowed drop actions
    *
    *  @type string
    *  @access public
    */
    this.dropEffect = null;
    var __dropEffect = null;
    
    /*
    *  Public methods
    *
    *  setData
    *  getData
    *  clearData
    *  getDragState
    *  getClonedElement
    */
    
    /*
    *  Used to set more info on dragged element
    *
    *  @param string value type
    *  @param mixed value
    *  @return boolean operation state
    *  @access public
    */
    this.setData = function (type,obj) {
      switch (type.toLowerCase()) {
        case "text" : 
          __text__ = obj;
          break;
        case "domnode" :
          __dom__ = obj;
          break;
        default:
          return false;
      }
      return true;
    }
    /*
    *  Used to retrieve info on dragged element
    *
    *  @param string value type
    *  @return mixed value or null
    *  @access public
    */
    this.getData = function (type) {
      switch (type.toLowerCase()) {
        case "text" : 
          return __text__;
        case "domnode" :
          return __dom__;
        default:
          return null;
      }
    }
    /*
    *  Used to clear some info on dragged element
    *
    *  @param string value type
    *  @access public
    */
    this.clearData = function () {
      switch (arguments[0].toLowerCase()) {
        case "text" : 
          __text__ = null;
          break;
        case "domnode" :
          __dom__ = null;
          break;
        case "" :
          __text__ = null;
          __dom__ = null;
          break;
      }
    }
    /*
    *  Used by plugins to retrieve drag operation state
    *
    *  Return values
    *  0 - drop not allowed
    *  1 - copy allowed
    *  2 - move allowed
    *  4 - link allowed
    *
    *  @return int state
    *  @access public
    */
    this.getDragState = function () {
      return Number(__dropEffect & __effectAllowed);
    }

    /*
    *  Used by plugins to retrieve clone of dragged element
    *
    *  @return object element clone
    *  @access public
    */
    this.getClonedElement = function () {
      var el = __element__.cloneNode(true);
      el.id = '';
      return el;
    }
    
    var __________________________Protected__________________________;
    /*
    *  Here and below are interfaces for:
    *  onDragStart
    *  onDrag
    *  onDragEnd
    *  onDragEnter
    *  onDragOver
    *  onDragLeave
    *  onDrop
    */
    
    /*
    *  Handles onDragStart
    *
    *  @param object event
    *  @param object DOM node
    *  @access protected
    */
    this.__handleDragStart = function (evt,el) {
      var res = executeHandler(evt,__element__,'__onDragStart',__element__);
      if (res !== false) applyEffectAllowed();
      return res;
    }
    /*
    *  Handles onDrag
    *
    *  @param object event
    *  @param object DOM node
    *  @access protected
    */
    this.__handleDrag = function (evt,el) {
      var res = executeHandler(evt,__element__,'__onDrag',__element__);
      if (res !== false) applyEffectAllowed();
      return res;
    }
    /*
    *  Handles onDragEnd
    *
    *  @param object event
    *  @param object DOM node
    *  @access protected
    */
    this.__handleDragEnd = function (evt,el) { if(el!=__element__) return executeHandler(evt,el?el:__element__,'__onDragEnd',el?el:__element__);}
    /*
    *  Handles onDragEnter
    *
    *  @param object event
    *  @param object DOM node
    *  @access protected
    */
    this.__handleDragEnter = function (evt,el) { 
      /*
      *  execute handler only if draggded node wants to be dropped
      */
      if (__effectAllowed > 0)
        return executeHandler(evt,el,'__onDragEnter',el);
      else
        return false;
    }
    
    /*
    *  Handles onDragOver
    *
    *  @param object event
    *  @param object DOM node
    *  @access protected
    */
    this.__handleDragOver = function (evt,el) {
      var res = false;
      /*
      *  execute handler only if draggded node wants to be dropped
      */
      if (__effectAllowed > 0) {
        res = executeHandler (evt,el,'__onDragOver',el);
        if (res !== false) {
          /*
          *  Setting dropEffect bitmask 
          */
          switch (String(self.dropEffect).toLowerCase()) {
            case 'copy' : __dropEffect = 1;
                          break;
            case 'move' : __dropEffect = 2;
                          break;
            case 'link' : __dropEffect = 4;
                          break;
            default :     __dropEffect = 0;
          }
        }
      }
      return res;
    }
    
    /*
    *  Handles onDragLeave
    *
    *  @param object event
    *  @param object DOM node
    *  @access protected
    */
    this.__handleDragLeave = function (evt,el) {
      var res = false;
      /*
      *  execute handler only if draggded node wants to be dropped
      */
      if (__effectAllowed > 0) {
        res = executeHandler (evt,el,'__onDragLeave',el);
        /*
        *  Reset dropEffect
        */
        self.dropEffect = 'none';
        __dropEffect = 0;
      }
      return res;
    }
    
    /*
    *  Handles desired drop method
    *
    *  @param object event
    *  @param object DOM node
    *  @access protected
    */
    this.__handleDrop = function (evt,el) {
      var res = false;
      /*
      *  execute handler only if draggded node wants to be dropped
      *  and receiving wants accept it
      */
      if (__effectAllowed & __dropEffect) {
        if (!el.__onDrop) return false;
        res = executeHandler (evt,el,'__onDrop',el);
        /*
        *  We will continue only if __onDrop handler will return 'true'
        */
        if (res !== false) {
          switch (__dropEffect & __effectAllowed) {
            case 1 : 
              el.appendChild(__element__.cloneNode(true));
              break;
            case 2 :
                try {
                 el.appendChild(__element__);
                } 
                 catch (e) {
                 alert('Node cannot be moved here');
                }
              break;
          }
        }
      }
      return res;
    }
    var __________________________Privates__________________________;

    /*
    *  Executes handler.
    *
    *  @param object event
    *  @param object element to call handler
    *  @param string handler hame
    *  @param object target element
    *  @return mixed false or handler execution result
    *  @access private
    */
    var executeHandler = function (evt,el,h,target) {
      /*
      *  if element has no such handler
      */
      if (!el || !el[h]) return false;
      /*
      *  setting current target
      */
      evt.__currentTarget = target;
      /*
      *  calculate mouse coords over target
      */
      __DDI__.geometry.recalcCurrentTarget(evt);
      /*
      *  execute handler
      */
      return el[h](evt);
    }
    /*
    *  Converts literal effectAllowed to bitmask EffectAllowed
    *
    *  @access private
    */
    var applyEffectAllowed = function () {
      /*
      *  Applying effectAllowed bitmask
      */
      switch (String(self.effectAllowed).toLowerCase()) {
        case 'none' : __effectAllowed = 0;
                      break;
        case 'copy' : __effectAllowed = 1;
                      break;
        case 'move' : __effectAllowed = 2;
                      break;
        case 'link' : __effectAllowed = 4;
                      break;
        case 'copymove' : __effectAllowed = 3;
                          break;
        case 'copylink' : __effectAllowed = 5;
                          break;
        case 'movelink' : __effectAllowed = 6;
                          break;
        case 'all' : __effectAllowed = 7;
                     break;
      }
    }
  },

  /*
  *  Use to add/remove and execute plugins
  *
  *  Adding plugin to scope
  *  either:
  *  __DDI__.plugin.<plugin_name> = new function() { } (create new function object)
  *  or
  *  __DDI__.plugin.<plugin_name> = { } (simple object)
  *
  *  How to add plugin to execution queue:
  *  Call either __DDI__.pluginManager.setPlugin or __DDI__.setPlugin with parameter "<plugin_name>"
  *  Ex. __DDI__.setPlugin("fixMouseSelect");
  *
  *  How to remove plugin from execution queue:
  *  Call either __DDI__.pluginManager.removePlugin or __DDI__.removePlugin with parameter "<plugin_name>"
  *  Ex. __DDI__.removePlugin("fixNoMouseSelect");
  *
  */
  pluginManager : new (function () {
    var self = this;
    /*
    *  Stores plugins execution state
    *  true - plugin is available to execute
    *  false - plugin cannot be executed
    *
    *  @type Object
    *  @access private
    */
    var pluginState = {};
    /*
    *  Stores flags of forced plugin execution
    *
    *  @type Object
    *  @access private
    */
    var forcedPlugin = {};
    /*
    *  defines plugin groups:
    *  - core plugins, checks/enables/disables browser features
    *  - system plugins, implements some additional drag functionality like node moving
    *  - visual plugins, producing some visual effects
    *
    *  @var object
    *  @access private
    */
    var pluginGroups = {'DDI_CORE' : 1,
                        'DDI_SYSTEM' : 2,
                        'DDI_VISUAL' : 4
                       }
    /*
    *  stores plugin groups execution state
    *  true means allowed to be executed
    *
    *  @var array
    *  @access private
    */
    var pluginGroupState = {};
    for (var i in pluginGroups) {
      if (Object.prototype[i]) continue;
      pluginGroupState[i] = true;
    }
    /*
    *  Stores everything about plugins
    *
    *  @type Object
    *  @access private
    */
    var plugins =  {'runtime' : {'always' : {}
                                },
                    'dragstart' : { 'before' : {},
                                    'after' : {}
                                  },
                    'drag' : { 'before' : {},
                               'after' : {}
                             },
                    'dragend' : { 'before' : {},
                                  'after' : {}
                             }
                   }
    /*
    *  function sort plugin methods in the ascending order, 
    *  as described in pluginGroups
    *
    *  @param object methods to sort
    *  @return sorted methods
    *  @access private
    */
    var doPluginMethodSort = function (methods) {
      var tmp = [];
      /*
      *  put all methods in the array
      */
      for (var pm in methods) {
        if (Object.prototype[pm]) continue;
        tmp[tmp.length] = pm;
      }
      /*
      *   sort array depending on pluginGroups
      */
      tmp.sort(function(a,b){var pa = __DDI__.plugin[a]; 
                             var pb = __DDI__.plugin[b]; 
                             var pag = pluginGroups[pa.group];
                             var pbg = pluginGroups[pb.group];
                             if (pag<pbg) return -1;
                             if (pag>pbg) return 1;
                             return 0
                            });
      var tmpL = tmp.length;
      ret = {};
      /*
      *  fill return object in sorted order
      */
      for (var i=0; i<tmpL; i++) {
        ret[tmp[i]] = methods[tmp[i]];
      }
      /*
      *  free up memory
      */
      return ret;
    }
    var __________________________Public__________________________;
    /*
    *  Adds plugin in the scope
    *
    *  @param string event
    *  @param string position (before or after)
    *  @param string plugin name
    *  @return boolean operation state
    *  @access public
    */
    this.setPlugin = function(name) {
      var evt, phase;
      var plg = __DDI__.plugin[name];
      var res = false;
      /*
      *  make a check for each of event and phase
      */
      for (evt in plugins) {
        if (Object.prototype[evt]) continue;
        if (plg && plg[evt])
          for (phase in plugins[evt]) {
            if (Object.prototype[phase]) continue;
            if (!plugins[evt][phase][name] && plg[evt][phase] && typeof(plg[evt][phase]) == 'function') {
              /*
              *  adding plugin only if it exists, it is funciton, and was not installed before
              */
              plugins[evt][phase][name] = plg[evt][phase];
              /*
              *  by default plugin is ready to execute
              */
              pluginState[name] = true;
              /*
              *  do plugin methods sorting
              */
              plugins[evt][phase] = doPluginMethodSort(plugins[evt][phase]);
              res = true;
            }
          }
      }
      /*
      *  If plugin needs to be initialized - do it
      */
      if (res && plg.init) plg.init();
      if (res) {
        plg.enablePluginGroup = enablePluginGroup;
        plg.disablePluginGroup = disablePluginGroup;
        plg.forcePlugin = forcePlugin;
        plg.unforcePlugin = unforcePlugin;
      }
      return res;
    }
    /*
    *  Removes plugin from the scope
    *
    *  @param string event
    *  @param string position (before or after)
    *  @param string plugin name
    *  @return boolean operation state
    *  @access public
    */
    this.removePlugin = function(name) {
      var res = false;
      if (!__DDI__.plugin[name]) return false;
      /*
      *  make a check for each of event and phase
      */
      for (evt in plugins) {
        if (Object.prototype[evt]) continue;
        for (phase in plugins[evt]) {
          if (Object.prototype[phase]) continue;
          if (plugins[evt][phase][name]) {
            /*
            *  delete only real plugins
            */
            delete(plugins[evt][phase][name]);
            delete(pluginState[name]);
            delete(forcedPlugin[name]);
            res = true;
          }
        }
      }
      if (res && __DDI__.plugin[name]['shutdown']) __DDI__.plugin[name]['shutdown']();
      return res;
    }
    /*
    *  Disables plugin
    *
    *  @param string plugin name
    *  @access public
    */
    this.disablePlugin = function (name) {
      pluginState[name] = false;
    }
    /*
    *  Enables plugin
    *
    *  @param string plugin name
    *  @access public
    */
    this.enablePlugin = function (name) {
      pluginState[name] = true;
    }

    var __________________________Protected__________________________;

    /*
    *  Executes plugins in specified queue and phase
    *
    *  @param stirng event name, one of 'runtime', 'dragstart', 'drag', 'dragend'
    *  @param string event phase, one of 'before', 'after', 'always' (for 'runtime' plugins only)
    *  @param object event object
    *  @access public (should not be called outside DDI)
    */
    this.__call = function (evt,pos,event) {
      var pl;
      var res = true;
      if (plugins[evt] && plugins[evt][pos])
        for (pl in plugins[evt][pos]) {
          if (Object.prototype[pl]) continue;
          var g = __DDI__.plugin[pl].group;
          if (pluginState[pl] && pluginGroupState[g] || forcedPlugin[pl]) res = res&plugins[evt][pos][pl](event);
        }
      return res;
    }

    /*
    *  Enables plugin group execution
    *
    *  @param string plugin group
    *  @access protected
    */
    var enablePluginGroup = function (g) {
      if (pluginGroups[this.group]<pluginGroups[g]) pluginGroupState[g] = true;
    }
    /*
    *  Disables plugin group execution
    *
    *  @param string plugin group
    *  @access protected
    */
    var disablePluginGroup = function (g) {
      if (pluginGroups[this.group]<pluginGroups[g]) pluginGroupState[g] = false;
    }
    /*
    *  Forces plugin execution
    *
    *  @param string plugin name
    *  @access protected
    */
    var forcePlugin = function (name) {
      forcedPlugin[name] = true;
    }
    /*
    *  Disables forced plugin execution
    *
    *  @param string plugin name
    *  @access protected
    */
    var unforcePlugin = function (name) {
      delete(forcedPlugin[name]);
    }

  })(),
  geometry : new (function () {
    var self = this;
    var __________________________Public__________________________;
    /*
    *  pageX and pageY properties, mouse pointer position in document
    *
    *  @type integer
    *  @access public
    */
    this.pageX = null;
    this.pageY = null;
    /*
    *  pageDx and pageDy properties, difference between the current and last mouse position
    *
    *  @type integer
    *  @access public
    */
    this.dX;
    this.dY;

    /*
    *  mouse pointer coordinates, relative to event.__currentTarget node
    *
    *  @type integer
    *  @access public
    */
    this.currentTargetOffsetX;
    this.currentTargetOffsetY;

    /*
    *  mouse pointer coordinates, relative to event.__target node
    *
    *  @type integer
    *  @access public
    */
    this.targetOffsetX;
    this.targetOffsetY;

    /*
    *  Calculates some of geometry things
    *
    *  @param object where to apply recalculations, optional
    *  @access public
    */
    this.recalc = function (e) {
      /*
      *  Save previous coordinates
      */
      self.dX = self.pageX;
      self.dY = self.pageY;
      /*
      *  Finiding new cursor position
      */
      var c = document.getElementsByTagName((document.compatMode&&document.compatMode=="CSS1Compat")?"html":"body")[0];
      self.pageX = window.event?window.event.clientX+c.scrollLeft:e.pageX;
      self.pageY = window.event?window.event.clientY+c.scrollTop:e.pageY;
      
      /*
      *  Calculating difference
      */
      self.dX = self.dX==null?0:(self.pageX-self.dX);
      self.dY = self.dY==null?0:(self.pageY-self.dY);

      self.recalcCurrentTarget(e);
      self.recalcTarget(e);

      if (e!=null) self.apply (e);
    };
    /*
    *  Applies geometry calculations to the object
    *
    *  @param object event
    *  @param array properties to apply
    *  @access public
    */
    this.apply = function (e,p) {
      /*
      *  Setting cross-browser geometry properties on the object
      */
      if (p == null) {
        for (var i in self) if (!Object[i] && typeof self[i] != 'function') e['__'+i] = self[i]
      } else {
        var pL = p.length;
        for (var i=0;i<pL;i++) e['__'+p[i]] = parseInt(self[p[i]])
      }
    }
    /*
    *  Calculates offsets of element
    *
    *  @param object DOM node
    *  @return object X and Y offsets
    *  @access public
    */
    this.calcOffset = function(el) {
      var x=0,y=0;
      while (el) {
       x+=el.offsetLeft;
       y+=el.offsetTop;
       el = el.offsetParent;
      }
      return {'x':x,'y':y};
    }
    /*
    *  recalculates mouse coordinates over event.__target
    *
    *  @param object event object
    *  @return object x,y offset
    *  @access public
    */
    this.recalcTarget = function (e) {
      var off = recalcLayer (e, e.__target,'targetOffsetX','targetOffsetY');
      this.apply(e,['targetOffsetX','targetOffsetY']);
      return off;
    }
    /*
    *  recalculates mouse coordinates over event.__currentTarget
    *
    *  @param object event object
    *  @return object x,y offset
    *  @access public
    */
    this.recalcCurrentTarget = function (e) {
      var off = recalcLayer (e, e.__currentTarget,'currentTargetOffsetX','currentTargetOffsetY');
      this.apply(e,['currentTargetOffsetX','currentTargetOffsetY']);
      return off;
    }
    var __________________________Private__________________________;
    /*
    *  recalculates specified node offset and saves result in properties
    *
    *  @param object event object
    *  @param object target node
    *  @param object X propery
    *  @param object Y propery
    *  @return object x,y offset
    *  @access private
    */
    var recalcLayer = function (e, el, propX, propY) {
      /*
      *  save current position
      */
      var off = {'x' : 0, 'y' : 0}
      if (el) {
        off = self.calcOffset(el);
        /*
        *  apply properties as requested
        */
        self[propX] = off.x;
        e[propX] = off.x;
        el.__offsetX = off.x;
        e[propY] = off.y;
        self[propY] = off.y;
        el.__offsetY = off.y;
        /*
        *  apply correct mouse positions to element
        */
        el.__layerX = (e.clientX||e.x)-off.x;
        el.__layerY = (e.clientY||e.y)-off.y;
      }
      return off;
    }
  })(),
  /*
  *  Object to store user-defined plugins.
  */
  plugin : {},
  /*
  *  Flag showing the drag state
  */
  beforeDrag : false,
  /*
  *  Current node, mouse pointer flies over
  */
  currentTarget : false,
  /*
  *  Object that do dragging
  */
  dataTransfer : null,

  /*
  *  Binds to mousedown event on Document object 
  *  initializes dataTransfer, when triggered
  *
  */
  initDrag : function (evt) {
//    var evt = window.event || e;
    var el = evt.target || evt.srcElement;
    /*
    *  Set event target
    */
    evt.__target = el;
    /*
    *  recalculate all geometry
    */
    __DDI__.geometry.recalc(evt);
    /*
    *  Pointing to the target....
    */
    __DDI__.beforeDrag = el;
    /*
    *  check nodes from top one with __onDragStart handler
    */
    while (__DDI__.beforeDrag) {
      /*
      *  initialize library only if it has onDragStart handler
      */
      if (__DDI__.beforeDrag.__onDragStart) {
        /*
        *  Assign the current target
        */
        __DDI__.currentTarget = __DDI__.beforeDrag;
        /*
        *  set actual drag element
        */
        evt.__currentTarget = __DDI__.currentTarget;
        /*
        *  calculate mouse coords over target
        */
        __DDI__.geometry.recalcCurrentTarget(evt);
        /*
        *  create new dataTransfer object
        */
        __DDI__.dataTransfer = new __DDI__.__dataTransfer__(evt.__currentTarget);
        /*
        *  Apply __dataTransfer object to current event
        *  "__" (2 underscore signs) prefix is used to avoid conflicts with MSIE's dataTransfer object
        */
        evt.__dataTransfer = __DDI__.dataTransfer;
        /*
        *  Handling __onDragStart
        *  Handler may (and should) setup desired value to 'effectAllowed' property
        *  Continue if allowed
        */
        if (false !== __DDI__.dataTransfer.__handleDragStart(evt,el)) break;
        /*
        *  if __onDragStart returns false, it's considered as "do not drag me"
        */
        delete (__DDI__.dataTransfer);
      }
      /*
      *  and get parent node to check in next round
      */
      __DDI__.beforeDrag = __DDI__.beforeDrag.parentNode;
    }
    /*
    *  if we still have no node to start dragging... return
    */
    if (!__DDI__.currentTarget || __DDI__.currentTarget.parentNode == null) {
      delete (__DDI__.dataTransfer);
      __DDI__.currentTarget = null;
      return;
    }
    /*
    * Call plugins "dragStart.after"
    */
    __DDI__.pluginManager.__call('dragstart','after',evt);
  },
  /*
  *  Binds to mousemove event and continuously checks elements under mouse pointer,
  *  when we drag nothing
  */
  mouseMover : function (evt) {
//    var evt = window.event || e;
    var el = evt.srcElement || evt.target;
    /*
    *  Applying element target to event
    */
    evt.__target = el;
    /*
    *  Recalculate geometry stuff and apply it to the event
    */
    __DDI__.geometry.recalc(evt);
    /*
    *  call continuously running plugin method
    */
    __DDI__.pluginManager.__call('runtime','always',evt);
    /*
    *  Continue only if we have something to drag
    */
    if (!__DDI__.dataTransfer) return;
    /*
    *  Apply __dataTransfer object to current event
    */
    evt.__dataTransfer = __DDI__.dataTransfer;
    /*
    * Applying current target to the event
    */
    evt.__currentTarget = __DDI__.currentTarget;
    /*
    *  calculate mouse coords over target
    */
    __DDI__.geometry.recalcCurrentTarget(evt);
    /*
    *  Call plugins "drag.before"
    *  and continue execution only if all plugins agree (returned true or undefined)
    */
    if (__DDI__.pluginManager.__call('drag','before',evt)) {
      /*
      *  check each node for __onDragEnter handler
      *  node is not current
      */
      while (__DDI__.currentTarget !== el && 
             /*
             *  and has no __onDragEnter or has __onDragEnter and it returns false
             */
             (!el.__onDragEnter || el.__onDragEnter && 
              (evt.__currentTarget = el, __DDI__.dataTransfer.__handleDragEnter(evt,el))
             ) &&
             /*
             *  and after __onDragEnter it still has no __onDragOver
             */
             !el.__onDragOver && (el = el.parentNode)) {/* do nothing */ }
      /*
      *  If node is not the same as before
      */
      if (el && __DDI__.currentTarget != el) {
        /*
        *  Handle onDragLeave
        */
        __DDI__.dataTransfer.__handleDragLeave(evt,__DDI__.currentTarget);
        /*
        *  Assigning new element to currentTarget property
        */
        __DDI__.currentTarget = el;
      }
      /*
      * Applying current target to the event
      */
      evt.__currentTarget = __DDI__.currentTarget;
      /*
      *  calculate mouse coords over target
      */
      __DDI__.geometry.recalcCurrentTarget(evt);
      /*
      *  Handling onDrag, if allowed
      */
      __DDI__.dataTransfer.__handleDrag (evt,el);
      /*
      *  Handling onDragOver
      */
      __DDI__.dataTransfer.__handleDragOver (evt,el);
    }
    /*
    * Call plugins "drag.after"
    */
    __DDI__.pluginManager.__call('drag','after',evt);
  },
  /*
  *  Remove reference to dragged object and clean up everything
  */
  stopDrag : function (evt) {
    /*
    *  Drop the flag
    */
    __DDI__.beforeDrag = false;
    /*
    *  Clean up properties and call handlers only if we drag something
    */
    if (__DDI__.dataTransfer) {

//    var evt = window.event || e;
      var el = evt.srcElement || evt.target;
  
      /*
      *  Applying element target to event
      */
      evt.__target = el;
      /*
      *  recalculate all geometry
      */
      __DDI__.geometry.recalc(evt);
      /*
      *  Applying __dataTransfer object to current event
      */
      evt.__dataTransfer = __DDI__.dataTransfer;
  
      /*
      * Call plugins "dragEnd.before"
      */
      __DDI__.pluginManager.__call('dragend','before',evt);
      /*
      *  Handle __onDrop 
      */
      __DDI__.dataTransfer.__handleDrop(evt,__DDI__.currentTarget);
      /*
      *  Handle __onDragEnd
      */
      __DDI__.dataTransfer.__handleDragEnd(evt);
      __DDI__.dataTransfer.__handleDragEnd(evt,__DDI__.currentTarget);
      /*
      *  Free up memory from old dataTransfer object
      */
      delete (__DDI__.dataTransfer);
      __DDI__.currentTarget = null;
    }
    /*
    * Call plugins "dragEnd.after"
    */
    __DDI__.pluginManager.__call('dragend','after',evt);    
  },
  /*
  *  Wrapper to __DDI__.pluginManager.setPlugin
  */
  setPlugin : function (name) {
    return __DDI__.pluginManager.setPlugin(name);
  },
  /*
  *  Wrapper to __DDI__.pluginManager.removePlugin
  */
  removePlugin : function (name) {
    return __DDI__.pluginManager.removePlugin(name);
  },
  /*
  *  Wrapper to __DDI__.pluginManager.enablePlugin
  */
  enablePlugin : function (name) {
    return __DDI__.pluginManager.enablePlugin(name);
  },
  /*
  *  Wrapper to __DDI__.pluginManager.disablePlugin
  */
  disablePlugin : function (name) {
    return __DDI__.pluginManager.disablePlugin(name);
  },
  init : function () {
    document.attachEvent('onmousemove',__DDI__.mouseMover);
    document.attachEvent('onmousedown',__DDI__.initDrag);
    document.attachEvent('onmouseup',__DDI__.stopDrag);
  }
}).init();

/*
*  Creates element all-at-once
*
*  @param string tag name
*  @param string class name(s)
*  @param mixed array of event handlers as [['event','handler_name'], ['event1','handler_name1'], ....]
*  @param mixed array of style attributes as [['prop','style_desc'], ['prop1','style_desc1'], ....]
*  @param mixed array of child nodes
*  @param mixed array of additional properties
*  @return object DOM tree
*  @access public
*/
if (!document.createElementExt) {
  document.createElementExt = function (tag,c,h,s,ch,p) {
    var L, i;
    var el = document.createElement(tag);
    if (!el) return false;
    if(c) { el.setAttribute('className',c); el.setAttribute('class',c);}
    if(h) { L = h.length; for (i=0; i<L; i++) el.attachEvent(h[i][0],h[i][1]);}
    if(s) { L = s.length; for (i=0; i<L; i++) el.style[s[i][0]] = s[i][1];}
    if(ch) { L = ch.length; for (i=0; i<L; i++) el.appendChild(ch[i]);}
    if(p) { L = p.length; for (i=0; i<L; i++) el[p[i][0]] = p[i][1];}
    return el;
  }
}