Hi guys

I'm discovering the Ctools modal framework, and thanks to Roger López and his post at http://zroger.com/node/31, I'm understanding the basics, and it rocks !

Now I want to do something else. I created a view that lists nodes of a specific type, and in each row I added the field 'Edit link', so this link points to node/[nid]/edit. My goal is to make this node edit form appear in a modal using Ctools. With this, the user will be able to edit the node directly in the modal, and ideally after clicking the Submit button he'll be redirected to same view but refreshed so the changes appear immediately. After many trials - like creating a custom link in the view pointing to test/%ctools_js/go and altering this menu entry in a custom module - I can't find even make the form appear in a modal.

Maybe I'm on the wrong track, so any hint and how-to would be greatly appreciated. Thanks in advance.

Comments

DjebbZ’s picture

I'm on something, and I would love some feedback, because I'm stuck here...

First, let me sum up how Modal framework works to make sure I'm not doing nonsense.
To create a modal, you register a menu to this modal with a callback function with the ctools operation. You also need to somehow create a page that holds this link in the form of "test/%ctools_js/go". Right ?

I changed a bit what I said before. I created a custom field in the view UI that is the text "Upload a new version" output as a link to the path upload/%ctools_js/go with the proper class ctools-use-modal, so I have my link to the modal here. I registered this path in hook_menu. But the page does not appear in a modal, but in a normal page.

Here's my code for you to review, with comments on the problems I encountered :

/**
 * This hook is called at the very beginning of views processing, before anything is done.
 * It includes the ctools modal framework. I suppose it works, because the js are included in the page and my link to the modal has the class 'ctools-use-modal-processed'
 */
function mymodule_views_pre_view(&$view, &$display_id, &$args) {
	if($view->name == 'filebrowser' && $display_id == 'page_1') {
		ctools_include('modal');
  	ctools_modal_add_js();
	}
}

/**
* Implementation of hook_menu().
* Registers the link which calls the form in a modal if possible.
*/
function mymodule_menu() { 
  $items = array();
  $items['upload/%ctools_js/go'] = array(
    'page callback' => 'mymodule_modal_callback',
    'page arguments' => array(1),
    'access arguments' => array('access content'),
  );
  return $items;
}

/**
 * Menu callback that create the modal and its content, with a fallback to the normal page. 
 * Here I replaced my call to the 'file_node_form' I want to show with the classic 'user_login' form,
 * because I have problems showing a node edit form - I don't know how to pass the node object to the form, since the link doesn't even hold the node id.
 * Even when callin the 'user_login' form, this form doesn't appear on a modal, but on a normal Drupal page. I'm missing something...
 */
function mymodule_modal_callback($js = FALSE) {
  // whatever I do, $js is always false
  if ($js) {
    ctools_include('ajax');
    ctools_include('modal');
    $form_state = array(
      'ajax' => TRUE,
      'title' => t('Login'),
    );
    $output = ctools_modal_form_wrapper('user_login', $form_state);
    if (empty($output)) {
      $output[] = ctools_modal_command_loading();
      $output[] = ctools_ajax_command_redirect('filebrowser');
    }
    ctools_ajax_render($output);
  }
  else {
    return drupal_get_form('user_login');
  }
}
DjebbZ’s picture

Ok I found something and the modal appears now !

It seems that the call to the ctoos modal framework, aka ctools_include('modal'); ctools_modal_add_js(); , doesn't work where call in the view preprocess. So I added the code in the view's header, and now the modal stuff works ! Ahaaa :) Well... it's a bit dirty, since this code is not in my .module file. Any idea on how to include this code for this page ? I can't see how to use the hook_menu_alter...

Now the serious stuff. The form I want to display is a node edit form, so it needs the node object in the $form_state array. The problem is : when I'm in the function mymodule_modal_callback, how can I retrieve the node since this information (at least the node id) is in the view ? I have no way to predict which node it is...

tbenice’s picture

Im new to this and want to do something similar...did you ever figure it out? Maybe can you put the ctools calls in a template file instead of the views header?

DjebbZ’s picture

Sorry, I didn't find anything at this time and gave up with the modal.

AndrzejG’s picture

+ I am also VERY interested.

maedi’s picture

+me

DjebbZ’s picture

