Wysiwyg

Wysiwyg button with form #2

Welcome to the second article in my tutorial about building a wysiwyg button for drupal 7 which enables use to inserting tokens.

This will be a four step tutorial

  1. Step one will be building the module and a simple wysiwyg button
  2. Step two will cover a simple wysiwyg button
  3. Step three will expand the button with a multi field form
  4. Step four will make this form dynamic with ajax callbacks for removing and adding fields

For this part we will build a simple wysiwyg button which inserts the basic struckture of our token.

This code is highly inspired by this article on http://deglos.com

To begin with we need to tell drupal that we have a plugin for wysiwyg in our module. We do this my invoking the hook_wysiwyg_include_directory.

/**
 * implements hook_wysiwyg_include_directory
 */
function example_wysiwyg_include_directory($type) {
  return $type;
}

now we need to build the plugin structure, so i our module we will create a new folder called plugins. In this folder we need an .inc file with the same name as our plugin as well as a folder with the same name. In the folder we then need to create an images folder and place an icon image file there. I created a icon.png for this. besides the images folder we need to create two files, a js and a css file with the same names as the plugin.

So if we give our plugin the name tokenInsert the file structure should be

module_name/plugins/tokenInsert.inc
module_name/plugins/tokenInsert/tokenInsert.js
module_name/plugins/tokenInsert/tokenInsert.css
module_name/plugins/tokenInsert/images/icon.png

Okay now for the code in the files.

In the tokenInsert.inc file we will provide wysiwyg with information about our plugin by invoking the hook_wysiwyg_plugin

/**
 * Implementation of hook_wysiwyg_plugin().
 */
function example_tokenInsert_plugin() {
  $plugins['tokenInsert'] = array(
    'title' => t('Insert object'),
    'icon file' => 'icon.png',
    'icon title' => t('Insert objects'),
    'settings' => array(),
  );
  return $plugins;
}

that is it for this file.

The javascript file will provide the functionality for wysiwyg.

// $Id$
(function ($) {
 
Drupal.wysiwyg.plugins['example'] = {
 
  /**
   * Return whether the passed node belongs to this plugin (note that "node" in this context is a JQuery node, not a Drupal node).
   *
   * We identify code managed by this example plugin by giving it the HTML class
   * 'wysiwyg-plugin-example'.
   */
  isNode: function(node) {
    res = $(node).is('img.wysiwyg-plugin-example');
    return ($(node).is('img.wysiwyg-plugin-example'));
  },
 
  /**
   * Invoke is called when the toolbar button is clicked.
   */
  invoke: function(data, settings, instanceId) {
     // Typically, an icon might be added to the WYSIWYG, which HTML gets added
     // to the plain-text version.
     if (data.format == 'html') {
       var content = this._getPlaceholder(settings);
     }
     else {
       var content = '<!--wysiwyg-plugin-example-->';
     }
     if (typeof content != 'undefined') {
       Drupal.wysiwyg.instances[instanceId].insert(content);
     }
   },
 
  /**
   * Replace all <!--wysiwyg-plugin-example--> tags with the icon.
   */
  attach: function(content, settings, instanceId) {
    content = content.replace(/<!--wysiwyg-plugin-example-->/g, this._getPlaceholder(settings));
    return content;
  },
 
  /**
   * Replace the icons with <!--wysiwyg_example_plugin--> tags in content upon detaching editor.
   */
  detach: function(content, settings, instanceId) {
    var $content = $('<div>' + content + '</div>');
    $.each($('img.wysiwyg-plugin-example', $content), function (i, elem) {
      elem.parentNode.insertBefore(document.createComment('wysiwyg-plugin-example'), elem);
      elem.parentNode.removeChild(elem);
    });
    return $content.html();
  },
 
  /**
   * Helper function to return a HTML placeholder.
   *
   * Here we provide an image to visually represent the hidden HTML in the Wysiwyg editor.
   */
  _getPlaceholder: function (settings) {
    return '<img src="' + settings.path + '/images/icon.png" alt="&lt;--wysiwyg-plugin-example-&gt;" title="&lt;--wysiwyg-plugin-example--&gt;" class="wysiwyg-plugin-example drupal-content" />';
  }
};
 
})(jQuery);

The css file should contain styling specific for this plugin and what it renders, make sure to keep it aas general as possible so it is easy to overwrite.

That is it for our initial button.

Now we need to expand it with a multifield form to allow us to insert token elements based on user input.

Wysiwyg button with form #4

Welcome to the second article in my tutorial about building a wysiwyg button for drupal 7 which enables use to inserting tokens.

