In this article we will dissect one of my custom module which function is to load a drupal node and render it in a jQuery modal box.

The module will utilizes simpleModal jQuery plugin and custom ajax script to fetch drupal node and display it inside a modal box.

The work flow of the module:

1. Create the starting point by using theme function
2. When user click on the starting point url, custom jQuery script picks the starting point href that contains drupal node id (nid)
3. jQuery make an ajax request to the href url stated in the starting point url
4. Drupal process the jQuery ajax request and load the node by utilizing node_load().
5. Drupal then return the loaded node data back to jQuery via json
6. jQuery process the return data and display it via simpleModal jQuery plugin.

Now let us start building this module. Let us call the module drupie_ajax_node_simplemodal

Step 1
We need to create the module .info file, which it will be named drupie_ajax_node_simplemodal.info

The file will contains :

name = Drupie Ajax Node SimpleModal
description = A module to display node content utilizing jQuery SimpleModal plugin and Ajax callback
package = Drupie Modules
core = 6.x

Step 2

Now let us make the .module file for this module, the file will be named drupie_ajax_node_simplemodal.module

We need to invoke the hook_menu as follow:

/**
 * Implementation of hook_menu().
 */
function drupie_ajax_node_simplemodal_menu() { 
  $items = array();

 // this is the standard menu for the module administration page

  $items['admin/settings/drupie-ajax-node-simplemodal'] = array(
    'title' => 'Drupie ajax node simplemodal configuration page',
    'page callback' => 'drupal_get_form', 
    'page arguments' => array('drupie_ajax_node_simplemodal_settings_form'),
    'access arguments' => array('administer site configuration'),
    'file' => 'drupie_ajax_node_simplemodal.admin.inc', // this is the module admin form file
	);
  
  // This is the minimum information you can provide for a menu item.
  $items['simplemodal'] = array(
    'title' => '',
    'page callback' => 'node_load_simplemodal', // this is the call back function that will render the node during ajax call
    'access arguments' => array('access content'),
  );

  return $items;
}

Next we need to invoke the hook_init that will load our js file and css file when the module starts


/**
 * Implementation of hook_init().
 */
function drupie_ajax_node_simplemodal_init() {

// load the livequery jQuery plugin, you can use Drupal attach behavior to solve jQuery event bubbling, personally I like livequery much better
drupal_add_js(drupal_get_path('module', 'drupie_ajax_node_simplemodal') .'/jquery.livequery.js');

// Load the simpleModal plugin, we need this for our modal box
drupal_add_js(drupal_get_path('module', 'drupie_ajax_node_simplemodal') .'/jquery.simplemodal-1.3.5.min.js');

// load the module default css
drupal_add_css(drupal_get_path('module', 'drupie_ajax_node_simplemodal') .'/drupie_ajax_node_simplemodal_default.css');

// process the value from the configuration page and pass them to jQuery using drupal_add_js. This configuration value is directly taken from simpleModal page. it hasn't been tested thoroughly.
$settings['drupie_simplemodal'] = array (
  'appendTo' => variable_get('appendto', 'body'),
  'focus' => variable_get('focus', 'true'),
  'opacity' => variable_get('opacity', '50'),
  'overlayId' => variable_get('overlayid', 'simplemodal-overlay'),
  'containerId' => variable_get('containerid', 'simplemodal-container'),
  'dataId' => variable_get('dataid', 'simplemodal-data'),
  'minHeight' => variable_get('minheight', 'null'),
  'minWidth' => variable_get('minwidth', 'null'),
  'maxHeight' => variable_get('maxheight', 'null'),
  'maxWidth' => variable_get('maxwidth', 'null'),
  'autoResize' => variable_get('autoresize', 'false'),
  'autoPosition' => variable_get('autoposition', 'true'),
  'zIndex' => variable_get('zindex', '1000'),
  'close' => variable_get('close', 'true'),
  'closeHTML' => variable_get('closehtml', ''),
  'closeClass' => variable_get('closeclass', 'simplemodal-close'),	
  'escClose' => variable_get('escclass', 'true'),
  'overlayClass' => variable_get('overlayclass', 'false'),
  'position' => variable_get('position', 'null'),
  'persist' => variable_get('persist', 'false'),
  'modal' => variable_get('modal', 'true'),
  'pointer' => variable_get('pointer_class', 'simplemodal'),
);
// load the $settings array to jQuery using drupal_add_js
drupal_add_js($settings, 'setting');
drupal_add_js(drupal_get_path('module', 'drupie_ajax_node_simplemodal') .'/drupie_ajax_node_simplemodal.js');
}

