From dcd08a2dd1534c58c1e2ab92d7079785180503c9 Mon Sep 17 00:00:00 2001 From: "Jeremy Wall (zaphar)" Date: Fri, 19 Apr 2013 20:13:05 -0500 Subject: [PATCH] Add a Tagging module. * It uses an indexedb datastore. * Fix the api of the indexeddb wrapper now that I have some real world usage examples. * Implemented API Calls: * UpdateTags * GetTags * Exposed but unimplemented API Calls: * GetVersesForTag * Sync * Add a tooltip that shows the tags for a reference to the reference link. --- js/common.js | 28 ++++++++- js/db.js | 154 +++++++++++++++++++++++++++--------------------- js/main.js | 6 +- js/reference.js | 10 ++++ js/tagging.js | 50 ++++++++++++++++ 5 files changed, 177 insertions(+), 71 deletions(-) create mode 100644 js/tagging.js diff --git a/js/common.js b/js/common.js index edbcff19..ecff5c07 100644 --- a/js/common.js +++ b/js/common.js @@ -1,5 +1,5 @@ -define(['jquery', 'reference', 'jquery.ui'], - function($, reference) { +define(['jquery', 'reference', 'tagging', 'jquery.ui'], + function($, reference, tagging) { function SortNumeric(x, y) { return x - y; } @@ -296,6 +296,7 @@ define(['jquery', 'reference', 'jquery.ui'], r += "
"; } } + // TODO(jwall): hover should show tags for the verse. var t = $("
" + "

" + ref.toString() + "

" + r + "

"); t.find(".hiddenlink").click(function(e) { @@ -304,6 +305,29 @@ define(['jquery', 'reference', 'jquery.ui'], t.find(".removeresult").click(function(e) { Util.RemoveResult(e); }); + // TODO(jwall): support longtouch events on mobile. + t.find(".resultbody h2").tooltip({ + items: ".resultbody h2", + content: function(tt) { + tagging.GetTags( + ref.toString(), { + success: function(data) { + var tags = data ? + (data.data ? + data.data.tagList : null) : null; + var hoverContent = + $("
Tags:
"); + hoverContent.append( + tags ? tags.join(", ") : "N/A"); + tt(hoverContent); + }}); + } + }); + t.find(".hiddenlink").tooltip( + { + items: ".hiddenlink" + } + ); $("#result").prepend(t); } catch (err) { diff --git a/js/db.js b/js/db.js index 4a1e314c..209af1fd 100644 --- a/js/db.js +++ b/js/db.js @@ -2,15 +2,12 @@ // See http://www.w3.org/TR/IndexedDB/ for more details about the // IDB* interfaces referenced below. define( -'db', { - // Construct db wrapper. - // If debug is true then logging will be turned on. - Init: function(debug) { - var self = { +'db', function() { + return { version: 1, - dbName: "localdata", - stores: ['settings', 'verselists'], - debug: false, + dbName: "dynamic_bible_db", + stores: ['tags'], + debug: true, // Internal method not for external use. handleError: function(e) { this.debug && console.log("Error: ", e); @@ -19,21 +16,25 @@ define( handleUpdate: function(e) { var db = e.target.result; e.target.transaction.onerror = this.handleError; + this.debug && console.log("Updating database", this.dbName); for (i in this.stores) { - if (db.objectStoreNames.contains(this.stores[i])) { - // TODO(jwall): handle this more gracefully? - db.deleteObjectStore(this.stores[i]); + if (!db.objectStoreNames.contains(this.stores[i])) { + this.debug && console.log("Creating objectStore", this.stores[i]); + db.createObjectStore(this.stores[i], {keyPath: "key"}); } - var store = db.createObjectStore(this.stores[i], - {keyPath: "key"}); } }, - // Do operations on an open database. + // Turn on debug logging. + SetDebug: function() { + this.debug = self; + }, + // Session does operations on an open database. // ops is expected to be a function taking an IDBDatabase object // as a parameter. Session: function(ops) { var self = this; - var req = indexedDB.open(this.dbname, this.version); + self.debug && console.log('Opening session for ', this.dbName, this.version); + var req = indexedDB.open(this.dbName, this.version); // HandleUpgrades req.onupgradeneeded = function(e) { self.handleUpdate(e); @@ -42,7 +43,8 @@ define( ops(e.target.result); }; }, - // Do operations on an open transaction scoped to the list of stores. + // Transaction does operations on an open transaction scoped to + // the list of stores. // ops is expected to be a function taking an IDBTransation object // as a parameter. // type is an optional transaction type. values should be one of the @@ -61,72 +63,92 @@ define( }); }, // Get a key from the the object store. - // success is expected to be a function taking an IDBRequest object - // as a parameter. - // error is optional and expected to be a function taking an IDBRequest - // object as a parameter. - Get: function(storeName, key, success, error) { + // callbacks is a dict with two fields. + // callbacks.success is expected to be a function taking an + // IDBRequest object as a parameter. + // callbacks.error is optional and expected to be a function + // taking an IDBRequest object as a parameter. + // trans is optional and is expected to be a a function taking + // an IDBTransaction object. If none is provided + // one will be started for you. + Get: function(storeName, key, callbacks, trans) { var self = this; - self.Transaction( - [storeName], function(trans) { - var store = trans.objectStore(storeName); - var req = store.get(key); - req.onsuccess = success; - req.onerror = error || self.handleError; - }); + var op = function(trans) { + var store = trans.objectStore(storeName); + var req = store.get(key); + req.onsuccess = callbacks.success; + req.onerror = callbacks.error || self.handleError; + }; + if (trans) { + ops(trans); + } else { + self.Transaction([storeName], op); + } }, // Store or update a key with data in an object store. - // success is expected to be a function taking an IDBRequest object - // as a parameter. - // error is optional and expected to be a function taking an IDBRequest - // object as a parameter. - Update: function(storeName, key, data, success, error) { + // callbacks is a dict with up to two fields. + // success is expected to be a function taking an IDBRequest + // object as a parameter. + // error is also optional and expected to be a function taking an + // IDBRequest object as a parameter. + // The trans argument is optional and is expected to be a a + // function taking an IDBTransaction object. If none is provided + // one will be started for you. + Update: function(storeName, key, data, callbacks, trans) { var self = this; - self.Transaction( - [storeName], function(trans) { - var store = trans.objectStore(storeName); - var req = store.put({"key": key, "data": data}); - req.onsuccess = success; - req.onerror = error || self.handleError; - }, "readwrite"); + var op = function(trans) { + var store = trans.objectStore(storeName); + var req = store.put( + {"key": key, "data": data, ts: new Date().getTime()}); + req.onsuccess = callbacks.success; + req.onerror = callbacks.error || self.handleError; + }; + if (trans) { + op(trans); + } else { + self.Transaction( + [storeName], op, "readwrite"); + } }, // Delete a key from an object store. - // success is expected to be a function taking an IDBRequest object - // as a parameter. - // error is optional and expected to be a function taking an IDBRequest - // object as a parameter. - Delete: function(storeName, key, success, error) { + // callbacks is a dict with up to two fields. + // callbacks.success is expected to be a function taking an + // IDBRequest object as a parameter. + // callbacks.error is optional and expected to be a function + // taking an IDBRequest object as a parameter. + Delete: function(storeName, key, callbacks) { var self = this; self.Transaction( [storeName], function(trans) { var store = trans.objectStore(storeName); var req = store.delete(key); - req.onsuccess = success; - req.onerror = error || self.handleError; + req.onsuccess = callbacks.success; + req.onerror = callbacks.error || self.handleError; }, "readwrite"); }, // Query a range of values from an object store. // range is expected to be an IDBKeyRange. - // success is expected to be a function taking an IDBRequest object - // as a parameter. - // error is optional and expected to be a function taking an IDBRequest - // object as a parameter. - Query: function(storeName, range, success, error) { + // callbacks is a dict with up to two fields. + // callbacks.success is expected to be a function taking an + // IDBRequest object as a parameter. + // callbacks.error is optional and expected to be a function + // taking an IDBRequest object as a parameter. + // The trans argument is optional and is expected to be a a + // function taking an IDBTransaction object. If none is provided + // one will be started for you. + Query: function(storeName, range, callbacks, trans) { var self = this; - self.Transaction( - [storeName], function(trans) { - var store = trans.objectStore(storeName); - var req = store.openCursor(range); - req.onsuccess = success; - req.onerror = error || self.handleError; - }); + var op = function(trans) { + var store = trans.objectStore(storeName); + var req = store.openCursor(range); + req.onsuccess = callbacks.success; + req.onerror = callbacks.error || self.handleError; + }; + if (trans) { + op(trans); + } else { + self.Transaction([storeName], op); + } } }; - self.debug = debug; - self.Session( - function(e) { - self.debug && console.log("Database Init complete"); - }); - return self; - } }); diff --git a/js/main.js b/js/main.js index 0a73e9c1..1489fea8 100644 --- a/js/main.js +++ b/js/main.js @@ -1,5 +1,5 @@ -require(["jquery", "db", "common", "reference", "jquery.cookie", "jquery.ui"], - function($, db, common, ref) { +require(["jquery", "db", "common", "jquery.cookie", "jquery.ui"], + function($, db, common) { $(document).ready(function() { $("#searchform").submit(function() @@ -66,4 +66,4 @@ require(["jquery", "db", "common", "reference", "jquery.cookie", "jquery.ui"], }); }); - \ No newline at end of file + diff --git a/js/reference.js b/js/reference.js index 9be9742c..17aa94ff 100644 --- a/js/reference.js +++ b/js/reference.js @@ -658,6 +658,16 @@ define("reference", return ref.concat("-").concat(this.endchapter) .concat(":").concat(this.endverse); }; + Reference.prototype.toDict = function() { + return { + startBook: this.bookname, + startChapter: this.startchapter, + startVerse: this.startverse, + endBook: this.bookname, + endChapter: this.startchapter, + endVerse: this.endverse + }; + }; return { Parse: function(ref) {return new Reference(ref);}, bookName: bookName diff --git a/js/tagging.js b/js/tagging.js new file mode 100644 index 00000000..518bdd92 --- /dev/null +++ b/js/tagging.js @@ -0,0 +1,50 @@ +(function() { +var tagStore = 'tags'; +define( +['db', 'reference'], function(db, reference) { + return { + // UpdateTags updates a given references tags asynchronously. + // ref should be a Reference object. + // tags should be a list of strings representing the tags for this + // reference. + // callbacks.success is expected to be a function that takes + // an IDBRequest as an argument. + // callbacks.error is expected to be a function that takes + // an IDBRequest as an argument. + UpdateTags: function(ref, tags, callbacks) { + db.Transaction( + [tagStore], function(trans) { + db.Update( + 'tags', ref.toString(), + {tagList: tags, Ref: ref.toDict()}, + callbacks, trans); + }, 'readwrite'); + }, + // GetTags retrieves a given references tags asynchronously. + // ref should be a Reference object. + // callbacks.success is expected to be a function that takes + // the list of tags as an argument. + // callbacks.error is expected to be a function that takes + // an IDBRequest as an argument. + GetTags: function(ref, callbacks) { + db.Get(tagStore, ref.toString(), + {success: function(req) { + var data = req.target.result; + // TODO suppport book and chapter indexes for + // range lookups. + callbacks.success(data); + }}); + }, + GetVersesForTag: function() { + console.log('TODO(jwall): Implement GetVersesForTag'); + }, + // Sync retrieves a given references tags asynchronously. + // url should be a valid http URL to the service to sync the tags + // to. + // callbacks + Sync: function(url, callbacks) { + console.log('TODO(jwall): Implement Sync'); + } + }; +}); +})();