This will be a four step tutorial

  1. Step one will be building the module and a simple wysiwyg button
  2. Step two will cover a simple wysiwyg button
  3. Step three will expand the button with a multi field form
  4. Step four will make this form dynamic with ajax callbacks for removing and adding fields

This next part will cover how to expand the form to use ajax for dynamic adding and removing fields.

I followed this exellent article about how to implement det dynamic adding and removing fields, but since we are using jquery ajax to load our for we don't have the luxury of drupals ajax to ensure ajax handling for our form and we need to add this in.

/**
 * Insert token form
 */
function example_insert_form($form, &$form_state) {
  drupal_add_library('system', 'ui.dialog');
  $form['#tree'] = TRUE;
  if (empty($form_state['num_objects'])) {
    $form_state['num_objects'] = 1;
  }
  $ids = null;
  $args = $form_state['build_info']['args'];
  if(isset($args[0])) {
    $ids = explode(',', $args[0]);
    if($form_state['num_objects'] == 1) {
      $form_state['num_objects'] = count($ids);
    }
  }
  $form['objects'] = array(
    '#type' => 'fieldset',
    '#title' => t('Objects'),
    '#prefix' => '<div id="example-fieldset-wrapper">',
    '#suffix' => '</div>',
  );
 
  for($i = 0; $i < $form_state['num_objects']; $i++) {
    $form['objects'][$i]['object'] = array(
      '#type' => 'textfield',
      '#title' => t('Entity id'),
      '#prefix' => '<div class="col1">',
      '#suffix' => '</div>',
      '#default_value' => isset($form_state['values'][$i]) ? $form_state['values'][$i]['object'] : '',
    );
    if(isset($ids[$i])) {
      $form['objects'][$i]['object']['#default_value'] = $ids[$i];
    }
  }
 
  $form['objects']['add_item'] = array(
    '#type' => 'submit',
    '#value' => t('Add another'),
    '#submit' => array('example_add_more_add_one'),
    // See the examples in ajax_example.module for more details on the
    // properties of #ajax.
    '#ajax' => array(
      'callback' => 'example_add_more_callback',
      'wrapper' => 'example-fieldset-wrapper',
      'method' => 'replace',
      'effect' => 'fade',
    ),
  );
  if ($form_state['num_objects'] > 1) {
    $form['objects']['remove_item'] = array(
      '#type' => 'submit',
      '#value' => t('Remove one'),
      '#submit' => array('example_add_more_remove_one'),
      '#ajax' => array(
        'callback' => 'example_add_more_callback',
        'wrapper' => 'example-fieldset-wrapper',
        'method' => 'replace',
        'effect' => 'fade',
      ),
    );
  }
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Submit'),
    '#attributes' => array(
      'class' => array('form-save-ids'),
    ),
  );

  return $form;
}

/**
 * Callback for both ajax-enabled buttons.
 *
 * Selects and returns the fieldset with the names in it.
 */
function example_add_more_callback($form, $form_state) {
  return $form['objects'];
}

/**
 * Submit handler for the "add-one-more" button.
 *
 * Increments the max counter and causes a rebuild.
 */
function example_add_more_add_one($form, &$form_state) {
  $form_state['num_objects']++;
  $form_state['rebuild'] = TRUE;
}

/**
 * Submit handler for the "remove one" button.
 *
 * Decrements the max counter and causes a form rebuild.
 */
function example_add_more_remove_one($form, &$form_state) {
  if ($form_state['num_objects'] > 1) {
    $form_state['num_objects']--;
  }
  $form_state['rebuild'] = TRUE;
}

As you can see we are using a simple count setting to increase or decrease the number of text fields in the form. This works fine for our simple form and we just need to add a few changes to the js in order to forward this change from the client to the backend. So make the following change to the insert_form function:

  insert_form: function (data, settings, instanceId) {
    // Location, where to fetch the dialog.
    var aurl = Drupal.settings.basePath + 'example/insert/ajax';
    if(settings.ids) {
      aurl += '/' + settings.ids.join();
    }
    var dialogdiv = $('<div id="example-insert-dialog"></div>');
    dialogdiv.load(aurl, function(){
      var dialogClose = function () {
        try {
          dialogdiv.dialog('destroy').remove();
        } catch (e) {};
      };

Now that we have this in place we need to expand our menu so we can send the id variables to the form.

/**
 * implements hook_menu
 */
function example_menu() {
  $items = array();
  $items['example/insert/nojs'] = array(
    'page callback' => 'example_get_insert_form',
    'page arguments' => array(2),
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );
  $items['example/insert/ajax'] = array(
    'delivery callback' => 'ajax_deliver'
  ) + $items['example/insert/nojs'];
 
  $items['example/insert/nojs/%'] = array(
    'page callback' => 'example_get_insert_form',
    'page arguments' => array(2, 3),
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );
  $items['example/insert/ajax/%'] = array(
    'delivery callback' => 'ajax_deliver'
  ) + $items['example/insert/nojs/%'];
  return $items;
}

If you implement this, you will find that the ajax callbacks aren't exactly using ajax yet. This is because as I said we don't have the luxury of drupals ajax handing when we are using the jquery ajax load function. In order to fix this we need to make a few changed to the ajax get form function so we compile the scripts and add then to the data being returned.

/**
 * Retrive the insert form.
 */
function example_get_insert_form($ajax, $ids = '') {
  $is_ajax = $ajax === 'ajax';
  $form = drupal_get_form('example_insert_form', $ids);
  if ($is_ajax) {
    $form = drupal_render($form);
    // Generate the settings:
    $settings = '';
    $javascript = drupal_add_js();
    if(isset($javascript['settings'], $javascript['settings']['data'])) {
      $settings = '<script type="text/javascript">jQuery.extend(Drupal.settings, ';
      $settings .= drupal_json_encode(call_user_func_array('array_merge_recursive', $javascript['settings']['data']));
      $settings .=  ');</script>';
    }
    die($form . $settings);
  }
  else {
    return $form;
  }
}

Now that this is done, the form uses ajax to add or remove fields to the form and it keeps the already entered data while doing to.

I hope this helps and if you have any questions let me now. For a working version of this see ting_object

Wysiwyg button with form

At my work i recently had the opportunity to build a wysiwyg plugin in drupal for formatting a token we had created for a customer. This precented a number of challenges which I though others might also have, so I will try to share them here.

This will be a four step tutorial for drupal 7

  1. Step one will be building the module and a simple wysiwyg button
  2. Step two will cover a simple wysiwyg button
  3. Step three will expand the button with a multi field form
  4. Step four will make this form dynamic with ajax callbacks for removing and adding fields

Step 1 - building the module

First step is to create our module so as always we need an info file:

name = Ting token
description = Provides tokens for inserting ting objects in text fields.
core = 7.x
package = Ding!
files[] = ting_token.module
files[] = ting_token.token.inc

I will presume that you are familier with the setup of an info file so I won't go into details here. Now I will go into the token file for this module.

For these tokens we wanted a format which would insert a view_mode and one or more entities to be rendered. First we need a token file with the file name module_name.tokens.inc, in here we need to define hook_token_info

function example_token_info() {
  $type = array(
    'name' => t('Example entity'),
    'description' => t('Display an entity.'),
  );
 
  // Core tokens for nodes.
  $consent['example'] = array(
    'name' => t("Example entity"),
    'description' => t("Show an entity."),
  );

  return array(
    'types' => array('example' => $type),
    'tokens' => array('example' => $consent),
  );
}

Next up we need to handle the tokens once inserted by defining hook_tokens

function example_tokens($type, $tokens, array $data = array(), array $options = array()) {
  $url_options = array('absolute' => TRUE);
  if (isset($options['language'])) {
    $url_options['language'] = $options['language'];
    $language_code = $options['language']->language;
  }
  else {
    $language_code = NULL;
  }
  $sanitize = !empty($options['sanitize']);
  $replacements = array();
  if ($type == 'example') {
    foreach ($tokens as $name => $original) {
      $args = explode(':', $name);
      $view_mode = array_shift($args);
      $eid = implode(':', $args);
      if(strpos($eid, ',') !== FALSE) {
        $eids = explode(',', rawurldecode($eid));
      } else {
        $eids = array(rawurldecode($eid));
      }
      
      $entities = entity_load('example', $eids);
      $output = '<div class="example-inline-list">';
      foreach($entities as $id => $entity) {
        $object = entity_view($entity, $view_mode);
        if($view_mode == 'list_item') {
          $object['#attributes']['class'][] = format_string('compact');
        }
        $output .= drupal_render($object);
      }
      $output .= '</div>';
      $replacements[$original] = $output;
    }
  }

  return $replacements;
}

For this customer our entities are ting entities so we are using specialized functions for these, but this code should work for most purposes. You might want to specialize the rendering of entities to fit your needs.

For this part of the module we actually don't need to write anything in our module file, but we will need it for the next part.

Subscribe to Wysiwyg