Next step is to invoke the hook_theme for our pointer


function drupie_ajax_node_simplemodal_theme() {
  return array(
    'simplemodal' => array(
      'arguments' => array(
        'text' => null, // this is where we pass the first argument
        'nid' => null, // this is for the nid argument
       ),
    ),
    'simplemodal_content' => array(
      'arguments' => array(
        'node' => null, // this is for the nid argument
      ),
    ),
	);
}

After we have our module hook_theme, we need to create the theme function that match the hook_theme

/**
 * This is the theme for displaying the content in modal box
 */

function theme_simplemodal_content($node) {
  // The important value is the node_view($node)
  // The other html value is optional, you can use your own layout and html tags
  $output .= '';
  $output .= ''.        node_view($node) .'';					
  $output .= '';

return $output;
}

/**
  * This is the pointer theming function
  **/

function theme_simplemodal($text, $nid) {
// the $text is the link text that will be displayed
// the nid is the target node nid
// the variable pointer class is the class that will be linked with simpleModal
// we utilize drupal l() to create the url

  $output .= l($text, 'simplemodal/' . $nid, array('attributes' => array('class' =>  variable_get('pointer_class', 'simplemodal'))));
  return $output;
}

Final step for the .module file, create the actual node loading function that will respond to ajax callback and returning the value that will be displayed in the modal box

/**
 * this is where the loading and returning the data value to ajax function
 */
function node_load_simplemodal() {

  // get the target nid from drupal url arg()
  $nid = check_plain(arg(1));

  // this module only handles node/% url 
  // todo : add other kind of url parsing

  // check if user have permission to access the forwarded node
  if (is_numeric($nid)) {
  $check_access = menu_get_item($path = 'node/' . $nid , $router_item = NULL);

  	if ($check_access == true) {
  	 $node_data = node_load($nid);
  	 $output = theme('simplemodal_content', $node_data);
  	} 
  	else {
  	 $output .= ''. t('Unauthorized access') . '';
  	}
  	
  	if (arg(2) == 'json') {
     // Check if the call is made from ajax
     // return the data to jQuery using drupal_json()
     $javascript = drupal_add_js(NULL, NULL);
     $output_js = isset($javascript['setting']) ? 'jQuery.extend(Drupal.settings, '. drupal_to_js(call_user_func_array('array_merge_recursive', $javascript['setting'])) .');' : '';
  	 $output .= $output_js;
  	 return drupal_json(array('simplemodalContent'=>$output));
  	 exit;
  	} 
  	else {
  	  // if it is not made from ajax do normal drupal loading page using drupal_goto
      if ($check_access == true) {
  	    drupal_goto('node/' . $nid);
  	  } else {
  	    drupal_goto($path = '', $query = NULL, $fragment  = NULL, $http_response_code = 403);
  	  }
  	}
  }
}

Step Three

Now we need to create our custom js script that handles the event click for our pointer and do ajax calls to fetch the data. You need to name the file drupie_ajax_node_simplemodal.js


