0

I'm writing a module to get a simple node add form into a ctools modal dialog. I get the form into the modal ok, but when I click save I get this ajax error

An AJAX HTTP error occurred. HTTP Result Code: 500 Debugging information follows. Path: /itin-add1/ajax StatusText: Service unavailable (with message) ResponseText: EntityMalformedException: Missing bundle property on entity of type node. in entity_extract_ids() (line 7721 of /mnt/p0/data/www/docroots/mkm/includes/common.inc).

I've tried several examples, currently working with this one http://deeson-online.co.uk/labs/insert-form-pop-modal-ctools-and-drupal-7

I've also been examining the modal_forms module and it appears to be doing basically the same thing as the link above. As I say I get the form to appear in the modal ok, but saving the form fails. I've yet to find a good complete example of a node add in a modal.

I appreciate any advice or pointers to a good example of node add in a modal.

Thanks

<?php

# testmodal module

function testmodal_ctools_render_alter(&$info, &$page, &$context) {
  $data = array_values($context['contexts']);
  if (is_array($data) && count($data) && property_exists($data[0], "data")) {
    testmodal_node_view($data[0]->data, 'full', '');
  }
}

function testmodal_node_view($node, $view_mode, $langcode) {
  ctools_include('modal');
  ctools_modal_add_js();
}

# Insert a form into a pop-up modal with CTools and Drupal 7
# http://deeson-online.co.uk/labs/insert-form-pop-modal-ctools-and-drupal-7

function testmodal_menu() {

# We dont' need this. Our popup links appear on the /products page
#
# 1. "/mymodule/page" - A page that a user might travel to and see the
# link that, when clicked, will cause a modal to popup with the form in it.
# 2. "/mymodule/%ctools_js" - The second is a callback which returns the HTML of
# the modal form. The second argument is a variable. It's value will be either "nojs",
# if the users browser does not handle JavaScript, or "ajax" if it does.

  $items['itin-add0'] = array(
    'page callback' => 'testmodal_form',
    'title' => "hook",
    'access callback' => TRUE,
  );  

  $items['itin-add1/%ctools_js'] = array(
    'page callback' => 'testmodal_callback',
    'page arguments' => array(1),
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );
  return $items;
}

# 3 link generator - don't need it it's for their example
# 4 page call back - need to determine correct hook to load
#               ctools_include('modal');
#               ctools_modal_add_js();

# 5. Define the ajax callback
# This is what runs when the user's browser makes a request to either "/mymodule/nojs" or
# "/mymodule/ajax". In the case of nojs, the full form must be returned as normal. In the
# case of "ajax", the ajax delivery system sends back the information on creating a modal.

