hierarchical_select_cache.js 9.1 KB

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