#javascript #google-chrome-extension #indexeddb
#javascript #google-chrome-расширение #indexeddb
Вопрос:
Я работаю над расширением Chrome для онлайн-словаря японского языка и пытаюсь записывать и отображать статистику запросов пользователя, чтобы он знал, каких японских терминов ему не хватает больше всего.
Для этого я намеревался показать наиболее запрашиваемые кандзи (японские символы) за последние X дней. Итак, я создал простое хранилище объектов в своем расширении IndexedDB с парами кандзи / даты и ключами с автоматической нумерацией, но, похоже, нет способа выполнить эти запросы в IndexedDB?
Это единственный способ фильтровать по дате и подсчитывать вхождения с помощью курсора и делать это с обратным вызовом javascript?
РЕДАКТИРОВАТЬ: код, используемый для создания базы данных
const DB_NAME = "kioku"
const DB_VERSION = 1;
const KANJI_STORE = "kanji";
var db;
(function() {
var request = window.indexedDB.open(DB_NAME, DB_VERSION);
request.onupgradeneeded = function(e) {
db = e.target.result;
var kanjiStore = db.createObjectStore(KANJI_STORE, { autoIncrement: true });
kanjiStore.createIndex("date", "date", { unique: false });
}
})();
Добавление данных через обработчик событий
function newRecentKanjiHandler(request, sender, sendResponse) {
/* ...stuff before this point was irrelevant so I removed it */
var kanjiStore = db.transaction([KANJI_STORE], "readwrite").objectStore(KANJI_STORE);
kanjiStore.add({kanji: request.kanji, date: Date.now()});
}
Скриншот с образцами данных
В этом случае я хотел бы отобразить что-то вроде
x3: 杉
x2: 刀, 尻
x1: 刃
Для пользователя. Просто простой подсчет вхождений после фильтрации по диапазону дат, которого у меня нет в этом примере, потому что временные метки находятся в пределах часа, и я намереваюсь отфильтровать последние несколько дней или недель.
Комментарии:
1. Я дам вам полный ответ сегодня вечером, но это вполне возможно. Тем временем, можете ли вы опубликовать свою схему и некоторые примеры данных? Вероятно, будет достаточно скриншота панели ресурсов Chrome.
2. @buley Я не могу опубликовать скриншот, потому что AFAIK единственный способ получить к нему доступ — это открыть инструменты разработчика на фоновой странице расширения, и, похоже, есть ошибка, из-за которой Chrome вылетает каждый раз, когда я пытаюсь открыть эту страницу . Но я опубликую более подробную информацию о схеме.
3. @buley не обращайте внимания на мой последний комментарий. Я смог сделать снимок экрана, открыв инструменты разработчика на странице параметров моего расширения вместо фоновой страницы.
Ответ №1:
Мне нравится этот вопрос. Я уверен, что у кого-то будет лучший ответ, чем у меня, но вот случайный подход.
Во-первых, я должен указать на то, что у вас могут возникнуть проблемы при попытке присвоить event.target.result БД. Вы пытаетесь поместить объект, который действителен только в пределах его области, во внешнюю область. Это просто вызовет у вас проблемы позже. Я предлагаю узнать больше об асинхронных функциях в JavaScript.
Во-вторых, я бы начал с того, что отступил от текущего направления и рассмотрел некоторые основные предположения о проблеме, которую вы пытаетесь решить. Иногда IndexedDB — не лучшее решение. К сожалению, использование IndexedDB заставляет вас столкнуться с этими вопросами проектирования раньше, чем позже.
Вам нужно хранить каждое ключевое событие? Если нет, то масштаб проблемы ограничен количеством разных ключей кандзи, а не количеством событий. Вы могли бы вообще отказаться от использования IndexedDB и использовать простой объект, который сохраняется в локальном хранилище.
Если масштаб довольно большой, определите, сохраняете ли вы события только для целей вашего вопроса или для других целей? Если это только цель вашего собственного вопроса, тогда подумайте об изменении того, что вы храните (схемы), чтобы адресовать только его конкретной цели, а не неизвестному списку других целей.
Например, если он предназначен только для этой цели и его не нужно масштабировать, вы можете полностью избежать IndexedDB и использовать только локальное хранилище. Что-то вроде следующего:
function onCharEvent(char) {
var obj = JSON.parse(localStorage.KANJI_STATS || '');
var bar = obj[char] || {char: char, count: 0};
bar.count ;
bar.lastObserved = Date.now();
obj[char] = bar;
localStorage.KANJI_STATS = JSON.stringify(obj);
}
Если функция предназначена только для этой цели, и она должна быть крупномасштабной, вы можете упростить схему. Начните с представления того, как должен выглядеть результат запроса в виде списка после выполнения всех группировок, упорядочения и подсчета. Затем подумайте о том, как бы вы выполняли операции добавления и ввода в эту схему. Если я вас правильно понял, вы хотите получить набор результатов, который выглядит следующим образом:
Kanji char | Count | Last observed
----------------------------------
char1 | 1 | 1234123412345
char2 | 2 | 4643563456345
char3 | 3 | 3245234523452
Каждый экземпляр (запись) содержит уникальный символ. Количество представляет количество раз, когда наблюдался символ. Последнее наблюдаемое свойство представляет время, когда символ был замечен последним.
// Setup or change the schema
function onUpgradeNeeded() {
var db = this.result;
var store = db.createObjectStore('kanji', {keyPath: kanjiChar});
store.createIndex('lastObserved','lastObserved');
}
function openDB(onOpen) {
var openRequest = indexedDB.open(DBNAME,DBVERSION);
openRequest.onupgradeneeded = onUpgradeNeeded;
openrequest.onsuccess = function() {
onOpen(this.result);
};
}
function onKeyEvent(event) {
var kanjiChar = event.target.value;
openDB(function(db) {
put(db, kanjiChar);
});
}
// Insert or update the kanji in the store
function put(db, kanjiChar, oncomplete) {
var tx = db.transaction('kanji','readwrite');
tx.oncomplete = function() {
oncomplete(kanjiChar);
};
var store = tx.objectStore('kanji');
store.openCursor(kanji).onsuccess = function() {
var cursor = this.result;
if(cursor) {
var obj = cursor.value;
obj.count = obj.count ? obj.count 1 : 1;
obj.lastObserved = Date.now();
cursor.update(obj);
} else {
store.add({
kanjiChar: kanji,
count: 1,
lastObserved: Date.now()
});
}
};
}
function getStats(db, oncomplete) {
var tx = db.transaction('kanji');
var allStats = [];
tx.oncomplete = function() {
oncomplete(allStats);
};
var kanjiStore = tx.objectStore('kanji');
var lastObservedIndex = kanjiStore.index('lastObserved');
var LIMIT = 10;
var counter = 0;
lastObservedIndex.openCursor('prev').onsuccess = function() {
var cursor = this.result;
if(cursor) {
allStats.push(cursor.value);
if(counter < LIMIT) {
// Only continue
cursor.continue();
}
}
};
}
Комментарии:
1. Действительно, запись каждого события может быть необязательной, однако я хотел сохранить все данные из событий, чтобы я мог проанализировать их после пробного периода и решить, что работает лучше, а что можно выбросить. Тем не менее, ваш ответ заставил меня понять, что для цели, которую я намерен использовать, то есть для того, чтобы пользователи были проинформированы о том, какие кандзи они в конечном итоге запрашивают больше всего, и, кроме того, на каких кандзи им следует сосредоточить свои исследования, предложенное вами решение должно работать достаточно хорошо. Спасибо за быстрый ответ.
2. Спасибо, что приняли это, но, пожалуйста, поймите, я не хотел отговаривать вас от ваших поисков. Сохранение в течение определенного периода времени, чтобы больше подумать об этом, является действительной «альтернативной причиной», которая предполагает, что IndexedDB будет отличным решением. В этом смысле вы можете очень четко описать, как будет выглядеть результат операции GROUP BY? Например, что было бы, если бы вы делали это на старом добром SQL?
3. На самом деле я не знаю SQL, но я думаю, что это должно быть так
SELECT kanji, COUNT(*) as occurences FROM kanji WHERE date > <some value> GROUP BY kanji ORDER BY occurences DESC
. Грубый пример SQLFiddle