Drupal.behaviors.DrupieAjaxNodeSimplemodal = function (context)    {

// this is for the simpleModal options, the data is taken from the module administration page and parsed here using drupal_add_js().
// this is really optional, you can parse the value manually. Check out the simpleModal page to get a better idea of how to set the plugin

var option = {
  appendTo : Drupal.settings.drupie_simplemodal.appendTo,
  focus : Drupal.settings.drupie_simplemodal.focus, 
  opacity : Drupal.settings.drupie_simplemodal.opacity, 
  overlayId : Drupal.settings.drupie_simplemodal.overlayId, 
  containerId : Drupal.settings.drupie_simplemodal.containerId, 
  dataId : Drupal.settings.drupie_simplemodal.dataId, 
  minHeight : Drupal.settings.drupie_simplemodal.minHeight, 
  minWidth : Drupal.settings.drupie_simplemodal.minWidth, 
  maxHeight : Drupal.settings.drupie_simplemodal.maxHeight, 
  maxWidth : Drupal.settings.drupie_simplemodal.maxWidth,
  autoResize : Drupal.settings.drupie_simplemodal.autoResize, 
  autoPosition : Drupal.settings.drupie_simplemodal.autoPosition,
  zIndex : Drupal.settings.drupie_simplemodal.zIndex, 
  close : Drupal.settings.drupie_simplemodal.close, 
  closeHTML : Drupal.settings.drupie_simplemodal.closeHTML, 
  closeClass : Drupal.settings.drupie_simplemodal.closeClass, 
  escClose : Drupal.settings.drupie_simplemodal.escClass, 
  overlayClass : Drupal.settings.drupie_simplemodal.overlayClass, 
  position : Drupal.settings.drupie_simplemodal.position, 
  persist : Drupal.settings.drupie_simplemodal.persist,
  modal : Drupal.settings.drupie_simplemodal.modal,
};

// set our pointer class so jQuery knows which element to listen for event
var pointer = 'a.' + Drupal.settings.drupie_simplemodal.pointer;

// this is our function that invoke the element click event
$(pointer, context).click(
  function(e) {	
    // this will start when the element is clicked and before the ajax fetching process is successfull
    $(this).prepend(''); // build a throbber
    $(this).attr("disabled", true); // disable the pointer element to prevent double clicking
  
  // this is the variable that will be called when the ajax is successfully retrieve the json data
  var updateContent = function(data) {
  $.modal(data.simplemodalContent, option); // create the modal box with the fetched data
  $('.small-throbber').remove(); // remove the throbber
  $(this).removeAttr("disabled"); // re-enable the pointer element
  }
  // the actual ajax fetching function
  $.ajax({
  type: 'POST', // you can use POST or GET
  url: this.href + '/json', // Which url should be handle the ajax request. This is the url defined in the  html tag
  success: updateContent, // The js function that will be called upon success request
  dataType: 'json', //define the type of data that is going to get back from the server
  data: 'js=1' //Pass a key/value pair
  });

  return false;  // return false so the navigation stops here and not continue to the page in the link
  });
};

Step Four

Now we need to create the module administration form for configuring the module and simpleModal. You will need to create the file named :
drupie_ajax_node_simplemodal.admin.inc

/**
  * Setting form for drupie ajax node simplemodal
  * The default value is taken from simpleModal page
  **/
