Index: ajaxblocks.admin.inc
===================================================================
--- ajaxblocks.admin.inc	(revision 0)
+++ ajaxblocks.admin.inc	(working copy)
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * Form builder; Configure ajaxblocks settings for this site.
+ *
+ * @ingroup forms
+ * @see system_settings_form()
+ */
+function ajaxblocks_admin_settings_form(){
+  $json2_path = _ajaxblocks_get_json2_path();
+  if( !empty($json2_path) ){
+  	variable_set('ajaxblocks_json2_path', $json2_path);
+  } else {
+    if( empty($json2_path) ){
+      $form['ajaxblocks_json2_missing'] = array(
+        '#prefix' => '<p>' . t('Modern browsers support caching without extra files, but it is recommended to install the following for legacy support:') . '</p><ul>',
+        '#suffix' => '</ul>'
+      );
+      $form['ajaxblocks_json2_missing']['json2'] = array(
+        '#markup' =>
+          '<li>' . t(
+            'Install <em>json2.js</em> from <a href="!link" target="_blank">!link</a> at <em>sites/all/libraries/json2/json2.js</em>'
+            ,array('!link' => '')
+          ) .  '</li>',
+      );
+    }
+  }
+
+  $form['ajaxblocks_enable_cookie_caching'] = array(
+    '#type' => 'radios',
+    '#title' => t('Enable cookie caching'),
+    '#required' => TRUE,
+    '#options' => array(
+      0 => t('No'),
+      1 => t('Yes'),
+    ),
+    '#default_value' => variable_get('ajaxblocks_enable_cookie_caching', 0),
+    '#description' => t(
+      'Modern browsers (IE8+, Firefox, Chrome, Safari, etc) allow client side caching of ajaxblocks content without the use of cookies through'
+      . ' <a href="!link" target="_blank">Web Storage</a>.'
+      . ' Support older browsers (IE7 and below), caching in cookies is possible. Additional javascript libraries and transmission overhead are required.'
+      ,array('!link' => 'http://en.wikipedia.org/wiki/Web_storage')
+    ),
+  );
+
+  if( empty($json2_path) ){
+    $form['ajaxblocks_enable_cookie_caching']['#value'] = 0;
+    $form['ajaxblocks_enable_cookie_caching']['#disabled'] = TRUE;
+    $form['ajaxblocks_enable_cookie_caching_missing_files'] = array(
+      '#markup' => '<p>' . t('To enable cookie based caching for legacy browsers, you must download and install the json2.js:') . '</p>',
+    );
+  }
+
+  return system_settings_form($form);
+}
Index: ajaxblocks.info
===================================================================
--- ajaxblocks.info	(revision 87)
+++ ajaxblocks.info	(working copy)
@@ -7,5 +7,4 @@
 version = "7.x-1.3"
 core = "7.x"
 project = "ajaxblocks"
-datestamp = "1330085740"
-
+datestamp = "1330085740"
\ No newline at end of file
Index: ajaxblocks.install
===================================================================
--- ajaxblocks.install	(revision 87)
+++ ajaxblocks.install	(working copy)
@@ -61,6 +61,18 @@
         'length' => 1000,
         'default' => '',
       ),
+      'enable_client_cache' => array(
+        'description' => 'Boolean indicating whether the block should be cached by the client\'s browser.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'maximum_client_cache' => array(
+        'description' => 'Time a client\'s cache of a block is valid.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
     ),
     'primary key' => array('block_id'),
   );
@@ -70,9 +82,19 @@
 
 
 /**
+ * Implements hook_install
+ */
+function ajaxblocks_install(){
+	variable_set('ajaxblocks_enable_cookie_caching', 0);
+  variable_set('ajaxblocks_json2_path', NULL);
+}
+
+/**
  * Implements hook_uninstall().
  */
 function ajaxblocks_uninstall() {
+  variable_del('ajaxblocks_enable_cookie_caching');
+  variable_del('ajaxblocks_json2_path');
   cache_clear_all('ajaxblocks', 'cache');
 }
 
