jquery.tablednd.js 8.97 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257

jQuery.tableDnD = {
  /** Keep hold of the current table being dragged */
  currentTable : null,
  /** Keep hold of the current drag object if any */
  dragObject: null,
  /** The current mouse offset */
  mouseOffset: null,
  /** Remember the old value of Y so that we don't do too much processing */
  oldY: 0,

  /** Actually build the structure */
  build: function(options) {
    // Make sure options exists
    options = options || {};
    // Set up the defaults if any

    this.each(function() {
      // Remember the options
      this.tableDnDConfig = {
        onDragStyle: options.onDragStyle,
      onDropStyle: options.onDropStyle,
      // Add in the default class for whileDragging
      onDragClass: options.onDragClass ? options.onDragClass : "dragged",
      onDrop: options.onDrop,
      onDragStart: options.onDragStart,
      scrollAmount: options.scrollAmount ? options.scrollAmount : 5
      };
      // Now make the rows draggable
      jQuery.tableDnD.makeDraggable(this);
    });

    // Now we need to capture the mouse up and mouse move event
    // We can use bind so that we don't interfere with other event handlers
    jQuery(document)
      .bind('mousemove', jQuery.tableDnD.mousemove)
      .bind('mouseup', jQuery.tableDnD.mouseup);

    // Don't break the chain
    return this;
  },

  /** This function makes all the rows on the table draggable apart from those marked as "NoDrag" */
  makeDraggable: function(table) {
    // Now initialise the rows
    var rows = table.rows; //getElementsByTagName("tr")
    var config = table.tableDnDConfig;
    for (var i=0; i<rows.length; i++) {
      // To make non-draggable rows, add the nodrag class (eg for Category and Header rows) 
      // inspired by John Tarr and Famic
      var nodrag = $(rows[i]).hasClass("nodrag");
      if (! nodrag) { //There is no NoDnD attribute on rows I want to drag
        jQuery(rows[i]).mousedown(function(ev) {
          if (ev.target.tagName == "TD") {
            jQuery.tableDnD.dragObject = this;
            jQuery.tableDnD.currentTable = table;
            jQuery.tableDnD.mouseOffset = jQuery.tableDnD.getMouseOffset(this, ev);
            if (config.onDragStart) {
              // Call the onDrop method if there is one
              config.onDragStart(table, this);
            }
            return false;
          }
        }).css("cursor", "move"); // Store the tableDnD object
      }
    }
  },

  /** Get the mouse coordinates from the event (allowing for browser differences) */
  mouseCoords: function(ev){
    if(ev.pageX || ev.pageY){
      return {x:ev.pageX, y:ev.pageY};
    }
    return {
      x:ev.clientX + document.body.scrollLeft - document.body.clientLeft,
      y:ev.clientY + document.body.scrollTop  - document.body.clientTop
    };
  },

  /** Given a target element and a mouse event, get the mouse offset from that element.
    To do this we need the element's position and the mouse position */
  getMouseOffset: function(target, ev) {
    ev = ev || window.event;

    var docPos    = this.getPosition(target);
    var mousePos  = this.mouseCoords(ev);
    return {x:mousePos.x - docPos.x, y:mousePos.y - docPos.y};
  },

  /** Get the position of an element by going up the DOM tree and adding up all the offsets */
  getPosition: function(e){
    var left = 0;
    var top  = 0;
    /** Safari fix -- thanks to Luis Chato for this! */
    if (e.offsetHeight == 0) {
      /** Safari 2 doesn't correctly grab the offsetTop of a table row
        this is detailed here:
        http://jacob.peargrove.com/blog/2006/technical/table-row-offsettop-bug-in-safari/
        the solution is likewise noted there, grab the offset of a table cell in the row - the firstChild.
        note that firefox will return a text node as a first child, so designing a more thorough
        solution may need to take that into account, for now this seems to work in firefox, safari, ie */
      e = e.firstChild; // a table cell
    }

    while (e.offsetParent){
      left += e.offsetLeft;
      top  += e.offsetTop;
      e     = e.offsetParent;
    }

    left += e.offsetLeft;
    top  += e.offsetTop;

    return {x:left, y:top};
  },

  mousemove: function(ev) {
    if (jQuery.tableDnD.dragObject == null) {
      return;
    }

    var dragObj = jQuery(jQuery.tableDnD.dragObject);
    var config = jQuery.tableDnD.currentTable.tableDnDConfig;
    var mousePos = jQuery.tableDnD.mouseCoords(ev);
    var y = mousePos.y - jQuery.tableDnD.mouseOffset.y;
    //auto scroll the window
    var yOffset = window.pageYOffset;
    if (document.all) {
      // Windows version
      //yOffset=document.body.scrollTop;
      if (typeof document.compatMode != 'undefined' &&
          document.compatMode != 'BackCompat') {
            yOffset = document.documentElement.scrollTop;
          }
      else if (typeof document.body != 'undefined') {
        yOffset=document.body.scrollTop;
      }

    }

    if (mousePos.y-yOffset < config.scrollAmount) {
      window.scrollBy(0, -config.scrollAmount);
    } else {
      var windowHeight = window.innerHeight ? window.innerHeight
        : document.documentElement.clientHeight ? document.documentElement.clientHeight : document.body.clientHeight;
      if (windowHeight-(mousePos.y-yOffset) < config.scrollAmount) {
        window.scrollBy(0, config.scrollAmount);
      }
    }


    if (y != jQuery.tableDnD.oldY) {
      // work out if we're going up or down...
      var movingDown = y > jQuery.tableDnD.oldY;
      // update the old value
      jQuery.tableDnD.oldY = y;
      // update the style to show we're dragging
      if (config.onDragClass) {
        dragObj.addClass(config.onDragClass);
      } else {
        dragObj.css(config.onDragStyle);
      }
      // If we're over a row then move the dragged row to there so that the user sees the
      // effect dynamically
      var currentRow = jQuery.tableDnD.findDropTargetRow(dragObj, y);
      if (currentRow) {
        // TODO worry about what happens when there are multiple TBODIES
        if (movingDown && jQuery.tableDnD.dragObject != currentRow) {
          jQuery.tableDnD.dragObject.parentNode.insertBefore(jQuery.tableDnD.dragObject, currentRow.nextSibling);
        } else if (! movingDown && jQuery.tableDnD.dragObject != currentRow) {
          jQuery.tableDnD.dragObject.parentNode.insertBefore(jQuery.tableDnD.dragObject, currentRow);
        }
      }
    }

    return false;
  },

  /** We're only worried about the y position really, because we can only move rows up and down */
  findDropTargetRow: function(draggedRow, y) {
    var rows = jQuery.tableDnD.currentTable.rows;
    for (var i=0; i<rows.length; i++) {
      var row = rows[i];
      var rowY    = this.getPosition(row).y;
      var rowHeight = parseInt(row.offsetHeight)/2;
      if (row.offsetHeight == 0) {
        rowY = this.getPosition(row.firstChild).y;
        rowHeight = parseInt(row.firstChild.offsetHeight)/2;
      }
      // Because we always have to insert before, we need to offset the height a bit
      if ((y > rowY - rowHeight) && (y < (rowY + rowHeight))) {
        // that's the row we're over
        // If it's the same as the current row, ignore it
        if (row == draggedRow) {return null;}
        var config = jQuery.tableDnD.currentTable.tableDnDConfig;
        if (config.onAllowDrop) {
          if (config.onAllowDrop(draggedRow, row)) {
            return row;
          } else {
            return null;
          }
        } else {
          // If a row has nodrop class, then don't allow dropping (inspired by John Tarr and Famic)
          var nodrop = $(row).hasClass("nodrop");
          if (! nodrop) {
            return row;
          } else {
            return null;
          }
        }
        return row;
      }
    }
    return null;
  },

  mouseup: function(e) {
    if (jQuery.tableDnD.currentTable && jQuery.tableDnD.dragObject) {
      var droppedRow = jQuery.tableDnD.dragObject;
      var config = jQuery.tableDnD.currentTable.tableDnDConfig;
      // If we have a dragObject, then we need to release it,
      // The row will already have been moved to the right place so we just reset stuff
      if (config.onDragClass) {
        jQuery(droppedRow).removeClass(config.onDragClass);
      } else {
        jQuery(droppedRow).css(config.onDropStyle);
      }
      jQuery.tableDnD.dragObject   = null;
      if (config.onDrop) {
        // Call the onDrop method if there is one
        config.onDrop(jQuery.tableDnD.currentTable, droppedRow);
      }
      jQuery.tableDnD.currentTable = null; // let go of the table too
    }
  },

  serialize: function() {
    if (jQuery.tableDnD.currentTable) {
      var result = "";
      var tableId = jQuery.tableDnD.currentTable.id;
      var rows = jQuery.tableDnD.currentTable.rows;
      for (var i=0; i<rows.length; i++) {
        if (result.length > 0) result += "&";
        result += tableId + '[]=' + rows[i].id;
      }
      return result;
    } else {
      return "Error: No Table id set, you need to set an id on your table and every row";
    }
  }
}

jQuery.fn.extend(
    {
      tableDnD : jQuery.tableDnD.build
    }
    );