I'm new to module development and I'm wondering where I would save data inputted from the user for my custom field. I have my own databases table structure so I'll be doing my own sql queries, but I'm really unsure how and where I can actually get the input information when the user hits the submit button. Is it in the hook_widget for the submit operation? If that's the case, when I save something and print out the contents of $field and $items, I don't get anything returned from those arrays so I don't know how to access the data being submitted. Is there an existing module I can refer to as an example?

This is what I have for my code so far. Basically I'm building a custom field for adding rows of ingredients, with 3 columns.

<?php

function ingredients_field_settings($op, $field) {

    switch ($op) {
    
    case 'form':
        return form;
        
    case 'callbacks':
        return array(
            'view' => CONTENT_CALLBACK_DEFAULT
          );
    }
}



function ingredients_field($op, &$node, $field, &$items, $teaser, $page) {

}


function ingredients_widget_info() {
    $info = array(
        'ingredients' => array(
            'label' => t('Ingredients'),
            'field types' => array('ingredients'),
        ),
    );

  return $info;
}

function ingredients_widget_settings($op, $widget) {
  switch ($op) {
    case 'form':
        $form = array();
        $form['num_rows'] = array(
            '#type' => 'textfield',
            '#title' => t('Default number of rows'),
            '#default_value' => $widget['num_rows'] ? $widget['num_rows'] : 10,
            '#size' => 3,
            '#maxlength' => 3,
            '#description' => t('Set the number of ingredient input lines to be displayed by default (user can dynamically add lines later on as needed).'),
            '#required' => TRUE,
        );
      return $form;

    case 'validate':
        if (!isset($widget['num_rows']))
            form_set_error('num_rows', t('You must specify a default number of ingredient input lines  to display to the user'));
        break;

    case 'save':
        return array('num_rows');

    case 'callbacks':
        return array('default value' => CONTENT_CALLBACK_CUSTOM);    
  }
}


function ingredients_widget($op, &$node, $field, &$items, $delta = NULL) {

    switch ($op) {
        case 'form':

            $defaultNumRows = $field['widget']['num_rows'];

            $form = array();
                
            $form['ingredient'] = array (
                '#type' => 'fieldset',
                '#title' => $field['widget']['label'],
                '#collapsible' => TRUE,
                '#collapsed' => FALSE,
                '#tree' => TRUE,
                '#theme' => 'ingredients_form_fieldgroup',
                '#attributes' => array('class' => 'container-ingredient'),
            );
            $form['ingredient']['num_rows'] = array (
                '#value' => $defaultNumRows,
            );
              
            for ($i = 0; $i < $defaultNumRows; $i++) {
                $form['ingredient']['values'][$i] = build_ingredients_input_fields();
            }
        
            return $form;
        
        case 'submit':
            /* database insert queries here? */
            db_query("INSERT INTO {ingredient_groups} (
                            name)
                      VALUES ('%s')",
                               $items['values'][0]['ingredient_name']);   /* unable to see user input data */

            return $form;
    
      return;
  }
}

// Create form values and attributes
function build_ingredients_input_fields() {

  include_once(drupal_get_path('module', 'ingredients') .'/ingredients.inc');

    $form['quantity'] = array(
        '#type' => 'textfield',
        '#maxlength' => 8,
        '#size' => 8,
    );    
    
    $form['units'] = array(
        '#type' => 'select',
        '#options' =>  array('option 1', 'option2'), /*ingredient_get_units(), */
    );  
    
    $form['ingredient_name'] = array(
        '#type' => 'textfield',
        '#size' => 64,
        '#maxlength' => 128,
    );              
    return $form;
}

/**
 *  Theme entire ingredient field form.
 *
 *  Display inline rows of input fields for quantity, units, and ingredient names
 *
 */
function theme_ingredients_form_fieldgroup($form) {

    foreach($form as $line => $element) {

        if ($line == 'values' && is_array($element)) {            
            for ($i = 0; $i < $form['num_rows']['#value']; $i++) {                    
                $rows[] = array(
                    drupal_render($element[$i]['quantity']),
                    drupal_render($element[$i]['units']),
                    drupal_render($element[$i]['ingredient_name'])
                );
            }
        }
    }
    
    $header = array(t('Quantity'), t('Units'), t('Ingredient'));
    $output = theme('table', $header, $rows);

/*    $output .= drupal_render($form);*/
    
    return $output;
}

?>

Another question I have is what am I supposed to implement in my hook_field()? It seems like everything I need is all in my widget functions.

Any help would be greatly appreciated!

Thanks.

Comments

mooffie’s picture

when I save something and print out the contents of $field and $items, I don't get anything returned from those arrays
[...]
$items['values'][0]['ingredient_name']); /* unable to see user input data */

That's because you have:

