Continuing my work from here, I have a modal window that displays a text field and submit button. When the form is submitted, a Solr search is run using ajax, and the first ten results are displayed in a table with a pager.

 * Implementation of hook_menu.
function nb_alters_menu() {
  $items = array();

  $items['imgsearch/%ctools_js/%node'] = array(
    'page callback' => 'imgsearch_page',
    'page arguments' => array(1, 2),
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,

  $items['imgsearch/%ctools_js/imgadd/%/%'] = array(
    'title' => 'Add Image',
    'page callback' => 'nb_alters_add_image',
    'page arguments' => array(1, 3, 4),
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,

  return $items;

 * Page callback for imgsearch modal popup window.
function imgsearch_page($ajax, $node) {
  if ($ajax) {
  // Load the modal library and add the modal javascript.

  $form_state = array(
    'ajax' => TRUE,
    'title' => t('Image Search Form'),
    'base_node' => $node,

  // Add custom settings for form size.
    'imgsearch-modal-style' => array(
      'modalSize' => array(
        'type' => 'fixed',
        'width' => 750,
        'height' => 750,
  ), 'setting');

  // Use ctools to generate ajax instructions for the browser to create
  // a form in a modal popup.
  $output = ctools_modal_form_wrapper('imgsearch_form', $form_state);

  // If the form has been submitted, there may be additional instructions
  // such as dismissing the modal popup.
  if (!empty($form_state['ajax_commands'])) {
    $output = $form_state['ajax_commands'];

  // Return the ajax instructions to the browser via ajax_render().
  print ajax_render($output);
  else {
    return drupal_get_form('imgsearch_form', $node);

 * Drupal form to be put in a modal.
function imgsearch_form($form, $form_state, $node) {
  $form = array();

  $form['search_terms'] = array(
    '#type' => 'textfield',
    '#title' => t('Search text'),
    '#description' => t('Enter the search terms'),

  $form['replace_div'] = array(
    '#prefix' => '<div id="imgsearch-replace-div">',
    '#suffix' => '</div>',
    '#weight' => 6,

  $form['node'] = array(
    '#type' => 'value',
    '#value' => $node,

  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Submit'),

  return $form;

 * Drupal form submit handler.
function imgsearch_form_submit(&$form, &$form_state) {
  // See apachesolr_saved_searches_get_results() in ozmosys project for code to run solr search.
  $return = nb_alters_search($form_state['values']['search_terms']);

  if (is_array($return['images']) && count($return['images'] > 0)) {
    // Create object to store file and target field info. To be stored in ctools cache.
    $file_info = new stdClass();
    // Calculate the field name.  It is a multivalue field, so we just need the next available option
    // in the $field_images['und'] array.
    $field_images = $form_state['base_node']->field_images['und'];
    $next_value = count($field_images);

    $file_info->fieldname = '#edit-field-images-und-' . $next_value . '-imgsearch-file-url';
    $file_info->fids = array();

    // Theme the results as a table.
    $header = array(t('Image'), t('File Name'), t('Add to field'));
    $rows = array();
    foreach ($return['images'] as $image) {
    // Create image style derivative for each image.
      $imagestyle = array(
        'style_name' => 'thumbnail',
        'path' => $image['filepath'] . $image['filename'],
        'width' => '',
        'height' => '',
        'alt' => '',
        'title' => $image['filename'],
      $styled_image = theme('image_style', $imagestyle);
      $fid = $image['fid'];

      $rows[] = array(
        'image' => $styled_image,
        'name' => $image['filename'],
        'add' => ctools_ajax_text_button("select", "imgsearch/nojs/imgadd/" . $fid . '/' . $form_state['base_node']->nid, t('Select')),

      $file_info->fids[$fid] = $image['filename'];
    //Cache image name in ctools object cache so it can be used later in nb_alters_image_add()
    ctools_object_cache_set('imgsearch', $form_state['base_node']->nid, $file_info);

    //$output = theme('table', array('header' => $header, 'rows' => $rows, array('class' => array('imgsearch-table'))));

    // Create a render array ($build) which will be themed as a table with a
    // pager.
    $build['imgsearch_table'] = array(
      '#theme' => 'table',
      '#header' => $header,
      '#rows' => $rows,
      '#empty' => t('There were no matching results found'),

    // Attach the pager theme.
    pager_default_initialize($return['total_found'], $return['rows']);
    $build['imgsearch_pager'] = array('#theme' => 'pager');

    $output = drupal_render($build);
  else {
    $output = '<div class="no-results>No images found</div>';

  // Tell the browser to replace add the table with returned images to the form.
  $form_state['ajax_commands'][] = ajax_command_replace('#imgsearch-replace-div', $output);

My problem is getting the pager to work so that when the pager link is clicked, the search is run again via ajax, and the results are written to the appropriate div (imgsearch-replace-div) on the page. Currently, the pager link is to imgsearch/nojs/$nid?page=x, which is just to the page function that displays the modal, but it's the form submit function that runs the search again and replaces the div with the output. What do I need to do to get the form submission function to be run and the new set of results displayed, all via ajax?


  • It seems no one has answer to this ctools forms issue. Were you able to solve it? Jul 28, 2015 at 12:20
  • Nope, no answer yet. However, I'm trying a new approach (see this question).
    – wonder95
    Sep 8, 2015 at 23:28

1 Answer 1


I was able to get this working, but not without substantial re-working of my page callback and getting rid of the submit function. Details are in a blog post series I'm working on starting here, but the gist of it is that you can't use a submit function to start the search. This is because the links generated by the pager code always link back to the original page url, so you have to call the actual search from within the page callback, and then pass the page number param to your search function to get the correct results.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.