function testmodal_callback($ajax) {
  ctools_include('modal');
  ctools_modal_add_js();

  if ($ajax) {
    $form_state = array(
      'ajax' => TRUE,
      'title' => t('Itinerary Item Add Modal Form'),
    );

  // Use ctools to generate ajax instructions for the browser to create
  // a form in a modal popup.
    $output = ctools_modal_form_wrapper('testmodal_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);
    drupal_exit();
  } else {
    return drupal_get_form('testmodal_form');
  }
}

# 6. Create an example form The next thing to do for this example is to create
# the form and its submission function. In 5 above, we told ctools the forms name
# was "mymodule_form", so lets create that form now with a textfield and a submit
# button. You'll note that there is nothing special about the form itself and any Drupal
# form could therefore be used.

function testmodal_form() {

        global $user;

        module_load_include('inc', 'node', 'node.pages');  

        $node_type = 'itinerary_item';
        $form_id = $node_type . '_node_form';

        $node = new stdClass();
        $node->type = 'itinerary_item';
        node_object_prepare($node);
        $node->language = LANGUAGE_NONE;
        $node->body[LANGUAGE_NONE][0]['value']='';
        $node->uid = $user->uid;
        $node->title = 'testmodal title';

        $node->status = NODE_PUBLISHED;
        $node->promote = 0;
        $node->comment = 0;
        $node->active = 1;


        $form = drupal_get_form($form_id, $node);

        return $form;
}

# 7. Create the submission function When submitting the form we want to send back
# ajax commands to the browser to dismiss the modal and change the text on the link we
# clicked. You can see the ajax_command_replace function is targeting the div with the
# id set by our helper function back in 3. In situations where you are not using your
# own form but some other in the Drupal system you might need to add additional ajax
# actions by including your own submission functions to the form.

function testmodal_itinerary_item_node_form_submit($form, &$form_state) {
#  $form_state['ajax_commands'][] = ctools_modal_command_dismiss();
}

/**
 * Drupal form submit handler.
 */
function itinerary_item_node_form_submit($form, &$form_state) {
// Generate the new link using the submitted text value.
#  $link = _mypopup_make_link($form_state['values']['new_link_text']);

// Tell the browser to close the modal.
  $form_state['ajax_commands'][] = ctools_modal_command_dismiss();

// Tell the browser to replace the old link with the new one.
#   $form_state['ajax_commands'][] = ajax_command_replace('#magical-modal-link', $link);
}
1
  • Hello. Please put code into question directly. If it's too long to post it here, it's also too long to expect people to be willing to read when posted externally, and external links can decay in time, rendering your question useless. See sscce.org for guidelines about shortening your code for asking, if you need guidance.
    – Mołot
    Commented Apr 1, 2014 at 10:06

2 Answers 2

4

Here is my testing code (custom.module). Works fine:

function _my_module_includes_modal() {
  static $added = FALSE;
  if ($added == FALSE) {
    $added = TRUE;

    ctools_include('modal');
    ctools_include('ajax');
    ctools_modal_add_js();

    $custom_modal_style = array(
      'custom-modal-style' => array(
        'modalSize' => array(
          'type' => 'fixed',
          'width' => 800,
          'height' => 'auto',
          'addWidth' => 20,
          'addHeight' => 20,
          'contentRight' => 20,
          'contentBottom' => 20,
        ),
        'modalOptions' => array(
          'opacity' => (float) 0.6,
          'background-color' => '#000000',
        ),
        'closeText' => 'Close',
        'loadingText' => 'Loading data...',
        'animation' => 'fadeIn',
        'animationSpeed' => 'fast',
        'modalTheme' => 'my_module_modal',
        'throbberTheme' => 'my_module_throbber',
        'throbber' => '',
      ),
    );

    drupal_add_js($custom_modal_style, 'setting');
  }
}

function custom_menu() {
  $items = array();

  $items['my-path/%ctools_js'] = array(
    'title' => 'Node Form',
    'page callback' => 'nodeFormCallback',
    'page arguments' => array(1),
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );

  return $items;
}


function nodeFormCallback($js = NULL) {

  global $user;

  if (!$js) {
    drupal_access_denied();
    drupal_exit();
  }

  ctools_include('node.pages', 'node', '');
  ctools_include('modal');
  ctools_include('ajax');

  $node_type = 'tets'; //This is my content type. You need to change this to yours
  $node = (object) array(
    'uid' => $user->uid,
    'name' => (isset($user->name) ? $user->name : ''),
    'type' => $node_type,
    'language' => LANGUAGE_NONE,
  );

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

  $form_state['build_info']['args'] = array($node);
  $commands = ctools_modal_form_wrapper($node_type . '_node_form', $form_state);
  if (!empty($form_state['executed'])) {
    $commands = array();
    $commands[] = ctools_ajax_command_redirect('<front>', 1000);
  }

  print ajax_render($commands);
  exit;
}

// This is just an output for modal link.. You can generate it differently
function custom_block_info() {
  $blocks['custom_node_modal'] = array(
    'info' => t('Node Form'),
    'cache' => DRUPAL_NO_CACHE,
  );

  return $blocks;
}

function custom_block_view($delta = '') {
  $block = array();
  switch ($delta) {
    case 'custom_node_modal':
      $block['subject'] = t('Node Form ');
      $block['content'] = custom_node_modal_content();
      break;
  }

  return $block;
}


function custom_node_modal_content() {

  // This function must be called in that places where your link is located.  Or even you can call it globally in hook_init() or template_preproccess_page() fnctions 
  _my_module_includes_modal();

  $output = array();
  $output['custom_modal'] = array(
    '#markup' => ctools_modal_text_button(t('OPEN'), 'my-path/nojs', t('My Path'), 'ctools-modal-custom-modal-style'),
    '#attached' => array(
      'js' => array(
        drupal_get_path('module', 'custom') . '/custom.js' => array(
          'type' => 'file',
          'group' => JS_DEFAULT,
        ),
      ),
    ),
  );

  return render($output);
}

And JS (custom.js):

(function($) {
    Drupal.behaviors.CUSTOM_MODULE = {
        attach: function (context, settings) {

            Drupal.theme.prototype.my_module_modal = function () {
                var html = '';
                html += '<div id="ctools-modal" class="popups-box">';
                html += '  <div class="ctools-modal-content ctools-sample-modal-content c">';
                html += '     <div class="popups-container">';
                html += '       <div class="modal-header popups-title">';
                html += '        <span id="modal-title" class="modal-title"></span>';
                html += '        <span class="popups-close"><a class="close" href="#">' + Drupal.CTools.Modal.currentSettings.closeText + '</a></span>';
                html += '         <div class="clear-block"></div>';
                html += '         </div>';
                html += '         <div class="modal-scroll"><div id="modal-content" class="modal-content popups-body modal-content-fixed"></div></div>';
                html += '         <div class="popups-buttons"></div>';
                html += '         <div class="popups-footer"></div>';
                html += '      </div>';
                html += '  </div>';
                html += '</div>';
                return html;
            }


            Drupal.theme.prototype.my_module_throbber = function() {
                var html = '';
                html += '  <div id="modal-throbber">';
                html += '    <div class="modal-throbber-wrapper">';
                html +=        Drupal.CTools.Modal.currentSettings.throbber;
                html += '    </div>';
                html += '  </div>';

                return html;
            }
        }
    };
})(jQuery);

I'll explain a little what's going on here :)

  1. We created a custom module. Tutorials can be found here;
  2. added Ctools' required files and our custom style with _my_module_includes_modal() function and sent our style's settings to javascript;
  3. created router with hook_menu and specified its action (callback) as nodeFormCallback. In nodeFormCallback action we generated empty node object of our type and added to form_state array as argument and passed it to ctools_modal_form_wrapper function which wraps the form so that AJAX can be used properly;
  4. specified our custom style's template in custom.js file. my_module_modal and my_module_throbber elements of Drupal object are coming from _my_module_includes_modal() function;
  5. we finished all the requirements of ctools modal, now we can generate our link which will fire our modal. LInk can be generated with l function but the easist way is to use ctools_modal_text_button function which includes modal classes to anchor tag. There are many ways to display it, it's up to you.. you can create a block with hook_block_info and hook_block_view hooks or put it into template file, etc. But don't forget to call _my_module_includes_modal() function in that place..
