2015-08-17 17:00:26 -07:00
/ * *
* @ file
* Provide dragging capabilities to admin uis .
* /
/ * *
* Triggers when weights columns are toggled .
*
* @ event columnschange
* /
( function ( $ , Drupal , drupalSettings ) {
2015-10-21 21:44:50 -07:00
'use strict' ;
2015-08-17 17:00:26 -07:00
/ * *
* Store the state of weight columns display for all tables .
*
* Default value is to hide weight columns .
* /
var showWeight = JSON . parse ( localStorage . getItem ( 'Drupal.tableDrag.showWeight' ) ) ;
/ * *
* Drag and drop table rows with field manipulation .
*
* Using the drupal _attach _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 .
*
* @ type { Drupal ~ behavior }
* /
Drupal . behaviors . tableDrag = {
attach : function ( context , settings ) {
function initTableDrag ( table , base ) {
if ( table . length ) {
// Create the new tableDrag instance. Save in the Drupal variable
// to allow other scripts access to the object.
Drupal . tableDrag [ base ] = new Drupal . tableDrag ( table [ 0 ] , settings . tableDrag [ base ] ) ;
}
}
for ( var base in settings . tableDrag ) {
if ( settings . tableDrag . hasOwnProperty ( base ) ) {
initTableDrag ( $ ( context ) . find ( '#' + base ) . once ( 'tabledrag' ) , base ) ;
}
}
}
} ;
/ * *
* Provides table and field manipulation .
*
* @ constructor
*
* @ param { HTMLElement } table
* DOM object for the table to be made draggable .
* @ param { object } tableSettings
* Settings for the table added via drupal _add _dragtable ( ) .
* /
Drupal . tableDrag = function ( table , tableSettings ) {
var self = this ;
var $table = $ ( table ) ;
/ * *
* @ type { jQuery }
* /
this . $table = $ ( table ) ;
/ * *
*
* @ type { HTMLElement }
* /
this . table = table ;
/ * *
* @ type { object }
* /
this . tableSettings = tableSettings ;
/ * *
* Used to hold information about a current drag operation .
*
* @ type { ? HTMLElement }
* /
this . dragObject = null ;
/ * *
* Provides operations for row manipulation .
*
* @ type { ? HTMLElement }
* /
this . rowObject = null ;
/ * *
* Remember the previous element .
*
* @ type { ? HTMLElement }
* /
this . oldRowElement = null ;
/ * *
* Used to determine up or down direction from last mouse move .
*
* @ type { number }
* /
this . oldY = 0 ;
/ * *
* Whether anything in the entire table has changed .
*
* @ type { bool }
* /
this . changed = false ;
/ * *
* Maximum amount of allowed parenting .
*
* @ type { number }
* /
this . maxDepth = 0 ;
/ * *
* Direction of the table .
*
* @ type { number }
* /
this . rtl = $ ( this . table ) . css ( 'direction' ) === 'rtl' ? - 1 : 1 ;
/ * *
*
* @ type { bool }
* /
this . striping = $ ( this . table ) . data ( 'striping' ) === 1 ;
/ * *
* Configure the scroll settings .
*
* @ type { object }
*
* @ prop { number } amount
* @ prop { number } interval
* @ prop { number } trigger
* /
this . scrollSettings = { amount : 4 , interval : 50 , trigger : 70 } ;
/ * *
*
* @ type { ? number }
* /
this . scrollInterval = null ;
/ * *
*
* @ type { number }
* /
this . scrollY = 0 ;
/ * *
*
* @ type { number }
* /
this . windowHeight = 0 ;
/ * *
2016-03-02 12:40:24 -08:00
* Check this table ' s settings for parent relationships .
*
* For efficiency , large sections of code can be skipped if we don ' t need to
* track horizontal movement and indentations .
2015-08-17 17:00:26 -07:00
*
* @ type { bool }
* /
this . indentEnabled = false ;
for ( var group in tableSettings ) {
if ( tableSettings . hasOwnProperty ( group ) ) {
for ( var n in tableSettings [ group ] ) {
if ( tableSettings [ group ] . hasOwnProperty ( n ) ) {
if ( tableSettings [ group ] [ n ] . relationship === 'parent' ) {
this . indentEnabled = true ;
}
if ( tableSettings [ group ] [ n ] . limit > 0 ) {
this . maxDepth = tableSettings [ group ] [ n ] . limit ;
}
}
}
}
}
if ( this . indentEnabled ) {
/ * *
* Total width of indents , set in makeDraggable .
*
* @ type { number }
* /
this . indentCount = 1 ;
// Find the width of indentations to measure mouse movements against.
// Because the table doesn't need to start with any indentations, we
// manually append 2 indentations in the first draggable row, measure
// the offset, then remove.
var indent = Drupal . theme ( 'tableDragIndentation' ) ;
var testRow = $ ( '<tr/>' ) . addClass ( 'draggable' ) . appendTo ( table ) ;
var testCell = $ ( '<td/>' ) . appendTo ( testRow ) . prepend ( indent ) . prepend ( indent ) ;
var $indentation = testCell . find ( '.js-indentation' ) ;
/ * *
*
* @ type { number }
* /
this . indentAmount = $indentation . get ( 1 ) . offsetLeft - $indentation . get ( 0 ) . offsetLeft ;
testRow . remove ( ) ;
}
// Make each applicable row draggable.
// Match immediate children of the parent element to allow nesting.
$table . find ( '> tr.draggable, > tbody > tr.draggable' ) . each ( function ( ) { self . makeDraggable ( this ) ; } ) ;
// Add a link before the table for users to show or hide weight columns.
$table . before ( $ ( '<button type="button" class="link tabledrag-toggle-weight"></button>' )
. attr ( 'title' , Drupal . t ( 'Re-order rows by numerical weight instead of dragging.' ) )
. on ( 'click' , $ . proxy ( function ( e ) {
e . preventDefault ( ) ;
this . toggleColumns ( ) ;
} , this ) )
. wrap ( '<div class="tabledrag-toggle-weight-wrapper"></div>' )
. parent ( )
) ;
// Initialize the specified columns (for example, weight or parent columns)
// to show or hide according to user preference. This aids accessibility
// so that, e.g., screen reader users can choose to enter weight values and
// manipulate form elements directly, rather than using drag-and-drop..
self . initColumns ( ) ;
// Add event bindings to the document. The self variable is passed along
// as event handlers do not have direct access to the tableDrag object.
2015-11-17 13:42:33 -08:00
$ ( document ) . on ( 'touchmove' , function ( event ) { return self . dragRow ( event . originalEvent . touches [ 0 ] , self ) ; } ) ;
$ ( document ) . on ( 'touchend' , function ( event ) { return self . dropRow ( event . originalEvent . touches [ 0 ] , self ) ; } ) ;
$ ( document ) . on ( 'mousemove pointermove' , function ( event ) { return self . dragRow ( event , self ) ; } ) ;
$ ( document ) . on ( 'mouseup pointerup' , function ( event ) { return self . dropRow ( event , self ) ; } ) ;
2015-08-17 17:00:26 -07:00
// React to localStorage event showing or hiding weight columns.
$ ( window ) . on ( 'storage' , $ . proxy ( function ( e ) {
// Only react to 'Drupal.tableDrag.showWeight' value change.
if ( e . originalEvent . key === 'Drupal.tableDrag.showWeight' ) {
// This was changed in another window, get the new value for this
// window.
showWeight = JSON . parse ( e . originalEvent . newValue ) ;
this . displayColumns ( showWeight ) ;
}
} , this ) ) ;
} ;
/ * *
* Initialize columns containing form elements to be hidden by default .
*
* Identify and mark each cell with a CSS class so we can easily toggle
* show / hide it . Finally , hide columns if user does not have a
* 'Drupal.tableDrag.showWeight' localStorage value .
* /
Drupal . tableDrag . prototype . initColumns = function ( ) {
var $table = this . $table ;
var hidden ;
var cell ;
var columnIndex ;
for ( var group in this . tableSettings ) {
if ( this . tableSettings . hasOwnProperty ( group ) ) {
// Find the first field in this group.
for ( var d in this . tableSettings [ group ] ) {
if ( this . tableSettings [ group ] . hasOwnProperty ( d ) ) {
var field = $table . find ( '.' + this . tableSettings [ group ] [ d ] . target ) . eq ( 0 ) ;
if ( field . length && this . tableSettings [ group ] [ d ] . hidden ) {
hidden = this . tableSettings [ group ] [ d ] . hidden ;
cell = field . closest ( 'td' ) ;
break ;
}
}
}
// Mark the column containing this field so it can be hidden.
if ( hidden && cell [ 0 ] ) {
// Add 1 to our indexes. The nth-child selector is 1 based, not 0
// based. Match immediate children of the parent element to allow
// nesting.
columnIndex = cell . parent ( ) . find ( '> td' ) . index ( cell . get ( 0 ) ) + 1 ;
$table . find ( '> thead > tr, > tbody > tr, > tr' ) . each ( this . addColspanClass ( columnIndex ) ) ;
}
}
}
this . displayColumns ( showWeight ) ;
} ;
/ * *
* Mark cells that have colspan .
*
* In order to adjust the colspan instead of hiding them altogether .
*
* @ param { number } columnIndex
2016-03-02 12:40:24 -08:00
* The column index to add colspan class to .
2015-08-17 17:00:26 -07:00
*
* @ return { function }
2016-03-02 12:40:24 -08:00
* Function to add colspan class .
2015-08-17 17:00:26 -07:00
* /
Drupal . tableDrag . prototype . addColspanClass = function ( columnIndex ) {
return function ( ) {
// Get the columnIndex and adjust for any colspans in this row.
var $row = $ ( this ) ;
var index = columnIndex ;
var cells = $row . children ( ) ;
var cell ;
cells . each ( function ( n ) {
if ( n < index && this . colSpan && this . colSpan > 1 ) {
index -= this . colSpan - 1 ;
}
} ) ;
if ( index > 0 ) {
cell = cells . filter ( ':nth-child(' + index + ')' ) ;
if ( cell [ 0 ] . colSpan && cell [ 0 ] . colSpan > 1 ) {
// If this cell has a colspan, mark it so we can reduce the colspan.
cell . addClass ( 'tabledrag-has-colspan' ) ;
}
else {
// Mark this cell so we can hide it.
cell . addClass ( 'tabledrag-hide' ) ;
}
}
} ;
} ;
/ * *
* Hide or display weight columns . Triggers an event on change .
*
* @ fires event : columnschange
*
* @ param { bool } displayWeight
* 'true' will show weight columns .
* /
Drupal . tableDrag . prototype . displayColumns = function ( displayWeight ) {
if ( displayWeight ) {
this . showColumns ( ) ;
}
// Default action is to hide columns.
else {
this . hideColumns ( ) ;
}
// Trigger an event to allow other scripts to react to this display change.
// Force the extra parameter as a bool.
$ ( 'table' ) . findOnce ( 'tabledrag' ) . trigger ( 'columnschange' , ! ! displayWeight ) ;
} ;
/ * *
* Toggle the weight column depending on 'showWeight' value .
*
* Store only default override .
* /
Drupal . tableDrag . prototype . toggleColumns = function ( ) {
showWeight = ! showWeight ;
this . displayColumns ( showWeight ) ;
if ( showWeight ) {
// Save default override.
localStorage . setItem ( 'Drupal.tableDrag.showWeight' , showWeight ) ;
}
else {
// Reset the value to its default.
localStorage . removeItem ( 'Drupal.tableDrag.showWeight' ) ;
}
} ;
/ * *
* Hide the columns containing weight / parent form elements .
*
* Undo showColumns ( ) .
* /
Drupal . tableDrag . prototype . hideColumns = function ( ) {
var $tables = $ ( 'table' ) . findOnce ( 'tabledrag' ) ;
// Hide weight/parent cells and headers.
$tables . find ( '.tabledrag-hide' ) . css ( 'display' , 'none' ) ;
// Show TableDrag handles.
$tables . find ( '.tabledrag-handle' ) . css ( 'display' , '' ) ;
// Reduce the colspan of any effected multi-span columns.
$tables . find ( '.tabledrag-has-colspan' ) . each ( function ( ) {
this . colSpan = this . colSpan - 1 ;
} ) ;
// Change link text.
$ ( '.tabledrag-toggle-weight' ) . text ( Drupal . t ( 'Show row weights' ) ) ;
} ;
/ * *
* Show the columns containing weight / parent form elements .
*
* Undo hideColumns ( ) .
* /
Drupal . tableDrag . prototype . showColumns = function ( ) {
var $tables = $ ( 'table' ) . findOnce ( 'tabledrag' ) ;
// Show weight/parent cells and headers.
$tables . find ( '.tabledrag-hide' ) . css ( 'display' , '' ) ;
// Hide TableDrag handles.
$tables . find ( '.tabledrag-handle' ) . css ( 'display' , 'none' ) ;
// Increase the colspan for any columns where it was previously reduced.
$tables . find ( '.tabledrag-has-colspan' ) . each ( function ( ) {
this . colSpan = this . colSpan + 1 ;
} ) ;
// Change link text.
$ ( '.tabledrag-toggle-weight' ) . text ( Drupal . t ( 'Hide row weights' ) ) ;
} ;
/ * *
* Find the target used within a particular row and group .
*
* @ param { string } group
2016-03-02 12:40:24 -08:00
* Group selector .
2015-08-17 17:00:26 -07:00
* @ param { HTMLElement } row
2016-03-02 12:40:24 -08:00
* The row HTML element .
2015-08-17 17:00:26 -07:00
*
* @ return { object }
2016-03-02 12:40:24 -08:00
* The table row settings .
2015-08-17 17:00:26 -07:00
* /
Drupal . tableDrag . prototype . rowSettings = function ( group , row ) {
var field = $ ( row ) . find ( '.' + group ) ;
var tableSettingsGroup = this . tableSettings [ group ] ;
for ( var delta in tableSettingsGroup ) {
if ( tableSettingsGroup . hasOwnProperty ( delta ) ) {
var targetClass = tableSettingsGroup [ delta ] . target ;
if ( field . is ( '.' + targetClass ) ) {
// Return a copy of the row settings.
var rowSettings = { } ;
for ( var n in tableSettingsGroup [ delta ] ) {
if ( tableSettingsGroup [ delta ] . hasOwnProperty ( n ) ) {
rowSettings [ n ] = tableSettingsGroup [ delta ] [ n ] ;
}
}
return rowSettings ;
}
}
}
} ;
/ * *
* Take an item and add event handlers to make it become draggable .
*
* @ param { HTMLElement } item
2016-03-02 12:40:24 -08:00
* The item to add event handlers to .
2015-08-17 17:00:26 -07:00
* /
Drupal . tableDrag . prototype . makeDraggable = function ( item ) {
var self = this ;
var $item = $ ( item ) ;
2016-03-02 12:40:24 -08:00
// Add a class to the title link.
2015-08-17 17:00:26 -07:00
$item . find ( 'td:first-of-type' ) . find ( 'a' ) . addClass ( 'menu-item__link' ) ;
// Create the handle.
var handle = $ ( '<a href="#" class="tabledrag-handle"><div class="handle"> </div></a>' ) . attr ( 'title' , Drupal . t ( 'Drag to re-order' ) ) ;
// Insert the handle after indentations (if any).
var $indentationLast = $item . find ( 'td:first-of-type' ) . find ( '.js-indentation' ) . eq ( - 1 ) ;
if ( $indentationLast . length ) {
$indentationLast . after ( handle ) ;
// Update the total width of indentation in this entire table.
self . indentCount = Math . max ( $item . find ( '.js-indentation' ) . length , self . indentCount ) ;
}
else {
$item . find ( 'td' ) . eq ( 0 ) . prepend ( handle ) ;
}
2015-11-17 13:42:33 -08:00
handle . on ( 'mousedown touchstart pointerdown' , function ( event ) {
event . preventDefault ( ) ;
if ( event . originalEvent . type === 'touchstart' ) {
2015-08-17 17:00:26 -07:00
event = event . originalEvent . touches [ 0 ] ;
2015-11-17 13:42:33 -08:00
}
self . dragStart ( event , self , item ) ;
} ) ;
2015-08-17 17:00:26 -07:00
// Prevent the anchor tag from jumping us to the top of the page.
handle . on ( 'click' , function ( e ) {
e . preventDefault ( ) ;
} ) ;
// Set blur cleanup when a handle is focused.
handle . on ( 'focus' , function ( ) {
self . safeBlur = true ;
} ) ;
// On blur, fire the same function as a touchend/mouseup. This is used to
// update values after a row has been moved through the keyboard support.
handle . on ( 'blur' , function ( event ) {
if ( self . rowObject && self . safeBlur ) {
self . dropRow ( event , self ) ;
}
} ) ;
// Add arrow-key support to the handle.
handle . on ( 'keydown' , function ( event ) {
// If a rowObject doesn't yet exist and this isn't the tab key.
if ( event . keyCode !== 9 && ! self . rowObject ) {
self . rowObject = new self . row ( item , 'keyboard' , self . indentEnabled , self . maxDepth , true ) ;
}
var keyChange = false ;
var groupHeight ;
2016-10-06 15:16:20 -07:00
/* eslint-disable no-fallthrough */
2015-08-17 17:00:26 -07:00
switch ( event . keyCode ) {
// Left arrow.
case 37 :
// Safari left arrow.
case 63234 :
keyChange = true ;
self . rowObject . indent ( - 1 * self . rtl ) ;
break ;
// Up arrow.
case 38 :
// Safari up arrow.
case 63232 :
var $previousRow = $ ( self . rowObject . element ) . prev ( 'tr:first-of-type' ) ;
var previousRow = $previousRow . get ( 0 ) ;
while ( previousRow && $previousRow . is ( ':hidden' ) ) {
$previousRow = $ ( previousRow ) . prev ( 'tr:first-of-type' ) ;
previousRow = $previousRow . get ( 0 ) ;
}
if ( previousRow ) {
// Do not allow the onBlur cleanup.
self . safeBlur = false ;
self . rowObject . direction = 'up' ;
keyChange = true ;
if ( $ ( item ) . is ( '.tabledrag-root' ) ) {
// Swap with the previous top-level row.
groupHeight = 0 ;
while ( previousRow && $previousRow . find ( '.js-indentation' ) . length ) {
$previousRow = $ ( previousRow ) . prev ( 'tr:first-of-type' ) ;
previousRow = $previousRow . get ( 0 ) ;
groupHeight += $previousRow . is ( ':hidden' ) ? 0 : previousRow . offsetHeight ;
}
if ( previousRow ) {
self . rowObject . swap ( 'before' , previousRow ) ;
// No need to check for indentation, 0 is the only valid one.
window . scrollBy ( 0 , - groupHeight ) ;
}
}
else if ( self . table . tBodies [ 0 ] . rows [ 0 ] !== previousRow || $previousRow . is ( '.draggable' ) ) {
// Swap with the previous row (unless previous row is the first
// one and undraggable).
self . rowObject . swap ( 'before' , previousRow ) ;
self . rowObject . interval = null ;
self . rowObject . indent ( 0 ) ;
window . scrollBy ( 0 , - parseInt ( item . offsetHeight , 10 ) ) ;
}
// Regain focus after the DOM manipulation.
handle . trigger ( 'focus' ) ;
}
break ;
// Right arrow.
case 39 :
// Safari right arrow.
case 63235 :
keyChange = true ;
self . rowObject . indent ( self . rtl ) ;
break ;
// Down arrow.
case 40 :
// Safari down arrow.
case 63233 :
var $nextRow = $ ( self . rowObject . group ) . eq ( - 1 ) . next ( 'tr:first-of-type' ) ;
var nextRow = $nextRow . get ( 0 ) ;
while ( nextRow && $nextRow . is ( ':hidden' ) ) {
$nextRow = $ ( nextRow ) . next ( 'tr:first-of-type' ) ;
nextRow = $nextRow . get ( 0 ) ;
}
if ( nextRow ) {
// Do not allow the onBlur cleanup.
self . safeBlur = false ;
self . rowObject . direction = 'down' ;
keyChange = true ;
if ( $ ( item ) . is ( '.tabledrag-root' ) ) {
// Swap with the next group (necessarily a top-level one).
groupHeight = 0 ;
var nextGroup = new self . row ( nextRow , 'keyboard' , self . indentEnabled , self . maxDepth , false ) ;
if ( nextGroup ) {
$ ( nextGroup . group ) . each ( function ( ) {
groupHeight += $ ( this ) . is ( ':hidden' ) ? 0 : this . offsetHeight ;
} ) ;
var nextGroupRow = $ ( nextGroup . group ) . eq ( - 1 ) . get ( 0 ) ;
self . rowObject . swap ( 'after' , nextGroupRow ) ;
// No need to check for indentation, 0 is the only valid one.
window . scrollBy ( 0 , parseInt ( groupHeight , 10 ) ) ;
}
}
else {
// Swap with the next row.
self . rowObject . swap ( 'after' , nextRow ) ;
self . rowObject . interval = null ;
self . rowObject . indent ( 0 ) ;
window . scrollBy ( 0 , parseInt ( item . offsetHeight , 10 ) ) ;
}
// Regain focus after the DOM manipulation.
handle . trigger ( 'focus' ) ;
}
break ;
}
2016-10-06 15:16:20 -07:00
/* eslint-enable no-fallthrough */
2015-08-17 17:00:26 -07:00
if ( self . rowObject && self . rowObject . changed === true ) {
$ ( item ) . addClass ( 'drag' ) ;
if ( self . oldRowElement ) {
$ ( self . oldRowElement ) . removeClass ( 'drag-previous' ) ;
}
self . oldRowElement = item ;
if ( self . striping === true ) {
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 suppress scrolling on keydown, but all
// other browsers need to return false on keypress.
// http://www.quirksmode.org/js/keys.html
handle . on ( 'keypress' , function ( event ) {
2016-10-06 15:16:20 -07:00
/* eslint-disable no-fallthrough */
2015-08-17 17:00:26 -07:00
switch ( event . keyCode ) {
// Left arrow.
case 37 :
// Up arrow.
case 38 :
// Right arrow.
case 39 :
// Down arrow.
case 40 :
return false ;
}
2016-10-06 15:16:20 -07:00
/* eslint-enable no-fallthrough */
2015-08-17 17:00:26 -07:00
} ) ;
} ;
/ * *
* Pointer event initiator , creates drag object and information .
*
* @ param { jQuery . Event } event
* The event object that trigger the drag .
* @ param { Drupal . tableDrag } self
* The drag handle .
* @ param { HTMLElement } item
* The item that that is being dragged .
* /
Drupal . tableDrag . prototype . dragStart = function ( event , self , item ) {
// Create a new dragObject recording the pointer information.
self . dragObject = { } ;
self . dragObject . initOffset = self . getPointerOffset ( item , event ) ;
self . dragObject . initPointerCoords = self . pointerCoords ( event ) ;
if ( self . indentEnabled ) {
self . dragObject . indentPointerPos = self . dragObject . initPointerCoords ;
}
// If there's a lingering row object from the keyboard, remove its focus.
if ( self . rowObject ) {
$ ( self . rowObject . element ) . find ( 'a.tabledrag-handle' ) . trigger ( 'blur' ) ;
}
// Create a new rowObject for manipulation of this row.
self . rowObject = new self . row ( item , 'pointer' , self . indentEnabled , self . maxDepth , true ) ;
// Save the position of the table.
self . table . topY = $ ( self . table ) . offset ( ) . top ;
self . table . bottomY = self . table . topY + self . table . offsetHeight ;
// Add classes to the handle and row.
$ ( item ) . addClass ( 'drag' ) ;
// Set the document to use the move cursor during drag.
$ ( 'body' ) . addClass ( 'drag' ) ;
if ( self . oldRowElement ) {
$ ( self . oldRowElement ) . removeClass ( 'drag-previous' ) ;
}
} ;
/ * *
* Pointer movement handler , bound to document .
*
* @ param { jQuery . Event } event
2016-03-02 12:40:24 -08:00
* The pointer event .
2015-08-17 17:00:26 -07:00
* @ param { Drupal . tableDrag } self
2016-03-02 12:40:24 -08:00
* The tableDrag instance .
2015-08-17 17:00:26 -07:00
*
* @ return { bool | undefined }
2016-03-02 12:40:24 -08:00
* Undefined if no dragObject is defined , false otherwise .
2015-08-17 17:00:26 -07:00
* /
Drupal . tableDrag . prototype . dragRow = function ( event , self ) {
if ( self . dragObject ) {
self . currentPointerCoords = self . pointerCoords ( event ) ;
var y = self . currentPointerCoords . y - self . dragObject . initOffset . y ;
var x = self . currentPointerCoords . x - self . dragObject . initOffset . x ;
// Check for row swapping and vertical scrolling.
if ( y !== self . oldY ) {
self . rowObject . direction = y > self . oldY ? 'down' : 'up' ;
// Update the old value.
self . oldY = y ;
// Check if the window should be scrolled (and how fast).
var scrollAmount = self . checkScroll ( self . currentPointerCoords . 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 ) ;
}
if ( self . striping === true ) {
self . restripeTable ( ) ;
}
}
}
// Similar to row swapping, handle indentations.
if ( self . indentEnabled ) {
var xDiff = self . currentPointerCoords . x - self . dragObject . indentPointerPos . x ;
// Set the number of indentations the pointer has been moved left or
// right.
var indentDiff = Math . round ( xDiff / self . indentAmount ) ;
// 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 pointer indentations.
self . dragObject . indentPointerPos . x += self . indentAmount * indentChange * self . rtl ;
self . indentCount = Math . max ( self . indentCount , self . rowObject . indents ) ;
}
return false ;
}
} ;
/ * *
* Pointerup behavior .
*
* @ param { jQuery . Event } event
2016-03-02 12:40:24 -08:00
* The pointer event .
2015-08-17 17:00:26 -07:00
* @ param { Drupal . tableDrag } self
2016-03-02 12:40:24 -08:00
* The tableDrag instance .
2015-08-17 17:00:26 -07:00
* /
Drupal . tableDrag . prototype . dropRow = function ( event , self ) {
var droppedRow ;
var $droppedRow ;
// Drop row functionality.
if ( self . rowObject !== null ) {
droppedRow = self . rowObject . element ;
$droppedRow = $ ( droppedRow ) ;
// The row is already in the right place so we just release it.
if ( self . rowObject . changed === true ) {
// Update the fields in the dropped row.
self . updateFields ( droppedRow ) ;
// If a setting exists for affecting the entire group, update all the
// fields in the entire dragged group.
for ( var group in self . tableSettings ) {
if ( self . tableSettings . hasOwnProperty ( group ) ) {
var rowSettings = self . rowSettings ( group , droppedRow ) ;
if ( rowSettings . relationship === 'group' ) {
for ( var n in self . rowObject . children ) {
if ( self . rowObject . children . hasOwnProperty ( n ) ) {
self . updateField ( self . rowObject . children [ n ] , group ) ;
}
}
}
}
}
self . rowObject . markChanged ( ) ;
if ( self . changed === false ) {
$ ( Drupal . theme ( 'tableDragChangedWarning' ) ) . insertBefore ( 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 pointerup events.
if ( self . dragObject !== null ) {
self . dragObject = null ;
$ ( 'body' ) . removeClass ( 'drag' ) ;
clearInterval ( self . scrollInterval ) ;
}
} ;
/ * *
* Get the coordinates from the event ( allowing for browser differences ) .
*
* @ param { jQuery . Event } event
2016-03-02 12:40:24 -08:00
* The pointer event .
2015-08-17 17:00:26 -07:00
*
2016-03-02 12:40:24 -08:00
* @ return { object }
* An object with ` x ` and ` y ` keys indicating the position .
2015-08-17 17:00:26 -07:00
* /
Drupal . tableDrag . prototype . pointerCoords = 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
} ;
} ;
/ * *
* Get the event offset from the target element .
*
* Given a target element and a pointer event , get the event offset from that
* element . To do this we need the element ' s position and the target position .
*
* @ param { HTMLElement } target
2016-03-02 12:40:24 -08:00
* The target HTML element .
2015-08-17 17:00:26 -07:00
* @ param { jQuery . Event } event
2016-03-02 12:40:24 -08:00
* The pointer event .
2015-08-17 17:00:26 -07:00
*
2016-03-02 12:40:24 -08:00
* @ return { object }
* An object with ` x ` and ` y ` keys indicating the position .
2015-08-17 17:00:26 -07:00
* /
Drupal . tableDrag . prototype . getPointerOffset = function ( target , event ) {
var docPos = $ ( target ) . offset ( ) ;
var pointerPos = this . pointerCoords ( event ) ;
return { x : pointerPos . x - docPos . left , y : pointerPos . y - docPos . top } ;
} ;
/ * *
* Find the row the mouse is currently over .
*
* This row is then taken and swapped with the one being dragged .
*
* @ param { number } x
* The x coordinate of the mouse on the page ( not the screen ) .
* @ param { number } y
* The y coordinate of the mouse on the page ( not the screen ) .
*
* @ return { * }
2016-03-02 12:40:24 -08:00
* The drop target row , if found .
2015-08-17 17:00:26 -07:00
* /
Drupal . tableDrag . prototype . findDropTargetRow = function ( x , y ) {
var rows = $ ( this . table . tBodies [ 0 ] . rows ) . not ( ':hidden' ) ;
for ( var n = 0 ; n < rows . length ; n ++ ) {
var row = rows [ n ] ;
var $row = $ ( row ) ;
var rowY = $row . offset ( ) . top ;
var rowHeight ;
// Because Safari does not 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 ( row . offsetHeight === 0 ) {
rowHeight = parseInt ( row . firstChild . offsetHeight , 10 ) / 2 ;
}
// Other browsers.
else {
rowHeight = parseInt ( row . offsetHeight , 10 ) / 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 ;
}
}
}
else {
// Do not allow a row to be swapped with itself.
if ( row === this . rowObject . element ) {
return null ;
}
}
// Check that swapping with this row is allowed.
if ( ! this . rowObject . isValidSwap ( row ) ) {
return null ;
}
// We 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:first-of-type' ) ;
row = $row . get ( 0 ) ;
}
return row ;
}
}
return null ;
} ;
/ * *
* After the row is dropped , update the table fields .
*
* @ param { HTMLElement } changedRow
* DOM object for the row that was just dropped .
* /
Drupal . tableDrag . prototype . updateFields = function ( changedRow ) {
for ( var group in this . tableSettings ) {
if ( this . tableSettings . hasOwnProperty ( group ) ) {
// Each group may have a different setting for relationship, so we find
// the source rows for each separately.
this . updateField ( changedRow , group ) ;
}
}
} ;
/ * *
* After the row is dropped , update a single table field .
*
* @ param { HTMLElement } changedRow
* DOM object for the row that was just dropped .
* @ param { string } group
* The settings group on which field updates will occur .
* /
Drupal . tableDrag . prototype . updateField = function ( changedRow , group ) {
var rowSettings = this . rowSettings ( group , changedRow ) ;
var $changedRow = $ ( changedRow ) ;
var sourceRow ;
var $previousRow ;
var previousRow ;
var useSibling ;
// Set the row as its own target.
if ( rowSettings . relationship === 'self' || rowSettings . relationship === 'group' ) {
sourceRow = changedRow ;
}
// Siblings are easy, check previous and next rows.
else if ( rowSettings . relationship === 'sibling' ) {
$previousRow = $changedRow . prev ( 'tr:first-of-type' ) ;
previousRow = $previousRow . get ( 0 ) ;
var $nextRow = $changedRow . next ( 'tr:first-of-type' ) ;
var nextRow = $nextRow . get ( 0 ) ;
sourceRow = changedRow ;
if ( $previousRow . is ( '.draggable' ) && $previousRow . find ( '.' + group ) . length ) {
if ( this . indentEnabled ) {
if ( $previousRow . find ( '.js-indentations' ) . length === $changedRow . find ( '.js-indentations' ) . length ) {
sourceRow = previousRow ;
}
}
else {
sourceRow = previousRow ;
}
}
else if ( $nextRow . is ( '.draggable' ) && $nextRow . find ( '.' + group ) . length ) {
if ( this . indentEnabled ) {
if ( $nextRow . find ( '.js-indentations' ) . length === $changedRow . find ( '.js-indentations' ) . length ) {
sourceRow = nextRow ;
}
}
else {
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' ) {
$previousRow = $changedRow . prev ( 'tr' ) ;
previousRow = $previousRow ;
while ( $previousRow . length && $previousRow . find ( '.js-indentation' ) . length >= this . rowObject . indents ) {
$previousRow = $previousRow . prev ( 'tr' ) ;
previousRow = $previousRow ;
}
// If we found a row.
if ( $previousRow . length ) {
sourceRow = $previousRow . get ( 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 guaranteed to
// be at the root level. Find the first item, then compare this row
// against it as a sibling.
sourceRow = $ ( this . table ) . find ( 'tr.draggable:first-of-type' ) . get ( 0 ) ;
if ( sourceRow === this . rowObject . element ) {
sourceRow = $ ( this . rowObject . group [ this . rowObject . group . length - 1 ] ) . next ( 'tr.draggable' ) . get ( 0 ) ;
}
useSibling = true ;
}
}
// 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 ) ;
// 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 ;
}
var targetClass = '.' + rowSettings . target ;
var targetElement = $changedRow . find ( targetClass ) . 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 = $ ( sourceElement ) . closest ( 'tr' ) . find ( '.js-indentation' ) . length ;
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 = [ ] ;
$ ( targetElement ) . find ( 'option' ) . each ( function ( ) {
values . push ( this . value ) ;
} ) ;
var maxVal = values [ values . length - 1 ] ;
// Populate the values in the siblings.
$ ( siblings ) . find ( targetClass ) . each ( function ( ) {
// If there are more items than possible values, assign the
// maximum value to the row.
if ( values . length > 0 ) {
this . value = values . shift ( ) ;
}
else {
this . value = maxVal ;
}
} ) ;
}
else {
// Assume a numeric input field.
var weight = parseInt ( $ ( siblings [ 0 ] ) . find ( targetClass ) . val ( ) , 10 ) || 0 ;
$ ( siblings ) . find ( targetClass ) . each ( function ( ) {
this . value = weight ;
weight ++ ;
} ) ;
}
break ;
}
}
} ;
/ * *
* Copy all tableDrag related classes from one row to another .
*
* 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 .
*
* @ param { HTMLElement } sourceRow
2016-03-02 12:40:24 -08:00
* The element for the source row .
2015-08-17 17:00:26 -07:00
* @ param { HTMLElement } targetRow
2016-03-02 12:40:24 -08:00
* The element for the target row .
2015-08-17 17:00:26 -07:00
* @ param { string } group
2016-03-02 12:40:24 -08:00
* The group selector .
2015-08-17 17:00:26 -07:00
* /
Drupal . tableDrag . prototype . copyDragClasses = function ( sourceRow , targetRow , group ) {
var sourceElement = $ ( sourceRow ) . find ( '.' + group ) ;
var targetElement = $ ( targetRow ) . find ( '.' + group ) ;
if ( sourceElement . length && targetElement . length ) {
targetElement [ 0 ] . className = sourceElement [ 0 ] . className ;
}
} ;
/ * *
2016-03-02 12:40:24 -08:00
* Check the suggested scroll of the table .
*
2015-08-17 17:00:26 -07:00
* @ param { number } cursorY
2016-03-02 12:40:24 -08:00
* The Y position of the cursor .
*
2015-08-17 17:00:26 -07:00
* @ return { number }
2016-03-02 12:40:24 -08:00
* The suggested scroll .
2015-08-17 17:00:26 -07:00
* /
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 ;
if ( document . all ) {
scrollY = this . scrollY = ! de . scrollTop ? b . scrollTop : de . scrollTop ;
}
else {
scrollY = this . scrollY = 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 ;
}
} ;
/ * *
2016-03-02 12:40:24 -08:00
* Set the scroll for the table .
*
2015-08-17 17:00:26 -07:00
* @ param { number } scrollAmount
2016-03-02 12:40:24 -08:00
* The amount of scroll to apply to the window .
2015-08-17 17:00:26 -07:00
* /
Drupal . tableDrag . prototype . setScroll = function ( scrollAmount ) {
var self = this ;
this . scrollInterval = setInterval ( function ( ) {
// Update the scroll values stored in the object.
self . checkScroll ( self . currentPointerCoords . 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 ) ;
} ;
/ * *
* Command to restripe table properly .
* /
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.
// Match immediate children of the parent element to allow nesting.
$ ( this . table ) . find ( '> tbody > tr.draggable:visible, > tr.draggable:visible' )
. removeClass ( 'odd even' )
. filter ( ':odd' ) . addClass ( 'even' ) . end ( )
. filter ( ':even' ) . addClass ( 'odd' ) ;
} ;
/ * *
* Stub function . Allows a custom handler when a row begins dragging .
*
2016-03-02 12:40:24 -08:00
* @ return { null }
* Returns null when the stub function is used .
2015-08-17 17:00:26 -07:00
* /
Drupal . tableDrag . prototype . onDrag = function ( ) {
return null ;
} ;
/ * *
* Stub function . Allows a custom handler when a row is dropped .
*
2016-03-02 12:40:24 -08:00
* @ return { null }
* Returns null when the stub function is used .
2015-08-17 17:00:26 -07:00
* /
Drupal . tableDrag . prototype . onDrop = function ( ) {
return null ;
} ;
/ * *
* Constructor to make a new object to manipulate a table row .
*
* @ param { HTMLElement } tableRow
* The DOM element for the table row we will be manipulating .
* @ param { string } method
* The method in which this row is being moved . Either 'keyboard' or
* 'mouse' .
* @ param { bool } indentEnabled
* Whether the containing table uses indentations . Used for optimizations .
* @ param { number } maxDepth
* The maximum amount of indentations this row may contain .
* @ param { bool } addClasses
* Whether we want to add classes to this row to indicate child
* relationships .
* /
Drupal . tableDrag . prototype . row = function ( tableRow , method , indentEnabled , maxDepth , addClasses ) {
var $tableRow = $ ( tableRow ) ;
this . element = tableRow ;
this . method = method ;
this . group = [ tableRow ] ;
this . groupDepth = $tableRow . find ( '.js-indentation' ) . length ;
this . changed = false ;
this . table = $tableRow . closest ( 'table' ) [ 0 ] ;
this . indentEnabled = indentEnabled ;
this . maxDepth = maxDepth ;
// Direction the row is being moved.
this . direction = '' ;
if ( this . indentEnabled ) {
this . indents = $tableRow . find ( '.js-indentation' ) . length ;
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 ( $ ( this . group [ n ] ) . find ( '.js-indentation' ) . length , this . groupDepth ) ;
}
}
} ;
/ * *
* Find all children of rowObject by indentation .
*
* @ param { bool } addClasses
* Whether we want to add classes to this row to indicate child
* relationships .
*
* @ return { Array }
2016-03-02 12:40:24 -08:00
* An array of children of the row .
2015-08-17 17:00:26 -07:00
* /
Drupal . tableDrag . prototype . row . prototype . findChildren = function ( addClasses ) {
var parentIndentation = this . indents ;
var currentRow = $ ( this . element , this . table ) . next ( 'tr.draggable' ) ;
var rows = [ ] ;
var child = 0 ;
function rowIndentation ( indentNum , el ) {
var self = $ ( el ) ;
if ( child === 1 && ( indentNum === parentIndentation ) ) {
self . addClass ( 'tree-child-first' ) ;
}
if ( indentNum === parentIndentation ) {
self . addClass ( 'tree-child' ) ;
}
else if ( indentNum > parentIndentation ) {
self . addClass ( 'tree-child-horizontal' ) ;
}
}
while ( currentRow . length ) {
// A greater indentation indicates this is a child.
if ( currentRow . find ( '.js-indentation' ) . length > parentIndentation ) {
child ++ ;
rows . push ( currentRow [ 0 ] ) ;
if ( addClasses ) {
currentRow . find ( '.js-indentation' ) . each ( rowIndentation ) ;
}
}
else {
break ;
}
currentRow = currentRow . next ( 'tr.draggable' ) ;
}
if ( addClasses && rows . length ) {
$ ( rows [ rows . length - 1 ] ) . find ( '.js-indentation:nth-child(' + ( parentIndentation + 1 ) + ')' ) . addClass ( 'tree-child-last' ) ;
}
return rows ;
} ;
/ * *
* Ensure that two rows are allowed to be swapped .
*
* @ param { HTMLElement } row
* DOM object for the row being considered for swapping .
*
* @ return { bool }
2016-03-02 12:40:24 -08:00
* Whether the swap is a valid swap or not .
2015-08-17 17:00:26 -07:00
* /
Drupal . tableDrag . prototype . row . prototype . isValidSwap = function ( row ) {
var $row = $ ( row ) ;
if ( this . indentEnabled ) {
var prevRow ;
var nextRow ;
if ( this . direction === 'down' ) {
prevRow = row ;
nextRow = $row . next ( 'tr' ) . get ( 0 ) ;
}
else {
prevRow = $row . prev ( 'tr' ) . get ( 0 ) ;
nextRow = row ;
}
this . interval = this . validIndentInterval ( prevRow , nextRow ) ;
// We have an invalid swap if the valid indentations interval is empty.
if ( this . interval . min > this . interval . max ) {
return false ;
}
}
// Do not let an un-draggable first row have anything put before it.
if ( this . table . tBodies [ 0 ] . rows [ 0 ] === row && $row . is ( ':not(.draggable)' ) ) {
return false ;
}
return true ;
} ;
/ * *
* Perform the swap between two rows .
*
* @ param { string } position
* Whether the swap will occur 'before' or 'after' the given row .
* @ param { HTMLElement } row
* DOM element what will be swapped with the row group .
* /
Drupal . tableDrag . prototype . row . prototype . swap = function ( position , row ) {
// Makes sure only DOM object are passed to Drupal.detachBehaviors().
this . group . forEach ( function ( row ) {
Drupal . detachBehaviors ( row , drupalSettings , 'move' ) ;
} ) ;
$ ( row ) [ position ] ( this . group ) ;
// Makes sure only DOM object are passed to Drupal.attachBehaviors()s.
this . group . forEach ( function ( row ) {
Drupal . attachBehaviors ( row , drupalSettings ) ;
} ) ;
this . changed = true ;
this . onSwap ( row ) ;
} ;
/ * *
* Determine the valid indentations interval for the row at a given position .
*
* @ param { ? HTMLElement } prevRow
* DOM object for the row before the tested position
* ( or null for first position in the table ) .
* @ param { ? HTMLElement } nextRow
* DOM object for the row after the tested position
* ( or null for last position in the table ) .
*
2016-03-02 12:40:24 -08:00
* @ return { object }
* An object with the keys ` min ` and ` max ` to indicate the valid indent
* interval .
2015-08-17 17:00:26 -07:00
* /
Drupal . tableDrag . prototype . row . prototype . validIndentInterval = function ( prevRow , nextRow ) {
var $prevRow = $ ( prevRow ) ;
var minIndent ;
var maxIndent ;
// Minimum indentation:
// Do not orphan the next row.
minIndent = nextRow ? $ ( nextRow ) . find ( '.js-indentation' ) . length : 0 ;
// Maximum indentation:
if ( ! prevRow || $prevRow . is ( ':not(.draggable)' ) || $ ( this . element ) . is ( '.tabledrag-root' ) ) {
// Do not indent:
// - the first row in the table,
// - rows dragged below a non-draggable row,
// - 'root' rows.
maxIndent = 0 ;
}
else {
// Do not go deeper than as a child of the previous row.
maxIndent = $prevRow . find ( '.js-indentation' ) . length + ( $prevRow . is ( '.tabledrag-leaf' ) ? 0 : 1 ) ;
// Limit by the maximum allowed depth for the table.
if ( this . maxDepth ) {
maxIndent = Math . min ( maxIndent , this . maxDepth - ( this . groupDepth - this . indents ) ) ;
}
}
2015-09-04 13:20:09 -07:00
return { min : minIndent , max : maxIndent } ;
2015-08-17 17:00:26 -07:00
} ;
/ * *
* Indent a row within the legal bounds of the table .
*
* @ param { number } indentDiff
* The number of additional indentations proposed for the row ( can be
* positive or negative ) . This number will be adjusted to nearest valid
* indentation level for the row .
*
* @ return { number }
2016-03-02 12:40:24 -08:00
* The number of indentations applied .
2015-08-17 17:00:26 -07:00
* /
Drupal . tableDrag . prototype . row . prototype . indent = function ( indentDiff ) {
var $group = $ ( this . group ) ;
// Determine the valid indentations interval if not available yet.
if ( ! this . interval ) {
var prevRow = $ ( this . element ) . prev ( 'tr' ) . get ( 0 ) ;
var nextRow = $group . eq ( - 1 ) . next ( 'tr' ) . get ( 0 ) ;
this . interval = this . validIndentInterval ( prevRow , nextRow ) ;
}
// Adjust to the nearest valid indentation.
var indent = this . indents + indentDiff ;
indent = Math . max ( indent , this . interval . min ) ;
indent = Math . min ( indent , this . interval . max ) ;
indentDiff = indent - this . indents ;
for ( var n = 1 ; n <= Math . abs ( indentDiff ) ; n ++ ) {
// Add or remove indentations.
if ( indentDiff < 0 ) {
$group . find ( '.js-indentation:first-of-type' ) . remove ( ) ;
this . indents -- ;
}
else {
$group . find ( 'td:first-of-type' ) . prepend ( Drupal . theme ( 'tableDragIndentation' ) ) ;
this . indents ++ ;
}
}
if ( indentDiff ) {
// Update indentation for this row.
this . changed = true ;
this . groupDepth += indentDiff ;
this . onIndent ( ) ;
}
return indentDiff ;
} ;
/ * *
* Find all siblings for a row .
*
* According to its subgroup or indentation . Note that the passed - in row is
* included in the list of siblings .
*
* @ param { object } rowSettings
* The field settings we ' re using to identify what constitutes a sibling .
*
* @ return { Array }
2016-03-02 12:40:24 -08:00
* An array of siblings .
2015-08-17 17:00:26 -07:00
* /
Drupal . tableDrag . prototype . row . prototype . findSiblings = function ( rowSettings ) {
var siblings = [ ] ;
var directions = [ 'prev' , 'next' ] ;
var rowIndentation = this . indents ;
var checkRowIndentation ;
for ( var d = 0 ; d < directions . length ; d ++ ) {
var checkRow = $ ( this . element ) [ directions [ d ] ] ( ) ;
while ( checkRow . length ) {
// Check that the sibling contains a similar target field.
if ( checkRow . find ( '.' + rowSettings . target ) ) {
// Either add immediately if this is a flat table, or check to ensure
// that this row has the same level of indentation.
if ( this . indentEnabled ) {
checkRowIndentation = checkRow . find ( '.js-indentation' ) . 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 ( var n in this . children ) {
if ( this . children . hasOwnProperty ( n ) ) {
$ ( this . children [ n ] ) . find ( '.js-indentation' )
. 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 = $ ( this . element ) . find ( 'td:first-of-type' ) ;
if ( cell . find ( 'abbr.tabledrag-changed' ) . length === 0 ) {
cell . append ( marker ) ;
}
} ;
/ * *
* Stub function . Allows a custom handler when a row is indented .
*
2016-03-02 12:40:24 -08:00
* @ return { null }
* Returns null when the stub function is used .
2015-08-17 17:00:26 -07:00
* /
Drupal . tableDrag . prototype . row . prototype . onIndent = function ( ) {
return null ;
} ;
/ * *
* Stub function . Allows a custom handler when a row is swapped .
*
* @ param { HTMLElement } swappedRow
2016-03-02 12:40:24 -08:00
* The element for the swapped row .
2015-08-17 17:00:26 -07:00
*
2016-03-02 12:40:24 -08:00
* @ return { null }
* Returns null when the stub function is used .
2015-08-17 17:00:26 -07:00
* /
Drupal . tableDrag . prototype . row . prototype . onSwap = function ( swappedRow ) {
return null ;
} ;
$ . extend ( Drupal . theme , /** @lends Drupal.theme */ {
/ * *
* @ return { string }
2016-03-02 12:40:24 -08:00
* Markup for the marker .
2015-08-17 17:00:26 -07:00
* /
tableDragChangedMarker : function ( ) {
return '<abbr class="warning tabledrag-changed" title="' + Drupal . t ( 'Changed' ) + '">*</abbr>' ;
} ,
/ * *
* @ return { string }
2016-03-02 12:40:24 -08:00
* Markup for the indentation .
2015-08-17 17:00:26 -07:00
* /
tableDragIndentation : function ( ) {
return '<div class="js-indentation indentation"> </div>' ;
} ,
/ * *
* @ return { string }
2016-03-02 12:40:24 -08:00
* Markup for the warning .
2015-08-17 17:00:26 -07:00
* /
tableDragChangedWarning : function ( ) {
return '<div class="tabledrag-changed-warning messages messages--warning" role="alert">' + Drupal . theme ( 'tableDragChangedMarker' ) + ' ' + Drupal . t ( 'You have unsaved changes.' ) + '</div>' ;
}
} ) ;
} ) ( jQuery , Drupal , drupalSettings ) ;