On Tuesday 30 January 2007 20:45, Nedjo Rogers wrote:
I've done a couple of projects that needed custom "widgets" to use within Forms API
2. Define a custom #type, so that you will have full control over output (e.g., define your own theme function). This is initially attractive, but in the end a limited approach, since you lose the existing properties of a #type.
I pounded my head against this issue. I created a module to link various drupal objects together in flexible ways, using a concept of triples (subject, predicate, object) which has been mentioned on this list before. I wanted a special widget to connect nodes together. In my case, I wanted a node edit form to show list other nodes connected to the current node as checkboxes, and also provide a textarea where new node titles could be typed to add to the list. So to define my form element, I wanted something like the following. In this case, we're editing a node of type 'site', which has one or more 'master' nodes associated with it, as founders. Notice the form element is of type 'triples_node_set' and the relevant data is passed in an attribute called '#triples'. $form['founder'] = array('#type' => 'triples_node_set', '#title' => t('Founding Master'), '#description' => t('Which master founded this site? <br/>Use the text field below to add a name. Enable javascript for auto-complete feature. <br/>Note that you must first enter the master\'s name in the database. [%link]<br/>Note also that the preview button will not reflect changes made here. You must hit submit to see the changes.', array('%link' => l(t('add master'), 'node/add/jonang_master', array('target' => '_blank'))) ), '#triples_object_node_type' => 'jonang_master', '#triples' => array('subject_type' => 'node', 'subject_id' => $node->nid, 'predicate' => 'founded_by', 'object_type' => 'node', ), ); So the trick is to preserve all that '#triples' info with the form when it is submitted. The work is done in the process function. In this case, we build a set of checkboxes and a textfield, wrapped in a fieldset. But most importantly, what our process function returns includes the original $element (which has the #triples data) and it artificially sets the '#parents' array so that the data can be found later. It's a lot trickier than it would ideally be, but it can be done. function triples_node_set_process($element) { //drupal_set_message('triples_node_set_process' . dprint_r($element, 1)); $data = $element['#triples']; $triples = triples_load_all($data); if ($triples[$data['predicate']]) { // display a checkbox for existing relationships foreach ($triples[$data['predicate']] as $triple) { $node = node_load($triple->object_id); $checkboxes[$triple->triple_id] = array('#type' => 'checkbox', '#title' => $node->title, '#default_value' => 1, ); } } // display a form for creating new relationships // use comma-separated list of node titles // TODO: autocomplete $addfield = array('#type' => 'textfield', '#title' => t('Add'), '#description' => t('Comma-separated list of names'), '#autocomplete_path' => 'triples/autocomplete/node/' . $element['#triples_object_node_type'], ); // put everything in a fieldset $fieldset = $element; $fieldset['#type'] = 'fieldset'; $fieldset['checks'] = $checkboxes; $fieldset['add'] = $addfield; $fieldset['#collapsible'] = TRUE; $fieldset['#tree'] = TRUE; unset($fieldset['#process']); return array('#tree' => TRUE, // include the converted element in the form 'element' => $fieldset, // also have the form return the extra data we will need later 'triples' => array('#type' => 'value', '#value' => $element, '#parents' => array('triples', 'triples_'._triples_count(), ), ), '#weight' => $fieldset['#weight'], ); } Here's a submit function which finds and uses the #triples data passed in the original form element: function triples_form_submit($form_id, $form_values) { //drupal_set_message("triples_form_submit($form_id)". dprint_r($form_values, 1)); // save all triples in the form: if (count($form_values['triples'])) { foreach ($form_values['triples'] as $element) { $value = $form_values; // learn the submitted value foreach ($element['#parents'] as $key) $value = $value[$key]; // learn the triples data $data = $element['#triples']; // debug //drupal_set_message('triples_submit_form, value and triples data:' . dprint_r($value, 1) . dprint_r($data, 1)); // invoke handler $func = $element['#type'] . '_submit'; if (function_exists($func)) { $func($element, $value); } } } }