I'm not sure if this is a problem specific to this module, or specific to the way I've implemented it. I've basically copied the code from the todos module into my module, and renamed some of the functions.

Unfortunately, when I try and submit to gain todo items in my forms (which I am going to fix later to suit my module), an error message comes up saying that 'mymodule_node_form' does not match 'mynodetype_node_form' and there are bad parameters for my form.

This problem doesn't happen without the Ahah form element, so I assume there is some problem with my implementation of the protocol.

If it is necessary, I'll split my module into two modules, but I feel that there should be a way to handle this.

I'll provide some code example later tonight.

Dave

Comments

starbow’s picture

I am not really clear on this issue. Maybe it will become clearer after you submit your code.
cheers,
-t

dwees’s picture

Here is the relevant code:


/*
 *  Implementation of hook_node_types
 */
function hook_node_types() {
  return array('scene', 'character');
} 

/*
 *  Implementation of hook_node_info
 */
function screenplay_node_info() {
  return array( 
    'scene' => array( 
      'name' => t('Scene'), 
      'module' => 'screenplay', 
      'description' => t("A scene is an individual scene in a movie.  They may be structured so that one scene is a 'child' of another scene, and therefore from this you can create 'acts'."), 
    ),
    'character' => array( 
      'name' => t('Character'), 
      'module' => 'screenplay', 
      'description' => t("A character is a construct that is used to deliver lines and perform actions in a scene.  You can edit character attributes here, and have those matched to the scenes."), 
    )
  );
}

/*
 *  Implementation of hook_form
 */
function screenplay_form(&$node, &$param) {
  // Will need to split the logic up based on node types, either scene or character.
  //$type = node_get_types('type', $node);

  $form = array();

  // In this form, we want to include the ability to:
  // 1.  Add a line-type to the scene (hopefully dynamically).  These line types should be:
  //     a) Dialog
  //     b) Action
  //     c) Stage directions
  // 2.  Scenes should also be interfaces to quickly add characters (without backgrounds or images).
  // 3.  Should be able to create a setting for a scene, this can also be used to include some 
  //     background material for the scene
  if ($node->type == 'scene') {

    $form['title'] = array(
      '#type' => 'textfield',
      '#title' => t('Name'),
      '#description' => t('Enter a name of this scene.  Your scene name should be unique (so it is easily identifiable) but this is not required.  This field is required.'),
      '#required' => TRUE
    );
		 
    $form['body'] = array (
      '#type' => 'textarea',
      '#title' => t('Background'),
      '#description' => t('Enter some background information about this character')
    );

    $options = array(
      t('<-select->'),
      t('add character'),
      t('add dialog'),
      t('add action'),
      t('add stage direction')
    );
	 
    $form['screenplay_action'] = array(
      '#type' => 'select',
      '#title' => t('Scene action'),
      '#description' => t('Perform an action for this scene.'),
      '#options' => $options,
      '#default_value' => 0
    );
	 
    // To create the extra bit of the form, we may need to use hook_form_alter?
    $form['items'] = array (
      '#type' => 'item',
      '#ahah_bindings' => array( 
  	array (
         'selector' => 'input.todos_item_remove',
         'event' => 'click',
         'wrapper' => 'todos_items_wrapper',
         'path' => 'todos/todos_update_js'),
        array (
         'selector' => 'select.todos_item_weight',
         'event' => 'change',
         'wrapper' => 'todos_items_wrapper',
         'path' => 'todos/todos_update_js')) 	
    );

    if( module_exists ('ahah_forms') ) {

      $form['items'] += dynamic_subform_get_embedded('screenplay_node_form', 'screenplay_items_subform', $node);

    } else { // for non-ahah

      $form['items'] += screenplay_items_subform($node);

    }
  }

  // In this form, we want to include the ability to:
  // 1.  Upload an image for the character
  // 2.  Pick a color for the characters lines.  This be restricted to available colors
  // 3.  Add background information for a character
  if ($node->type == 'character') {
    // Add the module specific style information and js for the color picker
    $path = drupal_get_path('module', 'screenplay');
    drupal_add_css($path . '/css/js_color_picker_v2.css');
    drupal_add_js($path . '/js/color_functions.js');
    drupal_add_js($path . '/js/js_color_picker_v2.js');
    drupal_add_js('var form_widget_amount_slider_handle = "' . url($path . '/images/slider_handle.gif') . '"', 'inline');
		 
    $form['title'] = array(
      '#type' => 'textfield',
      '#title' => t('Name'),
      '#description' => t('Enter a name for your character.  This can be a first name, and a last name, or just a nickname, but it will be what is displayed for your character throughout the scenes he/she is in.  This field is required.'),
      '#required' => TRUE
    );
		 
    $form['body'] = array (
      '#type' => 'textarea',
      '#title' => t('Background'),
      '#description' => t('Enter some background information about this character')
    );
		   
     /*$form['image'] = array(
       '#type' => 'file',
       '#title' => t('Image Upload'),
       '#description' => t('Upload Character Sketch or Photo'),
       '#size' => 50
     );*/
		 
     $form['color'] = array(
       '#type' => 'textfield',
       '#title' => t('Character Colour'),
       '#description' => t('Pick the colour you want the lines and actions of this character to be displayed.'),
       '#attributes' => array('onclick' => 'showColorPicker(this,this)'),
       '#required' => TRUE
     );
		 
     //$form['#attributes']['enctype'] = 'multipart/form-data';
  }
  
  return $form;
}