Status: Active » Closed (won't fix)

Closing my own issue, since I never found any solution, and had no support.

couturier’s picture

Version: 6.x-1.7 » 7.x-1.x-dev
Status: Closed (won't fix) » Active

DjebbZ, we are currently working on a very similar solution to this problem at this thread: http://drupal.org/node/828794

I'm content with putting either the whole node or just the comment form into a modal, so these are similar issues. If you would be interested in taking a look at the comments at the other issue, especially Comment #20, that would be great. It sounds like you have more experience with modals than I do, and I feel like we are so close to getting this to work. Thanks!

begun’s picture

@DjebbZ I have just finished implementing the exact thing you have been trying to do. I had actually planned to write this up as a mini How-to, but haven't got the time right now. In the mean time, here is some code to put you in the right direction. Check out the $op = 'edit' part of namecards_modalframe_node(). I will assume that you already have the edit node links in your views set up correctly.

function namecards_menu() {
  $weight = 0;
  $items['namecards/modalframe/node/%/%/%ctools_js'] = array(
    'page callback' => 'namecards_modalframe_node',
    'page arguments' => array(3, 4, 5),
    'access callback' => 'namecards_menu_access_callback_function',
    'access arguments' => array(3, 4),
    'type' => MENU_CALLBACK,
  );
 
  return $items;
}

function namecards_modalframe_node($op, $nid, $js = FALSE) {
  $output = '';
  
  if ($js) {
    ctools_include('ajax');
    ctools_include('modal');
  }
  
  switch ($op) {
    case 'view':
      if ($js) {
        $output .= theme('namecards_download_cvs_link', $nid);
        // Create table showing contact's information
        $table_structure = namecards_get_table_data($nid);
        $output .= theme('table', array('header' => $table_structure['header'], 'rows' => $table_structure['rows'], 'attributes' => $table_structure['attributes']));
        $output .= theme('namecards_download_cvs_link', $nid);
        // Add close button to bottom of page.
        return ctools_modal_render(t('Details'), $output);
      }
      else {
        $output .= theme('namecards_download_cvs_link', $nid);
        // Create table showing contact's information
        $table_structure = namecards_get_table_data($nid);
        $output .= theme('table', array('header' => $table_structure['header'], 'rows' => $table_structure['rows'], 'attributes' => $table_structure['attributes']));
        $output .= theme('namecards_download_cvs_link', $nid);
        // Add close button to bottom of page.
        $form = drupal_get_form('namecards_modalframe_close_button_form');
        $output .= drupal_render($form);
        return $output;
      }
      break;
    
    case 'edit':
      $node = node_load($nid);
      if ($js) {
        ctools_include('node.pages', 'node', '');
        $form_state = array(
          'ajax' => $js,
          'title' => t('Edit'),
        );
        $form_state['build_info'] = array(
          'args' => array($node),
        );
        $output = ctools_modal_form_wrapper($node->type . '_node_form', $form_state);
        
        // Close modal if form has been processed.
        if (!empty($form_state['executed'])) {
          $output = array();
          if (isset($form_state['namecards_modal_close'])) {
            // Close button was pressed so close modal.
            $output[] = ctools_modal_command_dismiss();
          }
          else {
            // Close modal and reload parent page.
            $output[] = ctools_modal_command_dismiss();
            $output[] = ctools_ajax_command_reload();
          }
        }
        print ajax_render($output);
        exit;
      }
      else {
        module_load_include('inc', 'node', 'node.pages');
        $node_edit_form = node_page_edit($node);
        return drupal_render($node_edit_form);
      }
      break;

    case 'delete':
      $node = node_load($nid);
      $lang = $node->language;
      if (!empty($node->namecards_namecard_given_name[$lang][0]['safe_value'])) {
        $given_name = $node->namecards_namecard_given_name[$lang][0]['safe_value'];
      } 
      else {
        $given_name = '';
      }
      $surname = check_plain($node->title);
      $contact_name = $given_name . ' ' . $surname;
      
      if ($js) {
        $form_state = array(
          'title' => t('Delete'),
          'ajax' => $js,
          'build_info' => array(
            'args' => array(
              $contact_name, 
              $nid,
            ),
          ),
        );
        $output = ctools_modal_form_wrapper('namecards_node_delete_confirmation_form', $form_state);
        
        // Close modal if form has been processed.
        if (!empty($form_state['executed'])) {
          $output = array();
          if (isset($form_state['namecards_modal_close'])) {
            // Close button was pressed so close modal.
            $output[] = ctools_modal_command_dismiss();
          }
          else {
            // Close modal and reload parent page.
            $output[] = ctools_modal_command_dismiss();
            $output[] = ctools_ajax_command_reload();
          }
        }
        
        print ajax_render($output);
        exit;
      }
      else {
        $node_delete_form = drupal_get_form('namecards_node_delete_confirmation_form', $contact_name, $nid);
        $output .= drupal_render($node_delete_form);
        return $output;
      }
      break;
      
    default:
      break;
  }
}

Also don't forget to load the necessary libraries and settings in the parent page for the modal. Note that I had to include 'ajax-responder' in order for all the node edit form features to work (e.g. drag-drop fields, etc.). Don't get scared by the size of this. You don't need the stuff referring to $modal_styles for it to work (I am just using it here to apply some customizations to the visual appearance of the modal).

  ctools_include('modal');
  ctools_include('ajax');
  ctools_add_js('ajax-responder');
  ctools_modal_add_js();
  // Create our own javascript that will be used to theme a modal.
  $modal_styles = array(
      'namecards-style-details' => array(
          'modalSize' => array(
              'type' => 'fixed',
              'width' => 800,
              'height' => 600,
              'contentRight' => 29,
              'contentBottom' => 49,
          ),
          'modalOptions' => array(
              'opacity' => .5,
              'background-color' => '#000',
          ),
          'closeText' => t(''),
          'closeImage' => theme('image', array('path' => ctools_image_path('icon-close-window.png', 'namecards', 'images'), 'alt' => t('Close'), 'title' => t('Close'))),
          'loadingText' => t('Loading...'),
          'animation' => 'fadeIn',
          'animationSpeed' => 'medium',
          'modalTheme' => 'CToolsNamecardsModal',
      ),
      'namecards-style-edit' => array(
          'modalSize' => array(
              'type' => 'fixed',
              'width' => 600,
              'height' => 800,
              'contentRight' => 29,
              'contentBottom' => 49,
          ),
          'modalOptions' => array(
              'opacity' => .5,
              'background-color' => '#000',
          ),
          'closeText' => t(''),
          'closeImage' => theme('image', array('path' => ctools_image_path('icon-close-window.png', 'namecards', 'images'), 'alt' => t('Close'), 'title' => t('Close'))),
          'loadingText' => t('Loading...'),
          'animation' => 'fadeIn',
          'animationSpeed' => 'medium',
          'modalTheme' => 'CToolsNamecardsModal',
          'throbber' => theme('image', array('path' => ctools_image_path('throbber.gif'), 'alt' => t('Loading...'), 'title' => t('Loading'))),
      ),
      'namecards-style-delete' => array(
          'modalSize' => array(
              'type' => 'fixed',
              'width' => 300,
              'height' => 150,
              'contentRight' => 29,
              'contentBottom' => 49,
          ),
          'modalOptions' => array(
              'opacity' => .5,
              'background-color' => '#000',
          ),
          'closeText' => t(''),
          'closeImage' => theme('image', array('path' => ctools_image_path('icon-close-window.png', 'namecards', 'images'), 'alt' => t('Close'), 'title' => t('Close'))),
          'loadingText' => t('Loading...'),
          'animation' => 'fadeIn',
          'animationSpeed' => 'medium',
          'modalTheme' => 'CToolsNamecardsModal',
      ),
  );
  drupal_add_js($modal_styles, 'setting');
  drupal_add_js(drupal_get_path('module', 'namecards') . '/js/namecards_ctools_modal.js');

Hope this helps

julien66’s picture

Mm... Trying to achieve the same thing since several hours already 8|
I have trouble with validation and I just can't seem to find what i am doing wrong.
Here is my code :

function snippets_menu() {

   $items['modalframe/node/%node/%ctools_js'] = array(
    'page callback' => 'modalframe_node',
    'page arguments' => array(2,3),
    'access arguments' => array('user_access'),
    'type' => MENU_CALLBACK,
  );

return $items;
}

function modalframe_node($node, $js = FALSE){

	if ($js){        
	ctools_include('node.pages', 'node', '');
  	ctools_include('modal');
  	ctools_include('ajax');
	ctools_add_js('ajax-responder');
  	ctools_modal_add_js();


	$form_state = array(
          'ajax' => TRUE,
          'title' => t('Edit'),
 	);

	$form_state['build_info'] = array(
          'args' => array($node),
	);

        $output = ctools_modal_form_wrapper($node->type . '_node_form', $form_state);	

	print ajax_render($output);
        exit;
	}
	else {
        module_load_include('inc', 'node', 'node.pages');
        $node_edit_form = node_page_edit($node);
        return drupal_render($node_edit_form);
        }
}

When I save the modal form, this results in : " The content on this page has either been modified by another user, or you have already submitted modifications using this form. As a result, your changes cannot be saved " !
Pretty hard.

EDIT : It's now working after adding :

if (!empty($form_state['executed'])) {
          $output = array();
	  $output[] = ctools_modal_command_dismiss();
	}

just after $output = ctools_modal_form_wrapper($node->type . '_node_form', $form_state);

mustanggb’s picture

Issue summary: View changes
Status: Active » Closed (outdated)