2007-11-29 10:57:38 +00:00
// $Id$
2007-11-14 09:50:00 +00:00
/ * *
* Drag and drop table rows with field manipulation .
*
* Using the drupal _add _tabledrag ( ) function , any table with weights or parent
* relationships may be made into draggable tables . Columns containing a field
* may optionally be hidden , providing a better user experience .
*
* Created tableDrag instances may be modified with custom behaviors by
* overriding the . onDrag , . onDrop , . row . onSwap , and . row . onIndent methods .
* See blocks . js for an example of adding additional functionality to tableDrag .
* /
Drupal . behaviors . tableDrag = function ( context ) {
for ( var base in Drupal . settings . tableDrag ) {
2007-12-16 10:36:53 +00:00
if ( ! $ ( '#' + base + '.tabledrag-processed' , context ) . size ( ) ) {
2007-11-14 09:50:00 +00:00
var tableSettings = Drupal . settings . tableDrag [ base ] ;
$ ( '#' + base ) . filter ( ':not(.tabledrag-processed)' ) . each ( function ( ) {
// Create the new tableDrag instance. Save in the Drupal variable
// to allow other scripts access to the object.
Drupal . tableDrag [ base ] = new Drupal . tableDrag ( this , tableSettings ) ;
} ) ;
$ ( '#' + base ) . addClass ( 'tabledrag-processed' ) ;
}
}
} ;
/ * *
* Constructor for the tableDrag object . Provides table and field manipulation .
*
* @ param table
* DOM object for the table to be made draggable .
* @ param tableSettings
* Settings for the table added via drupal _add _dragtable ( ) .
* /
Drupal . tableDrag = function ( table , tableSettings ) {
var self = this ;
// Required object variables.
this . table = table ;
this . tableSettings = tableSettings ;
this . dragObject = null ; // Used to hold information about a current drag operation.
this . rowObject = null ; // Provides operations for row manipulation.
this . oldRowElement = null ; // Remember the previous element.
this . oldY = 0 ; // Used to determine up or down direction from last mouse move.
this . changed = false ; // Whether anything in the entire table has changed.
2007-12-06 09:53:53 +00:00
this . maxDepth = 0 ; // Maximum amount of allowed parenting.
2007-12-16 10:36:53 +00:00
this . rtl = $ ( this . table ) . css ( 'direction' ) == 'rtl' ? - 1 : 1 ; // Direction of the table.
2007-11-14 09:50:00 +00:00
// Configure the scroll settings.
this . scrollSettings = { amount : 4 , interval : 50 , trigger : 70 } ;
this . scrollInterval = null ;
this . scrollY = 0 ;
this . windowHeight = 0 ;
2007-11-20 10:18:43 +00:00
// Check this table's settings to see if there are parent relationships in
// this table. For efficiency, large sections of code can be skipped if we
// don't need to track horizontal movement and indentations.
this . indentEnabled = false ;
for ( group in tableSettings ) {
for ( n in tableSettings [ group ] ) {
if ( tableSettings [ group ] [ n ] [ 'relationship' ] == 'parent' ) {
this . indentEnabled = true ;
}
2007-11-26 16:19:37 +00:00
if ( tableSettings [ group ] [ n ] [ 'limit' ] > 0 ) {
this . maxDepth = tableSettings [ group ] [ n ] [ 'limit' ] ;
}
2007-11-20 10:18:43 +00:00
}
}
2007-11-14 09:50:00 +00:00
if ( this . indentEnabled ) {
this . oldX = 0 ;
this . indentCount = 1 ; // Total width of indents, set in makeDraggable.
2007-11-20 10:18:43 +00:00
// Find the width of indentations to measure mouse movements against.
// Because the table doesn't need to start with any indentations, we
// manually create an empty div, check it's width, then remove.
var indent = $ ( Drupal . theme ( 'tableDragIndentation' ) ) . appendTo ( 'body' ) ;
this . indentAmount = parseInt ( indent . css ( 'width' ) ) ;
indent . remove ( ) ;
2007-11-14 09:50:00 +00:00
}
// Make each applicable row draggable.
$ ( 'tr.draggable' , table ) . each ( function ( ) { self . makeDraggable ( this ) ; } ) ;
// Hide columns containing affected form elements.
this . hideColumns ( ) ;
// Add mouse bindings to the document. The self variable is passed along
// as event handlers do not have direct access to the tableDrag object.
2007-11-27 13:10:24 +00:00
$ ( document ) . bind ( 'mousemove' , function ( event ) { self . dragRow ( event , self ) ; } ) ;
$ ( document ) . bind ( 'mouseup' , function ( event ) { self . dropRow ( event , self ) ; } ) ;
2007-11-14 09:50:00 +00:00
} ;
/ * *
* Hide the columns containing form elements according to the settings for
* this tableDrag instance .
* /
Drupal . tableDrag . prototype . hideColumns = function ( ) {
for ( var group in this . tableSettings ) {
// Find the first field in this group.
for ( var d in this . tableSettings [ group ] ) {
if ( $ ( '.' + this . tableSettings [ group ] [ d ] [ 'target' ] , this . table ) . size ( ) ) {
var hidden = this . tableSettings [ group ] [ d ] [ 'hidden' ] ;
var field = $ ( '.' + this . tableSettings [ group ] [ d ] [ 'target' ] + ':first' , this . table ) ;
var cell = field . parents ( 'td:first, th:first' ) ;
break ;
}
}
// Hide the column containing this field.
if ( hidden && cell [ 0 ] && cell . css ( 'display' ) != 'none' ) {
// Add 1 to our indexes. The nth-child selector is 1 based, not 0 based.
var columnIndex = $ ( 'td' , field . parents ( 'tr:first' ) ) . index ( cell . get ( 0 ) ) + 1 ;
var headerIndex = $ ( 'td:not(:hidden)' , field . parents ( 'tr:first' ) ) . index ( cell . get ( 0 ) ) + 1 ;
$ ( 'tbody tr' , this . table ) . each ( function ( ) {
// Find and hide the cell in the table body.
var cell = $ ( 'td:nth-child(' + columnIndex + ')' , this ) ;
if ( cell . size ( ) ) {
cell . css ( 'display' , 'none' ) ;
}
// We might be dealing with a row spanning the entire table.
// Reduce the colspan on the first cell to prevent the cell from
// overshooting the table.
else {
cell = $ ( 'td:first' , this ) ;
cell . attr ( 'colspan' , cell . attr ( 'colspan' ) - 1 ) ;
}
} ) ;
$ ( 'thead tr' , this . table ) . each ( function ( ) {
// Remove table header cells entirely (Safari doesn't hide properly).
var th = $ ( 'th:nth-child(' + headerIndex + ')' , this ) ;
if ( th . size ( ) ) {
th . remove ( ) ;
}
} ) ;
}
}
} ;
/ * *
* Find the target used within a particular row and group .
* /
Drupal . tableDrag . prototype . rowSettings = function ( group , row ) {
var field = $ ( '.' + group , row ) ;
for ( delta in this . tableSettings [ group ] ) {
var targetClass = this . tableSettings [ group ] [ delta ] [ 'target' ] ;
if ( field . is ( '.' + targetClass ) ) {
// Return a copy of the row settings.
var rowSettings = new Object ( ) ;
for ( var n in this . tableSettings [ group ] [ delta ] ) {
rowSettings [ n ] = this . tableSettings [ group ] [ delta ] [ n ] ;
}
return rowSettings ;
}
}
} ;
/ * *
* Take an item and add event handlers to make it become draggable .
* /
Drupal . tableDrag . prototype . makeDraggable = function ( item ) {
var self = this ;
// Create the handle.
2007-12-16 10:36:53 +00:00
var handle = $ ( '<a href="#" class="tabledrag-handle"><div class="handle"> </div></a>' ) . attr ( 'title' , Drupal . t ( 'Drag to re-order' ) ) ;
2007-11-14 09:50:00 +00:00
// Insert the handle after indentations (if any).
if ( $ ( 'td:first .indentation:last' , item ) . after ( handle ) . size ( ) ) {
// Update the total width of indentation in this entire table.
self . indentCount = Math . max ( $ ( '.indentation' , item ) . size ( ) , self . indentCount ) ;
}
else {
$ ( 'td:first' , item ) . prepend ( handle ) ;
}
// Add hover action for the handle.
handle . hover ( function ( ) {
self . dragObject == null ? $ ( this ) . addClass ( 'tabledrag-handle-hover' ) : null ;
} , function ( ) {
self . dragObject == null ? $ ( this ) . removeClass ( 'tabledrag-handle-hover' ) : null ;
} ) ;
// Add the mousedown action for the handle.
handle . mousedown ( function ( event ) {
// Create a new dragObject recording the event information.
self . dragObject = new Object ( ) ;
self . dragObject . initMouseOffset = self . getMouseOffset ( item , event ) ;
self . dragObject . initMouseCoords = self . mouseCoords ( event ) ;
if ( self . indentEnabled ) {
self . dragObject . indentMousePos = self . dragObject . initMouseCoords ;
}
2007-11-26 16:36:44 +00:00
// If there's a lingering row object from the keyboard, remove its focus.
2007-11-14 09:50:00 +00:00
if ( self . rowObject ) {
$ ( 'a.tabledrag-handle' , self . rowObject . element ) . blur ( ) ;
}
// Create a new rowObject for manipulation of this row.
2007-11-26 16:19:37 +00:00
self . rowObject = new self . row ( item , 'mouse' , self . indentEnabled , self . maxDepth , true ) ;
2007-11-14 09:50:00 +00:00
// Save the position of the table.
self . table . topY = self . getPosition ( self . table ) . y ;
self . table . bottomY = self . table . topY + self . table . offsetHeight ;
// Add classes to the handle and row.
$ ( this ) . addClass ( 'tabledrag-handle-hover' ) ;
$ ( item ) . addClass ( 'drag' ) ;
// Set the document to use the move cursor during drag.
$ ( 'body' ) . addClass ( 'drag' ) ;
if ( self . oldRowElement ) {
$ ( self . oldRowElement ) . removeClass ( 'drag-previous' ) ;
}
// Hack for IE6 that flickers uncontrollably if select lists are moved.
if ( navigator . userAgent . indexOf ( 'MSIE 6.' ) != - 1 ) {
$ ( 'select' , this . table ) . css ( 'display' , 'none' ) ;
}
// Hack for Konqueror, prevent the blur handler from firing.
// Konqueror always gives links focus, even after returning false on mousedown.
self . safeBlur = false ;
// Call optional placeholder function.
self . onDrag ( ) ;
return false ;
} ) ;
// Prevent the anchor tag from jumping us to the top of the page.
handle . click ( function ( ) {
return false ;
} ) ;
// Similar to the hover event, add a class when the handle is focused.
handle . focus ( function ( ) {
$ ( this ) . addClass ( 'tabledrag-handle-hover' ) ;
self . safeBlur = true ;
} ) ;
// Remove the handle class on blur and fire the same function as a mouseup.
handle . blur ( function ( event ) {
$ ( this ) . removeClass ( 'tabledrag-handle-hover' ) ;
if ( self . rowObject && self . safeBlur ) {
2007-11-27 13:10:24 +00:00
self . dropRow ( event , self ) ;
2007-11-14 09:50:00 +00:00
}
} ) ;
// Add arrow-key support to the handle.
handle . keydown ( function ( event ) {
// If a rowObject doesn't yet exist and this isn't the tab key.
if ( event . keyCode != 9 && ! self . rowObject ) {
2007-11-26 16:19:37 +00:00
self . rowObject = new self . row ( item , 'keyboard' , self . indentEnabled , self . maxDepth , true ) ;
2007-11-14 09:50:00 +00:00
}
var keyChange = false ;
switch ( event . keyCode ) {
case 37 : // Left arrow.
case 63234 : // Safari left arrow.
keyChange = true ;
2007-12-13 10:14:18 +00:00
self . rowObject . indent ( - 1 * self . rtl ) ;
2007-11-14 09:50:00 +00:00
break ;
case 38 : // Up arrow.
case 63232 : // Safari up arrow.
var previousRow = $ ( self . rowObject . element ) . prev ( 'tr' ) . get ( 0 ) ;
while ( previousRow && $ ( previousRow ) . is ( ':hidden' ) ) {
previousRow = $ ( previousRow ) . prev ( 'tr' ) . get ( 0 ) ;
}
if ( previousRow ) {
self . safeBlur = false ; // Do not allow the onBlur cleanup.
self . rowObject . direction = 'up' ;
keyChange = true ;
2007-11-20 10:18:43 +00:00
if ( self . rowObject . isValidSwap ( previousRow , 0 ) ) {
2007-11-14 09:50:00 +00:00
self . rowObject . swap ( 'before' , previousRow ) ;
window . scrollBy ( 0 , - parseInt ( item . offsetHeight ) ) ;
}
else if ( self . table . tBodies [ 0 ] . rows [ 0 ] != previousRow || $ ( previousRow ) . is ( '.draggable' ) ) {
self . rowObject . swap ( 'before' , previousRow ) ;
self . rowObject . indent ( - 1 ) ;
window . scrollBy ( 0 , - parseInt ( item . offsetHeight ) ) ;
}
handle . get ( 0 ) . focus ( ) ; // Regain focus after the DOM manipulation.
}
break ;
case 39 : // Right arrow.
case 63235 : // Safari right arrow.
keyChange = true ;
2007-12-13 10:14:18 +00:00
self . rowObject . indent ( 1 * self . rtl ) ;
2007-11-14 09:50:00 +00:00
break ;
case 40 : // Down arrow.
case 63233 : // Safari down arrow.
var nextRow = $ ( self . rowObject . group ) . filter ( ':last' ) . next ( 'tr' ) . get ( 0 ) ;
while ( nextRow && $ ( nextRow ) . is ( ':hidden' ) ) {
nextRow = $ ( nextRow ) . next ( 'tr' ) . get ( 0 ) ;
}
if ( nextRow ) {
self . safeBlur = false ; // Do not allow the onBlur cleanup.
self . rowObject . direction = 'down' ;
keyChange = true ;
2007-11-20 10:18:43 +00:00
if ( self . rowObject . isValidSwap ( nextRow , 0 ) ) {
2007-11-14 09:50:00 +00:00
self . rowObject . swap ( 'after' , nextRow ) ;
}
else {
self . rowObject . swap ( 'after' , nextRow ) ;
self . rowObject . indent ( - 1 ) ;
}
handle . get ( 0 ) . focus ( ) ; // Regain focus after the DOM manipulation.
window . scrollBy ( 0 , parseInt ( item . offsetHeight ) ) ;
}
break ;
}
if ( self . rowObject && self . rowObject . changed == true ) {
$ ( item ) . addClass ( 'drag' ) ;
if ( self . oldRowElement ) {
$ ( self . oldRowElement ) . removeClass ( 'drag-previous' ) ;
}
self . oldRowElement = item ;
self . restripeTable ( ) ;
self . onDrag ( ) ;
}
// Returning false if we have an arrow key to prevent scrolling.
if ( keyChange ) {
return false ;
}
} ) ;
// Compatibility addition, return false on keypress to prevent unwanted scrolling.
// IE and Safari will supress scrolling on keydown, but all other browsers
// need to return false on keypress. http://www.quirksmode.org/js/keys.html
handle . keypress ( function ( event ) {
switch ( event . keyCode ) {
case 37 : // Left arrow.
case 38 : // Up arrow.
case 39 : // Right arrow.
case 40 : // Down arrow.
return false ;
}
} ) ;
} ;
/ * *
* Mousemove event handler , bound to document .
* /
2007-11-27 13:10:24 +00:00
Drupal . tableDrag . prototype . dragRow = function ( event , self ) {
2007-11-14 09:50:00 +00:00
if ( self . dragObject ) {
self . currentMouseCoords = self . mouseCoords ( event ) ;
var y = self . currentMouseCoords . y - self . dragObject . initMouseOffset . y ;
var x = self . currentMouseCoords . x - self . dragObject . initMouseOffset . x ;
// Check for row swapping and vertical scrolling.
if ( y != self . oldY ) {
self . rowObject . direction = y > self . oldY ? 'down' : 'up' ;
self . oldY = y ; // Update the old value.
// Check if the window should be scrolled (and how fast).
var scrollAmount = self . checkScroll ( self . currentMouseCoords . y ) ;
// Stop any current scrolling.
clearInterval ( self . scrollInterval ) ;
// Continue scrolling if the mouse has moved in the scroll direction.
if ( scrollAmount > 0 && self . rowObject . direction == 'down' || scrollAmount < 0 && self . rowObject . direction == 'up' ) {
self . setScroll ( scrollAmount ) ;
}
// If we have a valid target, perform the swap and restripe the table.
var currentRow = self . findDropTargetRow ( x , y ) ;
if ( currentRow ) {
if ( self . rowObject . direction == 'down' ) {
self . rowObject . swap ( 'after' , currentRow , self ) ;
}
else {
self . rowObject . swap ( 'before' , currentRow , self ) ;
}
self . restripeTable ( ) ;
}
}
// Similar to row swapping, handle indentations.
if ( self . indentEnabled && x != self . oldX ) {
self . oldX = x ;
var xDiff = self . currentMouseCoords . x - self . dragObject . indentMousePos . x ;
// Set the number of indentations the mouse has been moved left or right.
2007-12-13 10:14:18 +00:00
var indentDiff = parseInt ( xDiff / self . indentAmount * self . rtl ) ;
2007-11-14 09:50:00 +00:00
// Limit the indentation to no less than the left edge of the table and no
// more than the total amount of indentation in the table.
indentDiff = indentDiff > 0 ? Math . min ( indentDiff , self . indentCount - self . rowObject . indents + 1 ) : Math . max ( indentDiff , - self . rowObject . indents ) ;
if ( indentDiff != 0 ) {
// Indent the row with our estimated diff, which may be further
// restricted according to the rows around this row.
var indentChange = self . rowObject . indent ( indentDiff ) ;
// Update table and mouse indentations.
2007-12-13 10:14:18 +00:00
self . dragObject . indentMousePos . x += self . indentAmount * indentChange * self . rtl ;
2007-11-14 09:50:00 +00:00
self . indentCount = Math . max ( self . indentCount , self . rowObject . indents ) ;
}
}
return false ;
}
} ;
/ * *
* Mouseup event handler , bound to document .
* Blur event handler , bound to drag handle for keyboard support .
* /
2007-11-27 13:10:24 +00:00
Drupal . tableDrag . prototype . dropRow = function ( event , self ) {
2007-11-14 09:50:00 +00:00
// Drop row functionality shared between mouseup and blur events.
if ( self . rowObject != null ) {
var droppedRow = self . rowObject . element ;
// The row is already in the right place so we just release it.
if ( self . rowObject . changed == true ) {
2007-11-26 19:46:52 +00:00
// Update the fields in the dropped row.
2007-11-14 09:50:00 +00:00
self . updateFields ( droppedRow ) ;
2007-11-26 19:46:52 +00:00
// If a setting exists for affecting the entire group, update all the
// fields in the entire dragged group.
for ( var group in self . tableSettings ) {
var rowSettings = self . rowSettings ( group , droppedRow ) ;
if ( rowSettings . relationship == 'group' ) {
for ( n in self . rowObject . children ) {
self . updateField ( self . rowObject . children [ n ] , group ) ;
}
}
}
2007-11-14 09:50:00 +00:00
self . rowObject . markChanged ( ) ;
if ( self . changed == false ) {
$ ( Drupal . theme ( 'tableDragChangedWarning' ) ) . insertAfter ( self . table ) . hide ( ) . fadeIn ( 'slow' ) ;
self . changed = true ;
}
}
if ( self . indentEnabled ) {
self . rowObject . removeIndentClasses ( ) ;
}
if ( self . oldRowElement ) {
$ ( self . oldRowElement ) . removeClass ( 'drag-previous' ) ;
}
$ ( droppedRow ) . removeClass ( 'drag' ) . addClass ( 'drag-previous' ) ;
self . oldRowElement = droppedRow ;
self . onDrop ( ) ;
self . rowObject = null ;
}
// Functionality specific only to mouseup event.
if ( self . dragObject != null ) {
$ ( '.tabledrag-handle' , droppedRow ) . removeClass ( 'tabledrag-handle-hover' ) ;
self . dragObject = null ;
$ ( 'body' ) . removeClass ( 'drag' ) ;
clearInterval ( self . scrollInterval ) ;
// Hack for IE6 that flickers uncontrollably if select lists are moved.
if ( navigator . userAgent . indexOf ( 'MSIE 6.' ) != - 1 ) {
$ ( 'select' , this . table ) . css ( 'display' , 'block' ) ;
}
}
} ;
/ * *
* Get the position of an element by adding up parent offsets in the DOM tree .
* /
Drupal . tableDrag . prototype . getPosition = function ( element ) {
var left = 0 ;
var top = 0 ;
// Because Safari doesn't report offsetHeight on table rows, but does on table
// cells, grab the firstChild of the row and use that instead.
// http://jacob.peargrove.com/blog/2006/technical/table-row-offsettop-bug-in-safari
if ( element . offsetHeight == 0 ) {
element = element . firstChild ; // a table cell
}
while ( element . offsetParent ) {
left += element . offsetLeft ;
top += element . offsetTop ;
element = element . offsetParent ;
}
left += element . offsetLeft ;
top += element . offsetTop ;
return { x : left , y : top } ;
} ;
/ * *
* Get the mouse coordinates from the event ( allowing for browser differences ) .
* /
Drupal . tableDrag . prototype . mouseCoords = function ( event ) {
if ( event . pageX || event . pageY ) {
return { x : event . pageX , y : event . pageY } ;
}
return {
x : event . clientX + document . body . scrollLeft - document . body . clientLeft ,
y : event . 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 .
* /
Drupal . tableDrag . prototype . getMouseOffset = function ( target , event ) {
var docPos = this . getPosition ( target ) ;
var mousePos = this . mouseCoords ( event ) ;
return { x : mousePos . x - docPos . x , y : mousePos . y - docPos . y } ;
} ;
/ * *
* Find the row the mouse is currently over . This row is then taken and swapped
* with the one being dragged .
*
* @ param x
* The x coordinate of the mouse on the page ( not the screen ) .
* @ param y
* The y coordinate of the mouse on the page ( not the screen ) .
* /
Drupal . tableDrag . prototype . findDropTargetRow = function ( x , y ) {
var rows = this . table . tBodies [ 0 ] . rows ;
for ( var n = 0 ; n < rows . length ; n ++ ) {
var row = rows [ n ] ;
var indentDiff = 0 ;
// Safari fix see Drupal.tableDrag.prototype.getPosition()
if ( row . offsetHeight == 0 ) {
var rowY = this . getPosition ( row . firstChild ) . y ;
var rowHeight = parseInt ( row . firstChild . offsetHeight ) / 2 ;
}
// Other browsers.
else {
var rowY = this . getPosition ( row ) . y ;
var rowHeight = parseInt ( row . offsetHeight ) / 2 ;
}
// Because we always insert before, we need to offset the height a bit.
if ( ( y > ( rowY - rowHeight ) ) && ( y < ( rowY + rowHeight ) ) ) {
if ( this . indentEnabled ) {
// Check that this row is not a child of the row being dragged.
for ( n in this . rowObject . group ) {
if ( this . rowObject . group [ n ] == row ) {
return null ;
}
}
// Check that moving this row will not cause invalid relationships.
var xDiff = this . currentMouseCoords . x - this . dragObject . initMouseCoords . x ;
indentDiff = parseInt ( xDiff / this . indentAmount ) ;
}
if ( ! this . rowObject . isValidSwap ( row , indentDiff ) ) {
return null ;
}
// We've may have found the row the mouse just passed over, but it doesn't
// take into account hidden rows. Skip backwards until we find a draggable
// row.
while ( $ ( row ) . is ( ':hidden' ) && $ ( row ) . prev ( 'tr' ) . is ( ':hidden' ) ) {
row = $ ( row ) . prev ( 'tr' ) . get ( 0 ) ;
}
return row ;
}
}
return null ;
} ;
/ * *
* After the row is dropped , update the table fields according to the settings
* set for this table .
*
* @ param changedRow
* DOM object for the row that was just dropped .
* /
Drupal . tableDrag . prototype . updateFields = function ( changedRow ) {
for ( var group in this . tableSettings ) {
// Each group may have a different setting for relationship, so we find
// the source rows for each seperately.
2007-11-26 19:46:52 +00:00
this . updateField ( changedRow , group ) ;
}
2007-12-06 09:53:53 +00:00
} ;
2007-11-14 09:50:00 +00:00
2007-11-26 19:46:52 +00:00
/ * *
* After the row is dropped , update a single table field according to specific
* settings .
*
* @ param changedRow
* DOM object for the row that was just dropped .
* @ param group
* The settings group on which field updates will occur .
* /
Drupal . tableDrag . prototype . updateField = function ( changedRow , group ) {
var rowSettings = this . rowSettings ( group , changedRow ) ;
// Set the row as it's own target.
if ( rowSettings . relationship == 'self' || rowSettings . relationship == 'group' ) {
var sourceRow = changedRow ;
}
// Siblings are easy, check previous and next rows.
else if ( rowSettings . relationship == 'sibling' ) {
var previousRow = $ ( changedRow ) . prev ( 'tr' ) . get ( 0 ) ;
var nextRow = $ ( changedRow ) . next ( 'tr' ) . get ( 0 ) ;
var sourceRow = changedRow ;
if ( $ ( previousRow ) . is ( '.draggable' ) && $ ( '.' + group , previousRow ) . length ) {
if ( this . indentEnabled ) {
if ( $ ( '.indentations' , previousRow ) . size ( ) == $ ( '.indentations' , changedRow ) ) {
2007-11-14 09:50:00 +00:00
sourceRow = previousRow ;
}
}
2007-11-26 19:46:52 +00:00
else {
sourceRow = previousRow ;
2007-11-14 09:50:00 +00:00
}
}
2007-11-26 19:46:52 +00:00
else if ( $ ( nextRow ) . is ( '.draggable' ) && $ ( '.' + group , nextRow ) . length ) {
if ( this . indentEnabled ) {
if ( $ ( '.indentations' , nextRow ) . size ( ) == $ ( '.indentations' , changedRow ) ) {
sourceRow = nextRow ;
}
2007-11-14 09:50:00 +00:00
}
else {
2007-11-26 19:46:52 +00:00
sourceRow = nextRow ;
}
}
}
// Parents, look up the tree until we find a field not in this group.
// Go up as many parents as indentations in the changed row.
else if ( rowSettings . relationship == 'parent' ) {
var previousRow = $ ( changedRow ) . prev ( 'tr' ) ;
while ( previousRow . length && $ ( '.indentation' , previousRow ) . length >= this . rowObject . indents ) {
previousRow = previousRow . prev ( 'tr' ) ;
}
// If we found a row.
if ( previousRow . length ) {
sourceRow = previousRow [ 0 ] ;
}
// Otherwise we went all the way to the left of the table without finding
// a parent, meaning this item has been placed at the root level.
else {
// Use the first row in the table as source, because it's garanteed to
// be at the root level. Find the first item, then compare this row
// against it as a sibling.
sourceRow = $ ( 'tr.draggable:first' ) . get ( 0 ) ;
if ( sourceRow == this . rowObject . element ) {
sourceRow = $ ( this . rowObject . group [ this . rowObject . group . length - 1 ] ) . next ( 'tr.draggable' ) . get ( 0 ) ;
2007-11-14 09:50:00 +00:00
}
2007-11-26 19:46:52 +00:00
var useSibling = true ;
2007-11-14 09:50:00 +00:00
}
2007-11-26 19:46:52 +00:00
}
2007-11-14 09:50:00 +00:00
2007-11-26 19:46:52 +00:00
// Because we may have moved the row from one category to another,
// take a look at our sibling and borrow its sources and targets.
this . copyDragClasses ( sourceRow , changedRow , group ) ;
rowSettings = this . rowSettings ( group , changedRow ) ;
2007-11-14 09:50:00 +00:00
2007-11-26 19:46:52 +00:00
// In the case that we're looking for a parent, but the row is at the top
// of the tree, copy our sibling's values.
if ( useSibling ) {
rowSettings . relationship = 'sibling' ;
rowSettings . source = rowSettings . target ;
}
2007-11-14 09:50:00 +00:00
2007-11-26 19:46:52 +00:00
var targetClass = '.' + rowSettings . target ;
var targetElement = $ ( targetClass , changedRow ) . get ( 0 ) ;
// Check if a target element exists in this row.
if ( targetElement ) {
var sourceClass = '.' + rowSettings . source ;
var sourceElement = $ ( sourceClass , sourceRow ) . get ( 0 ) ;
switch ( rowSettings . action ) {
case 'depth' :
// Get the depth of the target row.
targetElement . value = $ ( '.indentation' , $ ( sourceElement ) . parents ( 'tr:first' ) ) . size ( ) ;
break ;
case 'match' :
// Update the value.
targetElement . value = sourceElement . value ;
break ;
case 'order' :
var siblings = this . rowObject . findSiblings ( rowSettings ) ;
if ( $ ( targetElement ) . is ( 'select' ) ) {
// Get a list of acceptable values.
var values = new Array ( ) ;
$ ( 'option' , targetElement ) . each ( function ( ) {
values . push ( this . value ) ;
} ) ;
// Populate the values in the siblings.
$ ( targetClass , siblings ) . each ( function ( ) {
this . value = values . shift ( ) ;
} ) ;
}
else {
// Assume a numeric input field.
var weight = parseInt ( $ ( targetClass , siblings [ 0 ] ) . val ( ) ) || 0 ;
$ ( targetClass , siblings ) . each ( function ( ) {
this . value = weight ;
weight ++ ;
} ) ;
}
break ;
2007-11-14 09:50:00 +00:00
}
}
} ;
/ * *
* Copy all special tableDrag classes from one row ' s form elements to a
* different one , removing any special classes that the destination row
* may have had .
* /
Drupal . tableDrag . prototype . copyDragClasses = function ( sourceRow , targetRow , group ) {
var sourceElement = $ ( '.' + group , sourceRow ) ;
var targetElement = $ ( '.' + group , targetRow ) ;
if ( sourceElement . length && targetElement . length ) {
targetElement [ 0 ] . className = sourceElement [ 0 ] . className ;
}
} ;
Drupal . tableDrag . prototype . checkScroll = function ( cursorY ) {
var de = document . documentElement ;
var b = document . body ;
var windowHeight = this . windowHeight = window . innerHeight || ( de . clientHeight && de . clientWidth != 0 ? de . clientHeight : b . offsetHeight ) ;
var scrollY = this . scrollY = ( document . all ? ( ! de . scrollTop ? b . scrollTop : de . scrollTop ) : ( window . pageYOffset ? window . pageYOffset : window . scrollY ) ) ;
var trigger = this . scrollSettings . trigger ;
var delta = 0 ;
// Return a scroll speed relative to the edge of the screen.
if ( cursorY - scrollY > windowHeight - trigger ) {
delta = trigger / ( windowHeight + scrollY - cursorY ) ;
delta = ( delta > 0 && delta < trigger ) ? delta : trigger ;
return delta * this . scrollSettings . amount ;
}
else if ( cursorY - scrollY < trigger ) {
delta = trigger / ( cursorY - scrollY ) ;
delta = ( delta > 0 && delta < trigger ) ? delta : trigger ;
return - delta * this . scrollSettings . amount ;
}
} ;
Drupal . tableDrag . prototype . setScroll = function ( scrollAmount ) {
var self = this ;
this . scrollInterval = setInterval ( function ( ) {
// Update the scroll values stored in the object.
self . checkScroll ( self . currentMouseCoords . y ) ;
var aboveTable = self . scrollY > self . table . topY ;
var belowTable = self . scrollY + self . windowHeight < self . table . bottomY ;
if ( scrollAmount > 0 && belowTable || scrollAmount < 0 && aboveTable ) {
window . scrollBy ( 0 , scrollAmount ) ;
}
} , this . scrollSettings . interval ) ;
} ;
Drupal . tableDrag . prototype . restripeTable = function ( ) {
// :even and :odd are reversed because jquery counts from 0 and
// we count from 1, so we're out of sync.
$ ( 'tr.draggable' , this . table )
. filter ( ':odd' ) . filter ( '.odd' )
. removeClass ( 'odd' ) . addClass ( 'even' )
. end ( ) . end ( )
. filter ( ':even' ) . filter ( '.even' )
. removeClass ( 'even' ) . addClass ( 'odd' ) ;
} ;
/ * *
* Stub function . Allows a custom handler when a row begins dragging .
* /
Drupal . tableDrag . prototype . onDrag = function ( ) {
return null ;
} ;
/ * *
* Stub function . Allows a custom handler when a row is dropped .
* /
Drupal . tableDrag . prototype . onDrop = function ( ) {
return null ;
} ;
/ * *
* Constructor to make a new object to manipulate a table row .
*
* @ param tableRow
* The DOM element for the table row we will be manipulating .
* @ param method
* The method in which this row is being moved . Either 'keyboard' or 'mouse' .
* @ param indentEnabled
* Whether the containing table uses indentations . Used for optimizations .
2007-11-26 16:19:37 +00:00
* @ param maxDepth
* The maximum amount of indentations this row may contain .
2007-11-14 09:50:00 +00:00
* @ param addClasses
* Whether we want to add classes to this row to indicate child relationships .
* /
2007-11-26 16:19:37 +00:00
Drupal . tableDrag . prototype . row = function ( tableRow , method , indentEnabled , maxDepth , addClasses ) {
2007-11-14 09:50:00 +00:00
this . element = tableRow ;
this . method = method ;
this . group = new Array ( tableRow ) ;
this . groupDepth = $ ( '.indentation' , tableRow ) . size ( ) ;
this . changed = false ;
this . table = $ ( tableRow ) . parents ( 'table:first' ) . get ( 0 ) ;
this . indentEnabled = indentEnabled ;
2007-11-26 16:19:37 +00:00
this . maxDepth = maxDepth ;
2007-11-14 09:50:00 +00:00
this . direction = '' ; // Direction the row is being moved.
if ( this . indentEnabled ) {
this . indents = $ ( '.indentation' , tableRow ) . size ( ) ;
this . children = this . findChildren ( addClasses ) ;
this . group = $ . merge ( this . group , this . children ) ;
// Find the depth of this entire group.
for ( var n = 0 ; n < this . group . length ; n ++ ) {
this . groupDepth = Math . max ( $ ( '.indentation' , this . group [ n ] ) . size ( ) , this . groupDepth ) ;
}
}
} ;
/ * *
* Find all children of rowObject by indentation .
*
* @ param addClasses
* Whether we want to add classes to this row to indicate child relationships .
* /
Drupal . tableDrag . prototype . row . prototype . findChildren = function ( addClasses ) {
var parentIndentation = this . indents ;
var currentRow = $ ( this . element , this . table ) . next ( 'tr.draggable' ) ;
var rows = new Array ( ) ;
var child = 0 ;
while ( currentRow . length ) {
var rowIndentation = $ ( '.indentation' , currentRow ) . length ;
// A greater indentation indicates this is a child.
if ( rowIndentation > parentIndentation ) {
child ++ ;
rows . push ( currentRow [ 0 ] ) ;
if ( addClasses ) {
$ ( '.indentation' , currentRow ) . each ( function ( indentNum ) {
if ( child == 1 && ( indentNum == parentIndentation ) ) {
$ ( this ) . addClass ( 'tree-child-first' ) ;
}
if ( indentNum == parentIndentation ) {
$ ( this ) . addClass ( 'tree-child' ) ;
}
else if ( indentNum > parentIndentation ) {
$ ( this ) . addClass ( 'tree-child-horizontal' ) ;
}
} ) ;
}
}
else {
break ;
}
currentRow = currentRow . next ( 'tr.draggable' ) ;
}
if ( addClasses && rows . length ) {
$ ( '.indentation:nth-child(' + ( parentIndentation + 1 ) + ')' , rows [ rows . length - 1 ] ) . addClass ( 'tree-child-last' ) ;
}
return rows ;
} ;
/ * *
* Ensure that two rows are allowed to be swapped . Returns false if swapping
* will cause invalid parent relationships .
*
* @ param row
* The row being considered for swapping .
* @ param indentDiff
* A horizontal difference in indendations .
* /
Drupal . tableDrag . prototype . row . prototype . isValidSwap = function ( row , indentDiff ) {
if ( this . indentEnabled ) {
var rowIndents = $ ( '.indentation' , row ) . size ( ) ;
var prevIndents = $ ( '.indentation' , $ ( row ) . prev ( 'tr' ) ) . size ( ) ;
var nextIndents = $ ( '.indentation' , $ ( row ) . next ( 'tr' ) ) . size ( ) ;
if (
( this . direction == 'down' ) && (
// Prevent being able to drag a row downward with 2 indentations from a parent.
this . indents > rowIndents + 1 ||
// Prevent orphaning children when dragging into a parent.
this . indents < nextIndents - indentDiff
) ||
( this . direction == 'up' ) && (
// Prevent being able to drag a row upward with 2 indentations from a parent.
this . indents < rowIndents ||
// Prevent orphaning children when dragging between a child and parent.
this . indents > prevIndents + 1 - indentDiff
)
) {
return false ;
}
}
// Restriction special cases for the first row in the table.
if ( this . table . tBodies [ 0 ] . rows [ 0 ] == row ) {
// Do not let the first row contain indentations
// or let an un-draggable first row have anything put before it.
2007-11-20 10:18:43 +00:00
if ( ( this . indents + indentDiff ) > 0 || $ ( row ) . is ( ':not(.draggable)' ) ) {
2007-11-14 09:50:00 +00:00
return false ;
}
}
return true ;
} ;
/ * *
* Perform the swap between two rows .
*
* @ param position
* Whether the swap will occur 'before' or 'after' the given row .
* @ param row
* DOM element what will be swapped with the row group .
* /
Drupal . tableDrag . prototype . row . prototype . swap = function ( position , row ) {
$ ( row ) [ position ] ( this . group ) ;
this . changed = true ;
this . onSwap ( row ) ;
} ;
/ * *
* Indent a row within the legal bounds of the table .
*
* @ param indentDiff
* The number of indentations the row group should receive , can be negative or
* positive .
* /
Drupal . tableDrag . prototype . row . prototype . indent = function ( indentDiff ) {
if ( indentDiff > 0 ) {
var prevRow = $ ( this . group ) . filter ( ':first' ) . prev ( 'tr' ) . get ( 0 ) ;
if ( prevRow ) {
var prevIndent = $ ( '.indentation' , $ ( this . group ) . filter ( ':first' ) . prev ( 'tr' ) ) . size ( ) ;
indentDiff = Math . min ( prevIndent - this . indents + 1 , indentDiff ) ;
}
else {
indentDiff = 0 ; // First row may not contain indents.
}
}
else {
var nextIndent = $ ( '.indentation' , $ ( this . group ) . filter ( ':last' ) . next ( 'tr' ) ) . size ( ) ;
indentDiff = Math . max ( nextIndent - this . indents , indentDiff ) ;
}
2007-11-26 16:19:37 +00:00
// Never allow indentation greater the set limit.
if ( this . maxDepth && indentDiff + this . groupDepth > this . maxDepth ) {
2007-11-14 09:50:00 +00:00
indentDiff = 0 ;
}
for ( var n = 1 ; n <= Math . abs ( indentDiff ) ; n ++ ) {
// Add or remove indentations.
if ( indentDiff < 0 ) {
$ ( '.indentation:first' , this . group ) . remove ( ) ;
this . indents -- ;
}
else {
$ ( 'td:first' , this . group ) . prepend ( Drupal . theme ( 'tableDragIndentation' ) ) ;
this . indents ++ ;
}
}
if ( indentDiff ) {
this . changed = true ;
}
// Update indentation for this row.
this . groupDepth += indentDiff ;
this . onIndent ( ) ;
return indentDiff ;
} ;
/ * *
* Find all siblings for a row , either according to its subgroup or indentation .
* Note that the passed in row is included in the list of siblings .
*
* @ param settings
* The field settings we ' re using to identify what constitutes a sibling .
* /
Drupal . tableDrag . prototype . row . prototype . findSiblings = function ( rowSettings ) {
var siblings = new Array ( ) ;
var directions = new Array ( 'prev' , 'next' ) ;
var rowIndentation = this . indents ;
for ( var d in directions ) {
var checkRow = $ ( this . element ) [ directions [ d ] ] ( ) ;
while ( checkRow . length ) {
// Check that the sibling contains a similar target field.
if ( $ ( '.' + rowSettings . target , checkRow ) ) {
// Either add immediately if this is a flat table, or check to ensure
// that this row has the same level of indentaiton.
if ( this . indentEnabled ) {
var checkRowIndentation = $ ( '.indentation' , checkRow ) . length
}
if ( ! ( this . indentEnabled ) || ( checkRowIndentation == rowIndentation ) ) {
siblings . push ( checkRow [ 0 ] ) ;
}
else if ( checkRowIndentation < rowIndentation ) {
// No need to keep looking for siblings when we get to a parent.
break ;
}
}
else {
break ;
}
checkRow = $ ( checkRow ) [ directions [ d ] ] ( ) ;
}
// Since siblings are added in reverse order for previous, reverse the
// completed list of previous siblings. Add the current row and continue.
if ( directions [ d ] == 'prev' ) {
siblings . reverse ( ) ;
siblings . push ( this . element ) ;
}
}
return siblings ;
} ;
/ * *
* Remove indentation helper classes from the current row group .
* /
Drupal . tableDrag . prototype . row . prototype . removeIndentClasses = function ( ) {
for ( n in this . children ) {
$ ( '.indentation' , this . children [ n ] )
. removeClass ( 'tree-child' )
. removeClass ( 'tree-child-first' )
. removeClass ( 'tree-child-last' )
. removeClass ( 'tree-child-horizontal' ) ;
}
} ;
/ * *
* Add an asterisk or other marker to the changed row .
* /
Drupal . tableDrag . prototype . row . prototype . markChanged = function ( ) {
var marker = Drupal . theme ( 'tableDragChangedMarker' ) ;
var cell = $ ( 'td:first' , this . element ) ;
if ( $ ( 'span.tabledrag-changed' , cell ) . length == 0 ) {
cell . append ( marker ) ;
}
} ;
/ * *
* Stub function . Allows a custom handler when a row is indented .
* /
Drupal . tableDrag . prototype . row . prototype . onIndent = function ( ) {
return null ;
} ;
/ * *
* Stub function . Allows a custom handler when a row is swapped .
* /
Drupal . tableDrag . prototype . row . prototype . onSwap = function ( swappedRow ) {
return null ;
} ;
Drupal . theme . prototype . tableDragChangedMarker = function ( ) {
return '<span class="warning tabledrag-changed">*</span>' ;
} ;
Drupal . theme . prototype . tableDragIndentation = function ( ) {
return '<div class="indentation"> </div>' ;
} ;
Drupal . theme . prototype . tableDragChangedWarning = function ( ) {
return '<div class="warning">' + Drupal . theme ( 'tableDragChangedMarker' ) + ' ' + Drupal . t ( "Changes made in this table will not be saved until the form is submitted." ) + '</div>' ;
} ;