When adding a markup component to a webform using form builder, the wysiwyg doesn't load. It looks like it's because the wysiwyg.js isn't properly returned when form builder makes its ajax request. In form_builder_json_output(), the javascript settings are properly returned, but any additional javascript scripts that are added aren't.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

quicksketch’s picture

Thanks for the report. I've noticed this also.

duellj’s picture

Another thing to note is that even if the correct wysiwyg javascript is added to the component form, the output from the wysiwyg is never saved, since form_builder is watching for onchange events from the textarea, and the textarea doesn't get updated from the wysiwyg unless a submit button is pressed (or the text format is changed).

duellj’s picture

I was able to hack a solution:

/**
 * Implements hook_form_FORM_ID_alter().
 */
function mymodule_form_form_builder_webform_save_form_alter(&$form, &$form_state) {
  // Add in the necessary wysiwyg js so the markup component should work.
  $profile = wysiwyg_get_profile('wysiwyg_profile'); // use whatever text format key that has a wysiwyg attached to it.
  // Check editor theme (and reset it if not/no longer available).
  $theme = wysiwyg_get_editor_themes($profile, (isset($profile->settings['theme']) ? $profile->settings['theme'] : ''));

  // Add plugin settings (first) for this text format.
  wysiwyg_add_plugin_settings($profile);
  // Add profile settings for this text format.
  wysiwyg_add_editor_settings($profile, $theme);

  // Add textarea library.
  drupal_add_library('system', 'drupal.textarea');
}


/**
 * Implements hook_wysiwyg_editor_settings_alter().
 */
