[development] Forms API "Widgets", AJAX and non-JavaScript
Dave Cohen
drupal at dave-cohen.com
Thu Feb 1 04:41:49 UTC 2007
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);
}
}
}
}
More information about the development
mailing list