7
  • Downvoters, can you explain what's wrong with this answer?? comments please...
    – xurshid29
    Commented Apr 1, 2014 at 11:25
  • You just got +1 from me, for the explanation.
    – Mołot
    Commented Apr 1, 2014 at 11:26
  • @GisleHannemyr Sure.. I'm just disappointed even though votes are not so important to me..
    – xurshid29
    Commented Apr 1, 2014 at 11:45
  • Well don't tell I didn't warn you ;)
    – Mołot
    Commented Apr 1, 2014 at 11:46
  • 1
    Thanks @xurshid29 you are so awesome, you saved me days of headaches! One small thing to add is that if you deal with admin-only node forms, you can use the admin theme by default and save the styling... I did this by adding "/admin/" to the callback url, so '/admin/my-path/nojs' Commented Nov 22, 2016 at 19:29
0

I think the down votes might have been because even though code that works was provided, there was no explanation as to what it was that was wrong with the previous code, or why it didn't work.

I ran into this same problem (with relations, not nodes) and and the answer for me was to use drupal_build_form instead of drupal_get_form.

Note that when passing parameters to drupal_build_form they need to be in the initial $form_state variable (second parameter), not added as an additional (third) parameter like in drupal_get_form. That may mean a little code tweaking, but in the end Drupal will stop complaining about missing entity bundles ;)

before: (does not work)

    $form = drupal_get_form('relation_ui_edit_form', $form_state, $relation);

after: (does work)

    $form_state['build_info']['args'] = array($relation);
    $form = drupal_build_form('relation_ui_edit_form', $form_state);

I'm still not entirely sure myself why using drupal_get_form doesn't work within modals (ajax) when that approach does work on stand-alone forms.

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.