function mymodule_wysiwyg_editor_settings_alter(&$settings, &$context) {
  // Trigger autosave.
  $settings['setup'] = 'mymodule_autosave_setup';
  drupal_add_js('
    function mymodule_autosave_setup(ed) {
      ed.onKeyDown.add(function(ed, e) {
        ed.save();
        jQuery("#" + ed.id).change();
      });
    }
  ', array('type' => 'inline'));
}

Basically this manually adds the wysiwyg js into the form, and then adds an autosave callback for TinyMCE to update the textarea and trigger a change (which updates the form builder form).

Obviously this won't work for other wysiwygs, and I'm not sure how this would integrate into form_builder, but it's a short term solution for me.

avergara’s picture

Hi duellj,

Do you happen to know what is the alternative for drupal_add_library('system', 'drupal.textarea'); on drupal 6?

avergara’s picture

Seems like simply adding a dummy textarea with filter_format works for drupal 6

function mymodule_form_form_builder_webform_save_form_alter(&$form, &$form_state) {
  $form['dummy']['dummy_textarea'] = array(
	'#type' => 'textarea',
	'#title' => t('My textarea'),
	'#default_value' => '',
  );
  $form['dummy']['format'] = filter_form();
  $form['dummy']['#access'] = false;
}
cgove’s picture

The fix in #3 works for form builder, but breaks the WYSIWYG in the panels in-place editor.

heyyo’s picture

Issue summary: View changes

I'm looking for the same kind of solution but for Ckeditor only.

abarpetia’s picture

Hello heyyo,
Did you got this solution for Ckeditor?

pjbarry21’s picture

I'm also running into this problem with ckeditor. Doesn't load in markup.

Plits’s picture

Hello,

I had the same issue with a customized form builder using ckeditor. I managed to make it work with a simple hack :

<?php
/**
 * Implements hook_form_alter().
 */

function yourmodule_form_form_builder_webform_save_form_alter(&$form, &$form_state) {

  $element = array(
    '#type' => 'text_format',
    '#format' => 'html_text',
    '#id' => 'dummy',
  );
  
  drupal_add_js(drupal_get_path('module', 'yourmodule') . '/js/' . 'yourmodule.form.js');
  ckeditor_pre_render_text_format($element);

}

?>

You will also need to add some JS to properly handle the saving mechanism :


  Drupal.behaviors.yourModule = {};
  Drupal.behaviors.yourModule.attach = function(context) {

    $('#form-builder-wrapper').once('yourmodule-formbuilder-ready', function() {

    /**
     * Manage the autosave on ckeditor fields in form builder
     */
      CKEDITOR = CKEDITOR || {};
      CKEDITOR.on("instanceReady", function(e)
      {
        var editor = e.editor;
        editor.on('change', function() { editor.updateElement() });
      });

      /**
       * Overwrite Drupal.formBuilder.closeActive
       */

      Drupal.formBuilder.closeActive = function(callback) {

        // Destroy all ckeditor instances
        CKEDITOR = CKEDITOR || {};
        CKEDITOR.instances = CKEDITOR.instances || {};

        for(name in CKEDITOR.instances)
        {
            CKEDITOR.instances[name].destroy(true);
        }

        if (Drupal.formBuilder.activeElement) {
          var $activeForm = Drupal.formBuilder.fieldConfigureForm ? $(Drupal.formBuilder.fieldConfigureForm).find('form') : $(Drupal.formBuilder.activeElement).find('form');

          if ($activeForm.length) {
            Drupal.freezeHeight();
            $activeForm.slideUp(function(){
              $(this).remove();
              // Set a message in the custom configure form location if it exists.
              if (Drupal.formBuilder.fieldConfigureForm) {
                $(Drupal.formBuilder.fieldConfigureForm).html(Drupal.settings.formBuilder.noFieldSelected);
              }
              if (callback) {
                callback.call();
              }
            });
          }
        }
        else if (callback && $.isFunction(callback)) {
          callback.call();
        }

        return false;
      };


    });
  }

This will load the required ckeditor files without creating any unwanted for element in the DOM and it should work fine.

Regards,
Alan

Lams’s picture

The solution in #7 was breaking form builder ui drag and drop component for me (form builder v7.x-1.7). The code chokes on CKEDITOR = CKEDITOR || {}; (error is CKEDITOR undefined). I attempted to fix this; but, it was surprisingly difficult.

I am still using the php component of #7; but, I replaced the JS with the following:

I wrote a different technique that isolates the configure button for markup fields specifically and unbinds the click event so that the field opens in a new window where CkEditor can load. The destination value of the link also needed to be fixed (in some cases) so that the user could come back to the webform and save. The jQuery targeting could be improved; but, it is working.

jQuery(document).ready(function(){
  yourmodule_ckeditor_fix();
  //make sure we get any newly added fields
  jQuery("#form-builder-wrapper").ajaxComplete(function(e, xhr, settings){
    yourmodule_ckeditor_fix();
  });
});

function yourmodule_ckeditor_fix() {
  //target formbuilder markup field configure buttons
  jQuery("[id^=form-builder-element-]").each(function(){
    if(jQuery(this).attr("class").includes("form-builder-element-markup")) {  
      jQuery(this).closest('.form-builder-wrapper').find(".configure").each(function(){ 
        if(jQuery(this).is("a")) {
          //unbind the in-place editor so user will be taken to a new page where ckeditor can load
          jQuery(this).unbind("click");
          //remove the existing destination and make sure it comes back to the webform page so user can save
          var noQs = jQuery(this).attr("href").split("?")[0];
          var newQs = noQs + "?destination=" + window.location.pathname;
          jQuery(this).attr("href", newQs);
          
        }
      });
    }
  });
}

Why form builder 7.x-1.7? The later versions (I was using 7.x-1.14) seem to focus on moving to OOP, which is great; but, they broke (among other things) the ability to successfully create radio buttons (issues with options). Fixing this cascaded other issues for me; so, I researched and found this more stable version.

torotil’s picture

I've committed a fix for #2709179: Loading CSS/JS in element configuration does not work today. This means #attached and drupal_add_* should now work in field configuration forms. Does this change anything wrt this issue?

franz.skaaning’s picture

FileSize
10.99 KB

Using 7.18 still does not work with ckeditor on webform.

Also when closing an area the spinner just keeps spinning if I try to reopen the same or another.

spinning-around.png

torotil’s picture

A fix for the spinning is already in 7.x-1.x. That means that the remaining issue is now with form_builder's auto-submit.

Usually before submitting a form the wysiwyg editor first puts the updated text into the original (and hidden) textarea. This does not happen with when the field-configuration is submitted via auto-submit so the content is lost. I don't have a ready solution for that yet.

interdruper’s picture

If it helps... in my case, the exception reported in #2665706: Database updates #7002 Integrity constraint violation is associated with this issue. There is one more error while markup element becomes unusable:

callback form_builder_deliver_ajax_or_html not found: admin/structure/form-builder/configure/webform/1210/new_1472815707400.

CKEditor is being used.

torotil’s picture

@interdruper: In what way do you think the issues are related?

JeffM2001’s picture

Status: Active » Needs review
FileSize
3.78 KB

Here's a patch that gets the Markup component working with the wysiwyg module. The approach is roughly based on what's done in panopoly_magic — wrapping the wysiwyg.attach.* javascript functions with additional code that adds event handlers. I've tested it to work with both TinyMCE and CKeditor.

Note, if you are also using panopoly_magic, you will need the patch from this issue as well: #2924074: Don't wrap WYSIWYG attach when not necessary

torotil’s picture

Many thanks for the patch @Jeff.

There is an issue in #641900: Integration: Trigger javascript on WYSIWYG attach? which seems to aim at providing a more reliable JS interface for this. Is there already something usable in there?

Until then I think it’s ok to use some sort of hack.

torotil’s picture

Another idea to try to make this simpler:

JavaScripts in the head are guaranteed to run in the specified order. So if we …

  1. Make sure that our JS is loaded after that of wysiwyg (weight).
  2. Wrap the function when the script is first executed (outside of the behavior.attach function).

Then it should work too without too much of a hassle.

JeffM2001’s picture

@torotil the last comment in that wysiwyg issue is 8 years old, so I wouldn't expect much to come from that.

On the suggestion in #19, I could be wrong, but I suspect that won't work when the element is added via AJAX. Even if form_builder.js is last in the head, it won't be able to wrap Drupal.wysiwyg.editor.attach.tinymce at that point because the wysiwyg plugin scripts aren't loaded until the element is rendered (wysiwyg_pre_render_text_format() calls wysiwyg_get_profile() calls wysiwyg_load_editor()). Perhaps if the script was attached when the markup component form is generated, but I'm not sure where to do that.

This whole thing is pretty complicated with lots of moving parts, and I tried a bunch of different things before finding the panopoly_magic solution. I'd love for there to be a simpler solution if we can find it.

gbirch’s picture

For what it's worth, the following approach seems to work with ckeditor 7.x-1.19 and form_builder 7.x-2.0-alpha8.

1) Edit the relevant text_format properties to replace:
'#wysiwyg' => FALSE,
with
'#pre_render' => ['ckeditor_pre_render_text_format'],

