Forms API "Widgets", AJAX and non-JavaScript
I've done a couple of projects that needed custom "widgets" to use within Forms API, something that you might write into code with something like.... $form['some_text'] = array('#type' => 'textfield', ....); $form['event_date'] = drupal_widget_date($title, $default, $options....); ... I've done stuff like this for CiviCRM objects like contacts and groups, but the idea of getting these kinds of things into reusable functions seems pretty attractive, since: * It makes it fairly simple to implement some kinds of custom JavaScript widgets transparently. * You could get consistent "fallback" behavior for non-JavaScript capable browsers. * The code is easier to understand, and probably more reliable if you're doing something unusual. Has anyone developed anything along this lines? I'm especially interested in finding easier ways to integrate jquery-type widgets for more flexible kinds of autocomplete (say, combo-boxes instead of the simple pop-up select that autocomplete does now), and integrating safer and saner non-JavaScript behavior to a form field that are more consistent. I'm not sure if I'm being clear enough here, but do any of you have ideas similar to this, and how have you implemented them? Thanks, Rob
I've done a couple of projects that needed custom "widgets" to use within Forms API
A couple of ways I've seen this done are: 1. Add properties to your element and then, in a form_alter module, change the element accordingly. E.g. jscalendar.module in javascript tools, which attaches a 'jscalendar' class to specified textfields. 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 suppose either way has its place. It gets tricky when we want to pass additional data along with a form element. This can be done in a few ways: a. In an id or class (limited) b. In a hidden form field (as with autocomplete) c. Through a drupal_add_js() call of type 'setting'. For an example, see my Javascript patch to check/uncheck and enable/disable modules based on dependencies, http://drupal.org/node/107038 (better yet, be the first to review it!)
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); } } } }
Rob Thorne wrote:
I've done a couple of projects that needed custom "widgets" to use within Forms API, something that you might write into code with something like....
$form['some_text'] = array('#type' => 'textfield', ....); $form['event_date'] = drupal_widget_date($title, $default, $options....); ...
Are you maybe looking for hook_elements? Then you could do: $form['event_date'] = array('#type' => 'my_date', ...); Cheers, Gerhard
That's a good one to know about. Yeah, I'd probably want to do that. The other side of this is: the JavaScript widget. Anybody recommend how to get the effect of a dojo combo box using jquery? Rob Gerhard Killesreiter wrote:
Rob Thorne wrote:
I've done a couple of projects that needed custom "widgets" to use within Forms API, something that you might write into code with something like....
$form['some_text'] = array('#type' => 'textfield', ....); $form['event_date'] = drupal_widget_date($title, $default, $options....); ...
Are you maybe looking for hook_elements?
Then you could do:
$form['event_date'] = array('#type' => 'my_date', ...);
Cheers, Gerhard
participants (4)
-
Dave Cohen -
Gerhard Killesreiter -
Nedjo Rogers -
Rob Thorne