function screenplay_items_subform($node, $form_values=null, $pass=null) {
	if( is_array( $node ) ) drupal_set_message( "screenplay_items_subform: missing node!");
	if( !empty($form_values) && !is_array($form_values)) drupal_set_message( "screenplay_items_subform: bad form_values" );

	// get the pre-existing items - can be in either $edit or $node
	$items = _screenplay_extract_items($form_values, $node);

	// process add and remove commands - modifies $items
	$items = _screenplay_process_commands($form_values, $items);

	// expand the $items array into form elements - modifies $element
	$todos = _screenplay_expand_items($items);
	// add a control block for adding new items - modifies $element

	$todos += _screenplay_add_control($form_values, $pass);
	
	return $todos;
}

/**
 * Extract the items from the form_values array or
 * If post is empty, we may be doing an edit -> load into node
 * Or fresh add with no todoss
 */
function _screenplay_extract_items(&$edit, $node) {
        $items = array();
	if( is_array( $edit['items'] ) ) {
		$items = $edit['items'];
	}
	elseif( is_array($node->items) ) {
		// todoss have been loaded into node (by apinode:load)
		$items = $node->items;
		$edit['next_id'] = $node->next_id;
	}
	return $items;
}

/**
 * Modify the items array based on commands in the $edit
 */
function _screenplay_process_commands( &$edit, $items ) {
        if( is_array($edit) && count($edit) > 0 ) {
		// process the potention addition
          if( $edit['op'] == t( 'Add New Item' ) ) {
	    $new_item = $edit['new_item'];
	    if( $new_item['text'] ) { // we are good to do add
	       $item_id = $edit['next_id']; // get distinct id for item 
               $edit['next_id'] = $edit['next_id'] + 1;
	       $items[$item_id] = $new_item;
	       // remove from params, so it doesn't show up on the form (consume), but only on successful add
	       unset( $edit['new_item'] );
	    }
          }
	  // now process the removes
	  foreach( $items as $item_id => $item ) {
	    if( isset( $item['remove'] ) && $item['remove'] == 1 )  {
        	unset( $items[$item_id] );
            }
          }
	}
	return $items;
}

/**
 * Expand the items into form elements
 */
function _screenplay_expand_items( $items ) {
	$element['items'] = array (
	    '#tree' => TRUE,
	    '#id' => 'todos_items',
	    '#theme' => 'todos_items',
	);
	foreach( $items as $item_id => $item ) {
		$element['items'][$item_id] = array(
		  	'#tree' => TRUE,
		);
		$element['items'][$item_id]['text'] = array(
		  	'#type' => 'textfield',
		  	'#default_value' => $item['text'],
		);
		$element['items'][$item_id]['weight'] = array(
		  	'#type' => 'weight',
		  	'#default_value' => $item['weight'],
		    '#attributes' => array( 'class' => 'todos_item_weight' ),
		);
		$element['items'][$item_id]['remove'] = array(
		  	'#type' => 'checkbox',
		  	'#default_value' => $item['remove'],
		    '#attributes' => array( 'class' => 'todos_item_remove' ),
		);
	}
	return $element;
}

/**
 * control to add item to todos
 * @param $edit: values posted from the form
 * @param $pass: 
 *  	validate_pass: building to sanitize $_POST -  never use #value, only #default_value.
 * 		render_pass: building to render to html - ok to use #value. 
 */
function _screenplay_add_control($edit, $pass=null) {
	$element['new_item'] = array (
	    '#type' => 'fieldset',
	    '#title' => t( 'Add Item' ),
	    '#tree' => TRUE,
	);
	$element['new_item']['text'] = array (
	    '#type' => 'textfield',
	    '#title' => t( 'Text' ),
	    '#size' => 40,
	    '#maxlength' => 128,
	);
	$element['new_item']['weight'] = array (
	    '#type' => 'weight',
	    '#title' => t( 'Weight' ),
	    '#delta' => 10, 
	);
	if ($pass=='render_pass') {
		$element['new_item']['text']['#value'] = $edit['new_item']['text'];
		$element['new_item']['weight']['#value'] = empty($edit['new_item']['weight']) ? 0 : $edit['new_item']['weight'];
	}
	$element['new_item']['add_item_button'] = array (
	    '#type' => 'button',
	    '#value' => t( 'Add New Item' ),
	    '#id' => 'todos_add_item_button', 
	    '#ahah_bindings' => array ( 
    		array( 
		    	'wrapper' => 
                           'todos_items_wrapper',
                           'event' => 'click',
                           'path' => 'todos/todos_update_js',
  			),
  		),
	);
 	$element['next_id'] = array( 
          '#type' => 'hidden',
          '#value' => isset( $edit['next_id'] ) ? $edit['next_id'] : 0,
        );	
	
   return $element;
}

starbow’s picture

Ah. Your problem is that you are using todo/todo_update_js as the path in your bindings. todo_update_js is not a general submission function. It only works if you are submitting todo forms. You will need to copy that function into your module and adapt it to use your form:

function screenplay_update_js() {
	$items = dynamic_subform_get_prepped( 'screenplay_node_form', 'screenplay_items_subform', null	);	
	print theme('status_messages') . drupal_render($items);
}

Also, I notice that you are using the #theme = 'todo_items' to establish the 'todos_items_wrapper' wrapper for your dynamic items. This might work, but is a messy way to do it. I would recommend that you copy the theme_todo_items over into your module as theme_screenplay_items and then create and use your own wrapper, such as 'screenplay_items_wrapper'.

The todo.module is an example module, not a utility module. You should not have the string 'todo' show up anywhere in your module.

dwees’s picture

Status: Active » Closed (fixed)

I was just being lazy and trying to copy over your code, and I guessed I missed an important bit. :)

Dave