Commit 6049f237 authored by Dries's avatar Dries

- Patch #181066 by quicksketch et al: drag and drop of table rows on the block adminsitration page.

parent 44373cf0
......@@ -46,6 +46,7 @@ Drupal 6.0, xxxx-xx-xx (development version)
.info file.
* Dynamically check password strength and confirmation.
* Refactored poll administration.
* Drag and drop of table rows in the block administration page.
- Theme system:
* Added .info files to themes and made it easier to specify regions and
features.
......
......@@ -1926,6 +1926,113 @@ function drupal_get_js($scope = 'header', $javascript = NULL) {
return $output;
}
/**
* Assist in adding the tableDrag JavaScript behavior to a themed table.
*
* Draggable tables should be used wherever an outline or list of sortable items
* needs to be arranged by an end-user. Draggable tables are very flexible and
* can manipulate the value of form elements placed within individual columns.
*
* To setup a table to use drag and drop in place of weight select-lists or
* in place of a form that contains parent relationships, the form must be
* themed into a table. The table must have an id attribute set. If using
* theme_table(), the id may be set as such:
* @code
* $output = theme('table', $header, $rows, array('id' => 'my-module-table'));
* return $output;
* @endcode
*
* In the theme function for the form, a special class must be added to each
* form element within the same column, "grouping" them together.
*
* 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";
* @endcode
*
* Calling drupal_add_tabledrag() would then be written as such:
* @code
* drupal_add_tabledrag('my-module-table', 'sort', '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;
* @endcode
*
* $group is still 'my-element-weight', and the additional $subgroup variable
* will be passed in as 'my-elements-weight-'. $region. This also means that
* you'll need to call drupal_add_tabledrag() once for every region added.
*
* @code
* foreach ($regions as $region) {
* drupal_add_tabledrag('my-module-table', 'sort', 'sibling', 'my-elements-weight', 'my-elements-weight-'. $region);
* }
* @endcode
*
* In a situation where tree relationships are present, adding multiple
* subgroups is not necessary, because the table will contain indentations that
* provide enough information about the sibling and parent relationships.
* See theme_menu_overview_form() for an example creating a table containing
* parent relationships.
*
* Please note that this function should be called from the theme layer, such as
* in a .tpl.php file, theme_ function, or in a template_preprocess function,
* not in a form declartion. Though the same JavaScript could be added to the
* page using drupal_add_js() directly, this function helps keep template files
* clean and readable. It also prevents tabledrag.js from being added twice
* accidentally.
*
* @param $table_id
* 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.
* @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.
* @param $group
* A class name applied on all related form elements for this action.
* @param $subgroup
* (optional) If the group has several subgroups within it, this string should
* contain the class name identifying fields in the same subgroup.
* @param $source
* (optional) If the $action is 'match', this string should contain the class
* name identifying what field will be used as the source value when matching
* the value in $subgroup.
* @param $hidden
* (optional) The column containing the field elements may be entirely hidden
* from view dynamically when the JavaScript is loaded. Set to FALSE if the
* column should not be hidden.
* @see block-admin-display-form.tpl.php
* @see theme_menu_overview_form()
*/
function drupal_add_tabledrag($table_id, $action, $relationship, $group, $subgroup = NULL, $source = NULL, $hidden = TRUE) {
static $js_added = FALSE;
if (!$js_added) {
drupal_add_js('misc/tabledrag.js', 'core');
$js_added = TRUE;
}
// If a subgroup or source isn't set, assume it is the same as the group.
$target = isset($subgroup) ? $subgroup : $group;
$source = isset($source) ? $source : $target;
$settings['tableDrag'][$table_id][$group][] = array(
'target' => $target,
'source' => $source,
'relationship' => $relationship,
'action' => $action,
'hidden' => $hidden,
);
drupal_add_js($settings, 'setting');
}
/**
* Aggregate JS files, putting them in the files directory.
*
......
This diff is collapsed.
misc/tree.png

979 Bytes

......@@ -6,13 +6,14 @@
* Default theme implementation to configure blocks.
*
* Available variables:
* - $block_listing: An array of block controls within regions.
* - $block_regions: An array of regions. Keyed by name with the title as value.
* - $block_listing: An array of blocks keyed by region and then delta.
* - $form_submit: Form submit button.
* - $throttle: TRUE or FALSE depending on throttle module being enabled.
*
* Each $data in $block_listing contains:
* - $data->is_region_first: TRUE or FALSE depending on the listed blocks
* positioning. Used here to insert a region header.
* Each $block_listing[$region] contains an array of blocks for that region.
*
* Each $data in $block_listing[$region] contains:
* - $data->region_title: Region title for the listed block.
* - $data->block_title: Block title.
* - $data->region_select: Drop-down menu for assigning a region.
......@@ -25,9 +26,15 @@
* @see theme_block_admin_display()
*/
?>
<?php drupal_add_js('misc/tableheader.js'); ?>
<?php print $messages; ?>
<?php
// Add table javascript.
drupal_add_js('misc/tableheader.js');
drupal_add_js(drupal_get_path('module', 'block') .'/block.js');
foreach ($block_regions as $region => $title) {
drupal_add_tabledrag('blocks', 'match', 'sibling', 'block-region-select', 'block-region-'. $region, NULL, FALSE);
drupal_add_tabledrag('blocks', 'order', 'sibling', 'block-weight', 'block-weight-'. $region);
}
?>
<table id="blocks">
<thead>
<tr>
......@@ -42,15 +49,16 @@
</thead>
<tbody>
<?php $row = 0; ?>
<?php foreach ($block_listing as $data): ?>
<?php if ($data->is_region_first): ?>
<tr class="<?php print $row % 2 == 0 ? 'odd' : 'even'; ?>">
<td colspan="<?php print $throttle ? '7' : '6'; ?>" class="region"><?php print $data->region_title; ?></td>
<?php foreach ($block_regions as $region => $title): ?>
<tr class="region region-<?php print $region?>">
<td colspan="<?php print $throttle ? '6' : '5'; ?>" class="region"><?php print $title; ?></td>
</tr>
<?php $row++; ?>
<?php endif; ?>
<tr class="<?php print $row % 2 == 0 ? 'odd' : 'even'; ?><?php print $data->row_class ? ' '. $data->row_class : ''; ?>">
<td class="block"><?php print $data->block_title; ?><?php print $data->block_modified ? '<span class="warning">*</span>' : ''; ?></td>
<tr class="region-message region-<?php print $region?>-message <?php print empty($block_listing[$region]) ? 'region-empty' : 'region-populated'; ?>">
<td colspan="<?php print $throttle ? '6' : '5'; ?>"><em><?php print t('No blocks in this region'); ?></em></td>
</tr>
<?php foreach ($block_listing[$region] as $delta => $data): ?>
<tr class="draggable <?php print $row % 2 == 0 ? 'odd' : 'even'; ?><?php print $data->row_class ? ' '. $data->row_class : ''; ?>">
<td class="block"><?php print $data->block_title; ?></td>
<td><?php print $data->region_select; ?></td>
<td><?php print $data->weight_select; ?></td>
<?php if ($throttle): ?>
......@@ -60,6 +68,7 @@
<td><?php print $data->delete_link; ?></td>
</tr>
<?php $row++; ?>
<?php endforeach; ?>
<?php endforeach; ?>
</tbody>
</table>
......
/* $Id$ */
#blocks td.block {
padding-left: inherit;
padding-right: 1.5em;
}
#blocks select {
margin-left: 24px;
}
#blocks select.progress-disabled {
margin-left: 0px;
}
#blocks .progress .bar {
float: right;
}
This diff is collapsed.
......@@ -3,8 +3,12 @@
#blocks td.region {
font-weight: bold;
}
#blocks td.block {
padding-left: 1.5em; /* LTR */
#blocks tr.region-message {
font-weight: normal;
color: #999;
}
#blocks tr.region-populated {
display: none;
}
.block-region {
background-color: #ff6;
......@@ -12,12 +16,3 @@
margin-bottom: 4px;
padding: 3px;
}
#blocks select {
margin-right: 24px; /* LTR */
}
#blocks select.progress-disabled {
margin-right: 0px; /* LTR */
}
#blocks tr.ahah-new-content {
background-color: #ffd;
}
// $Id $
/**
* Move a block in the blocks table from one region to another via select list.
*
* This behavior is dependent on the tableDrag behavior, since it uses the
* objects initialized in that behavior to update the row.
*/
Drupal.behaviors.blockDrag = function(context) {
var table = $('table#blocks');
var tableDrag = Drupal.tableDrag.blocks; // Get the blocks tableDrag object.
// Add a handler for when a row is swapped, update empty regions.
tableDrag.row.prototype.onSwap = function(swappedRow) {
checkEmptyRegions(table, this);
};
// A custom message for the blocks page specifically.
Drupal.theme.tableDragChangedWarning = function () {
return '<div class="warning">' + Drupal.theme('tableDragChangedMarker') + ' ' + Drupal.t("The changes to these blocks will not be saved until the <em>Save blocks</em> button is clicked.") + '</div>';
};
// Add a handler so when a row is dropped, update fields dropped into new regions.
tableDrag.onDrop = function() {
dragObject = this;
if ($(dragObject.rowObject.element).prev('tr').is('.region-message')) {
var regionRow = $(dragObject.rowObject.element).prev('tr').get(0);
var regionName = regionRow.className.replace(/([^ ]+[ ]+)*region-([^ ]+)-message([ ]+[^ ]+)*/, '$2');
var regionField = $('select.block-region-select', dragObject.rowObject.element);
var weightField = $('select.block-weight', dragObject.rowObject.element);
var oldRegionName = weightField[0].className.replace(/([^ ]+[ ]+)*block-weight-([^ ]+)([ ]+[^ ]+)*/, '$2');
if (!regionField.is('.block-region-'+ regionName)) {
regionField.removeClass('block-region-' + oldRegionName).addClass('block-region-' + regionName);
weightField.removeClass('block-weight-' + oldRegionName).addClass('block-weight-' + regionName);
regionField.val(regionName);
}
}
};
// Add the behavior to each region select list.
$('select.block-region-select:not(.blockregionselect-processed)', context).each(function() {
$(this).change(function(event) {
// Make our new row and select field.
var row = $(this).parents('tr:first');
var select = $(this);
tableDrag.rowObject = new tableDrag.row(row);
// Find the correct region and insert the row as the first in the region.
$('tr.region-message', table).each(function() {
if ($(this).is('.region-' + select[0].value + '-message')) {
// Add the new row and remove the old one.
$(this).after(row);
// Manually update weights and restripe.
tableDrag.updateFields(row.get(0));
tableDrag.rowObject.changed = true;
if (tableDrag.oldRowElement) {
$(tableDrag.oldRowElement).removeClass('drag-previous');
}
tableDrag.oldRowElement = row.get(0);
tableDrag.restripeTable();
tableDrag.rowObject.markChanged();
tableDrag.oldRowElement = row;
$(row).addClass('drag-previous');
}
});
// Modify empty regions with added or removed fields.
checkEmptyRegions(table, row);
// Remove focus from selectbox.
select.get(0).blur();
});
$(this).addClass('blockregionselect-processed');
});
var checkEmptyRegions = function(table, rowObject) {
$('tr.region-message', table).each(function() {
// If the dragged row is in this region, but above the message row, swap it down one space.
if ($(this).prev('tr').get(0) == rowObject.element) {
// Prevent a recursion problem when using the keyboard to move rows up.
if ((rowObject.method != 'keyboard' || rowObject.direction == 'down')) {
rowObject.swap('after', this);
}
}
// This region has become empty
if ($(this).next('tr').is(':not(.draggable)') || $(this).next('tr').size() == 0) {
$(this).removeClass('region-populated').addClass('region-empty');
}
// This region has become populated.
else if ($(this).is('.region-empty')) {
$(this).removeClass('region-empty').addClass('region-populated');
}
});
};
};
......@@ -248,6 +248,9 @@ function _block_rehash() {
}
// Add defaults and save it into the database.
drupal_write_record('blocks', $block);
// Set region to none if not enabled.
$block['region'] = $block['status'] ? $block['region'] : BLOCK_REGION_NONE;
// Add to the list of blocks we return.
$blocks[] = $block;
}
else {
......@@ -257,7 +260,9 @@ function _block_rehash() {
// do not need to update the database here.
// Add 'info' to this block.
$old_blocks[$module][$delta]['info'] = $block['info'];
// Add this block to the list of blocks we return
// Set region to none if not enabled.
$old_blocks[$module][$delta]['region'] = $old_blocks[$module][$delta]['status'] ? $old_blocks[$module][$delta]['region'] : BLOCK_REGION_NONE;
// Add this block to the list of blocks we return.
$blocks[] = $old_blocks[$module][$delta];
// Remove this block from the list of blocks to be deleted.
unset($old_blocks[$module][$delta]);
......
......@@ -3,6 +3,9 @@
/*
** HTML elements
*/
body.drag {
cursor: move;
}
th.active img {
display: inline;
}
......@@ -11,6 +14,12 @@ tr.even, tr.odd {
border-bottom: 1px solid #ccc;
padding: 0.1em 0.6em;
}
tr.drag {
background-color: #fffff0;
}
tr.drag-previous {
background-color: #ffd;
}
td.active {
background-color: #ddd;
}
......@@ -32,6 +41,21 @@ thead th {
.breadcrumb {
padding-bottom: .5em
}
div.indentation {
width: 20px;
margin: -0.4em 0.2em -0.4em -0.4em;
padding: 0.4em 0 0.4em 0.6em;
float: left;
}
div.tree-child {
background: url(../../misc/tree.png) no-repeat 11px center;
}
div.tree-child-last {
background: url(../../misc/tree-bottom.png) no-repeat 11px center;
}
div.tree-child-horizontal {
background: url(../../misc/tree.png) no-repeat -11px center;
}
.error {
color: #e55;
}
......@@ -333,6 +357,30 @@ html.js .resizable-textarea textarea {
display: block;
}
/*
** Table drag and drop.
*/
a.tabledrag-handle {
cursor: move;
float: left;
height: 1.7em;
margin: -0.42em 0 -0.42em -0.5em;
padding: 0.42em 1.5em 0.42em 0.5em;
text-decoration: none;
}
a.tabledrag-handle:hover {
text-decoration: none;
}
a.tabledrag-handle .handle {
margin-top: 4px;
height: 13px;
width: 13px;
background: url(../../misc/draggable.png) no-repeat 0 0;
}
a.tabledrag-handle-hover .handle {
background-position: 0 -20px;
}
/*
** Teaser splitter
*/
......
......@@ -226,6 +226,14 @@ tr.even {
background-color: #fff;
}
tr.drag {
background-color: #fffff0;
}
tr.drag-previous {
background-color: #ffd;
}
tr.odd td.active {
background-color: #ddecf5;
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment