Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
project
drupal
Commits
6d4862c2
Commit
6d4862c2
authored
Nov 20, 2007
by
Gábor Hojtsy
Browse files
#181126
by quicksketch et al: drag and drop support for menus
parent
72a59b0b
Changes
5
Hide whitespace changes
Inline
Side-by-side
includes/common.inc
View file @
6d4862c2
...
...
@@ -1954,19 +1954,19 @@ function drupal_get_js($scope = 'header', $javascript = NULL) {
* In a situation where a single weight column is being sorted in the table, the
* classes could be added like this (in the theme function):
* @code
* $form['my_elements'][$delta]['weight']['attributes']['class'] = "my-elements-weight";
* $form['my_elements'][$delta]['weight']['
#
attributes']['class'] = "my-elements-weight";
* @endcode
*
* Calling drupal_add_tabledrag() would then be written as such:
* @code
* drupal_add_tabledrag('my-module-table', '
s
or
t
', 'sibling', 'my-elements-weight');
* drupal_add_tabledrag('my-module-table', 'or
der
', 'sibling', 'my-elements-weight');
* @endcode
*
* In a more complex case where there are several groups in one column (such as
* the block regions on the admin/build/block page), a separate subgroup class
* must also be added to differentiate the groups.
* @code
* $form['my_elements'][$region][$delta]['weight']['attributes']['class'] = "my-elements-weight my-elements-weight-". $region;
* $form['my_elements'][$region][$delta]['weight']['
#
attributes']['class'] = "my-elements-weight my-elements-weight-". $region;
* @endcode
*
* $group is still 'my-element-weight', and the additional $subgroup variable
...
...
@@ -1975,7 +1975,7 @@ function drupal_get_js($scope = 'header', $javascript = NULL) {
*
* @code
* foreach ($regions as $region) {
* drupal_add_tabledrag('my-module-table', '
s
or
t
', 'sibling', 'my-elements-weight', 'my-elements-weight-'. $region);
* drupal_add_tabledrag('my-module-table', 'or
der
', 'sibling', 'my-elements-weight', 'my-elements-weight-'. $region);
* }
* @endcode
*
...
...
@@ -1996,13 +1996,15 @@ function drupal_get_js($scope = 'header', $javascript = NULL) {
* String containing the target table's id attribute. If the table does not
* have an id, one will need to be set, such as <table id="my-module-table">.
* @param $action
* String describing the action to be done on the form item. Either 'match' or
* 'sort'. Match is typically used for parent relationships, sort is typically
* used to set weights on other form elements with the same group.
* String describing the action to be done on the form item. Either 'match'
* 'depth', or 'order'. Match is typically used for parent relationships.
* Order is typically used to set weights on other form elements with the same
* group. Depth updates the target element with the current indentation.
* @param $relationship
* String describing where the $action variable should be performed. Either
* 'parent' or 'sibling'. Parent will only look for fields up the tree.
* Sibling will look for fields in the same group in rows above and below it.
* 'parent', 'sibling', or 'self'. Parent will only look for fields up the
* tree. Sibling will look for fields in the same group in rows above and
* below it. Self affects the dragged row itself.
* @param $group
* A class name applied on all related form elements for this action.
* @param $subgroup
...
...
@@ -2929,6 +2931,9 @@ function drupal_common_themes() {
'progress_bar'
=>
array
(
'arguments'
=>
array
(
'percent'
=>
NULL
,
'message'
=>
NULL
),
),
'indentation'
=>
array
(
'arguments'
=>
array
(
'size'
=>
1
),
),
// from pager.inc
'pager'
=>
array
(
'arguments'
=>
array
(
'tags'
=>
array
(),
'limit'
=>
10
,
'element'
=>
0
,
'parameters'
=>
array
()),
...
...
includes/theme.inc
View file @
6d4862c2
...
...
@@ -1671,6 +1671,22 @@ function theme_progress_bar($percent, $message) {
return
$output
;
}
/**
* Create a standard indentation div. Used for drag and drop tables.
*
* @param $size
* Optional. The number of indentations to create.
* @return
* A string containing indentations.
*/
function
theme_indentation
(
$size
=
1
)
{
$output
=
''
;
for
(
$n
=
0
;
$n
<
$size
;
$n
++
)
{
$output
.
=
'<div class="indentation"> </div>'
;
}
return
$output
;
}
/**
* @} End of "defgroup themeable".
*/
...
...
misc/tabledrag.js
View file @
6d4862c2
...
...
@@ -53,15 +53,26 @@ Drupal.tableDrag = function(table, tableSettings) {
this
.
scrollY
=
0
;
this
.
windowHeight
=
0
;
// Check if this table contains indentations.
var
indents
=
$
(
'
div.indentation
'
,
table
);
this
.
indentEnabled
=
indents
.
size
()
>
0
?
true
:
false
;
// 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
;
}
}
}
if
(
this
.
indentEnabled
)
{
var
indentSize
=
indents
.
css
(
'
width
'
);
this
.
oldX
=
0
;
this
.
indentCount
=
1
;
// Total width of indents, set in makeDraggable.
this
.
indentIncrement
=
indentSize
.
replace
(
/
[
0-9
\.]
*/
,
''
);
this
.
indentAmount
=
parseInt
(
indentSize
);
// 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
();
}
// Make each applicable row draggable.
...
...
@@ -253,7 +264,7 @@ Drupal.tableDrag.prototype.makeDraggable = function(item) {
self
.
safeBlur
=
false
;
// Do not allow the onBlur cleanup.
self
.
rowObject
.
direction
=
'
up
'
;
keyChange
=
true
;
if
(
self
.
rowObject
.
isValidSwap
(
previousRow
,
'
up
'
,
0
))
{
if
(
self
.
rowObject
.
isValidSwap
(
previousRow
,
0
))
{
self
.
rowObject
.
swap
(
'
before
'
,
previousRow
);
window
.
scrollBy
(
0
,
-
parseInt
(
item
.
offsetHeight
));
}
...
...
@@ -280,7 +291,7 @@ Drupal.tableDrag.prototype.makeDraggable = function(item) {
self
.
safeBlur
=
false
;
// Do not allow the onBlur cleanup.
self
.
rowObject
.
direction
=
'
down
'
;
keyChange
=
true
;
if
(
self
.
rowObject
.
isValidSwap
(
nextRow
,
'
down
'
,
0
))
{
if
(
self
.
rowObject
.
isValidSwap
(
nextRow
,
0
))
{
self
.
rowObject
.
swap
(
'
after
'
,
nextRow
);
}
else
{
...
...
@@ -549,8 +560,12 @@ Drupal.tableDrag.prototype.updateFields = function(changedRow) {
// the source rows for each seperately.
var
rowSettings
=
this
.
rowSettings
(
group
,
changedRow
);
// Set the row as it's own target.
if
(
rowSettings
.
relationship
==
'
self
'
)
{
var
sourceRow
=
changedRow
;
}
// Siblings are easy, check previous and next rows.
if
(
rowSettings
.
relationship
==
'
sibling
'
)
{
else
if
(
rowSettings
.
relationship
==
'
sibling
'
)
{
var
previousRow
=
$
(
changedRow
).
prev
(
'
tr
'
).
get
(
0
);
var
nextRow
=
$
(
changedRow
).
next
(
'
tr
'
).
get
(
0
);
var
sourceRow
=
changedRow
;
...
...
@@ -586,11 +601,16 @@ Drupal.tableDrag.prototype.updateFields = function(changedRow) {
if
(
previousRow
.
length
)
{
sourceRow
=
previousRow
[
0
];
}
// Otherwise we went all the way to the
top
of the table
.
//
Assume that the first item has no indentions
.
// 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
{
// Basically copy first item's value.
sourceRow
=
$
(
'
tr.draggable:first
'
)[
0
];
// 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
);
}
var
useSibling
=
true
;
}
}
...
...
@@ -615,6 +635,10 @@ Drupal.tableDrag.prototype.updateFields = function(changedRow) {
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
;
...
...
@@ -634,7 +658,7 @@ Drupal.tableDrag.prototype.updateFields = function(changedRow) {
}
else
{
// Assume a numeric input field.
var
weight
=
0
;
var
weight
=
parseInt
(
$
(
targetClass
,
siblings
[
0
]).
val
())
||
0
;
$
(
targetClass
,
siblings
).
each
(
function
()
{
this
.
value
=
weight
;
weight
++
;
...
...
@@ -832,7 +856,7 @@ Drupal.tableDrag.prototype.row.prototype.isValidSwap = function(row, indentDiff)
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.
if
(
this
.
indents
>
0
||
$
(
row
).
is
(
'
:not(.draggable)
'
))
{
if
(
(
this
.
indents
+
indentDiff
)
>
0
||
$
(
row
).
is
(
'
:not(.draggable)
'
))
{
return
false
;
}
}
...
...
modules/menu/menu.admin.inc
View file @
6d4862c2
...
...
@@ -28,7 +28,7 @@ function menu_overview_page() {
*/
function
menu_overview_form
(
&
$form_state
,
$menu
)
{
global
$menu_admin
;
$sql
=
"
$sql
=
"
SELECT m.load_functions, m.to_arg_functions, m.access_callback, m.access_arguments, m.page_callback, m.page_arguments, m.title, m.title_callback, m.title_arguments, m.type, ml.*
FROM
{
menu_links
}
ml LEFT JOIN
{
menu_router
}
m ON m.path = ml.router_path
WHERE ml.menu_name = '%s'
...
...
@@ -66,7 +66,7 @@ function _menu_overview_tree_form($tree) {
$item
=
$data
[
'link'
];
// Don't show callbacks; these have $item['hidden'] < 0.
if
(
$item
&&
$item
[
'hidden'
]
>=
0
)
{
$mlid
=
$item
[
'mlid'
];
$mlid
=
'mlid:'
.
$item
[
'mlid'
];
$form
[
$mlid
][
'#item'
]
=
$item
;
$form
[
$mlid
][
'#attributes'
]
=
$item
[
'hidden'
]
?
array
(
'class'
=>
'menu-disabled'
)
:
array
(
'class'
=>
'menu-enabled'
);
$form
[
$mlid
][
'title'
][
'#value'
]
=
l
(
$item
[
'title'
],
$item
[
'href'
],
$item
[
'options'
])
.
(
$item
[
'hidden'
]
?
' ('
.
t
(
'disabled'
)
.
')'
:
''
);
...
...
@@ -78,6 +78,19 @@ function _menu_overview_tree_form($tree) {
'#type'
=>
'checkbox'
,
'#default_value'
=>
$item
[
'has_children'
]
&&
$item
[
'expanded'
],
);
$form
[
$mlid
][
'weight'
]
=
array
(
'#type'
=>
'weight'
,
'#default_value'
=>
isset
(
$form_state
[
$mlid
][
'weight'
])
?
$form_state
[
$mlid
][
'weight'
]
:
$item
[
'weight'
],
);
$form
[
$mlid
][
'mlid'
]
=
array
(
'#type'
=>
'hidden'
,
'#value'
=>
$item
[
'mlid'
],
);
$form
[
$mlid
][
'plid'
]
=
array
(
'#type'
=>
'textfield'
,
'#default_value'
=>
isset
(
$form_state
[
$mlid
][
'plid'
])
?
$form_state
[
$mlid
][
'plid'
]
:
$item
[
'plid'
],
'#size'
=>
6
,
);
// Build a list of operations.
$operations
=
array
();
$operations
[
'edit'
]
=
l
(
t
(
'edit'
),
'admin/build/menu/item/'
.
$item
[
'mlid'
]
.
'/edit'
);
...
...
@@ -103,27 +116,65 @@ function _menu_overview_tree_form($tree) {
return
$form
;
}
function
menu_overview_form_submit
(
$form
)
{
/**
* Submit handler for the menu overview form.
*
* This function takes great care in saving parent items first, then items
* underneath them. Saving items in the incorrect order can break the menu tree.
*
* @see menu_overview_form()
*/
function
menu_overview_form_submit
(
$form
,
&
$form_state
)
{
// When dealing with saving menu items, the order in which these items are
// saved is critical. If a changed child item is saved before its parent,
// the child item could be saved with an invalid path past its immediate
// parent. To prevent this, save items in the form in the same order they
// are sent by $_POST, ensuring parents are saved first, then their children.
// See http://drupal.org/node/181126#comment-632270
$order
=
array_flip
(
array_keys
(
$form
[
'#post'
]));
// Get the $_POST order.
$form
=
array_merge
(
$order
,
$form
);
// Update our original form with the new order.
$updated_items
=
array
();
$fields
=
array
(
'expanded'
,
'weight'
,
'plid'
);
foreach
(
element_children
(
$form
)
as
$mlid
)
{
if
(
isset
(
$form
[
$mlid
][
'
hidden
'
]))
{
if
(
isset
(
$form
[
$mlid
][
'
#item
'
]))
{
$element
=
$form
[
$mlid
];
// Update any fields that have changed in this menu item.
foreach
(
$fields
as
$field
)
{
if
(
$element
[
$field
][
'#value'
]
!=
$element
[
$field
][
'#default_value'
])
{
$element
[
'#item'
][
$field
]
=
$element
[
$field
][
'#value'
];
$updated_items
[
$mlid
]
=
$element
[
'#item'
];
}
}
// Hidden is a special case, the value needs to be reversed.
if
(
$element
[
'hidden'
][
'#value'
]
!=
$element
[
'hidden'
][
'#default_value'
])
{
$element
[
'#item'
][
'hidden'
]
=
!
$element
[
'hidden'
][
'#value'
];
menu_link_save
(
$element
[
'#item'
]);
}
if
(
$element
[
'expanded'
][
'#value'
]
!=
$element
[
'expanded'
][
'#default_value'
])
{
$element
[
'#item'
][
'expanded'
]
=
$element
[
'expanded'
][
'#value'
];
menu_link_save
(
$element
[
'#item'
]);
$updated_items
[
$mlid
]
=
$element
[
'#item'
];
}
}
}
// Save all our changed items to the database.
foreach
(
$updated_items
as
$item
)
{
menu_link_save
(
$item
);
}
}
/**
* Theme the menu overview form into a table.
*/
function
theme_menu_overview_form
(
$form
)
{
$header
=
array
(
t
(
'Enabled'
),
t
(
'Expanded'
),
t
(
'Menu item'
),
array
(
'data'
=>
t
(
'Operations'
),
'colspan'
=>
'3'
));
drupal_add_tabledrag
(
'menu-overview'
,
'match'
,
'parent'
,
'menu-plid'
,
'menu-plid'
,
'menu-mlid'
);
drupal_add_tabledrag
(
'menu-overview'
,
'order'
,
'sibling'
,
'menu-weight'
);
$header
=
array
(
t
(
'Menu item'
),
array
(
'data'
=>
t
(
'Expanded'
),
'class'
=>
'checkbox'
),
array
(
'data'
=>
t
(
'Enabled'
),
'class'
=>
'checkbox'
),
t
(
'Weight'
),
array
(
'data'
=>
t
(
'Operations'
),
'colspan'
=>
'3'
),
);
$rows
=
array
();
foreach
(
element_children
(
$form
)
as
$mlid
)
{
if
(
isset
(
$form
[
$mlid
][
'hidden'
]))
{
...
...
@@ -137,15 +188,23 @@ function theme_menu_overview_form($form) {
$operations
[]
=
''
;
}
// Add special classes to be used for tabledrag.js.
$element
[
'plid'
][
'#attributes'
][
'class'
]
=
'menu-plid'
;
$element
[
'mlid'
][
'#attributes'
][
'class'
]
=
'menu-mlid'
;
$element
[
'weight'
][
'#attributes'
][
'class'
]
=
'menu-weight'
;
// Change the parent field to a hidden. This allows any value but hides the field.
$element
[
'plid'
][
'#type'
]
=
'hidden'
;
$row
=
array
();
$row
[]
=
array
(
'data'
=>
drupal_render
(
$element
[
'hidden'
]),
'align'
=>
'center'
);
$row
[]
=
array
(
'data'
=>
drupal_render
(
$element
[
'expanded'
]),
'align'
=>
'center'
);
$depth
=
$element
[
'#item'
][
'depth'
];
$indentation
=
str_repeat
(
' '
,
$depth
-
1
)
.
(
$depth
>
1
?
'- '
:
''
);
$row
[]
=
$indentation
.
drupal_render
(
$element
[
'title'
]);
$row
[]
=
theme
(
'indentation'
,
$element
[
'#item'
][
'depth'
]
-
1
)
.
drupal_render
(
$element
[
'title'
]);
$row
[]
=
array
(
'data'
=>
drupal_render
(
$element
[
'expanded'
]),
'class'
=>
'checkbox'
);
$row
[]
=
array
(
'data'
=>
drupal_render
(
$element
[
'hidden'
]),
'class'
=>
'checkbox'
);
$row
[]
=
drupal_render
(
$element
[
'weight'
])
.
drupal_render
(
$element
[
'plid'
])
.
drupal_render
(
$element
[
'mlid'
]);
$row
=
array_merge
(
$row
,
$operations
);
$row
=
array_merge
(
array
(
'data'
=>
$row
),
$element
[
'#attributes'
]);
$row
[
'class'
]
=
!
empty
(
$row
[
'class'
])
?
$row
[
'class'
]
.
' draggable'
:
'draggable'
;
$rows
[]
=
$row
;
}
}
...
...
modules/system/system.css
View file @
6d4862c2
...
...
@@ -46,8 +46,9 @@ thead th {
}
div
.indentation
{
width
:
20px
;
height
:
1.7em
;
margin
:
-0.4em
0.2em
-0.4em
-0.4em
;
padding
:
0.4em
0
0.4em
0.6em
;
padding
:
0.4
2
em
0
0.4
2
em
0.6em
;
float
:
left
;
}
div
.tree-child
{
...
...
@@ -367,7 +368,7 @@ a.tabledrag-handle {
cursor
:
move
;
float
:
left
;
height
:
1.7em
;
margin
:
-0.4
2
em
0
-0.4
2
em
-0.5em
;
margin
:
-0.4em
0
-0.4em
-0.5em
;
padding
:
0.42em
1.5em
0.42em
0.5em
;
text-decoration
:
none
;
}
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment