Blog

  • Handlebars js

    I have recently begun using handlebars js for my various jquery mobile apps. This has been a great experience and here i will try to explain why and how i have used it. I will also include extensions and plugins which i have used to handle the development.

    The challenge has been entering dynamic content into lists or as changes based on user actions, and often the html for the dynamic content would be entered as strings in the javascript. But a solution for this is using a templating system like handlebars. It allows you to define templates in <script/> tags which can then be referenced in your code when you need dynamic content.

    For my projects I prefere not to have my templates in <script/> tags so I found several solutions which allowed me to make separate template files. For my projects I used sync_async_loading_handlebars which is a very simple script but it handles the task really well. Using jquery mobile i found that I got the best performance if I preloaded the templates using the T.prefetch function. This loads the template data into a cache object from where it can easily be referenced later on.

    Using sync_async_loading_handlebars i then entered dynamic content by creating a data object and calling T.render, as follows:

    1. var themeData = {
    2.   title: 'test',
    3.   src: 'data',
    4.   class: 'some class'
    5. };
    6. T.render('testTemplateItem', function(t) {
    7.   $('.target').append(t(themeData));
    8. });

    My template would then look something like this.

    1. <div class="{{class}}">
    2.   {{#if src}}
    3.   <div class="image-container"><img src="{{src}}" /></div>
    4.   {{/if}}
    5.   <h2>{{title}}</h2>
    6. </div>

    I also found a library of handlebars extensions which are very handy. The people behind swag have composed a library of extensions which contain most of the theming functionality you could possibly want and then some.

     

     

  • Drupal 7 form cancel button

    When creating a form it is often required to make a cancel or back button, but this provides a number og issues with form validation but fortunetly there is a way to prevent these issues.

    Create your cancel or back button.

    1. $form['actions']['back'] = array(
    2.   '#type' => 'submit',
    3.   '#value' => t('Back'),
    4. );

    You can use either type: submit, image_button or button. In order to prevent validation of the form elements you need to add #limit_validation_errors and #submit to the element.

    1. $form['actions']['back'] = array(
    2.   '#type' => 'submit',
    3.   '#value' => t('Back'),
    4.   '#limit_validation_errors' => array(),
    5.   '#submit' => array('example_form_submit'),
    6. );

    #limit_validation_errors contains an array of elements to validate on submit and since it is empty in this case nothing is validated

    In order for #limit_validation_errors to work you also need to specify a submit function to call otherwise the element attribute is ignored.

  • Openlayers behavior

    I recently worked on a site where they had a map of their stores which enabled users to search for their local store. Each store had a popup if you clicked on the icon with the store name and a link to the store page, where you could find opening hours and additional information about the perticular store.

    The customer wanted to change this so clicking on the store icon would send the user directly to the store page instead of opening a popup. The map was implemented using openlayers and there wasn't an exsiting behavior for this, which gave me the oppotunity to try and make a new open layers behavior.

    First of all you need to create a module or modify an existing and you would properly want to add a dependency on openlayers.

    Next you need to add hook_openlayers_behaviors()<(p>

    1. function my_module_openlayers_behaviors() {
    2.   return array(
    3.     'openlayers_behavior_my_behavior' => array(
    4.       'title' => t('My Behavior'),
    5.       'description' => t('On click go to store page'),
    6.       'type' => 'layer',
    7.       'path' => drupal_get_path('module', 'my_module') . '/includes/behaviors',
    8.       'file' => 'openlayers_behavior_my_behavior.inc',
    9.       'behavior' => array(
    10.         'class' => 'openlayers_behavior_my_behavior',
    11.         'parent' => 'openlayers_behavior',
    12.       ),
    13.     ),
    14.   );
    15. }

    This will notify openlayers that you have a new behavior in your includes/behaviors folder.

    In the openlayers_behavior_my_behavior.inc file you then declase a php class which describes how your behavior will work. In this class you have a number of options to acts on different stages of your behaviors life, but for this task we don't need to do much.

    1. class openlayers_behavior_mybehavior extends openlayers_behavior {}
    2.   function options_init() {
    3.     return array();
    4.   }
    5.  
    6.   function options_form($defaults) {
    7.     return array();
    8.   }
    9.  
    10.   function render(&$map) {
    11.     drupal_add_js(drupal_get_path('module', 'mybehavior') . '/includes/behaviors/js/openlayers_behavior_my_behavior.js');
    12.     return $this->options;
    13.   }
    14. }

    The options_init function provides initial values for the options_form function. The options_form allows you to provide a number of settings for your behavior. Many behaviors will need this function but it is not required.

    The render function is where the magic happens and it is a required function for the behavior. Here you can return any custom markup for your behavior but our case only needs to provide a javascript file.

    If you would like to see some examples of the behavior class, checkout the default behaviors provided with the openlayers module. Especially the popup behavior I found to be a good starting point.

    Next we have our javascript.

    1. Drupal.theme.prototype.openlayersLinkToNode = function(feature) {
    2.   var output = '';
    3.   return output;
    4. };
    5.  
    6. // Make sure the namespace exists
    7. Drupal.openlayers.linkToNode = Drupal.openlayers.linkToNode || {};
    8.  
    9. /**
    10.  * OpenLayers go to node Behavior
    11.  */
    12. Drupal.openlayers.addBehavior('openlayers_behavior_link_to_node', function (data, options) {
    13.   var map = data.openlayers;
    14.   var layers = [];
    15.   var selectedFeature;
    16.  
    17.   // For backwards compatiability, if layers is not
    18.   // defined, then include all vector layers
    19.   if (typeof options.layers == 'undefined' || options.layers.length == 0) {
    20.     layers = map.getLayersByClass('OpenLayers.Layer.Vector');
    21.   }
    22.   else {
    23.     for (var i in options.layers) {
    24.       var selectedLayer = map.getLayersBy('drupalID', options.layers[i]);
    25.       if (typeof selectedLayer[0] != 'undefined') {
    26.         layers.push(selectedLayer[0]);
    27.       }
    28.     }
    29.   }
    30.  
    31.   // if only 1 layer exists, do not add as an array.  Kind of a
    32.   // hack, see https://drupal.org/node/1393460
    33.   if (layers.length == 1) {
    34.     layers = layers[0];
    35.   }
    36.  
    37.   var linkToNodeSelect = new OpenLayers.Control.SelectFeature(layers, {
    38.       onSelect: function(feature) {
    39.         var nid = feature.attributes.nid;
    40.         window.location.href = 'node/' + nid;
    41.       },
    42.     }
    43.   );
    44.  
    45.   map.addControl(linkToNodeSelect);
    46.   linkToNodeSelect.activate();
    47.   Drupal.openlayers.linkToNode.linkToNodeSelect = linkToNodeSelect;
    48. });

    Most of this javascript is there to ensure that the functionality works.

    The important part of this script is the OpenLayers.Control.SelectFeature(layers, function) where we add our select functionality

    There we go, I hope it helps you on your way to making the openlayers behaviors of your dreams. :)

  • Animal city (working title)

    I am currently working on a prototype for a new game with the working title animal city. The game is aimed at the age 4 - 6 and features a number of animals out to save their village from an invation. I aim to upload a full prototype within the next month since i have a number of other projects to finish as well.

  • 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

    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.

    but first we need to exend our for to include ajax processing.

    1. /**
    2.  * Insert token form
    3.  */
    4. function example_insert_form($form, &$form_state) {
    5.   drupal_add_library('system', 'ui.dialog');
    6.   $form['#tree'] = TRUE;
    7.   if (empty($form_state['num_objects'])) {
    8.     $form_state['num_objects'] = 1;
    9.   }
    10.   $ids = null;
    11.   $args = $form_state['build_info']['args'];
    12.   if(isset($args[0])) {
    13.     $ids = explode(',', $args[0]);
    14.     if($form_state['num_objects'] == 1) {
    15.       $form_state['num_objects'] = count($ids);
    16.     }
    17.   }
    18.   $form['objects'] = array(
    19.     '#type' => 'fieldset',
    20.     '#title' => t('Objects'),
    21.     '#prefix' => '<div id="example-fieldset-wrapper">',
    22.     '#suffix' => '</div>',
    23.   );
    24.  
    25.   for($i = 0; $i < $form_state['num_objects']; $i++) {
    26.     $form['objects'][$i]['object'] = array(
    27.       '#type' => 'textfield',
    28.       '#title' => t('Entity id'),
    29.       '#prefix' => '<div class="col1">',
    30.       '#suffix' => '</div>',
    31.       '#default_value' => isset($form_state['values'][$i]) ? $form_state['values'][$i]['object'] : '',
    32.     );
    33.     if(isset($ids[$i])) {
    34.       $form['objects'][$i]['object']['#default_value'] = $ids[$i];
    35.     }
    36.   }
    37.  
    38.   $form['objects']['add_item'] = array(
    39.     '#type' => 'submit',
    40.     '#value' => t('Add another'),
    41.     '#submit' => array('example_add_more_add_one'),
    42.     // See the examples in ajax_example.module for more details on the
    43.     // properties of #ajax.
    44.     '#ajax' => array(
    45.       'callback' => 'example_add_more_callback',
    46.       'wrapper' => 'example-fieldset-wrapper',
    47.       'method' => 'replace',
    48.       'effect' => 'fade',
    49.     ),
    50.   );
    51.   if ($form_state['num_objects'] > 1) {
    52.     $form['objects']['remove_item'] = array(
    53.       '#type' => 'submit',
    54.       '#value' => t('Remove one'),
    55.       '#submit' => array('example_add_more_remove_one'),
    56.       '#ajax' => array(
    57.         'callback' => 'example_add_more_callback',
    58.         'wrapper' => 'example-fieldset-wrapper',
    59.         'method' => 'replace',
    60.         'effect' => 'fade',
    61.       ),
    62.     );
    63.   }
    64.   $form['submit'] = array(
    65.     '#type' => 'submit',
    66.     '#value' => t('Submit'),
    67.     '#attributes' => array(
    68.       'class' => array('form-save-ids'),
    69.     ),
    70.   );
    71.  
    72.   return $form;
    73. }
    74.  
    75. /**
    76.  * Callback for both ajax-enabled buttons.
    77.  *
    78.  * Selects and returns the fieldset with the names in it.
    79.  */
    80. function example_add_more_callback($form, $form_state) {
    81.   return $form['objects'];
    82. }
    83.  
    84. /**
    85.  * Submit handler for the "add-one-more" button.
    86.  *
    87.  * Increments the max counter and causes a rebuild.
    88.  */
    89. function example_add_more_add_one($form, &$form_state) {
    90.   $form_state['num_objects']++;
    91.   $form_state['rebuild'] = TRUE;
    92. }
    93.  
    94. /**
    95.  * Submit handler for the "remove one" button.
    96.  *
    97.  * Decrements the max counter and causes a form rebuild.
    98.  */
    99. function example_add_more_remove_one($form, &$form_state) {
    100.   if ($form_state['num_objects'] > 1) {
    101.     $form_state['num_objects']--;
    102.   }
    103.   $form_state['rebuild'] = TRUE;
    104. }

    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:

    1.   insert_form: function (data, settings, instanceId) {
    2.     // Location, where to fetch the dialog.
    3.     var aurl = Drupal.settings.basePath + 'example/insert/ajax';
    4.     if(settings.ids) {
    5.       aurl += '/' + settings.ids.join();
    6.     }
    7.     var dialogdiv = $('<div id="example-insert-dialog"></div>');
    8.     dialogdiv.load(aurl, function(){
    9.       var dialogClose = function () {
    10.         try {
    11.           dialogdiv.dialog('destroy').remove();
    12.         } catch (e) {};
    13.       };

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

    1. /**
    2.  * implements hook_menu
    3.  */
    4. function example_menu() {
    5.   $items = array();
    6.   $items['example/insert/nojs'] = array(
    7.     'page callback' => 'example_get_insert_form',
    8.     'page arguments' => array(2),
    9.     'access callback' => TRUE,
    10.     'type' => MENU_CALLBACK,
    11.   );
    12.   $items['example/insert/ajax'] = array(
    13.     'delivery callback' => 'ajax_deliver'
    14.   ) + $items['example/insert/nojs'];
    15.  
    16.   $items['example/insert/nojs/%'] = array(
    17.     'page callback' => 'example_get_insert_form',
    18.     'page arguments' => array(2, 3),
    19.     'access callback' => TRUE,
    20.     'type' => MENU_CALLBACK,
    21.   );
    22.   $items['example/insert/ajax/%'] = array(
    23.     'delivery callback' => 'ajax_deliver'
    24.   ) + $items['example/insert/nojs/%'];
    25.   return $items;
    26. }

    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.

    1. /**
    2.  * Retrive the insert form.
    3.  */
    4. function example_get_insert_form($ajax, $ids = '') {
    5.   $is_ajax = $ajax === 'ajax';
    6.   $form = drupal_get_form('example_insert_form', $ids);
    7.   if ($is_ajax) {
    8.     $form = drupal_render($form);
    9.     // Generate the settings:
    10.     $settings = '';
    11.     $javascript = drupal_add_js();
    12.     if(isset($javascript['settings'], $javascript['settings']['data'])) {
    13.       $settings = '<script type="text/javascript">jQuery.extend(Drupal.settings, ';
    14.       $settings .= drupal_json_encode(call_user_func_array('array_merge_recursive', $javascript['settings']['data']));
    15.       $settings .=  ');</script>';
    16.     }
    17.     die($form . $settings);
    18.   }
    19.   else {
    20.     return $form;
    21.   }
    22. }

    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 #3

    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

    This next part will cover how to expand the javascript wysiwyg button to include a form with multiple textfields which will the generate the token for displaying our entity.

    First we need a form which will allow the user to enter the id of the entity to display using the token. For this we will make a drupal form in our .module file.

     

     

    1. function ting_token_insert_form($form, &$form_state) {
    2.   drupal_add_library('system', 'ui.dialog');
    3.   $form['#tree'] = TRUE;
    4.  
    5.   $form['objects'] = array(
    6.     '#type' => 'fieldset',
    7.     '#title' => t('Entities'),
    8.     '#prefix' => '<div id="ting-token-fieldset-wrapper">',
    9.     '#suffix' => '</div>',
    10.   );
    11.  
    12.   $form['objects']['entity1'] = array(
    13.     '#type' => 'textfield',
    14.     '#title' => t('Entity id'),
    15.     '#default_value' => '',
    16.   );
    17.   $form['objects']['entity2'] = array(
    18.     '#type' => 'textfield',
    19.     '#title' => t('Entity id'),
    20.     '#default_value' => '',
    21.   );
    22.  
    23.   $form['submit'] = array(
    24.     '#type' => 'submit',
    25.     '#value' => t('Submit'),
    26.     '#attributes' => array(
    27.       'class' => array('form-save-ids'),
    28.     ),
    29.   );
    30.  
    31.   return $form;
    32. }

    Now with the form ready we need to create a way for our javascript to retrieve it, and for this we need a menu hook and a custom content function.

    1. /**
    2.  * implements hook_menu
    3.  */
    4. function example_menu() {
    5.   $items = array();
    6.   $items['example/insert/nojs'] = array(
    7.     'page callback' => 'example_get_insert_form',
    8.     'page arguments' => array(2),
    9.     'access callback' => TRUE,
    10.     'type' => MENU_CALLBACK,
    11.   );
    12.   $items['example/insert/ajax'] = array(
    13.     'delivery callback' => 'ajax_deliver'
    14.   ) + $items['example/insert/nojs'];
    15.   return $items;
    16. }
    17.  
    18. function example_get_insert_form($ajax) {
    19.   $is_ajax = $ajax === 'ajax';
    20.   $form = drupal_get_form('example_insert_form', $ids);
    21.   if ($is_ajax) {
    22.     $form = drupal_render($form);
    23.     die($form);
    24.   }
    25.   else {
    26.     return $form;
    27.   }
    28. }

    The menu hook is pretty strait forward, it uses drupals ajax system to handle the data return. The example_get_insert_form returns a rendered form is the parameter is ajax and a form render array if it's not. Now we are ready to rewrite our javascript.

    1. // $Id$
    2. (function ($) {
    3.  
    4. Drupal.wysiwyg.plugins['tokenInsert'] = {
    5.  
    6.   /**
    7.    * Return whether the passed node belongs to this plugin (note that "node" in this context is a JQuery node, not a Drupal node).
    8.    *
    9.    * We identify code managed by this example plugin by giving it the HTML class
    10.    * 'tokenInsert'.
    11.    */
    12.   isNode: function(node) {
    13.     res = $(node).is('.tokenInsert');
    14.     return ($(node).is('.tokenInsert'));
    15.   },
    16.  
    17.   /**
    18.    * Invoke is called when the toolbar button is clicked.
    19.    */
    20.   invoke: function(data, settings, instanceId) {
    21.     // Typically, an icon might be added to the WYSIWYG, which HTML gets added
    22.     // to the plain-text version.
    23.     if (data.format == 'html') {
    24.       var content = this._getIds(data.content);
    25.       if(content !== '') {
    26.         settings.ids = content
    27.       } else {
    28.         settings.ids = '';
    29.       }
    30.     }
    31.     else {
    32.       var content = '<!--exampleInsert-->';
    33.     }
    34.     if (typeof content !== 'undefined') {
    35.       Drupal.wysiwyg.plugins.tokenInsert.insert_form(data, settings, instanceId);
    36.     }
    37.   },
    38.   insert_form: function (data, settings, instanceId) {
    39.     // Location, where to fetch the dialog.
    40.     var aurl = Drupal.settings.basePath + 'example/insert/ajax';
    41.     var dialogdiv = $('<div id="example-insert-dialog"></div>');
    42.     dialogdiv.load(aurl, function(){
    43.       var dialogClose = function () {
    44.         try {
    45.           dialogdiv.dialog('destroy').remove();
    46.         } catch (e) {};
    47.       };
    48.       var btns = {};
    49.       btns[Drupal.t('Cancel')] = function () {
    50.         $(this).dialog("close");
    51.       };
    52.       var $this = this;
    53.       dialogdiv.find('.form-save-ids').click(function(evt) {
    54.         evt.preventDefault();
    55.         var ids = [],
    56.           $items = dialogdiv.find('#ting-token-fieldset-wrapper .form-text');
    57.         $items.each(function() {
    58.           ids.push($(this).val());
    59.         });
    60.         settings.tingIds = ids;
    61.         var content = Drupal.wysiwyg.plugins['tokenInsert']._getPlaceholder(settings);
    62.         Drupal.wysiwyg.instances[instanceId].insert(content);
    63.         dialogdiv.dialog("close");
    64.       });
    65.       dialogdiv.dialog({
    66.         modal: true,
    67.         autoOpen: false,
    68.         closeOnEscape: true,
    69.         resizable: true,
    70.         draggable: true,
    71.         autoresize: true,
    72.         namespace: 'jquery_ui_dialog_default_ns',
    73.         dialogClass: 'jquery_ui_dialog-dialog',
    74.         title: Drupal.t('Insert'),
    75.         buttons: btns,
    76.         width: '70%',
    77.         close: dialogClose
    78.       });
    79.       dialogdiv.dialog("open");
    80.       Drupal.attachBehaviors();
    81.     });
    82.   },
    83.   /**
    84.    * Replace all <!--exampleInsert--> tags with the icon.
    85.    */
    86.   attach: function(content, settings, instanceId) {
    87.     content = content.replace(/<!--exampleInsert-->/g, this._getPlaceholder(settings));
    88.     return content;
    89.   },
    90.  
    91.   /**
    92.    * Replace the icons with <!--exampleInsert--> tags in content upon detaching editor.
    93.    */
    94.   detach: function(content, settings, instanceId) {
    95.     var $content = $('<div>' + content + '</div>');
    96.     $.each($('.exampleInsert', $content), function (i, elem) {
    97.       elem.parentNode.removeChild(elem);
    98.     });
    99.     return $content.html();
    100.   },
    101.  
    102.   /**
    103.    * Helper function to return a HTML placeholder.
    104.    */
    105.   _getPlaceholder: function (settings) {
    106.     if(settings.ids) {
    107.       return '[ting:teaser:' + settings.ids.join() + ']';
    108.     }
    109.     return '';
    110.   },
    111.  
    112.   /**
    113.    * Helper function to return ids from a placeholder.
    114.    */
    115.   _getIds: function (content) {
    116.     var ids = ''
    117.     if(content.indexOf('[ting:teaser:') === 0 && content.indexOf(']') === (content.length - 1)) {
    118.       content = content.replace('[ting:teaser:', '');
    119.       content = content.replace(']', '');
    120.       ids = content.split(',');
    121.     }
    122.     return ids;
    123.   }
    124. };
    125.  
    126. })(jQuery);

    We can now insert two entities using our new insert form, but we still want more. We want to be able to insert multiple ids using ajax to add and remove id's, so stay tuned for the next tutorial.

  • 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

    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.

    1. /**
    2.  * implements hook_wysiwyg_include_directory
    3.  */
    4. function example_wysiwyg_include_directory($type) {
    5.   return $type;
    6. }

    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>/plugins/tokenInsert.inc
    <module>/plugins/tokenInsert/tokenInsert.js
    <module>/plugins/tokenInsert/tokenInsert.css
    <module>/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

    1. /**
    2.  * Implementation of hook_wysiwyg_plugin().
    3.  */
    4. function example_tokenInsert_plugin() {
    5.   $plugins['tokenInsert'] = array(
    6.     'title' => t('Insert object'),
    7.     'icon file' => 'icon.png',
    8.     'icon title' => t('Insert objects'),
    9.     'settings' => array(),
    10.   );
    11.   return $plugins;
    12. }

    that is it for this file.

    The javascript file will provide the functionality for wysiwyg.

    1. // $Id$
    2. (function ($) {
    3.  
    4. Drupal.wysiwyg.plugins['example'] = {
    5.  
    6.   /**
    7.    * Return whether the passed node belongs to this plugin (note that "node" in this context is a JQuery node, not a Drupal node).
    8.    *
    9.    * We identify code managed by this example plugin by giving it the HTML class
    10.    * 'wysiwyg-plugin-example'.
    11.    */
    12.   isNode: function(node) {
    13.     res = $(node).is('img.wysiwyg-plugin-example');
    14.     return ($(node).is('img.wysiwyg-plugin-example'));
    15.   },
    16.  
    17.   /**
    18.    * Invoke is called when the toolbar button is clicked.
    19.    */
    20.   invoke: function(data, settings, instanceId) {
    21.      // Typically, an icon might be added to the WYSIWYG, which HTML gets added
    22.      // to the plain-text version.
    23.      if (data.format == 'html') {
    24.        var content = this._getPlaceholder(settings);
    25.      }
    26.      else {
    27.        var content = '<!--wysiwyg-plugin-example-->';
    28.      }
    29.      if (typeof content != 'undefined') {
    30.        Drupal.wysiwyg.instances[instanceId].insert(content);
    31.      }
    32.    },
    33.  
    34.   /**
    35.    * Replace all <!--wysiwyg-plugin-example--> tags with the icon.
    36.    */
    37.   attach: function(content, settings, instanceId) {
    38.     content = content.replace(/<!--wysiwyg-plugin-example-->/g, this._getPlaceholder(settings));
    39.     return content;
    40.   },
    41.  
    42.   /**
    43.    * Replace the icons with <!--wysiwyg_example_plugin--> tags in content upon detaching editor.
    44.    */
    45.   detach: function(content, settings, instanceId) {
    46.     var $content = $('<div>' + content + '</div>');
    47.     $.each($('img.wysiwyg-plugin-example', $content), function (i, elem) {
    48.       elem.parentNode.insertBefore(document.createComment('wysiwyg-plugin-example'), elem);
    49.       elem.parentNode.removeChild(elem);
    50.     });
    51.     return $content.html();
    52.   },
    53.  
    54.   /**
    55.    * Helper function to return a HTML placeholder.
    56.    *
    57.    * Here we provide an image to visually represent the hidden HTML in the Wysiwyg editor.
    58.    */
    59.   _getPlaceholder: function (settings) {
    60.     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" />';
    61.   }
    62. };
    63.  
    64. })(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

    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

    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 hook_token_info

    1. function example_token_info() {
    2.   $type = array(
    3.     'name' => t('Example entity'),
    4.     'description' => t('Display an entity.'),
    5.   );
    6.  
    7.   // Core tokens for nodes.
    8.   $consent['example'] = array(
    9.     'name' => t("Example entity"),
    10.     'description' => t("Show an entity."),
    11.   );
    12.  
    13.   return array(
    14.     'types' => array('example' => $type),
    15.     'tokens' => array('example' => $consent),
    16.   );
    17. }

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

    1. function example_tokens($type, $tokens, array $data = array(), array $options = array()) {
    2.   $url_options = array('absolute' => TRUE);
    3.   if (isset($options['language'])) {
    4.     $url_options['language'] = $options['language'];
    5.     $language_code = $options['language']->language;
    6.   }
    7.   else {
    8.     $language_code = NULL;
    9.   }
    10.   $sanitize = !empty($options['sanitize']);
    11.   $replacements = array();
    12.   if ($type == 'example') {
    13.     foreach ($tokens as $name => $original) {
    14.       $args = explode(':', $name);
    15.       $view_mode = array_shift($args);
    16.       $eid = implode(':', $args);
    17.       if(strpos($eid, ',') !== FALSE) {
    18.         $eids = explode(',', rawurldecode($eid));
    19.       } else {
    20.         $eids = array(rawurldecode($eid));
    21.       }
    22.      
    23.       $entities = entity_load('example', $eids);
    24.       $output = '<div class="example-inline-list">';
    25.       foreach($entities as $id => $entity) {
    26.         $object = entity_view($entity, $view_mode);
    27.         if($view_mode == 'list_item') {
    28.           $object['#attributes']['class'][] = format_string('compact');
    29.         }
    30.         $output .= drupal_render($object);
    31.       }
    32.       $output .= '</div>';
    33.       $replacements[$original] = $output;
    34.     }
    35.   }
    36.  
    37.   return $replacements;
    38. }

    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.

  • Shadowrun combat manager

    I have released an app on google play for shadowrun 4th and 5th edition which handles the, at times, very complex initiative round for gamemasters. I hope it will help others as it is helping me manage the endless lists of combatens who are constantly changing positions in the list due to modifiers added during combat.

    The app will also be released on ios as soon as i have ironed out all the bugs which may exist.

    I have build the app using phonegap and jquery mobile, which has been a very fustrating process due to a number of challenges with the integration to the os. But it allows be to have one code base while publishing for multiple platforms.

    the app can be downloaded here

    It should be quite strait forward to use, but changing the version is done by clicking the menu button and selecting settings. Some users had difficulties finding this so I thought i would mention it.

    My current development road map is:

    1. Save list between uses
    2. Use number fields for initiative and phases
    3. Combat actions - effect on initiative
    4. Profiles
    5. Like the suggestion for matrix/magic have to see what I can do here.

    So far I am on version 1.4, with most bugs fixed and 2 items on my road map done. Next up is combat actions for fifth edition.

Pages