2

I have a content type with many fields, so that loading node/%node/edit is a pain ! In order to split node edit form in smaller forms (menu tabs), I used Page Manager.

What I did so far

  1. Enable the Node add/edit form coming with Page Manager, with selection rule node being edited is from foo_type
  2. Add three new custom panels pages in admin/structure/pages, with paths /node/%node/photos, /node/%node/prices, /node/%node/fd ; with selection rule node being edited is from foo_type
  3. Define its menu as local menu tab.
  4. Share foo_type fields between these forms, and add a submit node form to each.

This renders correctly three node edit forms accessible in new local task menu tabs :

created node edit menu tabs

The buggy part

If I edit the node from any of these forms :

  • the fields present in it are saved
  • the data from other fields saved before is still in the database, and also readable in the form fields if I show up the other forms (except for pictures), but it is not displayed anymore in the node.

As per @Letharion comments : " [...] while Panels happens to let you output only half the form as well, that is not supported by form api. One way or another, every field must be present. If not, form api will interpret it as "the user deleted the content of those fields" [...]

The question

I'm searching a way to patch the current solution, or a fully programmatic way to split node edit into tabs. Thanks for your contribution!

Edit:

Most of the issue is solved thanks to flexiform, except for inline entity commerce products. So I'm trying with MrD answer for this specific field, as exposed below.

The commerce product is saved (corresponding DB tables commerce_product, field_data_commerce_price and so on have a new record), but not attached to its product display (and no record in field_data_field_product). No error is returned.

How to correctly save that commerce product so that it is attached to its node display ?

function vts_nodedit_menu() {
  $items = array();

  $items['node/%node/prices'] = array(
    'title' => 'Prix et dates',
    'page callback' => 'node_page_prices',
    'page arguments' => array(1),
    'access callback' => 'node_access',
    'access arguments' => array('update', 1),
    'weight' => 4,
    'type' => MENU_LOCAL_TASK,
    'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
  );

  return $items;
}

function node_page_prices($node) {
  $form = drupal_get_form('vts_nodedit_form', $node, 'field_product');
  return $form;
}

function vts_nodedit_form($form, &$form_state, $node, $field_name) {
  $form = array();
  field_attach_form('node', $node, $form, $form_state, NULL, array('field_name' => $field_name));
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save')
  );
  return $form;
}

function vts_nodedit_form_submit($form, &$form_state) {
  $node = $form_state['build_info']['args'][0];

  //retrieve commerce product to be saved... NON DRUPAL way... :(
  $item = key($form_state['inline_entity_form']);
  $entities = end($form_state['inline_entity_form'][$item]['entities']);
  $product =$entities['entity'];//this is the correct product entity
  //SAVE THE COMMERCE PRODUCT
  commerce_product_save($product);
  //MrD solution 
  field_attach_submit('node', $node, $form, $form_state);
}
2
  • if you're not afraid of it using JS, I have done exactly this using the standard edit page, but split up using JS after the fact - since it is displaying the whole, it degrades nicely (or atleast it is still usable) if JS is turned off, just not as pretty
    – Geoff
    Jun 25, 2015 at 17:10
  • @Geoff, thanks. My main pb is the loading charge of all these filtered HTML fields and so on... My very first try was indeed to modify the form with JS (could be done also as per Goku answer this way), but this adds loading time instead of reducing it... This is why I need real splitting, not fake ;)
    – Kojo
    Jun 25, 2015 at 17:24

5 Answers 5

1
+50

I think the best way to do this is to use field_attach_form

You can see my example below

/**
 * Implements hook_menu();
 */
function foo_menu() {
  $items = array();

  $items['node/%node/settings'] = array(
    'title' => t('Settings'),
    'page callback' => 'node_page_settings',
    'page arguments' => array(1),
    'access callback' => 'node_access',
    'access arguments' => array('update', 1),
    'weight' => 0,
    'type' => MENU_LOCAL_TASK,
    'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
  );

  return $items;
}
//You can replace body with your field name
function node_page_settings($node) {
  $form = drupal_get_form('foo_form', $node, 'body');
  return $form;
}

/**
 * Implements hook_form();
 */
// Have default value if node exit data
function foo_form($form, &$form_state, $node, $field_name) {
  $form = array();
  field_attach_form('node', $node, $form, $form_state, NULL, array('field_name' => $field_name));
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save')
  );
  return $form;
}
/**
 * Implements hook_form_submit();
 */
