IndexedDBにはまってます。(その13)

IndexedDBにはまってます。(その12)では訂正の投稿いたしました。ご確認をお願いします。

さては仕切り直して「はまって」参ります。ラングオブジェクト(IDBKeyRange)によるデータベースの操作を始めます。

まずは比較してシンプルな操作とするため、オブジェクトストアをアウトオブラインキーのモードに設定して利用します。

コード内のオブジェクトストアのオプション指定を下のように記述して、プライマリキー(主キー)を指定しない「アウトオブラインキー(out-of-line keys)」のモードでオブジェクトストアを生成します。

なお、キージェネレータはオンにします。うっかりaddメソッドの第二パラメータが無くても動くようにしておきます。

const Store_option = {keyPath:null, autoIncrement:true};

では、つぎのコードをWebブラウザ(Google Chromeを利用)のコンソールにコピー&ペーストし、エンターキーで実行して、アウトオブラインキーモードでオブジェクトストアStore_nameを生成します。

const openRequest = indexedDB.open('DB_name');
openRequest.addEventListener('upgradeneeded', (event) => {
	const db = event.target.result;
	//const tx = event.target.transaction;
	console.log(`UPGRADE_DB_VER_>${db.version}_OLD_>${event.oldVersion}_NEW_>${event.newVersion}`);
	//今回の主題は次の行になります。
	//オブジェクトストアのオプション指定を変数に代入する。
	const Store_option = {keyPath:null, autoIncrement:true};
	try {
		//オブジェクトストア操作箇所
		//Store_optionをオブジェクトストアの生成メソッドのオプションパラメータとする。
		const objectStore = db.createObjectStore('Store_name', Store_option);
		objectStore.transaction.addEventListener('complete', (event)=>{
			if(event.target.objectStoreNames.contains('Store_name')) {
				console.log(`オブジェクトストアを生成しました。`);
			}
		}, false);
		objectStore.transaction.addEventListener('abort', (event)=>{console.error(`トランザクションが中断しました。`);}, false);
		objectStore.transaction.addEventListener('error', (event)=>{console.error(`トランザクションでエラーが発生しました。`);}, false);
	} catch(exception) {
		console.error(`例外が発生しました。_>${exception.message}`);
	}
}, false);
openRequest.addEventListener('success', (event)=>{
	const db = event.target.result;
	console.log(`データベースを開始しました。`);
	db.close();
	db.addEventListener('close', ()=>{console.log(`データベースが閉鎖されました。`);}, false);
}, false);
openRequest.addEventListener('error', (event)=>{console.error(`データベースの開始に失敗しました。`);}, false);

無事にデータベースDB_nameが開いて、オブジェクトストアStore_nameの生成されたことが、アプリケーション画面で確認できると思います。

つぎに、レコードを複数作って動作確認をしますので、次の配列コードをコンソールにコピー&ペーストして配列オブジェクトを準備します。

const records = [
	{key:2, data:{index:{en:1, sn:'SN0003'}, date:0, title:'ZINC', author:'hustlemouse', tag:['plate','Zn','shingle','brass'], note:'Zn', id:2}},
	{key:6, data:{index:{en:2, sn:'SN0001'}, date:0, title:'GOLD', author:'hustlemouse', tag:['coin','jewelry','plate','Au','finger'], note:'Au', id:6}},
	{key:5, data:{index:{en:3, sn:'SN0002'}, date:0, title:'SILVER', author:'hustlemouse', tag:['coin','jewelry','plate','tableware'], note:'Ag', id:5}},
	{key:7, data:{index:{en:4, sn:'SN0004'}, date:0, title:'PLATIUM', author:'hustlemouse', tag:['css','jewelry','plate','catalyst'], note:'Pt', id:7}},
	{key:1, data:{index:{en:5, sn:'SN0005'}, date:0, title:'COPPER', author:'hustlemouse', tag:['coin','pans','plate','shingle','brass'], note:'Cu', id:1}},
	{key:3, data:{index:{en:6, sn:'SN0006'}, date:0, title:'ALUMINUM', author:'hustlemouse', tag:['coin','sash','pans','Macbook'], note:'Al', id:3}}
];
コンソール画面で配列を確認

