html で文字列をローカルに保存する【名前を付けて保存】

今回は html で文字列を
ローカルに保存する場合の
ソースを公開してみようと思います。

名前を付けて保存機能があるブラウザでは
名前を付けて保存で保存します。
Chrome や Edge が該当します。

名前を付けて保存機能が無いブラウザでは
直接ダウンロードします。

***

今回のソースコード

```javascript:ローカルに保存するコード
let fileName = document.getElementById('fileNameTextBox').value;
const pattern = /[\\\/:\*\?\"<>\|]/;

if(pattern.test(fileName)){
	window.alert('ファイル名に使用できない文字が含まれています。');
}
else {
	const save = ""; //この save 変数に保存する文字列を入れて下さい。
	if(fileName == ''){
		var dt = new Date();
		var y = '' + dt.getFullYear();
		var m = ('00' + (dt.getMonth()+1)).slice(-2);
		var d = ('00' + dt.getDate()).slice(-2);
		var hour = ('00' + dt.getHours()).slice(-2);
		var minute = ('00' + dt.getMinutes()).slice(-2);
		var second = ('00' + dt.getSeconds()).slice(-2);
		fileName = "〇〇バックアップ" + (y + m + d + hour + minute + second); //〇〇にはサイト名を入れて下さい。
	}
	if(typeof window.showSaveFilePicker != 'function'){
		const blob = new Blob([save],{type:"text/plain"});
		let downloadTag = document.createElement('a');
		downloadTag.href = URL.createObjectURL(blob);
		downloadTag.download = fileName + ".txt";
		downloadTag.click();
		window.setTimeout( function() { URL.revokeObjectURL(blob);}, 10000 );
		window.alert('バックアップを保存しました。');
	} else {
		let downloadTag = document.createElement('a');
		downloadTag.addEventListener('click', async () => {
			const opts = {
				suggestedName: fileName,
				types: [{
					description: 'Text file',
					accept: {'text/plain': ['.txt']},
				}],
			};
			try{
				const handle = await window.showSaveFilePicker(opts);
				const writable = await handle.createWritable();
				await writable.write(save);
				await writable.close();
				window.alert('バックアップを保存しました。');
			} catch(e){
			}
		})
		downloadTag.click();
	}
}
```

上記コードでは
ファイル名はファイル名のテキストボックスを
用意するようにしていますが
ファイル名をこちらで指定してしまう事もできます。

***

セーブデータを圧縮して保存する場合

ブラウザでセーブデータを
圧縮して保存したい事は稀だとは思いますが、
セーブデータを圧縮して保存する場合は
以下のコードにすると良いでしょう。

```javascript:圧縮してセーブデータを保存・読み込みする場合
async function save() {
	let fileName = document.getElementById('fileNameTextBox').value;
	const pattern = /[\\\/:\*\?\"<>\|]/;

	if(pattern.test(fileName)){
		window.alert('ファイル名に使用できない文字が含まれています。');
	}
	else {
		let save = 'SaveDataTypeA!ver1.0.0!';
		let saveOne = ""; //ここに保存する文字列を入れて下さい。

		const readableStream = new Response(saveOne).body.pipeThrough(new CompressionStream('deflate'));
		save += await bolbToBase64(await new Response(readableStream).blob());
		if(fileName == ''){
			var dt = new Date();
			var y = '' + dt.getFullYear();
			var m = ('00' + (dt.getMonth()+1)).slice(-2);
			var d = ('00' + dt.getDate()).slice(-2);
			var hour = ('00' + dt.getHours()).slice(-2);
			var minute = ('00' + dt.getMinutes()).slice(-2);
			var second = ('00' + dt.getSeconds()).slice(-2);
			fileName = "〇〇バックアップ" + (y + m + d + hour + minute + second); //〇〇にはサイト名を入れて下さい。
		}

		if(typeof window.showSaveFilePicker != 'function'){
			const blob = new Blob([save],{type:"text/plain"});
			let downloadTag = document.createElement('a');
			downloadTag.href = URL.createObjectURL(blob);
			downloadTag.download = fileName + ".txt";
			downloadTag.click();
			window.setTimeout( function() { URL.revokeObjectURL(blob);}, 10000 );
			window.alert('バックアップを保存しました。');
		}
		else{
			let downloadTag = document.createElement('a');
			downloadTag.addEventListener('click', async () => {
				const opts = {
					suggestedName: fileName,
					types: [{
						description: 'Text file',
						accept: {'text/plain': ['.txt']},
					}],
				};
				try{
					const handle = await window.showSaveFilePicker(opts);
					const writable = await handle.createWritable();
					await writable.write(save);
					await writable.close();
					window.alert('バックアップを保存しました。');
				} catch(e){
				}
			})
			downloadTag.click();
		}
	}
}
async function bolbToBase64(blob) {
	return await new Promise(resolve => {
		const reader = new FileReader();
		reader.readAsDataURL(blob);
		reader.onloadend = () => {
			resolve(reader.result.replace(/data:.*\/.*;base64,/, ''));
		};
	});
}

document.getElementById('files').addEventListener('change', function(e) {
	var result=e.target.files[0];
	var reader=new FileReader();
	reader.readAsText(result);
	reader.addEventListener('load', function() {
		myLoad(reader.result);
	});
	e.target.value = '';
});
async function myLoad(loadText) {
	let index = loadText.indexOf('!');
	if(!loadText.startsWith('SaveData')){
		window.alert('ファイルの読み込みに失敗しました。');
		return;
	}
	const isTypeA = loadText.startsWith('SaveDataTypeA');
	loadText = loadText.substr(index + 1);
	index = loadText.indexOf('!');
	const versionInfo = loadText.substr(0, index);

	loadText = loadText.substr(index + 1);
	const oneTimeBlob = await base64ToBlob(loadText);
	const readableStream = oneTimeBlob.stream().pipeThrough(new DecompressionStream('deflate'));
	loadText = await new Response(readableStream).text();
	
	//ここで loadText をロードしたい変数に代入して下さい。
}
async function base64ToBlob(base64) {
	return await fetch('data:application/octet-stream;base64,' + base64).then(res => res.blob());
}
```

参考サイト：
https://168iroha.net/blog/article/202401031600/

[Compression Streams API](https://developer.mozilla.org/ja/docs/Web/API/Compression_Streams_API) を使用しています。
API の内部処理が変わってセーブデータが
正常に読み込めなくなった場合に
我々は責任を負わないものとさせて下さい。
何卒よろしくお願い致します。

***

以上 html で文字列をローカルに
保存するソースを公開しました。

この処理を書く時間を
短縮できたならば幸いです。

皆さんの開発の助けになれますように。
閲覧ありがとうございました。

***

追記

JavaScript の圧縮関連の
記事を書かれている方から
圧縮の箇所について提案頂きました。

用途ごとの最適な圧縮コードを頂けたため
是非コメント欄もご覧頂けると幸いです。