function foo_form_submit($form, &$form_state) {
  $node = $form_state['build_info']['args'][0];
  //common fields case
  field_attach_submit('node', $node, $form, $form_state);
  //add this for inline entity multi-values fields case (eg commerce products)
  $item = key($form_state['inline_entity_form']);
  $entities = $form_state['inline_entity_form'][$item]['entities'];
  $product = array();
  foreach ($entities as $key => $entity) {
    $product[$key] = array(
      'product_id' => $entity['entity']->product_id
    );
  }
  $node->field_product_store['und'] = $product;
  node_save($node);
}
8
  • 1
    I don't know many module. So I often code for my feature. In this case you can use my code. I tested my code with Inline entity form. It's ok. With submit function function foo_form_submit($form, $form_state) { $node = $form_state['build_info']['args'][0]; field_attach_submit('node', $node, $form, $form_state); }
    – MrD
    Jun 30, 2015 at 11:21
  • Can you say your url when you submit? And you can check function vts_nodedit_data_submit was called. You try use dpm($node) after $node = $form_state['build_info']['args'][0]; and click submit. Ì you see message say to me. The last, you can replace with $node = node_load(arg(1));
    – MrD
    Jul 1, 2015 at 1:57
  • Thanks. I already did these steps yesterday. Indeed vts_nodedit_form_submit is fired, and $node is correctly retrieved by $form_state['build_info']['args'][0], not by args[1]. And I stay on the same url node/%node/prices which I declare in hook_menu, but I don't think this has an effect, I could redirect either to node/%node
    – Kojo
    Jul 1, 2015 at 9:39
  • Your mind is that everything works? Only after submited you can't redirect either to node/%node?
    – MrD
    Jul 1, 2015 at 10:56
  • Sorry if I was not reactive, out of office till now. It is enough to add drupal_goto( 'node/'. $node->nid ) at the end of the submit fn, tested. But I really don't understand why you focus on redirect? What's working : the commerce product is saved. What's not : it is not attached to its node display, so it is useless. Thanks
    – Kojo
    Jul 1, 2015 at 12:53
1

This issue can be solved using Panels and Node Form Panes modules, also we need to enable "Page manager" module from Ctools.

  1. Open "Page manager" administration page(admin/structure/pages) and click on "Add custom page" link enter image description here
  2. Insert basic settings for new page enter image description here
  3. Set Context argument enter image description here enter image description here
  4. Add access settings
  5. Add menu settings enter image description here
  6. Choose node type enter image description here enter image description here
  7. Choose needed layout enter image description here
  8. Add needed fields enter image description here enter image description here
  9. Add "Save button" enter image description here enter image description here The result should be like this enter image description here
4
  • thanks. I installed that module and used the new field form summary from it, but this changes nothing to that issue. When you save the form, data from fields not called in that form don't show up anymore. Please try with your own content type, and tell me if it makes any difference with the issue I expose. If you put only one field in the panel form, does the node still displays correctly all its data after saving it?
    – Kojo
    Jun 26, 2015 at 13:00
  • You can test my settings here dkyi.ply.st/node/1. User: drupal, pass: drupal
    – Andrew
    Jun 26, 2015 at 13:20
  • Thanks for your efforts ! For tests purposes, I added another text field to your article content type. Make a test : edit node 1, add text to that field and an image, submit. Then go to edit-body tab, submit => new text and image are gone. This is my issue. Sorry, but that module doesn't solve that form API problem. Thank you anyway.
    – Kojo
    Jun 26, 2015 at 14:49
  • Yes. It seems that Node Form Panes module doesn't support some field types. But you can add your custom validate function for the node form and set value for these fields there. If you won't find better solution.
    – Andrew
    Jun 30, 2015 at 9:09
1

It sounds like you have done a lot of work already, and this continues to be a learning experience. Since the form is now managed how you want it and it is only the data management in the save process that is causing you trouble, I would suggest a few code additions. Using hook_form_ID_alter there are two methods you can try.

Method 1 Add a custom validation callback to the form.

$form['#validate'][] = 'MYMODULE_node_data_validate';

Then in your new validation function I suggest loading the node ($node = node_load(nid);) and propagating the data into the form_state. I am not sure this will work, but I have it listed as method 1 because it is the less disruptive path of the two that I have in mind at the moment.

Method 2 Replace the submit handler with your own. Similar to the validate function mentioned above, the idea here is to load the node to get the data that was not present on the form that you edited. However, this time you are going to invoke node_save yourself. Also you will need to end with a drupal_goto or some other method of sending the user someplace. Yes you could add an additional handler to the #submit array in the form variable, but either it will be too late to prevent the change the form is doing or it would just be a different name for method 1.