$form['ingredient'] = array (

You should not hard-code the name of the form element to 'ingredient'. If the administrator types 'Ingies' for the title of this field then the name of this field (and of the form element) would be 'field_ingies'. Never 'ingredient'.

Instead, do:

$field_name = $field['field_name'];
$form[$field_name] = array (
/* two more lines to fix */

(I explained this in the answer I gave you last time.)

That should fix your problem.

I have some more comments. I'll try to write them later. The most important one is that you should move the 'submit' handling code into hook_field. The widget should only deal with the UI. Database handling should be the responsibility of the field itself. Be wary of any 'submit/update/insert' code in hook_widget: it's probable wrong.

miss_michelle’s picture

Thanks for your reply. For some reason when I was referring to $field['field_name'] I always got back a null value, which is why I had to hard-code my field name. Nonetheless, I'm now able to retrieve the field name and the submitted user data. However, when I do the database updates in the submit section of hook_field() I always get double the entries, why is that? Also, what would be the difference between writing code under the submit or update sections?

Can you give me an example of what sort of submit code would be acceptable in hook_widget()?

Thanks!

mooffie’s picture

Hi.

For some reason when I was referring to $field['field_name'] I always got back a null value,

My crystal ball tells me you wrote feild_name instead of field_name. But don't take this for granted, it's an old model.

database updates in the submit section of hook_field() I always get double the entries, why is that?

You mean, the 'submit' hook is called twice? That's strange. Maybe you have some other error there (forgot a 'break' statement maybe?).

But you're not suposed to use the 'submit' hook for saving data. Use 'update' or 'insert'.

what would be the difference between writing code under the submit or update sections?

'submit' --for a field, but more commonly for a node as a whole-- is called just before either 'update' or 'insert' get called. Theoretically, we can do without 'submit'. It is used in rare cases when we want to massage the data a bit so that 'update' or 'insert' get to see something nicer. I think you'd better ignore it for the time being.

Can you give me an example of what sort of submit code would be acceptable in hook_widget()?

No, I absolutely can't. Karen Stevenson, who is the maintainer of CCK, has this to say about the widget's 'submit' hook:

The 'submit' operation for the widget is not being used by any modules now, and it's hard to see why it is needed.
[...]
The widget needs to be able to massage the returned data back into the format that will be stored in the database, which it can do with 'process form values',

mooffie’s picture

Some quick comments (sorry about possible typos):

1.

$form['ingredient']['values'][$i] = build_ingredients_input_fields()

Better remove the ['values']. It's not needed. Besides, it deviates from the norm, of $node->some_field_name[0..n][some_componnet_of_the_field]. Your code could work, but you'll have some terrible headaches in the future.

2.

Maybe a better arraingment is for the ingredient field to be just one ingredient. That is, one row. Checking the 'Multiple values' box will allow the user to enter several rows. You may want to add code to hook_field_setting's 'validate' to mandate checking this 'Multiple values' box.

But... can one node contain several receipts? This complicates matters a bit. We wouldn't know which ingredient belongs to which receipt.

3.

I have my own databases table structure so I'll be doing my own sql queries

Why? It's much easier to let CCK handle the DB for us.

(But currently your ingredient field is actually a matrix. SQL has no 'matrix' data type. So you can not yet have CCK handle the DB for you. That's one reason why I recommended making the ingredient field hold data for one ingredient only --one row.)

4.

I'm wondering where I would save data inputted from the user
[...]
Any help would be greatly appreciated!

You're new to drupal. You chose to skip the 'easy' things like coding forms yourself and... and other Drupal folklore. This may have some repercussions. But you're doing mightily well for a newbie. And you didn't give up. That's nice.

Is there an existing module I can refer to as an example?

There are some CCK fields/widgets nowadays that you can examine.

miss_michelle’s picture

Maybe a better arraingment is for the ingredient field to be just one ingredient. That is, one row. Checking the 'Multiple values' box will allow the user to enter several rows. You may want to add code to hook_field_setting's 'validate' to mandate checking this 'Multiple values' box.

But... can one node contain several receipts? This complicates matters a bit. We wouldn't know which ingredient belongs to which receipt.

How does the administrator determine how many ingredient rows are displayed then? One thing I want to implement later on is having the user dynamically add new rows as he/she needs. So there's a default minimum number of rows initially displayed, for example 5, and if there's a 6th ingredient, we can click on a button to add a new row automatically. Would this feature be able to be easily implemented with the 'Multiple Values' way of designing it? And no, one node cannot contain more than one recipe. However, a recipe can contain multiple groups of ingredients (for example, one list of ingredients for pie filling, and another list for pie topping)... I haven't gone around to implementing this part either, but a user should be able to dynamically add a new ingredient group just like a new ingredient field. (What would be the best way of designing this then?)

Why? It's much easier to let CCK handle the DB for us.

(But currently your ingredient field is actually a matrix. SQL has no 'matrix' data type. So you can not yet have CCK handle the DB for you. That's one reason why I recommended making the ingredient field hold data for one ingredient only --one row.)

Ok, I see what you mean. I will have multiple db tables anyway to store a separate list of measurement units, etc. but I guess just saving the db columns for my main field table would lessen some of the work :)

mooffie’s picture

How does the administrator determine how many ingredient rows are displayed then?

One doesn't. The widget has the only say in this. There's a talk about changing this.

Currently, for example, widgets display 3 empty fields past the filled-out ones. Recipes, by nature, have much more than 3 ingredients, so it would be wise to initialy show ~15 fields (that is, rows).

So there's a default minimum number of rows initially displayed, for example 5, and if there's a 6th ingredient, we can click on a button to add a new row automatically. Would this feature be able to be easily implemented with the 'Multiple Values' way of designing it?

You're on your own when you code the GUI.

The current 'Text' and 'Number' widgets are brain-dead simple: they don't allow you to add fields "dynamically." If you want more fields, you save the node and then return to edit it. You'll find 3 extra empty fields.

[...] and if there's a 6th ingredient, we can click on a button to add a new row automatically. Would this feature be able to be easily implemented

The simplest solution I see is to put 50 extra rows on the form(!). But, using Javascript, to hide the excessive ones. The 'Add row' button would simply reveal, via Javascript, the next hidden row.

no, one node cannot contain more than one recipe. However, a recipe can contain multiple groups of ingredients (for example, one list of ingredients for pie filling, and another list for pie topping)... I haven't gone around to implementing this part either, but a user should be able to dynamically add a new ingredient group just like a new ingredient field.

What would be the best way of designing this then?

I don't know.

zywieco’s picture

subscribing