Commit 7cc3d92d authored by webchick's avatar webchick

#616240 follow-up by yched, dereine, marvil07: Make Field UI screens...

#616240 follow-up by yched, dereine, marvil07: Make Field UI screens extensible from contrib - part II.
parent 3b21e82a
......@@ -1560,6 +1560,8 @@ function theme_breadcrumb($variables) {
* associative array with the following keys:
* - "data": an array of cells
* - Any HTML attributes, such as "class", to apply to the table row.
* - "no_striping": a boolean indicating that the row should receive no
* 'even / odd' styling. Defaults to FALSE.
* Each cell can be either a string or an associative array with the
* following keys:
* - "data": The string to display in the table cell.
......@@ -1728,8 +1730,10 @@ function theme_table($variables) {
}
if (count($cells)) {
// Add odd/even class
$class = $flip[$class];
$attributes['class'][] = $class;
if (empty($row['no_striping'])) {
$class = $flip[$class];
$attributes['class'][] = $class;
}
// Build row
$output .= ' <tr' . drupal_attributes($attributes) . '>';
......@@ -1999,7 +2003,7 @@ function theme_indentation($variables) {
/**
* Returns HTML output for a single table cell for theme_table().
*
*
* @param $cell
* Array of cell information, or string to display in cell.
* @param bool $header
......
......@@ -1016,8 +1016,11 @@ Drupal.tableDrag.prototype.row.prototype.validIndentInterval = function (prevRow
minIndent = nextRow ? $('.indentation', nextRow).size() : 0;
// Maximum indentation:
if (!prevRow || $(this.element).is('.tabledrag-root')) {
// Do not indent the first row in the table or 'root' rows..
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 {
......
......@@ -2065,6 +2065,33 @@ function hook_field_storage_pre_update($entity_type, $entity, &$skip_fields) {
}
}
/**
* Returns the maximum weight for the entity components handled by the module.
*
* Field API takes care of fields and 'extra_fields'. This hook is intended for
* third-party modules adding other entity components (e.g. field_group).
*
* @param $entity_type
* The type of entity; e.g. 'node' or 'user'.
* @param $bundle
* The bundle name.
* @param $context
* The context for which the maximum weight is requested. Either 'form', or
* the name of a view mode.
* @return
* The maximum weight of the entity's components, or NULL if no components
* were found.
*/
function hook_field_info_max_weight($entity_type, $bundle, $context) {
$weights = array();
foreach (my_module_entity_additions($entity_type, $bundle, $context) as $addition) {
$weights[] = $addition['weight'];
}
return $weights ? max($weights) : NULL;
}
/**
* Alters the display settings of a field before it gets displayed.
*
......
......@@ -761,16 +761,8 @@ function _field_write_instance($instance, $update = FALSE) {
);
// If no weight specified, make sure the field sinks at the bottom.
if (!isset($instance['widget']['weight'])) {
$weights = array();
foreach (field_info_instances($instance['entity_type'], $instance['bundle']) as $existing_instance) {
if ($instance['field_name'] != $existing_instance['field_name']) {
$weights[] = $existing_instance['widget']['weight'];
}
}
foreach (field_info_extra_fields($instance['entity_type'], $instance['bundle'], 'form') as $extra) {
$weights[] = $extra['weight'];
}
$instance['widget']['weight'] = $weights ? max($weights) + 1 : 0;
$max_weight = field_info_max_weight($instance['entity_type'], $instance['bundle'], 'form');
$instance['widget']['weight'] = !is_null($max_weight) ? $max_weight + 1 : 0;
}
// Check widget module.
$widget_type = field_info_widget_types($instance['widget']['type']);
......@@ -795,16 +787,8 @@ function _field_write_instance($instance, $update = FALSE) {
}
// If no weight specified, make sure the field sinks at the bottom.
if (!isset($display['weight'])) {
$weights = array();
foreach (field_info_instances($instance['entity_type'], $instance['bundle']) as $existing_instance) {
if ($instance['field_name'] != $existing_instance['field_name']) {
$weights[] = $existing_instance['display'][$view_mode]['weight'];
}
}
foreach (field_info_extra_fields($instance['entity_type'], $instance['bundle'], 'display') as $extra) {
$weights[] = $extra['display'][$view_mode]['weight'];
}
$display['weight'] = $weights ? max($weights) + 1 : 0;
$max_weight = field_info_max_weight($instance['entity_type'], $instance['bundle'], $view_mode);
$display['weight'] = !is_null($max_weight) ? $max_weight + 1 : 0;
}
$instance['display'][$view_mode] = $display;
}
......
......@@ -760,6 +760,47 @@ function field_info_extra_fields($entity_type, $bundle, $context) {
return array();
}
/**
* Returns the maximum weight of all the components in an entity.
*
* This includes fields, 'extra_fields', and other components added by
* third-party modules (e.g. field_group).
*
* @param $entity_type
* The type of entity; e.g. 'node' or 'user'.
* @param $bundle
* The bundle name.
* @param $context
* The context for which the maximum weight is requested. Either 'form', or
* the name of a view mode.
* @return
* The maximum weight of the entity's components, or NULL if no components
* were found.
*/
function field_info_max_weight($entity_type, $bundle, $context) {
$weights = array();
// Collect weights for fields.
foreach (field_info_instances($entity_type, $bundle) as $instance) {
if ($context == 'form') {
$weights[] = $instance['widget']['weight'];
}
else {
$weights[] = $instance['display'][$context]['weight'];
}
}
// Collect weights for extra fields.
foreach (field_info_extra_fields($entity_type, $bundle, $context) as $extra) {
$weights[] = $extra['weight'];
}
// Let other modules feedback about their own additions.
$weights = array_merge($weights, module_invoke_all('field_info_max_weight', $entity_type, $bundle, $context));
$max_weight = $weights ? max($weights) : NULL;
return $max_weight;
}
/**
* Returns a field type's default settings.
*
......
<?php
// $Id$
/**
* @file
* Default theme implementation to configure field display settings.
*
* Available variables:
* - $rows: The field display settings form broken down into rendered rows for
* printing as a table. The array is separated in two entries, 'visible' and
* 'hidden'.
* - $id: The HTML id for the table.
*
* @see field_ui_display_overview_form()
* @see template_preprocess_field_ui_display_overview_table()
*/
?>
<?php if ($rows): ?>
<div id="field-display-overview-wrapper">
<table id="field-display-overview" class="field-display-overview sticky-enabled">
<thead>
<tr>
<th><?php print t('Field'); ?></th>
<th><?php print t('Weight'); ?></th>
<th><?php print t('Label'); ?></th>
<th colspan="3"><?php print t('Format'); ?></th>
</tr>
</thead>
<tbody>
<tr class="region-message region-visible-message <?php print empty($rows['visible']) ? 'region-empty' : 'region-populated'; ?>">
<td colspan="5"><em><?php print t('No field is displayed'); ?></em></td>
</tr>
<?php
$count = 0;
foreach ($rows['visible'] as $row): ?>
<tr id="<?php print $row->id; ?>" class="<?php print $count % 2 == 0 ? 'odd' : 'even'; ?> <?php print $row->class ?>">
<td><span class="<?php print $row->label_class; ?>"><?php print $row->human_name; ?></span></td>
<td><?php print $row->weight . $row->hidden_name; ?></td>
<td><?php if (isset($row->label)) print $row->label; ?></td>
<?php if (isset($row->settings_edit_form)) : ?>
<td colspan="3">
<?php print $row->type; ?>
<?php print $row->settings_edit_form; ?>
</td>
<?php else :?>
<td>
<?php print $row->type; ?>
</td>
<td class="field-formatter-summary-cell">
<?php print $row->settings_summary; ?>
</td>
<td>
<?php print $row->settings_edit; ?>
</td>
<?php endif; ?>
</tr>
<?php $count++;
endforeach; ?>
<tr class="region-title region-title-hidden">
<td colspan="5"><?php print t('Hidden'); ?></td>
</tr>
<tr class="region-message region-hidden-message <?php print empty($rows['hidden']) ? 'region-empty' : 'region-populated'; ?>">
<td colspan="5"><em><?php print t('No field is hidden'); ?></em></td>
</tr>
<?php foreach ($rows['hidden'] as $row): ?>
<tr class="<?php print $count % 2 == 0 ? 'odd' : 'even'; ?> <?php print $row->class ?>">
<td><span class="<?php print $row->label_class; ?>"><?php print $row->human_name; ?></span></td>
<td><?php print $row->weight . $row->hidden_name; ?></td>
<td><?php if (isset($row->label)) print $row->label; ?></td>
<td><?php print $row->type; ?></td>
<td class="field-formatter-summary-cell">
<?php print $row->settings_summary; ?>
</td>
<td>
<?php print $row->settings_edit; ?>
</td>
</tr>
<?php $count++;
endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
/* $Id$ */
/* 'Manage fields' overview */
#field-overview tr.add-new .label-input {
table.field-ui-overview tr.add-new .label-input {
float: right;
}
This diff is collapsed.
/* $Id$ */
/* 'Manage fields' overview */
#field-overview tr.add-new .label-input {
/* 'Manage fields' and 'Manage display' overviews */
table.field-ui-overview tr.add-new .label-input {
float: left; /* LTR */
}
#field-overview tr.add-new .tabledrag-changed {
table.field-ui-overview tr.add-new .tabledrag-changed {
display: none;
}
#field-overview tr.add-new .description {
table.field-ui-overview tr.add-new .description {
margin-bottom: 0;
}
#field-overview tr.add-new .add-new-placeholder {
table.field-ui-overview tr.add-new .add-new-placeholder {
font-weight: bold;
padding-bottom: .5em;
}
table.field-ui-overview tr.region-add-new-title {
display: none;
}
/* 'Manage display' overview */
.field-display-overview tr.region-title td {
#field-display-overview tr.region-title td {
font-weight: bold;
}
.field-display-overview tr.region-populated {
#field-display-overview tr.region-message td {
font-style: italic;
}
#field-display-overview tr.region-populated {
display: none;
}
.field-display-overview .field-formatter-summary-cell {
#field-display-overview .field-formatter-summary-cell {
line-height: 1em;
}
.field-display-overview .field-formatter-summary {
#field-display-overview .field-formatter-summary {
float: left;
font-size: 0.9em;
}
.field-display-overview td.field-formatter-summary-cell span.warning {
#field-display-overview td.field-formatter-summary-cell span.warning {
display: block;
float: left;
margin-right: .5em;
}
.field-display-overview .field-formatter-settings-edit-wrapper {
#field-display-overview .field-formatter-settings-edit-wrapper {
float: right;
}
.field-display-overview .field-formatter-settings-edit {
#field-display-overview .field-formatter-settings-edit {
float: right;
}
.field-display-overview tr.field-formatter-settings-editing td {
#field-display-overview tr.field-formatter-settings-editing td {
vertical-align: top;
}
.field-display-overview tr.field-formatter-settings-editing .field-formatter-type {
#field-display-overview tr.field-formatter-settings-editing .field-formatter-type {
display: none;
}
.field-display-overview .field-formatter-settings-edit-form .formatter-name{
#field-display-overview .field-formatter-settings-edit-form .formatter-name{
font-weight: bold;
}
\ No newline at end of file
}
#field-ui-display-overview-form #edit-refresh {
display:none;
}
......@@ -2,61 +2,71 @@
(function($) {
Drupal.behaviors.fieldManageFields = {
attach: function (context) {
attachUpdateSelects(context);
Drupal.behaviors.fieldUIFieldOverview = {
attach: function (context, settings) {
$('table#field-overview', context).once('field-overview', function () {
Drupal.fieldUIFieldOverview.attachUpdateSelects(this, settings);
});
}
};
function attachUpdateSelects(context) {
var widgetTypes = Drupal.settings.fieldWidgetTypes;
var fields = Drupal.settings.fields;
Drupal.fieldUIFieldOverview = {
/**
* Implements dependent select dropdowns on the 'Manage fields' screen.
*/
attachUpdateSelects: function(table, settings) {
var widgetTypes = settings.fieldWidgetTypes;
var fields = settings.fields;
// Store the default text of widget selects.
$('.widget-type-select', table).each(function () {
this.initialValue = this.options[0].text;
});
// Store the default text of widget selects.
$('#field-overview .widget-type-select', context).each(function () {
this.initialValue = this.options[0].text;
});
// 'Field type' select updates its 'Widget' select.
$('.field-type-select', table).each(function () {
this.targetSelect = $('.widget-type-select', $(this).parents('tr').eq(0));
// 'Field type' select updates its 'Widget' select.
$('#field-overview .field-type-select', context).each(function () {
this.targetSelect = $('.widget-type-select', $(this).parents('tr').eq(0));
$(this).bind('change keyup', function () {
var selectedFieldType = this.options[this.selectedIndex].value;
var options = (selectedFieldType in widgetTypes ? widgetTypes[selectedFieldType] : []);
this.targetSelect.fieldUIPopulateOptions(options);
});
$(this).bind('change keyup', function () {
var selectedFieldType = this.options[this.selectedIndex].value;
var options = (selectedFieldType in widgetTypes ? widgetTypes[selectedFieldType] : []);
this.targetSelect.fieldPopulateOptions(options);
// Trigger change on initial pageload to get the right widget options
// when field type comes pre-selected (on failed validation).
$(this).trigger('change', false);
});
// Trigger change on initial pageload to get the right widget options
// when field type comes pre-selected (on failed validation).
$(this).trigger('change', false);
});
// 'Existing field' select updates its 'Widget' select and 'Label' textfield.
$('.field-select', table).each(function () {
this.targetSelect = $('.widget-type-select', $(this).parents('tr').eq(0));
this.targetTextfield = $('.label-textfield', $(this).parents('tr').eq(0));
$(this).bind('change keyup', function (e, updateText) {
var updateText = (typeof updateText == 'undefined' ? true : updateText);
var selectedField = this.options[this.selectedIndex].value;
var selectedFieldType = (selectedField in fields ? fields[selectedField].type : null);
var selectedFieldWidget = (selectedField in fields ? fields[selectedField].widget : null);
var options = (selectedFieldType && (selectedFieldType in widgetTypes) ? widgetTypes[selectedFieldType] : []);
this.targetSelect.fieldUIPopulateOptions(options, selectedFieldWidget);
if (updateText) {
$(this.targetTextfield).attr('value', (selectedField in fields ? fields[selectedField].label : ''));
}
});
// 'Existing field' select updates its 'Widget' select and 'Label' textfield.
$('#field-overview .field-select', context).each(function () {
this.targetSelect = $('.widget-type-select', $(this).parents('tr').eq(0));
this.targetTextfield = $('.label-textfield', $(this).parents('tr').eq(0));
$(this).bind('change keyup', function (e, updateText) {
var updateText = (typeof updateText == 'undefined' ? true : updateText);
var selectedField = this.options[this.selectedIndex].value;
var selectedFieldType = (selectedField in fields ? fields[selectedField].type : null);
var selectedFieldWidget = (selectedField in fields ? fields[selectedField].widget : null);
var options = (selectedFieldType && (selectedFieldType in widgetTypes) ? widgetTypes[selectedFieldType] : []);
this.targetSelect.fieldPopulateOptions(options, selectedFieldWidget);
if (updateText) {
$(this.targetTextfield).attr('value', (selectedField in fields ? fields[selectedField].label : ''));
}
// Trigger change on initial pageload to get the right widget options
// and label when field type comes pre-selected (on failed validation).
$(this).trigger('change', false);
});
},
};
// Trigger change on initial pageload to get the right widget options
// and label when field type comes pre-selected (on failed validation).
$(this).trigger('change', false);
});
}
jQuery.fn.fieldPopulateOptions = function (options, selected) {
/**
* Populates options in a select input.
*/
jQuery.fn.fieldUIPopulateOptions = function (options, selected) {
return this.each(function () {
var disabled = false;
if (options.length == 0) {
......@@ -81,145 +91,239 @@ jQuery.fn.fieldPopulateOptions = function (options, selected) {
});
};
/**
* Moves a field in the display settings table from visible to hidden.
*
* This behavior is dependent on the tableDrag behavior, since it uses the
* objects initialized in that behavior to update the row.
*/
Drupal.behaviors.fieldManageDisplayDrag = {
Drupal.behaviors.fieldUIDisplayOverview = {
attach: function (context, settings) {
// tableDrag is required for this behavior.
if (!$('table.field-display-overview', context).length || typeof Drupal.tableDrag == 'undefined') {
return;
$('table#field-display-overview', context).once('field-display-overview', function() {
Drupal.fieldUIOverview.attach(this, settings.fieldUIRowsData, Drupal.fieldUIDisplayOverview);
});
}
};
Drupal.fieldUIOverview = {
/**
* Attaches the fieldUIOverview behavior.
*/
attach: function (table, rowsData, rowHandlers) {
var tableDrag = Drupal.tableDrag[table.id];
// Add custom tabledrag callbacks.
tableDrag.onDrop = this.onDrop;
tableDrag.row.prototype.onSwap = this.onSwap;
// Create row handlers.
$('tr.draggable', table).each(function () {
// Extract server-side data for the row.
var row = this;
var data = rowsData[row.id];
data.tableDrag = tableDrag;
// Create the row handler, make it accessible from the DOM row element.
var rowHandler = eval('new rowHandlers.' + data.rowHandler + '(row, data);');
$(row).data('fieldUIRowHandler', rowHandler);
});
},
/**
* Event handler to be attached to form inputs triggering a region change.
*/
onChange: function () {
var $trigger = $(this);
var row = $trigger.parents('tr:first').get(0);
var rowHandler = $(row).data('fieldUIRowHandler');
var refreshRows = {};
refreshRows[rowHandler.name] = $trigger.get(0);
// Handle region change.
var region = rowHandler.getRegion();
if (region != rowHandler.region) {
// Remove parenting.
$('select.field-parent', row).val('');
// Let the row handler deal with the region change.
$.extend(refreshRows, rowHandler.regionChange(region));
// Update the row region.
rowHandler.region = region;
}
var defaultFormatters = Drupal.settings.fieldDefaultFormatters;
var tableDrag = Drupal.tableDrag['field-display-overview'];
// Add a handler for when a row is swapped, update empty regions.
tableDrag.row.prototype.onSwap = function (swappedRow) {
checkEmptyRegions(this.table, this);
};
// Add a handler to update the formatter selector when a row is dropped in
// or out of the 'Hidden' section.
tableDrag.onDrop = function () {
var dragObject = this;
var regionRow = $(dragObject.rowObject.element).prevAll('tr.region-message').get(0);
var visibility = regionRow.className.replace(/([^ ]+[ ]+)*region-([^ ]+)-message([ ]+[^ ]+)*/, '$2');
// Update the 'format' selector if the visibility changed.
var $select = $('select.field-formatter-type', dragObject.rowObject.element);
var oldVisibility = $select[0].className.replace(/([^ ]+[ ]+)*field-display-([^ ]+)([ ]+[^ ]+)*/, '$2');
if (visibility != oldVisibility) {
$select.removeClass('field-display-' + oldVisibility).addClass('field-display-' + visibility);
// Update the selected formatter if coming from an actual drag.
if (!$select.data('noUpdate')) {
if (visibility == 'visible') {
// Restore the formatter back to the previously selected one if
// available, or to the default formatter.
var value = $select.data('oldFormatter');
if (typeof value == 'undefined') {
// Extract field name from the name of the select.
var fieldName = $select[0].className.match(/\bfield-name-(\S+)\b/)[1].replace('-', '_');
// Pseudo-fields do not have an entry in the defaultFormatters
// array, we just return to 'visible' for those.
value = (fieldName in defaultFormatters) ? defaultFormatters[fieldName] : 'visible';
}
$select.data('oldFormatter', value);
}
else {
var value = 'hidden';
}
$select.val(value);
// Fire AJAX update of formatter settings.
$select.change();
// Ajax-update the rows.
Drupal.fieldUIOverview.AJAXRefreshRows(refreshRows);
},
/**
* Lets row handlers react when a row is dropped into a new region.
*/
onDrop: function () {
var dragObject = this;
var row = dragObject.rowObject.element;
var rowHandler = $(row).data('fieldUIRowHandler');
var regionRow = $(row).prevAll('tr.region-message').get(0);
var region = regionRow.className.replace(/([^ ]+[ ]+)*region-([^ ]+)-message([ ]+[^ ]+)*/, '$2');
if (region != rowHandler.region) {
// Let the row handler deal with the region change.
refreshRows = rowHandler.regionChange(region);
// Update the row region.
rowHandler.region = region;
// AJAX-update the rows.
Drupal.fieldUIOverview.AJAXRefreshRows(refreshRows);
}
},
/**
* Refreshes placeholder rows in empty regions while a row is being dragged.
*
* Copied from block.js.
*
* @param table
* The table DOM element.
* @param rowObject
* The tableDrag rowObject for the row being dragged.
*/
onSwap: function (draggedRow) {
var rowObject = this;
$('tr.region-message', rowObject.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.group[rowObject.group.length - 1]) {
// Prevent a recursion problem when using the keyboard to move rows up.
if ((rowObject.method != 'keyboard' || rowObject.direction == 'down')) {
rowObject.swap('after', this);
}
$select.removeData('noUpdate');
}
};
// Add the behavior to each formatter select list.
$('select.field-formatter-type', context).once('field-formatter-type', function () {
// Initialize 'previously selected formatter' as the incoming value.
if ($(this).val() != 'hidden') {