function hook_form_ID_alter(&$form, &$form_state, $form_id) {
    $form['#submit'] = array( 'hook_something_submit');
}
/**
 * Implements hook_form_FORM_ID_alter();
 */
function vts_nodedit_form_accompagn__node_form_alter(&$form, &$form_state, $form_id) {
    $form['#submit'] = array( 'vts_nodedit_data_submit');
}

function vts_nodedit_data_submit($form, &$form_state) {
  // possible cause of missing data is that the form build populates
  $original = node_load( $form_state['node']->nid );

  // this drupal core function does the work of a full node edit save
  // but we have to have the node to hand it
  // and I do not trust all of $form['node'] in your instance
  field_attach_submit('node', $original, $form, $form_state);

  drupal_set_message(t('The changes have been saved.'));

  if( isset( $form_state['redirect']) ){
    drupal_goto( $form_state['redirect'] );
  } else {
    drupal_goto( '/home' );
  }
}

EDIT
The difference between form_state['input'] and form_state['values'] is that input is the raw $_POST from the user. I think it would be a security concern to use that directly. The other validators that are running (why we use $form['#validate'][] and append instead of replace) are taking the new data from input, cleaning it, and putting it in values. Then if there are blank spots, they fill values with #default_value of the node. Default is typically the existing value. Therefore, a submit handler can ignore form_state[input] and have the benefits of drupal in just the form_state[values] array.
Input vs Values

5
  • I edited the question to reflect search progress. You'll see that form_state['values'] is not faulty. Data is present there, no need to load the node. I'm a bit lost...
    – Kojo
    Jun 26, 2015 at 10:44
  • Good effort so far. My solution2 answer originally was going to be load the node, loop through form_state['values'] and add any changes to the node, then node_save. However, because of the field differences I decided to trace the node.module's form save and found that field_attach_submit function is how drupal core does it. I have not traced the code in panel manager to see how it would differ. Jun 26, 2015 at 15:45
  • Maybe, if you have a few min more to edit your sol2, playing with form_state['values'] & form_state['input'] as per my edit, and make it a bit more clear for my poor mind, I can try it. What is strange moreover is that not all values are emptied, but they aren't displayed anymore ! this is really confusing
    – Kojo
    Jun 26, 2015 at 15:58
  • Another thought that just occured to me, what url are you taken to after save? Jun 26, 2015 at 16:11
  • Same behaviour as node/%node/edit, after submitting on node/%node/photos or others, redirect to node/%node. I insist that what is confusing is that not all values are emptied, but they aren't displayed anymore :(... other confusing point is that form_state[values] contain the correct data...
    – Kojo
    Jun 26, 2015 at 16:16
1

Almost the Graal for that feature, Flexiform project has an Edit Panels Pane display:

Edit an entity using a form embedded in a panel where the base entity is loaded from the panel context.

Just follow these steps :

  1. Add new custom pages in admin/structure/pages, with paths /node/%node/foo, /node/%node/bar and so on
  2. With selection rule node ID is from foo_type
  3. Define its menu as local menu tab
  4. Create some flexiforms, with the fields you wish to
  5. Instead of adding separate fields, add your flexiforms as pane contents

This saves correctly the nodes now, even with a few fields in each form. Only some complex fields such as Inline entity form/Multiple values are buggy...

0

Instead of doing it in code, you should consider using MSNF module

For each step you can define which fields should be included in this step. "Fields" includes core-fields like "Title", "Body" or "Authoring information" as well as fields defined by other modules

You can also consider using Field Group Module if you need simplicity.

Fieldgroup will, as the name implies, group fields together. All fieldable entities will have the possibility to add groups to wrap their fields together. Fieldgroup comes with default HTML wrappers like vertical tabs, horizontal tabs, accordions, fieldsets or div wrappers.

3
  • Thanks for your answer. I'm afraid it doesn't fit my need. I'm looking for a way to split node edition in different pages accessible by tabs, in order to reduce loading charge, not steps or tabs within a same page. I already wrapped fields together in a handmade version with a bit of JQuery. I also tried the fieldgroup multipages display, but none is a solution to load fields as wished.
    – Kojo
    Jun 23, 2015 at 12:33
  • 1
    Field group vertical tabs was my first thought also; good thinking Gokul. Jun 25, 2015 at 19:01
  • Fieldgroup would be perfect if I could ajaxify its tabs. There is Field Group Ajaxified Multipage project, but only for multi step forms...
    – Kojo
    Jun 26, 2015 at 16:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.