Hi,
I get the fields of an entity but I don't get the properties.
How do I also get the entity properties?

CommentFileSizeAuthor
#3 multiple-entity-form-2044449-01.patch1.04 KBrobertwb
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

joachim’s picture

Status: Active » Fixed

There's no system in Drupal or Entity API to determine which of an entity's properties appear in the form, or how to present them.

So this is not something that could be implemented, without special handling for every entity type.

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.

robertwb’s picture

Issue summary: View changes
Status: Closed (fixed) » Active
FileSize
1.04 KB

@Joachim - Hope you don't mind me re-opening this, but if I understand what @BruFFy wants, it is not too terribly hard. Or at least, it is not too terribly hard to hack. The attached patch adds an "extras" key in the array passed to multiple_entity_form, which contains any properly formatted field API field. It is pretty simple to pull the property fields from the entity's own base form. The code below shows an example of how to do this.

I would be interested in whether this seemed useful or not? If not, feel free to mark this closed again :) Thanks for your time.

function my_form($form, &$form_state) {
  // Define an array of data for the entities to show in the form.
  $entity_data = array();
  // First entity: node 1.
  $entity = entity_create('dh_boreholelog', array());
  $baseform = entity_ui_get_form('dh_boreholelog', $entity, $op = 'create', array());
  $bhl = array(
    'entity_type' => 'dh_boreholelog',
    'entity' => $entity,
    'fields' => array('bhlid'), // have to put something here or drupal goes into endless loop (maybe a bug??)
    'properties' => array('diameter','fromdepth'),
    'fieldset' => t('My First Node'),
  );
  foreach ($bhl['properties'] as $prop) {
    if (isset($baseform[$prop])) {
      //drupal_set_message("Found $prop in base form.");
      $bhl['extras'][$prop] = $baseform[$prop];
    }
  }
  $entity_data[] = $bhl;
  $form += multiple_entity_form($form, $form_state, $entity_data);
  // Add your submit button and other form elements you need.
  return $form;
}
joachim’s picture

Thanks for the patch, but I don't really get what this is doing.

Presumably, you could add your extras for the form array yourself after you've called multiple_entity_form().

Also, while you're adding things from $baseform[$prop], where are you processing them on submit?

robertwb’s picture

Thanks for the response - in general, this was just a proof of concept so that I could see if you thought it potentially worthwhile - so it's not perfectly functional yet. To your questions:

  • "what this is doing": Well, I'm *hoping* to do what you said couldn't be done :) - because I think that the utility of this module would be enhanced greatly by this ability. For example, the "title" field on a node is a property, something that I imagine would be nice to do. When you get to custom Entities that have many properties, this becomes critical IMO.
  • "you could add your extras for the form array yourself": Well certainly, but the same could be said about this entire module. By doing it this way you don't have to include extra code to determine the unique form-key that this module generated for each. Also, I think that this is a first step only, I think I'm going to be able to add all of this into the module, passing only a list of desired properties in and handling the addition of form elements in the multiple_entity_form handling code. Since we can determine the name of the form function in Entity API, we can codify the retrieval of the form and the subsequent grabbing of the field
  • "where are you processing " - that's my next step :). To that, it looks like "extra" is not a very good name, "properties" might be better since then the form handler function can manage the properties specifically.
joachim’s picture

> To that, it looks like "extra" is not a very good name

Agreed.

> "properties" might be better

Hmm... not so sure, as 'properties' potentially means Entity API metadata properties. 'Base form elements' is what I might say.

> the form handler function can manage the properties specifically.

That's a reasonable assumption, but nonetheless it's an assumption. There could be entity types that massage the form values before they save. The node author form element is one such example: it takes a user name, and node form submit handler does a lookup to get a uid. Those are the things you just can't account for.

robertwb’s picture

> Those are the things you just can't account for.

Why not just call the entity's own form handler in "multiple_entity_form_submit()"? With your current solution, it makes sense to only deal with the fields, but it seems that since you are creating well-formed FORMs, once you add entity properties and other custom 'base form elements' we could avoid the extra code altogether? Once we use entity_ui_get_form() to pull the form, we also get the handler passed back in $form['#submit']. Now, this might not be the best default behavior as you would encounter some malformed/incomplete data (especially on new records), but it would provide powerful functionality.

I will post up a draft after I explore this behavior more fully. Thanks for the attention on this.

joachim’s picture

> Why not just call the entity's own form handler in "multiple_entity_form_submit()"?

Several reasons.

First is that if you don't have all of the entity form's elements, then the submit handler will probably get confused and look for form values that aren't there.

Second, the Field Attach API has handling to deal with not being at the top level of a form, because they have to wor on all sorts of forms. Entity forms won't, since the submit handler is designed to work on only on the one form.

robertwb’s picture

> then the submit handler will probably get confused and look for form values that aren't there

My experience with this handler is that it doesn't get confused, it just moves on as if it were a null value. The coder of the multiform is of course responsible for making sure that all required fields are present.

> Entity forms won't, since the submit handler is designed to work on only on the one form.