できましたね。(画像ではconsole.dirメソッドを使い配列を確認してます。)

配列の要素は判りやすいように連想配列内にkeyとdataのプロパティで構成されています。

このあとは、ストアオブジェクトの操作になります。よって、データベース開始成功イベントの受け取り枠の中での操作になります。

トランザクションを発動して、参照したオブジェクトストアに対する「レコード操作箇所」で下のように、一気にfor文を使って、追加(add)操作にてレコードを格納します。

アウトオブラインキーモードなので、addメソッドに第二パラメータにkeyを割り当てて格納していきます。

let storeRequest;
for(let i=0; i<records.length; i++) {
	storeRequest = objectStore.add(records[i].data, records[i].key);
}

つぎのコードをのコンソールにコピー&ペーストし、エンターキーで実行します。(レコード格納ですからトランザクションはreadwriteモードです。)

const openRequest = indexedDB.open('DB_name');
openRequest.addEventListener('success', (event)=>{
	const db = event.target.result;
	console.log(`データベースを開始しました。`);
	if(db.objectStoreNames.contains('Store_name')) {
		const tx = db.transaction('Store_name', 'readwrite');
		tx.addEventListener('complete', (event)=>{console.log(`トランザクションが完遂しました。`);}, false);
		tx.addEventListener('error', (event)=>{console.error(`トランザクションでエラーが発生しました。`);}, false);
		tx.addEventListener('abort', (event)=>{console.error(`トランザクションが中断しました。`);}, false);
		const objectStore = tx.objectStore('Store_name');
		try {
			//レコード操作箇所
			let storeRequest;
			for(let i=0; i<records.length; i++) {
				storeRequest = objectStore.add(records[i].data, records[i].key);
			}
			storeRequest.addEventListener('success', (event)=>{console.log(`ストア内操作に成功しました。`);}, false);	
			storeRequest.addEventListener('error', (event)=>{console.error(`ストア内操作に失敗しました。`);}, false);
		} catch(exception) {
			console.error(`例外が発生しました。_>${exception.message}`);
		}
	} else console.error(`目的のオブジェクトストアはありませんでした。`);
	db.close();
	db.addEventListener('close', ()=>{console.log(`データベースが閉鎖されました。`);}, false);
}, false);
openRequest.addEventListener('error', (event)=>{console.error(`データベースの開始に失敗しました。`);}, false);
アプリケーション画面のオブジェクトストア

アプリケーション画面でレコードが格納されていることを確認できます。自動的にkeyの順番にソートされていることが見て取れます。

さあさあそれでは、ラングオブジェクトを利用してまずはレコード数を取得してみます。

先にラングオブジェクトの生成メソッドが必要です。「newRange = function({start, end}) {}」をIndexedDBにはまってます。(その11)からコピペして利用します。

コンソール画面にメソッドを登録

気分ではありますが、オブジェクトストアのトランザクション発動の後で「ラングオブジェクト生成」コードを入れます。

最初は、格納されている全レコード数を取得してみます。下のようにラングオブジェクトにnullが返ってくるように指定します。

//ラングオブジェクト生成
const range = newRange({start:{v:null, b:false}, end:{v:null, b:false}});

トランザクション内の「レコード操作箇所」はcountメソッドを使って記述します。

storeRequest = objectStore.count(range);

オブジェクトストア(storeRequest)のリクエストオブジェクトで発火する成功イベントを受取る枠内(ああややこしい。)で、レコード数が出力されるように仕込みます。

storeRequest.addEventListener('success', (event)=>{
	console.log(`ストア内操作に成功しました。_カウント_>>> ${event.target.result}`);
}, false);

つぎのコードをのコンソールにコピー&ペーストし、エンターキーで実行します。(読み込むだけですからトランザクションはreadonlyモードです。)

