Recreating Nodes with Ajax: Creating Copies of Existing Nodes Quickly in Multiple Forms

Let's say you've got an auction site like eBay where sellers get to list the items they own on your website. Each seller list their items by creating a node for the specific item and filling in umpteen number of textfields which denote the price of the item, color, brand, condition, warranty, shipping cost, and so on and so forth. As you can guess, this becomes a tedious task if you have a lot of items as it involves quite a number of fields to fill over and over again. For example, if the user wanted to list 5 identical Sony Xperia cellphones but in different colors, imagine having to fill the same details 5 different times.

So now, you've come up with a really good idea where the user doesn't have to go through this monumental task of creating a node, filling in the same details with regard to the color and then saving the node. What if, we provide the user with the ability to just re-create the same node with the touch of a button? And what if, this is an ajax button so the user can just re-list the nodes without having to reload the page. Now, for the Sony Xperia cellphone, all they would need to do is click edit on the created nodes and just change the color of the phone. Great! Now, how do we do that? Simple. We just make a call to drupal_render() and print the same form multiple times right? Um...yes..but, there's just a bit more, because this is an AJAX form. So, this is how we get started.

Relist Items

First, we create our AJAX form which only needs one button, which says 'Relist Items'.

* Function which will create a form with just one button in it. This form will
* hold the relist button which will load a previously created node.
function mymodule_relist_node_button_form($form, &$form_state, $node_info) {
  //Set the form tree structure
  $form['#tree'] = TRUE;
  //Store the nid in the form
  $form['relistnodes_relist_nid']['nid'] = array(
    '#type' => 'value',
    '#value' => $node_info['nid']
  //Create the relist button
  $form['relistnodes_relist_button'][$node_info['nid']] = array(
    '#type' => 'submit',
    '#name' => 'RelistNodes_relist_button_' . $node_info['nid'],
    '#value' => 'Relist Items',
    '#submit' => array('mymodule_RelistNodes_button_submit'),
    '#prefix' => '<div class ="mymodule_RelistNodes_button_wrapper" 
      id="mymodule_RelistNodes_button_wrapper-' . $node_info['nid'] . '">',
    '#ajax' => array(
      'callback' => 'mymodule_RelistNodes_button_ajax_callback',
      'wrapper' => 'mymodule_RelistNodes_relist_message_wrapper',
      'method' => 'prepend',
    '#suffix' => '</div>',
  return $form;
* This will inform the user that the new node has been created, if everything went successfully.
* Otherwise, it will display a message notifying them of the error that occurred while relisting.
function mymodule_RelistNodes_button_ajax_callback($form, &$form_state) {
  return $form_state['RelistNodes']['Message_To_User'];
* This function will load the node that the user wants to relist and save the values of this
* old node as a new node.
function mymodule_RelistNodes_button_submit($form, &$form_state) {
  //Enable the global user object
  global $user;
  //Load the node based on the nid they clicked
  $node = node_load($form_state['triggering_element']['#parents'][1]);
  //Save the node as a new node if the creator of the nid and the current user are the same
  if ($node->uid == $user->uid) {
    //Create a new node object.
	$newNode = (object) NULL;
	$newNode->type = 'product';
	$newNode->uid = $user->uid;
	$newNode->created = strtotime("now");
	$newNode->changed = strtotime("now");
	$newNode->status = 1;
	$newNode->comment = COMMENT_NODE_OPEN;
	$newNode->promote = 0;
	$newNode->moderate = 0;
	$newNode->sticky = 0;
	$newNode->language = 'und';
	//Prepare the node
	//Now save the item details into the node
	$newNode->item['id'] = $node->item['id'];
	$newNode->item['title'] = $node->item['title'];
	$newNode->item['color'] = $node->item['color'];
	$newNode->item['quantity'] = $node->item['quantity'];
	$newNode->item['condition'] = $node->item['condition'];
	$newNode->item['warranty'] = $node->item['warranty'];
	//...and so on and so forth...
	//Now save the node
	//If the new node saved correctly, we will get a nid.
	$newNodeNid = $newNode->nid;
	//Output a message to the user.
	if ($newNodeNid) {
	  //Add a success message to the user about the creation of the new node.
	  $form_state['RelistNodes']['Message_To_User'] = t('You have successfully relisted this item. Click !here to edit
	    your new item to make changes.', array(
	  	  '!item' => l(t('here'), 'node/' . $newNodeNid . '/edit'))
	else {
	  //Else, if the node was not created, we output a message accordingly.
	  $form_state['RelistNodes']['Message_To_User'] = t('Something has gone wrong and we could not relist this item.');
  //Rebuild the form
  $form_state['rebuild'] = TRUE;

But this isn't enough, since we are rendering the same form multiple times, we need to make sure each form gets their own form id so that they can be differentiated. In order to this, we need to register the form in hook_forms(). The hook_forms() function, basically, maps form_ids to form builder functions. This is how we do this:

 * Implementation of hook_forms().
function mymodule_forms($form_id, $args) {
  $forms = array();
  if (count($args)) {
    if (is_array($args[0])) {
        //If the relist nodes button form was requested, then call the following function to generate the form
        //The form id will be the nid that the user wants to the re-create.
        if ($form_id == 'mymodule_relist_node_button_form_' . $args[0]['nid']) {
          $forms[$form_id]['callback'] = 'mymodule_relist_node_button_form';
  return $forms;

Now, what we need to do is create a function (mymodule_relist_items()) which will grab all of the user's previously created items and display them in a table along with the relist node button form.

* Function to display list of user's previous nodes.
function mymodule_relist_items() {
  $output = '';
  //This is where we print the success message everytime a user relists a node.
  $output .= '<div class="mymodule_RelistNodes_relist_message_wrapper"></div>';
  //This is where you print the ajax relist node form.
  foreach ($users_nodes as $nid => $node) {
    //Do more processing here
	$rows[] = array(
	  array('data' => drupal_render(drupal_get_form('mymodule_relist_node_button_form_' . $nid, array('nid' => $nid))), 
            'class' => array('relist_button_form')),
  //Create the table using theme()
  $output .= theme('table', array(
    'header' => $header,
    'rows' => $rows
  $output .= theme('pager');
  return $output;

And finally, we create a display table that gives the user the option to relist their previous nodes in the node/add page. We can do this in the hook_form_alter() function or hook_form_FORMID_alter() function. For our particular case, we will use hook_form_FORMID_alter().

 * Implementation of hook_form_FORMID_alter().
function mymodule_form_item_node_form_alter(&$form, &$form_state, $form_id) {
  //Display the relist items table as a suffix in the node/add form so the user can 
  //choose to relist a previously created node.
  $html = mymodule_relist_items($user->uid);
  $form['#suffix'] = '<h2>' . t('Relist One of Your Previous Items') . '</h2>' . $html;

So, now, when the user goes to create a similar node, they will get a list of the previsouly created nodes, and all they would need to do is click the 'Relist Items' ajax button and it will automatically create a new node with all the items and item details already filled in. All the user needs to do is edit the node and make the appropriate changes to the items. All in all, it saves a ton of work for users creating multiple nodes with similar items to list. Which in turn, makes your site more user-friendly and efficient.