/** * @file * Attaches the behaviors for the Field UI module. */ (function($) { Drupal.behaviors.fieldUIFieldOverview = { attach: function (context, settings) { $('table#field-overview', context).once('field-overview', function () { Drupal.fieldUIFieldOverview.attachUpdateSelects(this, settings); }); } }; 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; }); // 'Field type' select updates its 'Widget' select. $('.field-type-select', table).each(function () { this.targetSelect = $('.widget-type-select', $(this).closest('tr')); $(this).bind('change keyup', function () { var selectedFieldType = this.options[this.selectedIndex].value; var options = (selectedFieldType in widgetTypes ? widgetTypes[selectedFieldType] : []); this.targetSelect.fieldUIPopulateOptions(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); }); // 'Existing field' select updates its 'Widget' select and 'Label' textfield. $('.field-select', table).each(function () { this.targetSelect = $('.widget-type-select', $(this).closest('tr')); this.targetTextfield = $('.label-textfield', $(this).closest('tr')); this.targetTextfield .data('field_ui_edited', false) .bind('keyup', function (e) { $(this).data('field_ui_edited', $(this).val() != ''); }); $(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); // Only overwrite the "Label" input if it has not been manually // changed, or if it is empty. if (updateText && !this.targetTextfield.data('field_ui_edited')) { this.targetTextfield.val(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); }); } }; /** * Populates options in a select input. */ jQuery.fn.fieldUIPopulateOptions = function (options, selected) { return this.each(function () { var disabled = false; if (options.length == 0) { options = [this.initialValue]; disabled = true; } // If possible, keep the same widget selected when changing field type. // This is based on textual value, since the internal value might be // different (options_buttons vs. node_reference_buttons). var previousSelectedText = this.options[this.selectedIndex].text; var html = ''; jQuery.each(options, function (value, text) { // Figure out which value should be selected. The 'selected' param // takes precedence. var is_selected = ((typeof selected != 'undefined' && value == selected) || (typeof selected == 'undefined' && text == previousSelectedText)); html += ''; }); $(this).html(html).attr('disabled', disabled ? 'disabled' : false); }); }; Drupal.behaviors.fieldUIDisplayOverview = { attach: function (context, settings) { $('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; if (row.id in rowsData) { var data = rowsData[row.id]; data.tableDrag = tableDrag; // Create the row handler, make it accessible from the DOM row element. var rowHandler = 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.closest('tr').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; } // 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'); if (typeof rowHandler !== 'undefined') { 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); } } // This region has become empty. if ($(this).next('tr').is(':not(.draggable)') || $(this).next('tr').length == 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'); } }); }, /** * Triggers Ajax refresh of selected rows. * * The 'format type' selects can trigger a series of changes in child rows. * The #ajax behavior is therefore not attached directly to the selects, but * triggered manually through a hidden #ajax 'Refresh' button. * * @param rows * A hash object, whose keys are the names of the rows to refresh (they * will receive the 'ajax-new-content' effect on the server side), and * whose values are the DOM element in the row that should get an Ajax * throbber. */ AJAXRefreshRows: function (rows) { // Separate keys and values. var rowNames = []; var ajaxElements = []; $.each(rows, function (rowName, ajaxElement) { rowNames.push(rowName); ajaxElements.push(ajaxElement); }); if (rowNames.length) { // Add a throbber next each of the ajaxElements. var $throbber = $('