const openRequest = indexedDB.open('DB_name');
openRequest.addEventListener('success', (event)=>{
	const db = event.target.result;
	console.log(`データベースを開始しました。`);
	if(db.objectStoreNames.contains('Store_name')) {
		const tx = db.transaction('Store_name', 'readonly');
		tx.addEventListener('complete', (event)=>{console.log(`トランザクションが完遂しました。`);}, false);
		tx.addEventListener('error', (event)=>{console.error(`トランザクションでエラーが発生しました。`);}, false);
		tx.addEventListener('abort', (event)=>{console.error(`トランザクションが中断しました。`);}, false);
		const objectStore = tx.objectStore('Store_name');
		//ラングオブジェクト生成
		const range = newRange({start:{v:null, b:false}, end:{v:null, b:false}});
		try {
			let storeRequest;
			//レコード操作箇所
			storeRequest = objectStore.count(range);
			storeRequest.addEventListener('success', (event)=>{console.log(`ストア内操作に成功しました。_カウント_>>> ${event.target.result}`);}, false);	
			storeRequest.addEventListener('error', (event)=>{console.error(`ストア内操作に失敗しました。`);}, false);
		} catch(exception) {
			console.error(`例外が発生しました。_>${exception.message}`);
		}
	} else console.error(`目的のオブジェクトストアはありませんでした。`);
	db.close();
	db.addEventListener('close', ()=>{console.log(`データベースが閉鎖されました。`);}, false);
}, false);
openRequest.addEventListener('error', (event)=>{console.error(`データベースの開始に失敗しました。`);}, false);
コンソール画面の入力と結果表示

「ストア内操作に成功しました。カウント>>> 6」とコンソールに出力があります。(ありますよね?)ラングオブジェクトがnullであることも確認します。

(追記)

countメソッドと、この後紹介するgetAllgetAllKeyメソッド(注意:これらはIDBObjectStoreのインスタンスメソッドです。)でもは、パラメータを「count(null)」とか「count()」のようにするとレコード全体を対象とします。

したら、ラングオブジェクトのパラメータを例えば下のように変更してみます。

//ラングオブジェクト生成
const range = newRange({start:{v:2, b:false}, end:{v:6, b:false}});
コンソール画面の入力と結果表示

ラングは2と6を含む範囲指定になりますから「カウント_>>> 4」になると思います。ラングオブジェクトに内容があることも確認します。

試しにbプロパティをtrueにしてみると、

//ラングオブジェクト生成
const range = newRange({start:{v:2, b:true}, end:{v:6, b:true}});
コンソール画面の入力と結果表示

ラングは2と6を含まない範囲指定になりますから「カウント_>>> 2」になると思います。

同じ容量でgetAllとgetAllKeysメソッドを試してみます。

取得オブジェクトがコンソールで確認できるようにconsole.dirメソッドを仕込みます。

//レコード操作箇所
storeRequest = objectStore.getAll(range);
storeRequest.addEventListener('success', (event)=>{
	console.log(`ストア内操作に成功しました。_>>> ${event.target.result}`);
	console.dir(event.target.result);
}, false);
コンソール画面の入力と結果表示
//レコード操作箇所
storeRequest = objectStore.getAllKeys(range);
storeRequest.addEventListener('success', (event)=>{
	console.log(`ストア内操作に成功しました。_>>> ${event.target.result}`);
	console.dir(event.target.result);
}, false);
コンソールの入力と結果表示

それぞれ確認ができたはずです。

countとgetAll、getAllKeysメソッドは第一パラメータにラングオブジェクトを指定します。主キーの出番は無いようです。

getAllとgetAllKeysメソッドには、第二パラメータがあって返す数を指定できます。(動作は確認しましたが、使い方を検討中です。)

次回は、getとgetKey、deleteメソッドのパラメータにラングオブジェクトを指定する場合を紹介します。

ラングオブジェクトはプライマリキー(主キー)だけでなくインデックス(補キー)でも有効で、さらにカーソル運用でも利用され、いよいよ頭がこんがらがって楽しいです。そかさ。