diff --git a/paragraphs.ajax.inc b/paragraphs.ajax.inc index 81d364d..2b4645a 100644 --- a/paragraphs.ajax.inc +++ b/paragraphs.ajax.inc @@ -48,6 +48,48 @@ function paragraphs_edit_js() { } /** + * Page callback to handle AJAX for collapse a paragraphs item. + * + * This is a direct page callback. The actual job of deleting the item is + * done in the submit handler for the button, so all we really need to + * do is process the form and then generate output. We generate this + * output by doing a replace command on the id of the entire form element. + */ +function paragraphs_collapse_js() { + // drupal_html_id() very helpfully ensures that all html IDS are unique + // on a page. Unfortunately what it doesn't realize is that the IDs + // we are generating are going to replace IDs that already exist, so + // this actually works against us. + if (isset($_POST['ajax_html_ids'])) { + unset($_POST['ajax_html_ids']); + } + + list($form, $form_state) = ajax_get_form(); + drupal_process_form($form['#form_id'], $form, $form_state); + + // Get the information on what we're removing. + $button = $form_state['triggering_element']; + // Go two levels up in the form, to the whole widget. + $element = drupal_array_get_nested_value($form, array_slice($button['#array_parents'], 0, -4)); + // Now send back the proper AJAX command to replace it. + $return = array( + '#type' => 'ajax', + '#commands' => array( + ajax_command_replace('#' . $element['#id'], drupal_render($element)) + ), + ); + + // Because we're doing this ourselves, messages aren't automatic. We have + // to add them. + $messages = theme('status_messages'); + if ($messages) { + $return['#commands'][] = ajax_command_prepend('#' . $element['#id'], $messages); + } + + return $return; +} + +/** * Page callback to handle AJAX for removing a paragraphs item. * * This is a direct page callback. The actual job of deleting the item is diff --git a/paragraphs.field_widget.inc b/paragraphs.field_widget.inc index 5da9ec5..0dd83a6 100644 --- a/paragraphs.field_widget.inc +++ b/paragraphs.field_widget.inc @@ -446,6 +446,24 @@ function paragraphs_field_widget_form_build(&$form, &$form_state, $field, $insta $element['#after_build'][] = 'paragraphs_field_widget_embed_delay_required_validation'; } } + + if ($default_edit_mode != 'open') { + $element['actions']['collapse_button'] = array( + '#delta' => $delta, + '#name' => implode('_', $parents) . '_collapse_button', + '#type' => 'submit', + '#value' => t('Collapse !type paragraph', array('!type' => $bundle_info->name)), + '#validate' => array(), + '#submit' => array('paragraphs_collapse_submit'), + '#limit_validation_errors' => array(), + '#ajax' => array( + 'path' => 'paragraphs/collapse/ajax', + 'effect' => 'fade', + ), + '#access' => entity_access('update', 'paragraphs_item', $paragraph_item), + '#weight' => 999, + ); + } } else { if($default_edit_mode === 'preview' && entity_access('view', 'paragraphs_item', $paragraph_item)) { @@ -465,6 +483,14 @@ function paragraphs_field_widget_form_build(&$form, &$form_state, $field, $insta '#type' => 'actions', '#weight' => 9999, ); + + if (isset($field_state['entity'][$delta]->must_be_saved) && $field_state['entity'][$delta]->must_be_saved) { + $element['actions']['must_be_saved'] = array( + '#markup' => '

' . t('Warning: this content must be saved to reflect changes on this paragraph item.') . '

', + '#weight' => 998, + ); + } + $element['actions']['edit_button'] = array( '#delta' => $delta, '#name' => implode('_', $parents) . '_edit_button', @@ -857,6 +883,65 @@ function paragraphs_edit_submit($form, &$form_state) { } /** + * Submit callback to collapse an item from the field UI multiple wrapper. + * + * When a collapse button is submitted, we need to find the item that it + * referenced and delete it. Since field UI has the deltas as a straight + * unbroken array key, we have to renumber everything down. Since we do this + * we *also* need to move all the deltas around in the $form_state['values'] + * and $form_state['input'] so that user changed values follow. This is a bit + * of a complicated process. + */ +function paragraphs_collapse_submit($form, &$form_state) { + $button = $form_state['triggering_element']; + $delta = $button['#delta']; + + // Where in the form we'll find the parent element. + $address = array_slice($button['#array_parents'], 0, -3); + + // Go one level up in the form, to the widgets container. + $parent_element = drupal_array_get_nested_value($form, $address); + $field_name = $parent_element['#field_name']; + $langcode = $parent_element['#language']; + $parents = $parent_element['#field_parents']; + + $field_state = field_form_get_state($parents, $field_name, $langcode, $form_state); + + if (isset($field_state['entity'][$delta])) { + $field_state['entity'][$delta]->being_edited = 0; + $field_state['entity'][$delta]->must_be_saved = 1; + } + + // Fix the weights. Field UI lets the weights be in a range of + // (-1 * item_count) to (item_count). This means that when we remove one, + // the range shrinks; weights outside of that range then get set to + // the first item in the select by the browser, floating them to the top. + // We use a brute force method because we lost weights on both ends + // and if the user has moved things around, we have to cascade because + // if I have items weight weights 3 and 4, and I change 4 to 3 but leave + // the 3, the order of the two 3s now is undefined and may not match what + // the user had selected. + $input = drupal_array_get_nested_value($form_state['input'], $address); + // Sort by weight, + // but first remove garbage values to ensure proper '_weight' sorting + unset($input['add_more']); + uasort($input, '_field_sort_items_helper'); + + // Reweight everything in the correct order. + $weight = -1 * $field_state['items_count'] + 1; + foreach ($input as $key => $item) { + if ($item) { + $input[$key]['_weight'] = $weight++; + } + } + + drupal_array_set_nested_value($form_state['input'], $address, $input); + field_form_set_state($parents, $field_name, $langcode, $form_state, $field_state); + + $form_state['rebuild'] = TRUE; +} + +/** * Submit callback to remove an item from the field UI multiple wrapper. * * When a remove button is submitted, we need to find the item that it diff --git a/paragraphs.module b/paragraphs.module index 2c2b692..dec41d6 100644 --- a/paragraphs.module +++ b/paragraphs.module @@ -259,6 +259,16 @@ function paragraphs_menu() { 'file' => 'paragraphs.ajax.inc', ); + $items['paragraphs/collapse/ajax'] = array( + 'title' => 'Close item callback', + 'page callback' => 'paragraphs_collapse_js', + 'delivery callback' => 'ajax_deliver', + 'access callback' => TRUE, + 'theme callback' => 'ajax_base_page_theme', + 'type' => MENU_CALLBACK, + 'file' => 'paragraphs.ajax.inc', + ); + $items['paragraphs/remove/ajax'] = array( 'title' => 'Remove item callback', 'page callback' => 'paragraphs_remove_js',