| 1 |
// $Id: hierarchical_select_cache.js,v 1.7 2008/09/17 21:36:53 wimleers Exp $
|
| 2 |
|
| 3 |
/**
|
| 4 |
* @file
|
| 5 |
* Cache system for Hierarchical Select.
|
| 6 |
* This cache system takes advantage of the HTML 5 client-side database
|
| 7 |
* storage specification to reduce the number of queries to the server. A lazy
|
| 8 |
* loading strategy is used.
|
| 9 |
*/
|
| 10 |
|
| 11 |
|
| 12 |
/**
|
| 13 |
* Note: this cache system can be replaced by another one, as long as you
|
| 14 |
* provide the following methods:
|
| 15 |
* - initialize()
|
| 16 |
* - status()
|
| 17 |
* - load()
|
| 18 |
* - sync()
|
| 19 |
* - updateHierarchicalSelect()
|
| 20 |
*
|
| 21 |
* TODO: better documentation
|
| 22 |
*/
|
| 23 |
|
| 24 |
(function ($) {
|
| 25 |
|
| 26 |
Drupal.HierarchicalSelect.cache = {};
|
| 27 |
|
| 28 |
Drupal.HierarchicalSelect.cache.initialize = function() {
|
| 29 |
try {
|
| 30 |
if (window.openDatabase) {
|
| 31 |
this.db = openDatabase("Hierarchical Select", "3.x", "Hierarchical Select cache", 200000);
|
| 32 |
|
| 33 |
this.db
|
| 34 |
// Create the housekeeping table if it doesn't exist yet.
|
| 35 |
.transaction(function(tx) {
|
| 36 |
tx.executeSql("SELECT COUNT(*) FROM hierarchical_select", [], null, function(tx, error) {
|
| 37 |
tx.executeSql("CREATE TABLE hierarchical_select (table_name TEXT UNIQUE, expires REAL)", []);
|
| 38 |
console.log("Created housekeeping table.");
|
| 39 |
});
|
| 40 |
})
|
| 41 |
// Empty tables that have expired, based on the information in the
|
| 42 |
// housekeeping table.
|
| 43 |
.transaction(function(tx) {
|
| 44 |
tx.executeSql("SELECT table_name FROM hierarchical_select WHERE expires < ?", [ new Date().getTime() ], function(tx, resultSet) {
|
| 45 |
for (var i = 0; i < resultSet.rows.length; i++) {
|
| 46 |
var row = resultSet.rows.item(i);
|
| 47 |
var newExpiresTimestamp = new Date().getTime() + 86400;
|
| 48 |
|
| 49 |
tx.executeSql("DELETE * FROM " + row.table_name);
|
| 50 |
tx.executeSql("UPDATE hierarchical_select SET expires = ? WHERE table_name = ?", [ newExpiresTimestamp, row.table_name ]);
|
| 51 |
|
| 52 |
console.log("Table "+ row.table_name +" was expired: emptied it. Will expire again in "+ (newExpiresTimestamp - new Date().getTime()) / 3600 +" hours.");
|
| 53 |
}
|
| 54 |
});
|
| 55 |
});
|
| 56 |
}
|
| 57 |
else {
|
| 58 |
this.db = false;
|
| 59 |
}
|
| 60 |
}
|
| 61 |
catch(err) { }
|
| 62 |
};
|
| 63 |
|
| 64 |
Drupal.HierarchicalSelect.cache.status = function() {
|
| 65 |
return Drupal.HierarchicalSelect.cache.db !== false;
|
| 66 |
};
|
| 67 |
|
| 68 |
Drupal.HierarchicalSelect.cache.table = function(hsid) {
|
| 69 |
return Drupal.settings.HierarchicalSelect.settings[hsid].cacheId;
|
| 70 |
};
|
| 71 |
|
| 72 |
Drupal.HierarchicalSelect.cache.load = function(hsid) {
|
| 73 |
// If necessary, create the cache table for the given Hierarchical Select.
|
| 74 |
Drupal.HierarchicalSelect.cache.db.transaction(function(tx) {
|
| 75 |
var table = Drupal.HierarchicalSelect.cache.table(hsid);
|
| 76 |
|
| 77 |
tx.executeSql("SELECT value FROM "+ table, [], function(tx, resultSet) {
|
| 78 |
console.log("" + resultSet.rows.length + " cached items in the " + table + " table.");
|
| 79 |
}, function(tx, error) {
|
| 80 |
var expiresTimestamp = new Date().getTime() + 86400;
|
| 81 |
|
| 82 |
tx.executeSql("CREATE TABLE "+ table +" (parent REAL, value REAL UNIQUE, label REAL, weight REAL)");
|
| 83 |
tx.executeSql("INSERT INTO hierarchical_select (table_name, expires) VALUES (?, ?)", [ table, expiresTimestamp ]);
|
| 84 |
|
| 85 |
console.log("Created table "+ table +", will expire in "+ (expiresTimestamp - new Date().getTime()) / 3600 +" hours.");
|
| 86 |
});
|
| 87 |
});
|
| 88 |
};
|
| 89 |
|
| 90 |
Drupal.HierarchicalSelect.cache.insertOnDuplicateKeyUpdate = function(table, row) {
|
| 91 |
// console.log("storing: value: "+ row.value +", label: "+ row.label +", parent: "+ row.parent +", weight: "+ row.weight);
|
| 92 |
Drupal.HierarchicalSelect.cache.db.transaction(function(tx) {
|
| 93 |
tx.executeSql("INSERT INTO "+ table +" (parent, value, label, weight) VALUES (?, ?, ?, ?)", [ row.parent, row.value, row.label, row.weight ], null, function(tx, error) {
|
| 94 |
// console.log("UPDATING value: "+ row.value +", label: "+ row.label +", parent: "+ row.parent +", weight: "+ row.weight);
|
| 95 |
tx.executeSql("UPDATE "+ table +" SET parent = ?, label = ?, weight = ? WHERE value = ?", [ row.parent, row.label, row.weight, row.value ], null, function(tx, error) {
|
| 96 |
// console.log("sql error: " + error.message);
|
| 97 |
});
|
| 98 |
});
|
| 99 |
});
|
| 100 |
};
|
| 101 |
|
| 102 |
Drupal.HierarchicalSelect.cache.sync = function(hsid, info) {
|
| 103 |
var table = Drupal.HierarchicalSelect.cache.table(hsid);
|
| 104 |
for (var id in info) {
|
| 105 |
var closure = function(_info, id) {
|
| 106 |
Drupal.HierarchicalSelect.cache.insertOnDuplicateKeyUpdate(table, _info[id]);
|
| 107 |
} (info, id);
|
| 108 |
}
|
| 109 |
};
|
| 110 |
|
| 111 |
Drupal.HierarchicalSelect.cache.hasChildren = function(hsid, value, successCallback, failCallback) {
|
| 112 |
var table = Drupal.HierarchicalSelect.cache.table(hsid);
|
| 113 |
Drupal.HierarchicalSelect.cache.db.transaction(function(tx) {
|
| 114 |
tx.executeSql("SELECT * FROM "+ table +" WHERE parent = ?", [ value ], function(tx, resultSet) {
|
| 115 |
if (resultSet.rows.length > 0) {
|
| 116 |
successCallback();
|
| 117 |
}
|
| 118 |
else {
|
| 119 |
failCallback();
|
| 120 |
}
|
| 121 |
});
|
| 122 |
});
|
| 123 |
};
|
| 124 |
|
| 125 |
Drupal.HierarchicalSelect.cache.getSubLevels = function(hsid, value, callback, previousSubLevels) {
|
| 126 |
var table = Drupal.HierarchicalSelect.cache.table(hsid);
|
| 127 |
|
| 128 |
var subLevels = new Array();
|
| 129 |
if (previousSubLevels != undefined) {
|
| 130 |
subLevels = previousSubLevels;
|
| 131 |
}
|
| 132 |
|
| 133 |
Drupal.HierarchicalSelect.cache.db.transaction(function(tx) {
|
| 134 |
tx.executeSql("SELECT value, label FROM "+ table +" WHERE parent = ? ORDER BY weight", [ value ], function(tx, resultSet) {
|
| 135 |
var numChildren = resultSet.rows.length;
|
| 136 |
|
| 137 |
// If there's only one child, check if it has the dummy "<value>-has-no-children" value.
|
| 138 |
if (numChildren == 1) {
|
| 139 |
var valueOfFirstRow = String(resultSet.rows.item(0).value);
|
| 140 |
var isDummy = valueOfFirstRow.match(/^.*-has-no-children$/);
|
| 141 |
}
|
| 142 |
|
| 143 |
// Only pass the children if there are any (and not a fake one either).
|
| 144 |
if (numChildren && !isDummy) {
|
| 145 |
var level = new Array();
|
| 146 |
for (var i = 0; i < resultSet.rows.length; i++) {
|
| 147 |
var row = resultSet.rows.item(i);
|
| 148 |
level[i] = { 'value' : row.value, 'label' : row.label };
|
| 149 |
console.log("child of "+ value +": ("+ row.value +", "+ row.label +")");
|
| 150 |
}
|
| 151 |
|
| 152 |
subLevels.push(level);
|
| 153 |
|
| 154 |
Drupal.HierarchicalSelect.cache.getSubLevels(hsid, level[0].value, callback, subLevels);
|
| 155 |
}
|
| 156 |
else {
|
| 157 |
if (subLevels.length > 0) {
|
| 158 |
callback(subLevels);
|
| 159 |
}
|
| 160 |
else {
|
| 161 |
callback(false);
|
| 162 |
}
|
| 163 |
}
|
| 164 |
});
|
| 165 |
});
|
| 166 |
};
|
| 167 |
|
| 168 |
Drupal.HierarchicalSelect.cache.createAndUpdateSelects = function(hsid, subLevels, lastUnchanged) {
|
| 169 |
// Remove all levels below the level in which a value was selected, if they
|
| 170 |
// exist.
|
| 171 |
// Note: the root level can never change because of this!
|
| 172 |
$('#hierarchical-select-'+ hsid +'-wrapper .hierarchical-select .selects select').slice(lastUnchanged).remove();
|
| 173 |
|
| 174 |
// Create the new sublevels, by cloning the root level and then modifying
|
| 175 |
// that clone.
|
| 176 |
var $rootSelect = $('#hierarchical-select-'+ hsid +'-wrapper .hierarchical-select .selects select:first');
|
| 177 |
for (var depth in subLevels) {
|
| 178 |
var optionElements = $.map(subLevels[depth], function(item) { return '<option value="'+ item.value +'">'+ item.label +'</option>'; });
|
| 179 |
|
| 180 |
var level = parseInt(lastUnchanged) + parseInt(depth);
|
| 181 |
|
| 182 |
$('#hierarchical-select-'+ hsid +'-wrapper .hierarchical-select .selects select:last').after(
|
| 183 |
$rootSelect.clone()
|
| 184 |
// Update the name attribute.
|
| 185 |
.attr('name', $rootSelect.attr('name').replace(/(.*)\d+\]$/, "$1"+ level +"]"))
|
| 186 |
// Update the id attribute.
|
| 187 |
.attr('id', $rootSelect.attr('id').replace(/(.*-hierarchical-select-selects-)\d+/, "$1"+ level))
|
| 188 |
// Remove the existing options and set the new ones.
|
| 189 |
.empty().append(optionElements.join(''))
|
| 190 |
);
|
| 191 |
}
|
| 192 |
};
|
| 193 |
|
| 194 |
Drupal.HierarchicalSelect.cache.updateHierarchicalSelect = function(hsid, value, settings, lastUnchanged, ajaxOptions) {
|
| 195 |
// If the selected value has children
|
| 196 |
Drupal.HierarchicalSelect.cache.hasChildren(hsid, value, function() {
|
| 197 |
console.log("Cache hit.");
|
| 198 |
Drupal.HierarchicalSelect.cache.getSubLevels(hsid, value, function(subLevels) {
|
| 199 |
Drupal.HierarchicalSelect.preUpdateAnimations(hsid, 'update-hierarchical-select', lastUnchanged, function() {
|
| 200 |
if (subLevels !== false) {
|
| 201 |
Drupal.HierarchicalSelect.cache.createAndUpdateSelects(hsid, subLevels, lastUnchanged);
|
| 202 |
}
|
| 203 |
else {
|
| 204 |
// Nothing must happen: the user selected a value that doesn't
|
| 205 |
// have any subLevels.
|
| 206 |
$('#hierarchical-select-' + hsid + '-wrapper .hierarchical-select .selects select').slice(lastUnchanged).remove();
|
| 207 |
}
|
| 208 |
|
| 209 |
Drupal.HierarchicalSelect.postUpdateAnimations(hsid, 'update-hierarchical-select', lastUnchanged, function() {
|
| 210 |
// Reattach the bindings.
|
| 211 |
Drupal.HierarchicalSelect.attachBindings(hsid);
|
| 212 |
|
| 213 |
Drupal.HierarchicalSelect.triggerEvents(hsid, 'update-hierarchical-select', settings);
|
| 214 |
|
| 215 |
// The selection of this hierarchical select has changed!
|
| 216 |
Drupal.HierarchicalSelect.triggerEvents(hsid, 'change-hierarchical-select', settings);
|
| 217 |
});
|
| 218 |
});
|
| 219 |
});
|
| 220 |
}, function() {
|
| 221 |
// This item was not yet requested before, so we still have to perform
|
| 222 |
// the dynamic form submit.
|
| 223 |
console.log("Cache miss. Querying the server.");
|
| 224 |
Drupal.HierarchicalSelect.preUpdateAnimations(hsid, 'update-hierarchical-select', lastUnchanged, function() {
|
| 225 |
$.ajax(ajaxOptions);
|
| 226 |
});
|
| 227 |
});
|
| 228 |
};
|
| 229 |
|
| 230 |
})(jQuery);
|