function drupie_ajax_node_simplemodal_settings_form() {

  $form = array();
  $form['drupie_simplemodal_module'] = array(
    '#type' => 'fieldset',
    '#weight' => -20,
    '#title' => t('Drupal related module setting'),
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
  );
  $form['drupie_simplemodal_module']['pointer_class'] = array(
    '#type' => 'textfield',
    '#title' => t('The pointer class'),
    '#description' => t('The pointer class that will be used in the theme_simplemodal \'a\' html element'),
    '#default_value' => variable_get('pointer_class', 'simplemodal'),
  );
  $form['drupie_simplemodal'] = array(
    '#type' => 'fieldset',
    '#weight' => -20,
    '#title' => t('Simplemodal jQuery plugin settings'),
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
  );
  $form['drupie_simplemodal']['appendto'] = array(
    '#type' => 'textfield',
    '#title' => t('appendTo'),
    '#description' => t('The jQuery selector to append the elements to. For ASP.NET, use \'form\''),
    '#default_value' => variable_get('appendto', 'body'),
  );
  $form['drupie_simplemodal']['focus'] = array(
    '#type' => 'select',
    '#options' => array( 'true' => 'true', 'false' => 'false'),
    '#title' => t('focus'),
    '#description' => t('Forces focus to remain on the modal dialog'),
    '#default_value' => variable_get('focus', 'true'),
  );
  $form['drupie_simplemodal']['opacity'] = array(
    '#type' => 'textfield',
    '#title' => t('opacity'),
    '#description' => t('The opacity value for the overlay div, from 0 - 100'),
    '#default_value' => variable_get('opacity', '50'),
  );
  $form['drupie_simplemodal']['overlayid'] = array(
    '#type' => 'textfield',
    '#title' => t('overlayId'),
    '#description' => t('The DOM element id for the overlay div'),
    '#default_value' => variable_get('overlayid', 'simplemodal-overlay'),
  );
  $form['drupie_simplemodal']['containerid'] = array(
    '#type' => 'textfield',
    '#title' => t('containerId'),
    '#description' => t('The DOM element id for the container div'),
    '#default_value' => variable_get('containerid', 'simplemodal-container'),
  );
  $form['drupie_simplemodal']['dataid'] = array(
    '#type' => 'textfield',
    '#title' => t('dataId'),
    '#description' => t('The DOM element id for the data div'),
    '#default_value' => variable_get('dataid', 'simplemodal-data'),
  );
  $form['drupie_simplemodal']['minheight'] = array(
    '#type' => 'textfield',
    '#title' => t('minHeight'),
    '#description' => t('The minimum height for the container'),
    '#default_value' => variable_get('minheight', ''),
  );
  $form['drupie_simplemodal']['minwidth'] = array(
    '#type' => 'textfield',
    '#title' => t('minWidth'),
    '#description' => t('The minimum width for the container'),
    '#default_value' => variable_get('minwidth', ''),
  );
  $form['drupie_simplemodal']['maxheight'] = array(
    '#type' => 'textfield',
    '#title' => t('maxheight'),
    '#description' => t('The maximum height for the container. If not specified, the window height is used.'),
    '#default_value' => variable_get('maxheight', ''),
  );	
  $form['drupie_simplemodal']['maxwidth'] = array(
    '#type' => 'textfield',
    '#title' => t('maxWidth'),
    '#description' => t('The maximum width for the container. If not specified, the window width is used.'),
    '#default_value' => variable_get('maxwidth', ''),
  );
  $form['drupie_simplemodal']['autoresize'] = array(
    '#type' => 'select',
    '#options' => array( 'true' => 'true', 'false' => 'false'),
    '#title' => t('autoResize'),
    '#description' => t('Resize container on window resize? Use with caution - this may have undesirable side-effects.'),
    '#default_value' => variable_get('autoresize', 'false'),
  );	
 $form['drupie_simplemodal']['autoposition'] = array(
    '#type' => 'select',
    '#options' => array( 'true' => 'true', 'false' => 'false'),
    '#title' => t('autoPosition'),
    '#description' => t('Automatically position container on dialog creation and window resize?'),
    '#default_value' => variable_get('autoposition', 'true'),
  );
  $form['drupie_simplemodal']['zindex'] = array(
    '#type' => 'textfield',
    '#title' => t('zIndex'),
    '#description' => t('Starting z-index value'),
    '#default_value' => variable_get('zindex', '1000'),
  );
  $form['drupie_simplemodal']['close'] = array(
    '#type' => 'select',
    '#options' => array( 'true' => 'true', 'false' => 'false'),
    '#title' => t('close'),
    '#description' => t('If true, closeHTML, escClose and overClose  will be used if set. If false, none of them will be used.'),
    '#default_value' => variable_get('close', 'true'),
  );
  $form['drupie_simplemodal']['closehtml'] = array(
    '#type' => 'textfield',
    '#title' => t('closeHTML'),
    '#description' => t('The HTML for the default close link. SimpleModal will automatically add the closeClass to this element.'),
    '#default_value' => variable_get('closehtml', ''),
  );	
  $form['drupie_simplemodal']['closeclass'] = array(
    '#type' => 'textfield',
    '#title' => t('closeClass'),
    '#description' => t('The CSS class used to bind to the close event'),
    '#default_value' => variable_get('closeclass', 'simplemodal-close'),
  );	
  $form['drupie_simplemodal']['escclose'] = array(
    '#type' => 'select',
    '#options' => array( 'true' => 'true', 'false' => 'false'),
    '#title' => t('escClose'),
    '#description' => t('Allow Esc keypress to close the dialog?'),
    '#default_value' => variable_get('escclose', 'true'),
  );
  $form['drupie_simplemodal']['overlayclose'] = array(
    '#type' => 'select',
    '#options' => array( 'true' => 'true', 'false' => 'false'),
    '#title' => t('overlayClose'),
    '#description' => t('Allow click on overlay to close the dialog?'),
    '#default_value' => variable_get('overlayclose', 'false'),
  );
  $form['drupie_simplemodal']['position'] = array(
    '#type' => 'textfield',
    '#title' => t('position'),
    '#description' => t('Position of container [top, left]. Can be number of pixels or percentage'),
    '#default_value' => variable_get('position', ''),
  );		
  $form['drupie_simplemodal']['persist'] = array(
    '#type' => 'select',
    '#options' => array( 'true' => 'true', 'false' => 'false'),
    '#title' => t('persist'),
    '#description' => t('Persist the data across modal calls? Only used for existing DOM elements. If true, the data will be maintained across modal calls, if false, the data will be reverted to its original state.'),
    '#default_value' => variable_get('persist', 'false'),
  );	
  $form['drupie_simplemodal']['modal'] = array(
    '#type' => 'select',
    '#options' => array( 'true' => 'true', 'false' => 'false'),
    '#title' => t('modal'),
    '#description' => t('If false, the overlay, iframe, and certain events will be disabled allowing the user to interace with the page below the dialog.'),
    '#default_value' => variable_get('modal', 'true'),
  );
	
  return system_settings_form($form);
}