@@ -80,3 +102,27 @@
 function ajaxblocks_update_last_removed() {
   return 6102;
 }
+
+/**
+ * Adds support for client side caching of ajaxblocks
+ */
+function ajaxblocks_update_7103(){
+	$spec = array(
+    'description' => 'Boolean indicating whether the block should be cached by the client\'s browser.',
+    'type' => 'int',
+    'not null' => TRUE,
+    'default' => 0,
+  );
+  db_add_field( 'ajaxblocks', 'enable_client_cache', $spec);
+
+  $spec = array(
+    'description' => 'Time a client\'s cache of a block is valid.',
+    'type' => 'int',
+    'not null' => TRUE,
+    'default' => 0,
+  );
+  db_add_field( 'ajaxblocks', 'maximum_client_cache', $spec);
+
+  variable_set('ajaxblocks_enable_cookie_caching', 0);
+  variable_set('ajaxblocks_json2_path', NULL);
+}
\ No newline at end of file
Index: ajaxblocks.js
===================================================================
--- ajaxblocks.js	(revision 87)
+++ ajaxblocks.js	(working copy)
@@ -10,22 +10,54 @@
     setTimeout(function () {Drupal.ajaxblocksSendRequest(request, 0);}, delay);
     return;
   }
-  $.ajax({
-    url: ((typeof Drupal.settings.ajaxblocks_path !== 'undefined') ? Drupal.settings.ajaxblocks_path : (Drupal.settings.basePath + "ajaxblocks")),
-    type: "GET",
-    dataType: "json",
-    data: request + '&nocache=1',
-    cache: false,
-    success: function (data) {
-      // Replaces the placeholder divs by the actual block contents returned by the AJAX call,
-      // executes the extra JavaScript code and attach behaviours if the apply to the blocks.
-      Drupal.freezeHeight();
-      for (var id in data) {
-        Drupal.ajaxblocksSetBlockContent(id, data[id]);
+  var date = new Date();
+  var now = date.getTime();
+  
+  var ajaxRequired = false;
+  // Loop over the blocks for this page
+  // Setup a request to the server if needed
+  var cache;
+  for (var id in Drupal.settings.ajaxblocks_blocks) {
+    cache = Drupal.ajaxblocksGetCache('ajaxblock-' + id);
+    if( cache != null
+        && ( cache.expires == null
+          || cache.expires > now
+        )
+      ){
+      Drupal.ajaxblocksSetBlockContent(id, cache);
+    } else {
+      ajaxRequired = true;
+    }
+  }
+
+  if( ajaxRequired ){
+    // Go back to the server
+    $.ajax({
+      url: ((typeof Drupal.settings.ajaxblocks_path !== 'undefined') ? Drupal.settings.ajaxblocks_path : (Drupal.settings.basePath + "ajaxblocks")),
+      type: "GET",
+      dataType: "json",
+      data: request + '&nocache=1',
+      cache: false,
+      success: function (data) {
+        // Replaces the placeholder divs by the actual block contents returned by the AJAX call,
+        // executes the extra JavaScript code and attach behaviours if the apply to the blocks.
+        Drupal.freezeHeight();
+        for (var id in data) {
+          Drupal.ajaxblocksSetBlockContent(id, data[id]);
+          if( Drupal.settings.ajaxblocks_blocks[id].enable_client_cache == 1 ) {
+            var expiration = parseInt(Drupal.settings.ajaxblocks_blocks[id].maximum_client_cache);
+            if( expiration == 0 ){
+              data[id].expires = null;
+            } else {
+              data[id].expires = now + expiration;
+            }
+            Drupal.ajaxblocksSetCache( 'ajaxblock-' + id, data[id] );
+          } 
+        }
+        Drupal.unfreezeHeight();
       }
-      Drupal.unfreezeHeight();
-    }
-  });
+    });
+  }
 }
 
 Drupal.ajaxblocksSetBlockContent = function (id, data) {
@@ -44,6 +76,38 @@
   Drupal.attachBehaviors(context);
 }
 
