Meteor modal

§
by
Jesper bisgaard

I recently had to create a modal pop up in Meteor js, which proved to be quite easy so I thought I might share the solution here.

For handling modals I used the exellent library anti:modals which handles opening and closing the modals on a page. Now you just have to hook up the content for the modal and you are ready to rock!

Once you have added the library to your meteor site you can go ahead and add your modals.

First you setup the modal template, I am using Bulma for markup and styling in this example.

I have created a template file editExample.html

<template name="editExample">
  <div class="modal is-active">
    <div class="modal-background"></div>
    <div class="modal-card">
      <header class="modal-card-head">
        <p class="modal-card-title">Edit example</p>
        <button id="close-edit-modal" class="delete" aria-label="close"></button>
      </header>
      <section class="modal-card-body">
        <form class="edit-example">
          <div class="field is-horizontal">
            <div class="field-label is-small">
              <label class="label" for="name">New example</label>
            </div>
            <div class="field-body">
              <div class="field">
                <p class="control is-expanded">
                  <input class="input is-small" type="name" name="name" placeholder="New example" value="{{example}}" />
                </p>
              </div>
            </div>
          </div>
          <div class="field is-horizontal">
            <div class="field-label">
              <!-- Left empty for spacing -->
            </div>
            <div class="field-body">
              <div class="field">
                <div class="control">
                  <input class="button is-primary is-small" type="submit" value="Save"/>
                </div>
              </div>
            </div>
          </div>
        </form>
      </section>
    </div>
  </div>
</template>

In this file we setup the html markup for our modal popup.

Now the next think we have got to do is create the functionality for this template. So we will create an editExample.js file.

import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';

// Import the template.
import './editExample.html';

// Store the example being passed to the modal.
Template.editExample.onCreated(function () {
  this.example = this.data.example;
});

// Setup event handling.
Template.editExample.events({
  // Clicking the close button.
  'click #close-edit-modal'() {
    // Close all active modal windows.
    AntiModals.dismissAll();
  },
  // Submitting the form in the modal window.
  'submit .edit-example'(event) {
    // Prevent default browser form submit.
    event.preventDefault();

    // Get value from form element.
    const target = event.target;
    const name = target.name.value;

    // Insert a expense into the collection.
    Meteor.call('example.update', this.example._id, name);

    // Clear form.
    target.name.value = '';

    // Close all active modal windows.
    AntiModals.dismissAll();
  },
});

Now we are ready to start using our modal popup.

In order to display your modal popup, we need to add a few things to a page or component.

First we need to import the new modal component.

import '../modal/editExample';

Next we add a button which will trigger an event. So in the html template where we want to use our modal we add:

<a class="button edit-example">Edit example</a>

And as the last piece of the puzzel we add the action which invokes the modal:

'click .edit-example'(e) {
  e.preventDefault();
  AntiModals.overlay('editExample', {'data': {'example': this}});
}

And thats it. A very simple library for displaying modal popups in meteor.

Wysiwyg button with form #4

§
by
Jesper bisgaard

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

§
by
Jesper bisgaard

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.

Handlebars js

§
by
Jesper bisgaard

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:

var themeData = { 
  title: 'test', 
  src: 'data', 
  class: 'some class' 
}; 
T.render('testTemplateItem', function(t) { $('.target').append(t(themeData)); }); 

My template would then look something like this.

<div class="{{class}}">
  {{#if src}}
  <div class="image-container"><img src="{{src}}" /></div>
  {{/if}}
  <h2>{{title}}</h2>
</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.

 

 

Hopscotch tour js

§
by
Jesper bisgaard

I had to include some help text for a mobile app i am developing and maintaining and for this we decided to use a tour script because it would allow us to keep the screen clear of static description text and at the same time give a smooth intoduction to the elements which needed explanations.

I tested out several scripts like joyride and pageguide, but found that the one which worked best with jquery mobile was hopscotch js. It allows lots of configuration and you configure the tours in json, which keeps the html of the app clean.

Drupal 7 form cancel button

§
by
Jesper bisgaard

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.

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

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.

$form['actions']['back'] = array(
  '#type' => 'submit', 
  '#value' => t('Back'), 
  '#limit_validation_errors' => array(), 
  '#submit' => array('example_form_submit'), 
); 

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

Death of the human policlub extrimist

§
by
Jesper bisgaard

A metahuman rights activist hires the runners to kill an extrimist policlub leader along with any member of his group they can find. They are hiding out somewere between Seattle and the NaN territories.

In order to find the group the runners have to infiltrate the local Seattle policlub and find out that a pair of their local members are delivering food to the extrimist group once a week.

The missing judge

§
by
Jesper bisgaard

One day when the players are sitting at their favorite barrens soycaf place a full scale gang war breakout right in the street where they are sitting. If one of the runners has a gang contact he or she can tell the runner that someone has kidnapped the judge, if no runner has such a contact perceptions checks will reveal that the gangs are accusing each other of the crime.

The judge is a powerful mage who acts as a broker between the gangs to avoid conflicts that could escalate to a war.

He has in fact been kidnapped by an agricultural firm by the name of Sivak which is in league with Wuxing and they are looking to expand into several areas of the barrens inhabited by the gangs.

Search for the elvish song stones

§
by
Jesper bisgaard

A dwarf in an expensive suade suite hires the runners to find five elvish song stones. A song stone is a jewel imbued with magical properties to play music and display wonderful visual colors at the same time. They each go for around 10.000 on the black marked. He offers the runners 3000¥ for each stone they get.

There are sixteem stones in Seattle and they are divided between 6 people.

  1. An elven black marked dealer has 2 stones.
  2. A human stock broker has 3 stone.
  3. A famous human singer has1 stones.
  4. The space needle has 1 on display.
  5. A local business owner has 2 stones (Gregory Jones).
  6. The seattle museum of arts has 7 stones on display, borrowed to them by the Tir Tairngire.

Hostile takeover

§
by
Jesper bisgaard

A company man hires the runners to steal a local companys research data. The company "NABER - New age bio enginering" resides in Snohomish and does a lot if research into biology and agriculture. Mr Johnson is from Ares and has orders to pave the way for a purchase of NABER. He offers the runners 5000 and 10% of the research's value.

NABER has employed a team of shadowrunners of their own to protect their recent findings, there is a decker, a rigger, a mage and 2 steet samurais.

The main shareholder in the company is Howard Jackson, there are 8 bordmembers and they have contracted a private company for aditional security "Firelance security".