2) Create a new js file with an override to form_builder's elementChange method:

(function ($) {
  Drupal.behaviors.yourModule = {};

  Drupal.behaviors.yourModule.attach = function(context) {
    /**
     * Override the formBuilder function.
     *
     * Upon changing a field, 1) update the ckeditor instance, if any,
     * then 2) submit via AJAX to the server.
     */
    Drupal.formBuilder.elementChange = function () {
      if (!Drupal.formBuilder.updatingElement) {
        // Custom code begins here.
        if (typeof Drupal.ckeditorInstance != 'undefined') {
          // Update the related textarea before submission.
          Drupal.ckeditorInstance.updateElement();
        }
        // End of custom code.
        $(this).parents('form:first').ajaxSubmit({
          success: Drupal.formBuilder.updateElement,
          dataType: 'json',
          data: {
            'js': 1,
            '_triggering_element_name': 'op',
          }
        });
      }

      // Clear any pending updates until further changes are made.
      if (Drupal.formBuilder.updateDelay) {
        clearTimeout(Drupal.formBuilder.updateDelay);
      }

      Drupal.formBuilder.updatingElement = true;
    };
  }

})(jQuery);

3) Add your js file to the form builder preview form:

/**
 * Implements hook_form_FORM_ID_alter().
 */
function YOURMODULE_form_form_builder_preview_alter(&$form, &$form_state, $form_id) {
  // Add javascript that permits use of ckeditor to edit form properties.
  $form['#attached']['js'][] = [
    'data' => drupal_get_path('module', 'YOURMODULE') . '/YOURJSFILE.js',
    'type' => 'file',
    'scope' => 'footer',
  ];
}