Adding WISIWYG Support To Drupal 7 Node Summaries

31st December 2011 - 8 minutes read time

I often get asked a simple request during a project, and the solution to the problem is sometimes more complex than I originally thought. One of these problems was adding a WYSIWYG editor to the summary field on the node edit form. There isn't an easy way to do this, but it is possible to get a good solution working.

Using the hook_form_alter() hook we can intercept and change the node edit form to change the type of the summary element from a textarea to a text_format element. In order to get the WYSIWYG component of the form working we will need to also add a format to the form element. For the purposes of this example I have created a module called wysiwyg_summary, so the hook is called wysiwyg_summary_form_alter().

function wysiwyg_summary_form_alter(&$form, &$form_state, $form_id) {
	if ($form_id == 'page_node_form' && isset($form['nid']) && isset($form['body'])) {
		$form['body'][$form['language']['#value']][0]['summary']['#format'] = 'filtered_html';
		$form['body'][$form['language']['#value']][0]['summary']['#type'] = 'text_format';
	}
}

This seems to work ok, but the first thing I noticed is that clicking on "Show summary" has a few weird effects on certain elements of text, adding multiple "Hide summary" labels. It turns out that the culprit of the problems is the text.js file from the node field text module (located at /modules/field/modules/text/text.js). This JavaScript file is included on the node form and needs to be updated slightly to allow for the difference in the edit summary form element. All we need to do to fix this is to change the find() function so that it only finds the first label of the parent element, rather than every label. Here is the new file in full.

(function ($) {

/**
 * Auto-hide summary textarea if empty and show hide and unhide links.
 */
Drupal.behaviors.textSummary = {
  attach: function (context, settings) {
    $('.text-summary', context).once('text-summary', function () {
      var $widget = $(this).closest('div.field-type-text-with-summary');
      var $summaries = $widget.find('div.text-summary-wrapper');

      $summaries.once('text-summary-wrapper').each(function(index) {
        var $summary = $(this);
        var $summaryLabel = $summary.find('label:first');
        var $full = $widget.find('.text-full').eq(index).closest('.form-item');
        var $fullLabel = $full.find('label:first');

        // Create a placeholder label when the field cardinality is
        // unlimited or greater than 1.
        if ($fullLabel.length == 0) {
          $fullLabel = $('').prependTo($full);
        }

        // Setup the edit/hide summary link.
        var $link = $('(' + Drupal.t('Hide summary') + ')').toggle(
          function () {
            $summary.hide();
            $(this).find('a').html(Drupal.t('Edit summary')).end().appendTo($fullLabel);
            return false;
          },
          function () {
            $summary.show();
            $(this).find('a').html(Drupal.t('Hide summary')).end().appendTo($summaryLabel);
            return false;
          }
        ).appendTo($summaryLabel);

        // If no summary is set, hide the summary field.
        if ($(this).find('.text-summary').val() == '') {
          $link.click();
        }
        return;
      });
    });
  }
};

})(jQuery);

To add this to the node form in the place of the original we need to alter the attached attribute of the summary field, therefore replacing the existing file. Here is the updated code.

function wysiwyg_summary_form_alter(&$form, &$form_state, $form_id) {
	if ($form_id == 'page_node_form' && isset($form['nid']) && isset($form['body'])) {
		$form['body'][$form['language']['#value']][0]['summary']['#format'] = 'filtered_html';
		$form['body'][$form['language']['#value']][0]['summary']['#type'] = 'text_format';
		$form['body'][$form['language']['#value']][0]['summary']['#attached']['js'] = array(drupal_get_path('module', 'wysiwyg_summary') . '/text.js');
	}
}

The final step in this is to allow the saving of the new structure of the data from the summary element. The best way to do this is to convert the data back to a format that Drupal expects using a submit function. The first thing to do is add the submit hook to the wysiwyg_summary_form_alter(). We use the array_unshift() function here so that the submit function is called before any other submit functions (specifically the core ones).

/**
 * Implements hook_form_alter().
 */
function wysiwyg_summary_form_alter(&$form, &$form_state, $form_id) {
	if ($form_id == 'page_node_form' && isset($form['nid']) && isset($form['body'])) {
		$form['body'][$form['language']['#value']][0]['summary']['#format'] = 'filtered_html';
		$form['body'][$form['language']['#value']][0]['summary']['#type'] = 'text_format';		
		$form['body'][$form['language']['#value']][0]['summary']['#attached']['js'] = array(drupal_get_path('module', 'wysiwyg_summary') . '/text.js');
		array_unshift($form['#submit'], 'wysiwyg_summary_form_alter_summary_submit');
	}
}

The job of the submit function is to change the value from the format summary element to a non-formatted summary element. One important thing to consider here is that although this text is entered using a filtered text element, it is not saved or printed using any specific text format. This therefore means that any text entered will be printed, including any malicious scripts. To get around this we can pass the entered text through the filter_xss() function to remove any of these problems. The tags we allow through here are enough to get rudimentary support for bold and links in text, which is what the original request was all about.

/**
 * Submit function defined by wysiwyg_summary_form_alter().
 */
function wysiwyg_summary_form_alter_summary_submit($form, &$form_state) {
  $allowed_tags = array('a', 'em', 'strong', 'cite', 'blockquote', 'ul', 'ol', 'li', 'dl', 'dt', 'dd', 'p');
	if (isset($form_state['input']['body'][$form['language']['#value']][0]['summary']['value'])) {
		$form_state['input']['body'][$form['language']['#value']][0]['summary'] =  filter_xss($form_state['input']['body'][$form['language']['#value']][0]['summary']['value'], $allowed_tags);
	}

	if (isset($form_state['values']['body'][$form['language']['#value']][0]['summary']['value'])) {
		$form_state['values']['body'][$form['language']['#value']][0]['summary'] = filter_xss($form_state['values']['body'][$form['language']['#value']][0]['summary']['value'], $allowed_tags);
	}
}

With this module in place the summary field now has partial WYSIWYG support. The only real problem is that it doesn't save or update filter formats at all so you won't have full control over how the content of the teaser is processed. To stop user confusion over the fact that the filter formats section is there but not usable, it might be a good idea to hide the format section using CSS. All that is needed is to put a display:none; rule on the ID edit-body-und-0-summary-format.

Update 01/10/2012: Alternatively, you could use the check_markup() function if the filter_xss() function is too restrictive. The good thing with using check_markup() is that it ties into your text filters (in this case it uses 'filtered_html') and therefore give you better control over what happens to the text.

Comments

Permalink
Thank's, your blogpost "Adding WISIWYG Support To Drupal 7 Node Summaries" was very helpfull. Good luck!

David (Mon, 11/05/2012 - 19:11)

Permalink
Why haven't you made this a contributed module?

Ray (Tue, 05/07/2013 - 20:08)

Permalink
Mainly because it was a very specific solution to the problem and didn't seem generic enough for a full blown module. Do you think it would be useful as a module in it's own right? I don't mind collating things and sandboxing it :)
Permalink

Thank you very much Phil, this post still relevant today and fortunately everything works as expected :) A couple of comments:

- There's no need to add the custom javascript file anymore, it looks like recent versions of text.js have been changed to look for the first label element only:

var $fullLabel = $full.find('label').first();

- When using this code in multi language sites, it's more reliable to look for the language of the body field rather than the one in the form, like this:

$form['body']['#language']
 

Proteo (Wed, 09/19/2018 - 17:12)

Add new comment

The content of this field is kept private and will not be shown publicly.