Final Step

Finally, we need to create the css to format our layout. You need to name the file : drupie_ajax_node_simplemodal_default.css

/** Modal **/

/* Container */
#simplemodal-container-wrapper {
color:#555; 
background: transparent;
padding:0;
margin: 0;
border: none;
width: 640px;
height: 640px;
font-size: 14px;
line-height: 120%;
}
#simplemodal-container {
color:#555; 
background: transparent; 
padding:0;
font-size: 14px;
line-height: 120%;
border: none;
width: 640px;
}
#simplemodal-container .pseudo-top-left {
background: url('images/top-popup.png') no-repeat top left;
width: 50%;
height: 20px;
float: left;
}
#simplemodal-container .pseudo-top-right {
background: url('images/top-popup.png') no-repeat top right;
width: 50%;
height: 20px;
float: right;
}
#simplemodal-container .pseudo-bottom-left {
background: url('images/bottom-popup.png') no-repeat top left;
width: 50%;
height: 20px;
float: left;
}
#simplemodal-container .pseudo-bottom-right {
background: url('images/bottom-popup.png') no-repeat top right;
width: 50%;
height: 20px;
float: right;
}
#simplemodal-container .content-pseudo-wrapper-left {
background: url('images/side-popup.png') repeat-y top left;
padding-left: 20px;
float: left;
width:620px;
}
#simplemodal-container .content-pseudo-wrapper-right {
background: url('images/side-popup.png') repeat-y top right;
padding-right: 20px;
float: left;
overflow-x: hidden;
width: 600px;
}
#simplemodal-container-wrapper  a.modalCloseImg {
background:url('images/x.png') no-repeat; 
width:25px; 
height:29px; 
display:inline; 
z-index:3200; 
position:absolute; 
top:-15px; 
right:-16px; 
cursor:pointer;
}

div.simplemodal-wrap {
background: transparent;
overflow:hidden;
width: 640px;
border: none;
}
div.simplemodal-container div.node {
width: 580px;
padding: 0;
margin: 0;
float: left;
background: #fff;
max-height: 580px;
overflow-y: auto;
}
div.simplemodal-container div.node h2.title {
padding: 0;
margin: 10px 0;
}
div.simplemodal-container form.webform-client-form input.form-submit {
background:url("images/button-blue-small-fixed.png") no-repeat scroll 0 7px transparent;
border:medium none;
float:left;
height:52px;
margin: 0;
padding: 0;
width: 195px;
outline: none;
line-height: 44px;
text-align: center;
font-size: 16px;
}

div.small-throbber {
background: url('images/small-throbber.gif') no-repeat center center;
height: 16px;
width: 16px;
float: right;
}

div.simplemodal-container form.webform-client-form input.form-text {
background: url('images/input-textfield-small.png') no-repeat top left;
width: 253px;
height: 45px;
border: none;
outline: none;
padding: 0 10px;
line-height: 45px;
float: left;
}

div.simplemodal-container form.webform-client-form textarea.form-textarea {
background: url('images/input-textarea-small.png') no-repeat top left;
width: 321px;
height: 220px;
float: left;
border: none;
outline: none;
padding: 10px;
}

div.simplemodal-container form.webform-client-form label {
color:#878487;
float:left;
font-weight:bold;
padding:10px 20px;
text-align:right;
width:120px;
}

div.simplemodal-container form.webform-client-form div.webform-component-select label {
width: auto;
}
div.simplemodal-container form.webform-client-form label.option {
font-weight:normal;
line-height:120%;
margin:0;
padding:0;
text-align:left;
width:auto;
}
div.simplemodal-container form.webform-client-form div.form-item {
background:none repeat scroll 0 0 #ECEEF3;
float:left;
margin:0.5% 0;
padding:1%;
width:98%;
}

To use the module you need to invoke this in your theme or page content (you may need to turn input php filter on).


theme('simplemodal', link-text-to-be-displayed, target-nid);

You can download the zipped file of the module here :

http://drupal.org/node/848536

or in this article attachment.

AttachmentSize
Package icon drupie_ajax_node_simplemodal.zip47.45 KB