+Drupal.ajaxblocksGetCache = function (id) {
+  var data = null;
+  if( Drupal.ajaxblocksLocalStorage ){
+    data = Drupal.ajaxblocksLocalStorage.getItem(id);
+  } else {
+    if( Drupal.settings.ajaxblocks_enable_cookie_cache ){
+      data = jQuery.cookie(id)
+    }
+  }
+  try {
+    data = JSON.parse(data);
+  } catch(err) {
+    return null;
+  }
+  return data;
+}
+
+Drupal.ajaxblocksSetCache = function (id, data) {
+  try {
+    data = JSON.stringify(data);
+  } catch (err){
+    return;
+  }
+  if( Drupal.ajaxblocksLocalStorage ){
+    Drupal.ajaxblocksLocalStorage.setItem(id, data);
+  } else {
+    if( Drupal.settings.ajaxblocks_enable_cookie_cache ){
+      jQuery.cookie(id, data, {path: Drupal.settings.basePath});
+    }
+  }
+}
+
 $(document).ready(function () {
   if (typeof Drupal.settings.ajaxblocks !== 'undefined') {
     Drupal.ajaxblocksSendRequest(Drupal.settings.ajaxblocks, Drupal.settings.ajaxblocks_delay);
@@ -57,3 +121,15 @@
 });
 
 })(jQuery);
+
+Drupal.ajaxblocksLocalStorage = (function() {
+  var uid = new Date,
+      storage,
+      result;
+  try {
+    (storage = window.localStorage).setItem(uid, uid);
+    result = storage.getItem(uid) == uid;
+    storage.removeItem(uid);
+    return result && storage;
+  } catch(e) {}
+}());
\ No newline at end of file
Index: ajaxblocks.module
===================================================================
--- ajaxblocks.module	(revision 87)
+++ ajaxblocks.module	(working copy)
@@ -17,11 +17,49 @@
     'type' => MENU_CALLBACK,
   );
 
+  $items['admin/config/development/ajaxblocks'] = array(
+    'title' => 'Ajaxblocks',
+    'description' => 'Control global ajaxblocks settings.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('ajaxblocks_admin_settings_form'),
+    'access arguments' => array('administer ajaxblocks settings'),
+    'file' => 'ajaxblocks.admin.inc',
+  );
+
   return $items;
 }
 
+/**
+ * Implements hook_permission
+ */
+function ajaxblocks_permission(){
+	$permissions = array();
 
+  $permissions['administer ajaxblocks settings'] = array(
+    'title' => t('Administer ajaxblocks settings'),
+    'description' => t('Allows access global settings for ajaxblocks.'),
+    'restrict access' => FALSE,
+  );
+
+  return $permissions;
+}
+
 /**
+ * Implements hook_init
+ */
+function ajaxblocks_init(){
+  $json2_path = variable_get('ajaxblocks_json2_path', NULL);
+  if( !empty($json2_path) ){
+    drupal_add_js( $json2_path );
+  }
+  // If cookie based caching is enabled, add the required js files
+	if( variable_get('ajaxblocks_enable_cookie_caching', 0) == 1 ){
+		drupal_add_js('misc/jquery.cookie.js');
+	}
+}
+
+
+/**
  * Implements hook_form_FORM_ID_alter().
  * Adds AJAX settings to the block configure page.
  */
@@ -91,6 +129,7 @@
     '#size' => 6,
     '#maxlength' => 6,
     '#title' => t('Time in milliseconds to wait before block loading'),
+    '#field_suffix' => ' ' . t('Milliseconds'),
     '#description' => t('You can defer block loading and create interesting effects if necessary.'),
     '#default_value' => isset($settings['delay']) ? intval($settings['delay']) : 0,
   );
@@ -124,10 +163,26 @@
     '#default_value' => isset($settings['uncached_roles']) ? $settings['uncached_roles'] : array(),
   );
 