Yes, but wouldn't this code (under ISOLATE FORM) essentially isolate the data for a given entity as if it were a top-level form?


  foreach ($form['#entity_form_keys'] as $form_key) {
    $entity_type  = $form[$form_key]['#entity_type'];
    $bundle       = $form[$form_key]['#bundle'];
    $entity       = $form[$form_key]['#entity'];
    $field_names  = $form[$form_key]['#field_names'];
    // ISOLATE FORM
    $singleform = $form[$form_key]; // isolate the form entry for a single standalone entity to pass to the entity's ow form handler
...
joachim’s picture

> $singleform = $form[$form_key]; // isolate the form entry for a single standalone entity to pass to the entity's ow form handler

That works for the form part, but the $form_state array is a different matter. IIRC, this module uses #tree to have the different entities' values in sub-arrays in $form_state['values']. FieldAPI knows to expect this. The problem is that entity forms might not. Also, there's a bug of sorts in core -- an inconsistency at least -- whereby you can't set #tree to FALSE have values collapse to a flat array again.

robertwb’s picture

@joachim -- thanks for the explanation. As you predicted, it was not as straight forward as "just working". It assembled nearly identical $form with a few exceptions that may prove critical. The approach that I took was to add a key to the form called "#use_base_form_handler", and then look for that handler in multiple_entity_form_submit() - to allow testing both methods without breaking the existing, working Ctools field based method. If I can get this to work, the entity based method should also handle any fields that are attached.

But, the $form_state is not behaving, as you suggested. What's odd is that while there are the 'values' and 'inputs' arrays on the form_state, I am testing with a form that has 4 iterations in it, but the form_state array only shows values for the last iteration of the form... not sure what's going on there.

    if ($form['#use_base_form_handler']) {
      drupal_set_message("Calling the base form handler for entity of type $entity_type");
      $entity = entity_create($entity_type, array());
      $entity_form_info = entity_form($entity_type, $entity);
      //$entity_form_info = entity_ui_controller($entity_type);
      drupal_set_message("Form <pre>" . print_r((array)$form[$form_key],1) . "</pre>");
      $submit_callback = $entity_form_info['#submit'][0];
      $form_state['complete form'][$form_key]['entity_type'] = $form_state['complete form'][$form_key]['#entity_type'];
      drupal_set_message("Form State <pre>" . print_r((array)$form_state['complete form'][$form_key],1) . "</pre>");
      if (function_exists($submit_callback)) {
        drupal_set_message("Calling $submit_callback ");
        $submit_callback($form[$form_key], $form_state['complete form'][$form_key]);
      }
    } else {
//use the working ctools field form handling
joachim’s picture

Oh, of course, yet -- the entity's native form submit handler will be wanting to call Field Attach API itself. And it's going by what it believes to be its own form structure, which is not what it's got.

robertwb’s picture

@Joachim - again, thanks for the help and guidance. I have spent a bit of unproductive time on this one ... unless you count learning a small bit about the ins and outs of this stuff. But, I have to ask, you did the Editable Views module, which does much the same thing as this, albeit for only multiple instances of the same entity -- and EV supports Fields and Properties. I am really wanting to have a robust, visually appealing multi-entity solution, and finding both EV and this one come up short, in different ways.

Do you think that the approaches of those two modules can be merged somehow? I am willing to spend a good bit of time on this. Thanks!

joachim’s picture

Adding a list of properties is a good idea.

We'd have to make the same assumption as Editable Views, which is that the properties requested can be edited with a simple textfield form element. Also, you'd get no validation.

In the submit helper, after the entity's been passed to FieldAPI, just get a wrapper for it and set each property value on it.

robertwb’s picture

> In the submit helper, after the entity's been passed to FieldAPI, just get a wrapper for it and set each property value on it.

I am working on some code for this now.

robertwb’s picture

@joachim - back on mining some code from EditableViews. Question: is the code to produce generic text inputs for Entity properties in EditableViews located in "editableviews_handler_field_entity_field_edit.inc"?

thanks

joachim’s picture

Um, probably? It's a very long time since I've worked with the code of this. Chuck in some dsm() statements and find out ;)

robertwb’s picture

:) - kinda wondered if that would be the case for you! Thanks for that almost endorsement - on it!

robertwb’s picture

I am creeping through this one ever so slowly, but the pieces of code that I think really handle most of the lifting here are located in "editableviews/handlers/editableviews_handler_field_entity_metadata_property.inc", and are:


  function edit_form_element_textfield(&$element, &$form_state, $wrapper) {
  ...
  }

  /**
   * Create a select element.
   *
   * @param &$element
   *  The element to alter.
   * @param &$form_state
   *  The form state.
   * @param $wrapper
   *  The wrapper for the entity whose property is to be shown in the element.
   */
  function edit_form_element_select(&$element, &$form_state, $wrapper) {
    // Just do the same thing as node_content_form().
  ...
  }

  /**
   * Create a checkbox element.
   *
   */
  function edit_form_element_checkbox(&$element, &$form_state, $wrapper) {
   ...
  }

robertwb’s picture

And this handles after submission:

  function edit_form_submit($entity_type, $entity, &$element, &$form_state) {
    // Get the value out of the form state.
    $value = $this->get_element_value($element, $form_state);
    if ($this->options['reverse_boolean']) {
      $value = !$value;
    }

    // We can set this on the wrapper with inpunity, as the validate step
    // already caught any exception this might throw.
    $wrapper = entity_metadata_wrapper($entity_type, $entity);
    $wrapper->{$this->options['property']}->set($value);
  }