js/SimpleDB.js

  1. class BasicEventHandler {
  2. constructor () {
  3. const eventPooler = {};
  4. const getNamespace = (str="")=> {
  5. const sl = str.split(".");
  6. var major = sl.shift();
  7. return {
  8. "major": major,
  9. "minor": sl.join(".") || "*"
  10. }
  11. }
  12. var id = 0;
  13. const makeId = ()=> {
  14. id++;
  15. return id;
  16. }
  17. this.on = (eventName, fn)=> {
  18. if (typeof fn !== "function") throw new error("Second parameter must be a function");
  19. const nameSpace = getNamespace(eventName);
  20. eventPooler[nameSpace.major] ||= {};
  21. eventPooler[nameSpace.major][nameSpace.minor] ||= {};
  22. eventPooler[nameSpace.major][nameSpace.minor][makeId()] = {
  23. fn:fn
  24. };
  25. }
  26. this.one = (eventName, fn)=> {
  27. if (typeof fn !== "function") throw new error("Second parameter must be a function")
  28. const nameSpace = getNamespace(eventName);
  29. eventPooler[nameSpace.major] ||= {};
  30. eventPooler[nameSpace.major][nameSpace.minor] ||= {};
  31. eventPooler[nameSpace.major][nameSpace.minor][makeId()] = {
  32. fn:fn,
  33. one:true
  34. };
  35. }
  36. this.off = (eventName)=> {
  37. if (!eventName.includes(".")) {
  38. if (!eventPooler[eventName]) return;
  39. delete eventPooler[eventName];
  40. } else {
  41. let nameSpace = getNamespace(eventName);
  42. try{
  43. delete eventPooler[nameSpace.major][nameSpace.minor];
  44. } catch(e) {
  45. }
  46. }
  47. }
  48. this.emmit = (eventName, ...args)=> {
  49. const nameSpace = getNamespace(eventName);
  50. if (!eventPooler[nameSpace.major]) return;
  51. if (!eventName.includes(".")) {
  52. for (let minor in eventPooler[nameSpace.major]) {
  53. for (let i in eventPooler[nameSpace.major][minor]) {
  54. eventPooler[nameSpace.major][minor][i].fn.apply(this, args);
  55. if (eventPooler[nameSpace.major][minor][i].one) {
  56. delete eventPooler[nameSpace.major][minor][i];
  57. if (Object.keys(eventPooler[nameSpace.major][minor]).length == 0 ) delete eventPooler[nameSpace.major][minor];
  58. }
  59. }
  60. }
  61. } else {
  62. if (!eventPooler[nameSpace.major][nameSpace.minor]) return;
  63. for (let i in eventPooler[nameSpace.major][nameSpace.minor]) {
  64. eventPooler[nameSpace.major][nameSpace.minor][i].fn.apply(this, args);
  65. if (eventPooler[nameSpace.major][nameSpace.minor][i].one) {
  66. delete eventPooler[nameSpace.major][nameSpace.minor][i];
  67. if (Object.keys(eventPooler[nameSpace.major][nameSpace.minor]).length == 0 ) delete eventPooler[nameSpace.major][nameSpace.minor];
  68. }
  69. }
  70. }
  71. }
  72. this.hasEvent = (eventName) => {
  73. try {
  74. const nameSpace = getNamespace(eventName);
  75. if (!eventPooler[nameSpace.major]) return false;
  76. if (!eventName.includes(".")) {
  77. if (Object.keys(eventPooler[nameSpace.major]).length > 0 ) return true;
  78. } else {
  79. if (Object.keys(eventPooler[nameSpace.major][nameSpace.minor]).length > 0) return true;
  80. }
  81. return false;
  82. } catch (e) {
  83. return false;
  84. }
  85. }
  86. this.trigger = this.emmit;
  87. }
  88. }
  89. /**
  90. * A simple indexedDB handler to store [LocalStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) like key value pair... except, it is better.
  91. * The benefits are:
  92. * - Can store large amount of data.
  93. * - Asynchronous and non-blocking.
  94. * - Loaded on demand unlike LocalStorage.
  95. * - Can store JavaScript object data.
  96. * @class
  97. * @param {String} [dbName=commonDB] - The DB name
  98. * @param {String} [tableName=keyValuePairs] - Table name
  99. * @memberof common
  100. * @example <caption>Basic usage</caption>
  101. * var myStorage = new DB();
  102. *
  103. * //store a value to "someKey"
  104. * await myStorage.set("someKey", {"someObject" : "with the value"}):
  105. *
  106. * //get the value of "someKey"
  107. * await myStorage.get("someKey");
  108. * // will returns:
  109. * {
  110. * "someObject": "with the value"
  111. * }
  112. */
  113. const SimpleDB = function(dbName="commonDB", tableName="keyValuePairs") {
  114. const evt = new BasicEventHandler();
  115. this.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB || window.shimIndexedDB;
  116. this.dbName = dbName || "commonDB";
  117. this.tableName = tableName || "keyValuePairs";
  118. this.readyPromise = new Promise((resolve) => {
  119. this.resolver = resolve;
  120. });
  121. const initTransaction = async () => {
  122. this.tx = this.db.transaction(this.tableName, "readwrite");
  123. this.store = this.tx.objectStore(this.tableName);
  124. }
  125. const init = async () => {
  126. return new Promise((resolve, reject) => {
  127. this.connection = indexedDB.open(this.dbName, 1);
  128. this.connected = false;
  129. this.connection.onupgradeneeded = () =>{
  130. this.db = this.connection.result;
  131. this.store = this.db.createObjectStore(this.tableName, {keyPath: "id"});
  132. //this.index = this.store.createIndex("NameIndex", ["name.last", "name.first"]);
  133. };
  134. this.connection.onsuccess = () => {
  135. // Start a new transaction
  136. this.db = this.connection.result;
  137. this.tx = this.db.transaction(this.tableName, "readwrite");
  138. this.store = this.tx.objectStore(this.tableName);
  139. //this.index = this.store.index("NameIndex");
  140. this.isInitialized = true;
  141. this.resolver(this);
  142. resolve(this);
  143. }
  144. })
  145. }
  146. this.untilReady = async () => {
  147. if (this.isInitialized) return this;
  148. return init();
  149. }
  150. /**
  151. * Set a value to the local DB
  152. * @async
  153. * @param {String} key - key of the key-value pair
  154. * @param {*} value - Value of the key-value pair. Value can be anything. Unlike LocalStorage, if you can also store object in the DB.
  155. * @returns {Promise<Event>} - Transaction event
  156. */
  157. this.set = async (key, value) => {
  158. await this.untilReady();
  159. evt.trigger("beforeSet", key, value);
  160. var isChanged = false;
  161. var prevValue
  162. if (evt.hasEvent("change")) {
  163. prevValue = await this.get(key);
  164. if (JSON.stringify(prevValue) !== JSON.stringify(value)) isChanged = true;
  165. }
  166. return new Promise((resolve, reject) => {
  167. initTransaction();
  168. var request = this.store.put({id: key, value: value});
  169. request.onsuccess = (e)=> {
  170. evt.trigger("set", key, value);
  171. if (isChanged) {
  172. evt.trigger("change", key, value, prevValue);
  173. }
  174. resolve(e)
  175. }
  176. request.onerror = (e)=> {
  177. reject(e)
  178. }
  179. })
  180. }
  181. this.setItem = this.set;
  182. /**
  183. * Get a value from local DB.
  184. * @async
  185. * @param {String} key - key of the key-value pair
  186. * @returns {Promise<*>} - The value
  187. */
  188. this.get = async (key) => {
  189. await this.untilReady();
  190. return new Promise((resolve, reject) => {
  191. initTransaction();
  192. var request = this.store.get(key);
  193. request.onsuccess = (e)=> {
  194. if (!request.result) return resolve();
  195. resolve(request.result.value)
  196. }
  197. request.onerror = (e)=> {
  198. reject(e)
  199. }
  200. })
  201. }
  202. this.getItem = this.get;
  203. /**
  204. * Delete a record from local DB.
  205. * @async
  206. * @param {String} key - key of the key-value pair
  207. * @returns {Promise<Event>} - Transaction event
  208. */
  209. this.delete = async (key) => {
  210. await this.untilReady();
  211. evt.trigger("beforeDelete", key);
  212. return new Promise((resolve, reject) => {
  213. initTransaction();
  214. var request = this.store.delete(key);
  215. request.onsuccess = (e)=> {
  216. evt.trigger("delete", key);
  217. resolve(e)
  218. }
  219. request.onerror = (e)=> {
  220. reject(e)
  221. }
  222. })
  223. }
  224. this.removeItem = this.delete;
  225. /**
  226. * Clear a database.
  227. * @returns {Promise<Event>} - Transaction event
  228. */
  229. this.clear = async () => {
  230. await this.untilReady();
  231. evt.trigger("beforeClear");
  232. return new Promise((resolve, reject) => {
  233. initTransaction();
  234. var request = this.store.clear();
  235. request.onsuccess = (e)=> {
  236. evt.trigger("clear", key);
  237. resolve(e)
  238. }
  239. request.onerror = (e)=> {
  240. reject(e)
  241. }
  242. })
  243. }
  244. this.on = (eventName, fn)=> {
  245. evt.on(eventName, fn);
  246. }
  247. this.one = (eventName, fn)=> {
  248. evt.one(eventName, fn);
  249. }
  250. this.off = (eventName) => {
  251. evt.off(eventName);
  252. }
  253. }
  254. module.exports = SimpleDB;