+  $form['visibility']['ajaxblocks']['ajaxblocks_enable_client_cache'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Enable Client Cache'),
+    '#description' => t('Enabling client cache will store the result of an ajaxblock call in the client\'s browser after its first load.'),
+    '#default_value' => isset($settings['enable_client_cache']) ? $settings['enable_client_cache'] : 0,
+  );
+
+  $form['visibility']['ajaxblocks']['ajaxblocks_maximum_client_cache'] = array(
+    '#type' => 'textfield',
+    '#required' => TRUE,
+    '#title' => t('Time in milliseconds to cache block content in client\'s browser'),
+    '#field_suffix' => ' ' . t('Milliseconds'),
+    '#description' => t('This setting will be used to invalidate cached data saved between client sessions, allow them to obtain fresh copies. Enter 0 to persist permanently.'),
+    '#default_value' => isset($settings['maximum_client_cache']) ? $settings['maximum_client_cache'] : 0,
+  );
+
+  $form['#submit'][] = 'ajaxblocks_validate_settings';
   $form['#submit'][] = 'ajaxblocks_save_settings';
 }
 
-
 /**
  * Additional submit handler for block settings form. Saves AJAX settings for the block.
  */
@@ -142,6 +197,8 @@
       'include_noscript' => (int) $form_state['values']['ajaxblocks_include_noscript'],
       'cached_roles' => implode(' ', array_filter($form_state['values']['ajaxblocks_cached_roles'])),
       'uncached_roles' => implode(' ', array_filter($form_state['values']['ajaxblocks_uncached_roles'])),
+      'enable_client_cache' => (int) $form_state['values']['ajaxblocks_enable_client_cache'],
+      'maximum_client_cache' => (int) $form_state['values']['ajaxblocks_maximum_client_cache'],
     ))
     ->execute();
   ajaxblocks_update_cache();
@@ -385,14 +442,17 @@
   $path = url('ajaxblocks');
   if ($path !== base_path() . 'ajaxblocks') {
     // Provide path for AJAX handler directly in non-trivial cases (for instance, language prefix).
-    drupal_add_js(array('ajaxblocks_path' => $path), 'setting');
+    drupal_add_js(array( 'ajaxblocks_path' => $path, ), 'setting');
   }
+  drupal_add_js(array( 'ajaxblocks_enable_cookie_cache' =>  variable_get('ajaxblocks_enable_cookie_caching', 0), ), 'setting');
   $block_ids = array();
   $block_ids_late = array();
   $min_delay = 1000000;
   $min_delay_late = 1000000;
   $use_loader_picture = FALSE;
+  $blocks = array();
   foreach ($ajax_blocks as $block_id => $settings) {
+    $blocks[$block_id] = $settings;
     if ($settings['is_late']) {
       $block_ids_late[] = $block_id;
       if ($min_delay_late > $settings['delay']) {
@@ -425,6 +485,7 @@
 
   if (count($block_ids) > 0) {
     drupal_add_js(array('ajaxblocks' => 'blocks=' . implode('/', $block_ids) . '&path=' . $_GET['q'] . $get_params), 'setting');
+    drupal_add_js( array('ajaxblocks_blocks' => $blocks), 'setting');
     if ($min_delay > 0) {
       drupal_add_js(array('ajaxblocks_delay' => $min_delay), 'setting');
     }
@@ -468,6 +529,23 @@
   return $in_ajax_handler;
 }
 
+/**
+ * Internal function to check for existance of client side cookie caching libraries
+ *
+ * @return mixed. NULL or string path
+ */
+function _ajaxblocks_get_json2_path(){
+  if (module_exists('libraries')
+    && file_exists(libraries_get_path('json2') . '/json2.js') ) {
+    return libraries_get_path('json2') . '/json2.js';
+  }
+  else {
+    if ( file_exists('sites/all/libraries/json2/json2.js') ) {
+      return 'sites/all/libraries/json2/json2.js';
+    }
+  }
+  return NULL;
+}
 
 /**
  * Returns TRUE if current operation is block loading via AJAX.
