SVGについて – javascriptでSVGの要素を制御する

Posted コメントするカテゴリー: javascript

SVGについて – javascriptでSVGの要素を制御する

javascriptでSVGの要素を動的に変更する方法を試してみます。

HTMLドキュメント内のdivタグにIDを付与し、javascriptでSVGを生成+描画する方法を試してみます。
簡単なサンプルを書きました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<svg id="svg_tag" width="500" height="400"  viewBox="0 0 500 400" xmlns="http://www.w3.org/2000/svg"></svg> 

<div id="move_line">直線(line)要素を回転させる(この文字をクリックすると動きます)</div>

<script type="text/javascript">
let line = document.createElementNS('http://www.w3.org/2000/svg', 'line')

line.setAttribute('id'    , 'svg_line_id1');
line.setAttribute('x1'    , '30');
line.setAttribute('y1'    , '50');
line.setAttribute('x2'    , '200');
line.setAttribute('y2'    , '150');
line.setAttribute('stroke', '#000000');

let svgtest_dom = document.getElementById("svg_tag");
svgtest_dom.appendChild(line);

let rotate_value = 0;

let element_move = document.getElementById('move_line');
element_move.addEventListener('click',  function() {
    let element_svg_line = document.getElementById('svg_line_id1');

    rotate_value += 10;
    element_svg_line.setAttribute("transform", "rotate(" + rotate_value + ", 100, 100)");

}, false);

</script>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

画面にアクセスして「直線(line)要素を動かす」をクリックすると、svgで描画された直線(line)が 回転(rotate)します。
rotate_value をスクリプト内部で定義して、クリックするごとに10づつ値を加算しています。

ポイントとなる点は、14行目~21行目でjavascript内でline要素を生成していますが、
その際に16行目でsvg要素に対してidを付与しています。

こうすることでHTMLドキュメント上では、次のようにline要素にIDが追加されます。

<line id="svg_line_id1" x1="30" y1="50" x2="200" y2="150" stroke="#000000"></line>

このIDをもとに、30行目で

let element_svg_line = document.getElementById('svg_line_id1');

とすることで、getElementByIdでline要素を取得し、プロパティにアクセスすることができています。

また、transformは移動・回転・伸縮・傾斜を制御するプロパティですが、このプロパティ以外を設定することで様々な動きが可能になります。

SVGについて – ドキュメント内にSVGを表示する(createElementNS)

Posted コメントするカテゴリー: javascript

SVGについて – ドキュメント内にSVGを表示する(createElementNS)

SVGについて、描画の方法を試してみます。

前回の投稿ではHTML5ドキュメント内にSVGタグを直接記述してSVGを描画しましたが、
ここではjavascriptのcreateElementNSメソッドを使って描画してみます。

HTMLドキュメント内のdivタグにIDを付与し、javascriptでSVGを生成+描画する方法

HTMLドキュメント内のdivタグにIDを付与し、javascriptでSVGを生成+描画する方法を試してみます。
HTML全体のコードは次のように書きました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="svgtest"></div>

<script type="text/javascript">
let test1 = document.createElementNS('http://www.w3.org/2000/svg', 'svg');

test1.setAttribute("width", "600");
test1.setAttribute("height", "400");
test1.setAttribute("viewbox", "0 0 600 400");
test1.setAttribute("style", "background-color:#cccccc;");

let line = document.createElementNS('http://www.w3.org/2000/svg', 'line')

line.setAttribute('x1', '20');
line.setAttribute('y1', '50');
line.setAttribute('x2', '500');
line.setAttribute('y2', '300');
line.setAttribute('stroke', '#000000');

test1.appendChild(line);

let svgtest_dom = document.getElementById("svgtest");
svgtest_dom.appendChild(test1);

</script>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

画面にアクセスすると、グレーのSVG領域に、黒で直線(line)が描画されます。

HTMLドキュメント内にはsvgタグを書いていないので、
javascript側で

document.createElementNS('http://www.w3.org/2000/svg', 'svg');

を記述し、
svgオブジェクトをcreateElementNSで生成しています。

HTMLドキュメント内のsvgタグに、javascriptでSVGを描画する方法

また、

<div id="svgtest"></div>

のdivタグをHTMLに記述せず、ここをsvgタグにすることもできます。
次のHTMLを用意しました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<svg id="svg_tag" width="500" height="400"  viewBox="0 0 500 400" xmlns="http://www.w3.org/2000/svg"></svg> 

<script type="text/javascript">
let line = document.createElementNS('http://www.w3.org/2000/svg', 'line')

line.setAttribute('x1', '30');
line.setAttribute('y1', '50');
line.setAttribute('x2', '400');
line.setAttribute('y2', '300');
line.setAttribute('stroke', '#000000');

let svgtest_dom = document.getElementById("svg_tag");
svgtest_dom.appendChild(line);

</script>

</body>
</html>

サーバ上のHTMLはこちら(test2.html)

画面にアクセスすると、直線だけが描画されます。
一つ前のサンプルとの違いは、javascript内でsvgタグを生成していない為、
生成時に背景色を指定していた処理がない為、直線のみが描画されています。

HTMLドキュメント内のsvgタグに、javascriptでSVGを描画する方法(SVGの要素を変更)

先程のサンプルは直線のみを描画でしたが、直線の要素を円に変更して試してみます。
下記のHTMLサンプルを用意しました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<svg id="svg_tag" width="500" height="400"  viewBox="0 0 500 400" xmlns="http://www.w3.org/2000/svg"></svg> 

<script type="text/javascript">
let circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle')

circle.setAttribute('cx', '50');
circle.setAttribute('cy', '50');
circle.setAttribute('r', '40');
circle.setAttribute('stroke', '#1');
circle.setAttribute('stroke-width', '300');
circle.setAttribute('fill', '#cccccc');

let svgtest_dom = document.getElementById("svg_tag");
svgtest_dom.appendChild(circle);

</script>

</body>
</html>

サーバ上のHTMLはこちら(test3.html)

画面にアクセスすると、円(circle)の描画がされていることが確認できます。

SVGについて – ドキュメント内にSVGを表示する

Posted コメントするカテゴリー: javascript

SVGについて – ドキュメント内にSVGを表示する

SVGについて、描画の方法を試してみます。

まず、HTML5ドキュメント内にSVGタグを直接記述する方法です。

rect(四角形)

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<svg width="150" height="100">
	<rect
		x="0"
		y="0"
		width="80"
		height="40"
		stroke="#000000"
		stroke-width="1"
		fill="#cccccc"
	/>
</svg>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

circle(円)

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<svg width="150" height="100">
	<circle
		cx="25"
		cy="25"
		r="20"
		stroke="#000000"
		stroke-width="1"
		fill="#cccccc"
	/>
</svg>

</body>
</html>

サーバ上のHTMLはこちら(test2.html)

ellipse(楕円)

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<svg width="300" height="250" viewBox="0 0 500 300">
	<ellipse
		cx="90"
		cy="5"
		rx="80"
		ry="30"
		stroke="#000000"
		stroke-width="1"
		fill="#cccccc"
	/>
</svg>

</body>
</html>

サーバ上のHTMLはこちら(test3.html)

line(直線)

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<svg width="300" height="250" viewBox="0 0 500 300">
	<line
		x="20"
		y1="5"
		x2="150"
		y2="180"
		stroke="#000000"
		stroke-width="1"
		fill="#cccccc"
	/>
</svg>

</body>
</html>

サーバ上のHTMLはこちら(test4.html)

path(折れ線)

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<svg width="300" height="250">
	<path
		d="M 10,0 90,150 120,20 60,5"
		stroke="#999"
		stroke-width="1"
		fill="#cccccc"
	/>
</svg>

</body>
</html>

サーバ上のHTMLはこちら(test5.html)

path(曲線)

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<svg width="300" height="250">
	<path
		d="M 10,0 90,150 120,20 60,5"
		stroke="#999"
		stroke-width="1"
		fill="#cccccc"
	/>
</svg>

</body>
</html>

サーバ上のHTMLはこちら(test6.html)

polyline(連続する線)

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<svg width="300" height="250">
	<polyline
		points="10,15 25,120 50,180 75,95 98,250 105,140 130,175 135,210 150,165"
		stroke="#000000"
		stroke-width="1"
		fill="none"
	/>
</svg>

</body>
</html>

サーバ上のHTMLはこちら(test7.html)

polygon(多角形)

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<svg width="300" height="250">
	<polygon
		points="10 15 25 120 50 180 75 95 98 250 105 140 130 175 135 210 150 135"
		stroke="#000000"
		stroke-width="1"
		fill="none"
	/>
</svg>

</body>
</html>

サーバ上のHTMLはこちら(test8.html)

SVGについて

Posted コメントするカテゴリー: javascript

SVGについて

SVGについて試してみます。

SVGは Scalavle Vector Grapthics の略です。

画像ファイルには、ラスター形式(jpg、gif、png等)と、ベクター形式があり、SVGはベクター情報になります。

大きな特徴としては、SVGは拡大縮小しても描画精度には影響しません。
ラスター画像の場合は、拡大するとピクセル単位の表示が拡大され、画像の精度が荒くなります。

ここではSVGで表現できる方法を一つ一つ試してみようと思います。

下記に簡単なサンプルを用意しました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 300">
	<defs>
		<linearGradient id="test1">
			<stop offset="0%" stop-color="#ccc" />
		</linearGradient>
	</defs>
	<rect x="10" y="10" width="300" height="200" stroke="#fff" storoke-width="20" fill="url(#test1)"/>
</svg>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

画面にアクセスすると、rectタグで指定したサイズの長方形が表示されます。
画面の大きさを変えた場合でも、長方形が拡大縮小されることがわかります。
ただし、この長方形は単色で塗りつぶしただけなので、SVGの特徴はわかりにくいです。
本来、ラスター画像なので、座標情報を元に画像が描画されているので、そのサンプルは別な投稿で試します。

「http://www.w3.org/2000/svg」という文字列は慣習的に決められており、この後のサンプルでも同じように記述します。

また、linearGradient要素に複数の色情報を記述すると、グラデーションで表示されます。
下記サンプルを試してみました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 300">
	<defs>
		<linearGradient id="test1">
			<stop offset="0%" stop-color="#cccccc" />
			<stop offset="50%" stop-color="#ffffff" />
			<stop offset="70%" stop-color="#b1d0d5" />
			<stop offset="90%" stop-color="#8eb5df" />
		</linearGradient>
	</defs>
	<rect x="10" y="10" width="300" height="200" stroke="#fff" storoke-width="20" fill="url(#test1)"/>
</svg>

</body>
</html>

サーバ上のHTMLはこちら(test2.html)

画面にアクセスすると、linearGradientタグ内に記載した値でグラデーションされた長方形が表示されます。
このサンプルも先程と同様、単純な長方形なので、まだSVGの特徴がつかめていません。
ここでは、SVGを出力する方法を試す程度にしておきます。

SVGの出力方法について

上記のサンプルは、あらかじめHTMLドキュメント内にSVG用のタグと、SVGの出力内容を記載する方法でした。

ここでは、別な方法としてjavascript内からSVGを生成して出力する方法を試します。

まず、HTMLドキュメント内にSVG描画用のDIVタグを記載し、その後にjavascriptでSVGを定義し、DIVタグに出力する方法です。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="svgtest"></div>

<script type="text/javascript">
let test1 = document.createElementNS('http://www.w3.org/2000/svg', 'svg');

test1.setAttribute("width"  , "600");
test1.setAttribute("height" , "400");
test1.setAttribute("viewbox", "0 0 600 400");
test1.setAttribute("style"  , "background-color:#e1e1e1;");

let line = document.createElementNS('http://www.w3.org/2000/svg', 'line')

line.setAttribute('x1'    , '30');
line.setAttribute('y1'    , '50');
line.setAttribute('x2'    , '300');
line.setAttribute('y2'    , '200');
line.setAttribute('stroke', '#000000');

test1.appendChild(line);

let svgtest_dom = document.getElementById("svgtest");
svgtest_dom.appendChild(test1);

</script>

</body>
</html>

サーバ上のHTMLはこちら(test3.html)

画面にアクセスすると、横600、縦400の長方形に、SVG用を動的に生成したline要素が表示されます。

サンプルの中で「createElementNS」というメソッドが出てきましたが、
これは名前空間URIと修飾名(第二引数)の要素を生成する命令です。(第三引数はここでは省略しています)
修飾名には

svg
line
rect
circle
ellipse
path
polygon

等があり、svg領域にどんな描画出力をするかを決定します。

メディアの状態について

Posted コメントするカテゴリー: javascript

メディアの状態について

メディアの再生状態について、検証してみます。
前回の投稿のサンプルをもとに、メディアの再生状態を把握するプロパティを抜粋して下記のサンプルを用意しました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<audio controls id="audiodata1">
	<source src="test.mp3" id="sound1">
</audio>

<div id="sound_play">サウンドを再生する</div>

<div id="sound_pause">サウンドを一時停止する</div>

<hr>
プロパティ出力<br />
<div id="property_result"></div>

<script type="text/javascript">

	// サウンド制御のDOM要素を取得
	let button_play = document.getElementById('sound_play');
	let button_pause = document.getElementById('sound_pause');

	// 再生イベント
	button_play.addEventListener('click', function() {
		let audiodata1 = document.getElementById("audiodata1");
		audiodata1.play();
		
		// プロパティの状態を確認する
		display_property();

	}, false);

	// 一時停止イベント
	button_pause.addEventListener('click', function() {
		let audiodata1 = document.getElementById("audiodata1");
		audiodata1.pause();

		// プロパティの状態を確認する
		display_property();

	}, false);

	function display_property()
	{
		// 結果確認用DOMを取得
		let property_result = document.getElementById("property_result");

		// サウンド制御のDOM要素を取得
		let audiodata1 = document.getElementById("audiodata1");

		// サウンドファイルのsource要素を取得
		let sound1 = document.getElementById('sound1');

		// 一旦表示を初期化する
		property_result.innerHTML = '';

		// メディアの長さ
		property_result.innerHTML += "duration        -> " + audiodata1.duration          + '<br />';
		// メディアの現在再生位置(秒)
		property_result.innerHTML += "currentTime     -> " + audiodata1.currentTime       + '<br />';

		// TimeRangesオブジェクト
		property_result.innerHTML += "buffered        -> " + audiodata1.buffered          + '<br />';
		property_result.innerHTML += "played          -> " + audiodata1.played            + '<br />';
		property_result.innerHTML += "seekable        -> " + audiodata1.seekable          + '<br />';

		// 開始位置と終了位置を取得
		property_result.innerHTML += "played.length   -> " + audiodata1.played.length     + '<br />';
		property_result.innerHTML += "buffered.length -> " + audiodata1.buffered.length   + '<br />';
		property_result.innerHTML += "seekable.length -> " + audiodata1.seekable.length   + '<br />';

		property_result.innerHTML += "played.start    -> " + audiodata1.played.start(0)   + '<br />';
		property_result.innerHTML += "buffered.start  -> " + audiodata1.buffered.start(0) + '<br />';
		property_result.innerHTML += "seekable.start  -> " + audiodata1.seekable.start(0) + '<br />';
		property_result.innerHTML += "played.end      -> " + audiodata1.played.end(0)     + '<br />';
		property_result.innerHTML += "buffered.end    -> " + audiodata1.buffered.end(0)   + '<br />';
		property_result.innerHTML += "seekable.end    -> " + audiodata1.seekable.end(0)   + '<br />';

		// 状態
		property_result.innerHTML += "readyState      -> " + audiodata1.readyState        + '<br />';
		property_result.innerHTML += "networkState    -> " + audiodata1.networkState      + '<br />';
		property_result.innerHTML += "error           -> " + audiodata1.error             + '<br />';
	}

</script>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

画面上でサウンドの再生、一時停止、等の操作を行うと、audioタグから取得した要素のプロパティが表示されます。

主なプロパティの意味は、次になります。

durationは再生するファイルの長さ(秒)
currentTimeは再生した秒数

また、下の3つは TimeRangesオブジェクト です。

buffered
played
seekable

bufferedは現在バッファされている時間の範囲、
playedは再生された時間の範囲、
seekableは現在移動できる時間の範囲
をそれぞれ返します。

TimeRangesオブジェクト はそれぞれlengthプロパティと、start、endメソッドを持ち、
lengthはオブジェクトで表す範囲、startとendは開始時間と終了時間を秒単位で返します。

また、readyStateプロパティ、networkStateプロパティはメディアオブジェクトの状態の情報を持ちます。
上記サンプル画面のサウンドを再生、一時停止、再生を行うと

readyState -> 4
networkState -> 1

という結果が画面に表示されます。

readyStateはメディアデータがどの程度読み込まれているかを示します。
それぞれ値と意味は次のようになります。

0 : HAVE_NOTHING
メディアファイルはロード未完了

1 : HAVE_METADATA
メディアファイルはロード完了
現在再生位置はロード未完了
currentTime位置のメディアは再生不可

2 : HAVE_CURRENT_DATA
currentTimeのロード完了

3 : HAVE_FUTURE_DATA
ロード完了
再生可能
全データを再生するには不十分

4 : HAVE_ENOUGH_DATA
ロード完了
再生可能
全データを再生するには十分(但し未保証)

networkStateはメディア要素がネットワークをどれくらい利用しているかを取得
それぞれ値と意味は次のようになります。

0 : NETWORK_EMPTY
ネットワーク未使用

1 : NETWORK_IDLE
ネットワークからロード未使用(ロード未完了とは異なる)
現在必要データをバッファ済

2 : NETWORK_LOADING
ネットワークからデータロード中

3 : NETWORK_NO_SOURCE
再生可能なメディア無し

また、errorプロパティは、サンプル画面ではnullを表示していますが、
もしメディアの再生にエラーが生じた場合は次の表示になります。

1 : MEDIA_ERR_ABORTED
ユーザによる読み込み停止

2 : MEDIA_ERR_NETWORK
ネットワークエラーにより、ロード停止

3 : MEDIA_ERR_DECODE
エンコーディングエラーにより再生不可

4 : MEDIA_ERR_SRC_NOT_SUPPORTED
メディアの種類が、使用ブラウザでは再生不可

エラーが発生しなかった場合は、nullになり、通常再生が可能になります。

メディアの操作について

Posted コメントするカテゴリー: javascript

メディアの操作について

javascriptでメディアを取り扱う方法を試してみます。

ここでいうメディアは、文字列以外のデータとして、画像(canvas含む)、音声、動画、等のファイルです。
一つ一つ試しながら理解してみます。

音声や動画ファイルの再生について

サーバ上にある音声ファイル(mp3やwav等)の再生方法は多岐に渡ります。

audioタグ(DOM要素の埋め込み)を書く方法や、Web Audio APIを使う方法

ここでは、audioを使って再生を制御する方法を試してみます。
具体的には、音声ファイルの場合は、次のように書きます。
audioオブジェクトを使う為のDOM要素と、src属性でサーバ上の音声ファイルを定義します。

<audio src="test.mp3">

ただし、このDOM要素だけをHTML上に書いても何もなりません。
サーバ側に「test.mp3」という音声ファイルを置き、下記のHTMLを書いてみました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<audio src="test.mp3">

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

画面にアクセスしてみると、音声も再生されず、画面上にも何も表示されません。
上記のHTMLはHTML5の audioタグを指定しているだけなので、実際に音声を再生させたり停止させたりするには、属性を追加しなければならないです。
また、音声ファイルに対してその他の処理をするには、javascript側でAPIを呼んで処理をするプログラムを書く必要があります。

audioについて – controls属性について

では、上記HTMLに対して、少し手を加えてみます。
先程の

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<audio controls src="test.mp3">

</body>
</html>

サーバ上のHTMLはこちら(test2.html)

実際に画面にアクセスしてみると、音声を再生する為の
再生ボタン
再生秒数
再生位置を示すバー
ボリューム
再生速度(chromeの場合)

のユーザインタフェースが表示され、音声の再生が可能になります。

注意点としては、音声の再生が可能にはなりますが、
最近の流れとして、スマホやタブレットでの再生、またOSのandroidやiOSでの再生事情を考慮すると
再生可否が担保できなかったり、再生用のユーザインタフェースが統一されていなかったりと、
色々と注意する点があるので、本実装に採用するかどうかはプロジェクトの方針によって決定するとよいと思います。

実際に、chromeとfirefoxでの表示の違いは以下のようになります。

chrome

firefox

では、ここで少し拡張をしてみます。
複数の音声を再生する為、サーバ側にtest2.mp3とtest3.mp3を置き、下記のHTMLを作成してアクセスしてみます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<audio controls src="test.mp3">
<audio controls src="test2.mp3">
<audio controls src="test3.mp3">

</body>
</html>

サーバ上のHTMLはこちら(test3.html)

実際にアクセスしてみるとわかりますが、
上記のHTMLの書き方では、再生欄が3つ表示されるわけではありません。
この動きを解決するには、次のようにします。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<audio controls>
	<source src="test.mp3">
</audio>

<audio controls>
	<source src="test2.mp3">
</audio>

<audio controls>
	<source src="test3.mp3">
</audio>

</body>
</html>

サーバ上のHTMLはこちら(test4.html)

画面にアクセスすると、音声ファイルに応じた、再生欄が3つ表示されます。

それぞれのファイルに応じた秒数が表示されているので、ファイルごとに再生欄が出し分けられていることがわかります。

また、もしsrc属性に、ブラウザがサポートしないファイルを指定した場合、
再生ができない旨を表示する必要があります。
その場合は次のように書きます。

<audio controls>
	<source src="test.mp3">
	<div>非サポートのファイルです</div>
</audio>

上記のように書くと、ブラウザがmp3の再生に対応していない場合、「非サポートのファイルです」という表示になります。

タイプ選択について

メディアがブラウザで再生できるかを確認する方法があります。

実際の場面では、どんな種類のメディアファイルを扱うかはプロジェクトの方針により決まりますが、
例えばユーザからなんらかのファイルを受け付け、それを再生するような場面があった場合、
そのメディアが再生可能かどうかを確認するというケースがあります。

そのような場合には、
HTMLMediaElementインターフェイスのメソッド「canPlayType」を使って調べることができます。

canPlayTypeはプログラムの実行環境でそのメディアが再生できるかを返します。
例えば、前述のサンプルに対して、再生可否を調べるには以下のように下記ます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<audio controls id="audiodata1">
	<source src="test.mp3">
</audio>

<div id="playability_result"></div>

<script type="text/javascript">

	// 音声1のDOM要素を取得
	let audiodata1 = document.getElementById("audiodata1");

	// 音声1の再生可否を取得
	let isPlayability1 = audiodata1.canPlayType('audio/mp3');

	// 音声1のDOM要素を取得し、画面に出力する
	let playability_result = document.getElementById("playability_result");
	playability_result.innerHTML += '再生可否 isPlayability1 -> ' + isPlayability1 + '<br />';

</script>

</body>
</html>

サーバ上のHTMLはこちら(test5.html)

画面にアクセスすると、対象のファイルが再生可能かどうかが表示されます。
今回の例では「probably」と表示され、再生ができることがわかります。

再生の制御について

画面上の audio タグから、メディアファイル(音声、映像)を制御することができます。
メソッドとして用意されているものは、下記です。

canPlayType()
load()
pause()
play()
captureStream()

一番最後の「captureStream」については、映像のファイルに対してのメソッドなので、
音声ファイルには使えません。

では、上記でサンプルで書いたコードに、playメソッドを使うように改修してみます。
下記HTMLを用意しました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<audio controls id="audiodata1">
	<source src="test.mp3">
</audio>

<div id="sound_play">音声を再生する</div>

<script type="text/javascript">

	// 音声再生のDOM要素を取得
	let sound_play_button = document.getElementById('sound_play');

	// 音声再生イベント
	sound_play_button.addEventListener('click',  function() {

		// 音声1のDOM要素を取得
		let audiodata1 = document.getElementById("audiodata1");

		// 再生
		audiodata1.play();

	}, false);

</script>

</body>
</html>

サーバ上のHTMLはこちら(test6.html)

画面にアクセスし「音声を再生する」をクリックすると、

<audio controls id="audiodata1">
	<source src="test.mp3">
</audio>

で指定したaudiodata1のDOM要素の音声が再生されます。

再生以外のその他のメソッドも試してみます。
次のHTMLを用意しました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<audio controls id="audiodata1">
	<source src="test.mp3">
</audio>

<div id="sound_play">音声を再生する</div>

<div id="sound_pause">音声を一時停止する</div>

<script type="text/javascript">

	// 音声制御のDOM要素を取得
	let button_play = document.getElementById('sound_play');
	let button_pause = document.getElementById('sound_pause');

	// 音声再生イベント
	button_play.addEventListener('click',  function() {

		// 音声1のDOM要素を取得
		let audiodata1 = document.getElementById("audiodata1");

		// 再生
		audiodata1.play();

	}, false);

	// 音声再生イベント
	button_pause.addEventListener('click',  function() {

		// 音声1のDOM要素を取得
		let audiodata1 = document.getElementById("audiodata1");

		// 再生
		audiodata1.pause();

	}, false);

</script>

</body>
</html>

サーバ上のHTMLはこちら(test7.html)

画面にアクセスし、音声を再生した後、「音声を一時停止する」をクリックすると、再生は一時停止します。
また「音声を一時停止する」をクリックすると再生は再開されます。
実際のアプリケーションでは、一時停止した後に、「音声を一時停止する」の文言を切り替えて、
「再生を再開する」旨の表示に切り替えるように工夫をします。

また、メソッドの他に、プロパティもあります。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<audio controls id="audiodata1">
	<source src="test.mp3">
</audio>

<div id="sound_play">音声を再生する</div>

<div id="sound_pause">音声を一時停止する</div>

<div id="sound_volume_up">音量を上げる</div>

<div id="sound_volume_down">音量を下げる</div>

<script type="text/javascript">

	// 音声制御のDOM要素を取得
	let button_play = document.getElementById('sound_play');
	let button_pause = document.getElementById('sound_pause');
	let button_volume_up = document.getElementById('sound_volume_up');
	let button_volume_down = document.getElementById('sound_volume_down');

	// 再生イベント
	button_play.addEventListener('click',  function() {
		let audiodata1 = document.getElementById("audiodata1");
		audiodata1.play();
	}, false);

	// 一時停止イベント
	button_pause.addEventListener('click',  function() {
		let audiodata1 = document.getElementById("audiodata1");
		audiodata1.pause();
	}, false);

	// 音量を上げる
	button_volume_up.addEventListener('click',  function() {
		let audiodata1 = document.getElementById("audiodata1");
		audiodata1.volume += 0.1;
	}, false);

	// 音量を下げる
	button_volume_down.addEventListener('click',  function() {
		let audiodata1 = document.getElementById("audiodata1");
		audiodata1.volume -= 0.1;
	}, false);

</script>

</body>
</html>

サーバ上のHTMLはこちら(test8.html)

画面にアクセスして、音声を再生した後、音量を上げる(または下げる) をクリックすると、音量が調整できます。
上記の例はほんの一例ですが、その他にもプロパティが用意されています。

これらはHTMLMediaElementのインタフェイスとして定義されています。
各プロパティの意味や使用方法はここでは書きませんが、実際の開発時には各プロパティを調査したうえで実装をします。

audioTracks
autoplay
buffered
controls
controlsList
crossOrigin
currentSrc
currentTime
defaultMuted
defaultPlaybackRate
disableRemotePlayback
duration
ended
loop
muted
paused
playbackRate
played
preload
preservesPitch
seekable
seeking
src
srcObject
textTracks
videoTracks
volume
error
networkState
readyState

(上記は、全てのプロパティを列挙はしていません)

上記のプロパティがどのような値を保持し、どのような値が取得できるのかを試してみます。
以下、プロパティの内容を出力するサンプルを書きました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<audio controls id="audiodata1">
	<source src="test.mp3">
</audio>

<div id="sound_play">サウンドを再生する</div>

<div id="sound_pause">サウンドを一時停止する</div>

<div id="sound_volume_up">音量を上げる</div>

<div id="sound_volume_down">音量を下げる</div>

<hr>
プロパティ出力<br />
<div id="property_result"></div>


<script type="text/javascript">

	// サウンド制御のDOM要素を取得
	let button_play = document.getElementById('sound_play');
	let button_pause = document.getElementById('sound_pause');
	let button_volume_up = document.getElementById('sound_volume_up');
	let button_volume_down = document.getElementById('sound_volume_down');

	// 再生イベント
	button_play.addEventListener('click',  function() {
		let audiodata1 = document.getElementById("audiodata1");
		audiodata1.play();
		display_property();
	}, false);

	// 一時停止イベント
	button_pause.addEventListener('click',  function() {
		let audiodata1 = document.getElementById("audiodata1");
		audiodata1.pause();
		display_property();
	}, false);

	// 音量を上げる
	button_volume_up.addEventListener('click',  function() {
		let audiodata1 = document.getElementById("audiodata1");
		audiodata1.volume += 0.1;
		display_property();
	}, false);

	// 音量を下げる
	button_volume_down.addEventListener('click',  function() {
		let audiodata1 = document.getElementById("audiodata1");
		audiodata1.volume -= 0.1;
		display_property();
	}, false);

	function display_property()
	{
		let property_result = document.getElementById("property_result");
		let audiodata_property = document.getElementById("audiodata1");

		// 一旦表示を初期化する
		property_result.innerHTML = '';

		property_result.innerHTML += "audioTracks              -> " + audiodata_property.audioTracks + '<br />';
		property_result.innerHTML += "autoplay                 -> " + audiodata_property.autoplay + '<br />';
		property_result.innerHTML += "buffered                 -> " + audiodata_property.buffered + '<br />';
		property_result.innerHTML += "controls                 -> " + audiodata_property.controls + '<br />';
		property_result.innerHTML += "controlsList             -> " + audiodata_property.controlsList + '<br />';
		property_result.innerHTML += "crossOrigin              -> " + audiodata_property.crossOrigin + '<br />';
		property_result.innerHTML += "currentSrc               -> " + audiodata_property.currentSrc + '<br />';
		property_result.innerHTML += "currentTime              -> " + audiodata_property.currentTime + '<br />';
		property_result.innerHTML += "defaultMuted             -> " + audiodata_property.defaultMuted + '<br />';
		property_result.innerHTML += "defaultPlaybackRate      -> " + audiodata_property.defaultPlaybackRate + '<br />';
		property_result.innerHTML += "disableRemotePlayback    -> " + audiodata_property.disableRemotePlayback + '<br />';
		property_result.innerHTML += "duration                 -> " + audiodata_property.duration + '<br />';
		property_result.innerHTML += "ended                    -> " + audiodata_property.ended + '<br />';
		property_result.innerHTML += "loop                     -> " + audiodata_property.loop + '<br />';
		property_result.innerHTML += "muted                    -> " + audiodata_property.muted + '<br />';
		property_result.innerHTML += "paused                   -> " + audiodata_property.paused + '<br />';
		property_result.innerHTML += "playbackRate             -> " + audiodata_property.playbackRate + '<br />';
		property_result.innerHTML += "played                   -> " + audiodata_property.played + '<br />';
		property_result.innerHTML += "preload                  -> " + audiodata_property.preload + '<br />';
		property_result.innerHTML += "preservesPitch           -> " + audiodata_property.preservesPitch + '<br />';
		property_result.innerHTML += "seekable                 -> " + audiodata_property.seekable + '<br />';
		property_result.innerHTML += "seeking                  -> " + audiodata_property.seeking + '<br />';
		property_result.innerHTML += "src                      -> " + audiodata_property.src + '<br />';
		property_result.innerHTML += "srcObject                -> " + audiodata_property.srcObject + '<br />';
		property_result.innerHTML += "textTracks               -> " + audiodata_property.textTracks + '<br />';
		property_result.innerHTML += "videoTracks              -> " + audiodata_property.videoTracks + '<br />';
		property_result.innerHTML += "volume                   -> " + audiodata_property.volume + '<br />';
		property_result.innerHTML += "error                    -> " + audiodata_property.error + '<br />';
		property_result.innerHTML += "networkState             -> " + audiodata_property.networkState + '<br />';
		property_result.innerHTML += "readyState               -> " + audiodata_property.readyState + '<br />';
	}

</script>

</body>
</html>

サーバ上のHTMLはこちら(test9.html)

画面にアクセスして、音声を再生、一時停止、等の操作をしたタイミングで、
各プロパティの値が更新されて内容を確認できます。

オフラインwebアプリケーションについて

Posted コメントするカテゴリー: javascript

オフラインwebアプリケーションについて

以前の投稿でOffline Web Applications について記載しましたが、ここでもう少し掘り下げて調べてみます。

注意点としては、この機能は現状では非推奨となっているので、
「こうゆう機能もある(あった)」という程度に留めておいてよいと思います。
(firefox62以降では使用できません)

オフラインwebアプリケーションを実現には、HTML5の仕組みのアプリケーションキャッシュ機能を使います。
具体的にはアプリケーション自身(HTML、CSS、javascript、画像、音声等のリソースを含む)のデータを全て保存します。

ブラウザのキャッシュとは違うので、ブラウザのキャッシュクリアをしてもデータは削除されず、
ユーザが意図して消さない限りは永続的に残ります。

こうすることでそのwebアプリケーションはオフライン環境でも動作する(アプリケーションからの通信を行わない前提になります)
ことが可能になります。
厳密に言うと、アプリケーションから通信が発生した場合、オンラインに切り替わった段階で通信処理が再開する。という動きになります。

また、全てのブラウザ(バージョン)で動作が保証されてはいないので、実行環境には注意が必要です。

アプリケーションキャッシュマニフェストについて

webアプリケーションキャッシュを使うには、マニフェストファイルが必要になります。
このファイルにはwebアプリケーションが必要とするURLの一覧が含まれています。

マニュフェストファイルの書き方

1行目に

CACHE MANIFEST

を記載します。

コメントは

#

と書きます。

セクションは

CACHE

NETWORK

FALLBACK

SETTINGS

があり、それぞれ次の意味になります。

CACHE
キャッシュするファイルを指定する

NETWORK
ここに書いたファイルはオンラインでサーバ接続が必要

FALLBACK
もしオンラインで接続することができない場合、ここに書いた代替のファイルにアクセスします。

具体的には以下のように書きます。
各ファイル名はダミーです。

CACHE MANIFEST
# ver1.0

CACHE:
test.html
test.css
test.js
images/test.png

NETWORK:
*

FALLBACK:
/fallback.php /dummy.html

マニュフェストファイルが更新された際に、キャッシュ内容の更新を行う為に、
2行目にはバージョン番号を記載することが多いです。

マニュフェストファイル自身の拡張子は指定されていません。
慣習的に.appcache等と指定する場合があります。
既に非推奨の機能なので、ここで実例を試すことはしませんが、後日、何か検証が必要な場面がでてきたら追記するかもしれません。

クッキーについて

Posted コメントするカテゴリー: javascript

クッキーについて

クッキーについてまとめます。
クッキーは Web Storage(localStorage、sessionStorage)よりも馴染みがあり、
歴史的理由から古くから使われている手法です。

サーバサイド言語からも操作でき、元々はサーバサイド側からの利用用途が多くあります。
また、javascriptからもクッキーを操作することができます。

有効期間やスコープはクッキーごとに指定でき、このあたりもWeb Storageとは異なります。

クッキー の仕様

クッキーの仕様についてまとめます。
以下の仕様があります。

保存ファイル
ブラウザごとにテキストファイル形式で保存

1サイトでの最大数
最大20個まで(ブラウザによる)

1つの最大容量
4096バイト(約4KB)

有効期限
保存時に個別に指定する

ブラウザ間の読み込み
共有は不可

スコープ
同一出身ポリシー内で利用

クッキー 使用可否の判定について

javascriptからクッキーが使用可能かを確認する方法があります。

Navigator.cookieEnabled

の値を取得することで、boolean(真/偽)値でクッキーが使用可能かがわかります。

クッキーの特徴について

クッキーの特徴(仕様)ついてまとめます。

・クッキーを保持する際は、サーバにHTTPヘッダが送信される(手法による)
・クッキーを保存する時に指定する
・有効期間を指定しないと、一時的な保存になる
・ブラウザの(最大)セッション期間との関連もあり、クッキー側だけで期間を自由に設定できない
・クッキー側からブラウザのセッション期間の上限を超えて保存する場合は、max-age属性(またはexpire属性)を使用する
・max-age属性とexpire属性を同時に設定した場合は、max-age属性が有効になる
・max-age属性は秒数で指定する
・max-age属性は負の値も設定できる
・expire属性は有効期限が切れる日時を指定する
・どちらの属性もブラウザのバージョンにより効かない場合がある
・path属性、domain属性
・path属性で設定されたパスに保存される
・path属性で設定されたパスに保存されたものは、サブディレクトリ(ネスト化)からは読み込み可能
・path属性で設定されたパスに保存されたものに、異なる親ディレクトリからは読み込みができない
・サブドメインから、メインドメイン側のクッキーを読み込みたい場合は、domain属性に「.ドメイン名」を指定し、path属性に「/」を指定すると読み込みができる。
・secure属性はネットワークを経由してクッキーを指定する論理値
・secure属性が「真」の場合は、https経由でのみクッキーの読み書きが可能
・クッキーの値にはセミコロン、カンマは使用不可です
・セミコロン、カンマを含む文字列を登録する場合は、文字列をエンコード(encodeURIComponent)して保存します(諸方法あり)

javascriptからクッキーの操作をする

javascriptからクッキーの操作をする方法をまとめます

クッキーの登録

基本的な形としては、

名前=値

となります。

コード上では、「test1」という名称のキーに、「testvalue123」という値を登録したい時は、次のように書きます。

document.cookie = "test1=testvalue123";

ただし、この方法では有効期限を設定していないので、デフォルトの保存期間になり、ブラウザを閉じると値が失われます。

では、上記の例に有効期限(秒)を設定して保持するように書きます。
例えば10分(600秒)の保持期間の場合は、次のように書きます。

document.cookie = "test1=testvalue123; max-age=600";

実際のアプリケーションでは、保持期間10分という時間を実用的な保持期間とすることは稀です。
また、値を格納する際は、秒→時間、時間→日、へと計算する工夫が必要です。

例えばクッキーの保持期間を1日(86,400秒)とする場合は、次のようになります。

// この場合は 
// 1秒 * 60で、60秒(1分)
// 60秒(1分) * 60(分) で、3600秒(1時間)
// 3600秒(1時間) * 24(時間)で、24時間(1日)
let cookie_test_time = 1 * 60 * 60 * 24;

document.cookie = "test1=testvalue123; max-age=" + cookie_test_time;

また、例えば5時間(18,000秒)にする場合は以下のようになります。

// この場合は 
// 1秒 * 60で、60秒(1分)
// 60秒(1分) * 60(分) で、3600秒(1時間)
// 3600秒(1時間) * 5(時間)で、5時間
let cookie_test_time = 1 * 60 * 60 * 5;

document.cookie = "test1=testvalue123; max-age=" + cookie_test_time;

計算式の一番最初の「1 *」は省略しても問題はないですが、ここでは秒を例える為に記載しています。

また、他の属性をパラメータに含める場合は、「;」(セミコロン)で区切って、続けて記述します。

クッキーの読み出し

javascriptからクッキーを読みだすと、domain属性で指定した範囲内のクッキーの値を全て読み込みます。

形としては

名前=値

の組み合わせで読み込まれます。
複数の名前がある場合は、「;」で区切られた形で連続したデータとして読み込まれます。

具体的には次のように書きます。

let load_cookie;

load_cookie = document.cookie;

上記の場合は、先程の例で保存した、「test1」という名称のキーと、「testvalue123」の値が取得できます。

また、値にセミコロンやカンマが含まれているデータは、文字列をデコードしたうえで処理をする必要があります。
その場合はjavascriptの組み込み関数の「decodeURIComponent」を使ってデコードをします。

クライアントサイドストレージ – Web Storageについて(その3)

Posted コメントするカテゴリー: javascript

クライアントサイドストレージ – Web Storageについて(その3)

前々回の投稿で少しだけ登場しましたが、

localStorageにキーと値を登録(更新)する時に

localStorage.test1 = "テストA";

と書くケースと、違うケースとして

localStorage.setItem("test1", "テストA");

と書く方法があると紹介しました。

ここでは、少し深く掘り下げて後者の書き方を試してようと思います。

具体的に後者の書き方のことを Strage API と呼び、
Strage API を使って、データの登録、呼び出し、削除等をプロパティを使って行うことができます。

Strage APIについて

Strage APIを使ったデータの操作についてまとめます。

データの登録、更新

setItem()メソッドを使います。

localStorage.setItem("test1", "テストA");

データの取得

getItem()メソッドを使います。

localStorage.getItem("test1");

上記の場合、「テストA」という文字列が取得できます。

データの削除

remoteItem()メソッドを使います。

localStorage.remoteItem("test1");

ローカルストレージからキー「test1」のデータを削除します。
キーと値の両方を削除します。

データの削除(ストレージからの全削除)

clear()メソッドを使います。

localStorage.clear();

全ローカルストレージを削除します。

Storage イベントについて

Storage APIを使用してデータの登録、更新、削除、等を行ったタイミングで、
windowオブジェクトでストレージイベントが発生します。

ここでポイントとなるのがwindowオブジェクトに対して発生するという点です。
具体的には、同じURLの画面をタブで2つ開いている場合や、画面内にフレームが存在し、
そのフレームで同一出身ポリシーの画面を開いている場合。ということが言えます。

また、登録するデータが既存と同じデータの場合は、このイベントは発生しません。
データが異なる場合にイベントが発生します。

Storage イベントを使う場合、addEventListener()を使用する他、windowオブジェクトに
onstorageプロパティを設定することでイベント発火を拾えます。

具体的には、次のような書き方になります。

addEventListenerを使う場合

window.addEventListener("storage", function(event)
{
    // ここで何らかの処理をする
    
});

onstorageプロパティを使う場合

window.onstorage = function(event)
{
    // ここで何らかの処理をする
    
}

Storage イベントの属性について

上記のaddEventListenerとonstorageプロパティの例では、
処理内容を全く記載していませんが、

functionの引数として渡ってくるイベントオブジェクト「event」に対しては、
次のようなプロパティがあります。

key
newValue
oldValue
storageArea
url

それぞれ、以下の意味があります。

key
 変更されたキーを表す
 clear()が呼び出された場合はnull

newValue
 key の新しい値
 removeItem()が呼び出された場合はnull

oldValue
 key の元の値
 新規登録時はnull

storageArea
 ストレージオブジェクトを取得(localStorage または sessionStorageの使用されているオブジェクト)

url
 keyが変更されたドキュメントのURL(文字列形式)

このStorage イベントを利用すると、同一出身ポリシーで呼び出されたwebアプリケーションで、
別タブや別ウィンドウで表示しているDOM操作を行ったタイミングで、
localStorageやsessionStorageを使いデータを保持し、値が変更されたタイミングでイベントを発火し、
別タブや別ウィンドウになんらかのイベントを発生させて同期を取るアプリケーションにすることができます。

クライアントサイドストレージ – Web Storageについて(その2)

Posted コメントするカテゴリー: javascript

クライアントサイドストレージ – Web Storageについて(その2)

クライアントサイドストレージのWeb Storageについて、補足です。

前回の投稿では、localStorage のサンプルを書きましたが、

ここでは、localStorage と sessionStorage の両方を取り上げて違いを見ていきたいと思います。

前回の投稿では、両者の違いは、「有効期限」「スコープ」と書きましたが、
もう少し具体的に違いをあげていきたいと思います。

localStorage と sessionStorage の違いについて

localStorrageとsessionStorageの違いをわかりやすく比較する為あ、下記の表にまとめました。

localStoragesessionStorage
保存容量5MB
(ブラウザにより5~10MB)
5MB
(ブラウザにより5~10MB)
有効期限永続的
(または削除するまで)
ウィンドウやタブを閉じるまで
(または削除するまで)
タブごとの挙動別タブでも共有されるタブごとに保持。共有されない
スコープ同一出身ポリシーごと同一出身ポリシーごと
スコープ
(別ウィンドウ、別タブ)
別タブ、
別ウィンドウ
からアクセス可能
別タブから可能

大きな違いは「有効期限」ですが、ブラウザのタブごとの挙動の違いも注意が必要です。

ただし、sessionStorageを使用し、別ウィンドウからアクセスした場合は、
アクセス負荷ですが、画面内部にiframeを使用して同一出身ポリシーの画面にアクセスした場合は、
同一出身内のsessionStorageの内容にアクセスすることができます。

クライアントサイドストレージ – Web Storageについて

Posted コメントするカテゴリー: javascript

クライアントサイドストレージ – Web Storageについて

クライアントサイドストレージのWeb Storageについて、実際に試してみます。

前回の投稿でも書きましたが、Web Storageは「localStorage」と「sessionStorage」から構成されています。

両方ともStorageオブジェクトを参照し、Storageオブジェクトは「キーと値の組み合わせ」の連想配列でデータを保持します。

sessionStorageとlocalStorageの違いは、

有効期限
スコープ

が違います。

また、データ保存容量は5MBまでです。(ブラウザにより異なるので注意が必要です)

実際にサンプルのコードを書いて試してみます。
以下のソースを用意しました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="result"></div>

<script type="text/javascript">

// ローカルストレージのキー「test1」に値「テストA」を更新
localStorage.test1 = "テストA";

// ローカルストレージの値(ここでは「test1」を呼び出す)
let test_value = localStorage.test1;

// 値の確認用DOMの取得
let result = document.getElementById("result");

// 画面に出力して確認
result.innerHTML += 'ローカルストレージの値 -> ' + test_value + '<br />';

</script>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

画面にアクセスすると、

と表示され、ローカルストレージに更新した値が確認できます。

また、開発者ツールのApplicationからLocal Storageに保存されているキーと値を確認できます。
上記サンプルにアクセスした場合、以下のように確認できます。

保存の方法は

localStorage.test1 = "テストA";

のように書きましたが、

windows.localStorage.setItem("test1", "テストA");

のように、localStorageのメソッドを使って更新する方法もあります。読み込みは「getItem(“test1”)」です。

ここで、上記のHTML(javascript)とは別のHTMLを作り、そこから localStorage に更新した キー test1 の値を呼び出してみます。

以下のHTMLを用意しました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="result"></div>

<script type="text/javascript">

// ローカルストレージの値(ここでは「test1」を呼び出す)
let test_value = localStorage.test1;

// 値の確認用DOMの取得
let result = document.getElementById("result");

// 画面に出力して確認
result.innerHTML += '別画面で登録したローカルストレージの値 -> ' + test_value + '<br />';

</script>

</body>
</html>

サーバ上のHTMLはこちら(test2.html)

画面にアクセスすると、以下の表示になります。

また、開発者ツールを格納すると、以下の表示になります。

別画面で更新した値を保持していることがわかり、別なウィンドウを開いてアクセスしても同様の画面になり、値が保持されていることがわかります。

上記の動きは「localStorage」と「sessionStorage」両方とも同様の動きをします。

保存が可能なデータについて

「localStorage」と「sessionStorage」では、保存できるデータの種類があります。

上記サンプルのように、単純な文字列の他、オブジェクト、配列、も保存できます。

クライアントサイドストレージ

Posted コメントするカテゴリー: javascript

クライアントサイドストレージ

クライアントサイドストレージについてまとめます。

javascriptは基本的にクライアント側で動作させることが多く(一部例外はあります)、
ブラウザで実行する都度、データを生成したり、処理したり、サーバからデータを取得して出力する
といった動作がほとんどです。

また、ブラウザを再ロードすると、実行途中だったデータはjavascriptのプログラムと
ともにまた初期状態にもどった形になります。

その動作仕様で問題がないプログラムの場合はよいのですが、
処理途中のデータや、javascriptのプログラム動作中の状態を保持しておきたいシーンがでてきます。

その場合に使用する方法がクライアントサイドストレージと呼ばれる、
各マシンごとにデータを保持しておく仕組み(機能)があります。

また、クライアントサイドストレージで保持する機能には下記の種類があります。

・Web Storage
・クッキー
・Offline Web Applications(API)
・Webデータベース
・ファイルシステムAPI

このブログでは、上記のデータ保存方法を一つ一つ実際に動作するHTML+javascritを用意して試していこうと思います。

Web Storage

HTML5で用意されているAPIです。
このAPIはlocalStorageとsessionStorageから構成されています。

構造としては、キーと値を組み合わせたものを保存するものです。

実装する際には、この機能にブラウザが対応しているかを確認する必要があります。

クッキー

かなり歴史のある方法です。
クッキーに保存した情報はテキストデータで格納され、容量も大きくはありません。
また、クッキーはjavascriptだけではなく、サーバサイド言語からも取り扱うことができます。

Offline Web Applications(API)

Offline Web Applicationsは、HTML5で定義されているAPIです。
webぺージ、スクリプト、css、画像、をキャッシュすることができます。

またjavascriptのプログラム自身も保存できるので、オフライン環境で動作するwebアプリケーションとすることもできます。

Webデータベース

この方法はある程度の大きなデータベースを必要とする場合に有効です。

各ブラウザにはIndexedDB APIというAPIが用意されており、
オブジェクト指向データベースとして動作します。

ファイルシステムAPI

File and Directory Entries APIというAPIで、FileSystem上のファイルを読み書きする操作ができます。

javascriptからは任意のファイルシステムエントリのfilesystemプロパティからオブジェクトにアクセスができ、それらを作成、管理するAPIがあります。

Server-Sent Eventsについて

Posted コメントするカテゴリー: javascript

Server-Sent Eventsについて

Server Sent Eventsを使ってサーバ側からクライアント側への通信をする方法があります。

通信時にはEventSorceオブジェクトのインスタンスを生成し、そのコンストラクタに
サーバ側のURLを渡します。

サーバ側では、任意の処理を行い、クライアント側に処理結果を返し、
クライアント側では返されたオブジェクトのmessageイベントを処理します。

messageイベントには、dataプロパティがあり、サーバ側から返された文字列が格納されています。

また、typeプロパティにはデフォルトでmessageが格納されています。
実装の仕方によっては、サーバ側から返されたイベントソースが複数あり、その全てのイベントを受取る方法として
typeプロパティを使うこともあります。

Server-Sent Eventsのプロトコルについて

Server-Sent Eventsのプロトコルについては、HTTP/1.1で動作します。
これは1回のリクエスト&レスポンス(通信)で、1つの送信&受信を行う。と定義されています。

(ちなみにHTTP/2では、一回の通信で、複数の送信&受信をまとめて通信することができます)

EventSorceオブジェクトのインスタンスを生成したときに、クライアントサーバに接続されます。
サーバ側はこの接続をオープン状態のまま維持します。

サーバ側の処理について

サーバ側の処理は、Server-Sent Eventsで接続されているオープン状態の接続に対してテキストを送信します。
その際の「Content-Type」は以下のようにします。

Content-Type: text/event-stream

また、サーバ側では、「データ名:値」のセットになるように文字列を出力します。
データ名は任意で大丈夫ですが、クライアント側と合わせる必要があります。

例えば次のようになります。

data: sample123

テキストが送信されると、クライアント側でイベントが発生して、その内容を受信および処理ができます。

ここまでをふまえて、簡単なHTMLを用意しました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="sse"></div>

<script type="text/javascript">

// データ確認用のdivタグ
let dom_sse = document.getElementById("sse");

// EventSorceオブジェクトのインスタンスを生成
let testsse = new EventSource("server_sent_event.php");

// サーバ側から応答があった時の処理
testsse.onmessage = function(event) {

	// サーバ側から送られた内容を受信し、JSONパースをする
	let recieve_data = JSON.parse(event.data);

	// ログ出力で内容確認
	console.log("recieve_data.data1 -> " + recieve_data.data1);
	console.log("recieve_data.data2 -> " + recieve_data.data2);

	// サーバ側から受信したテキストをDOMへ描画(data1のみ)
	dom_sse.innerHTML += recieve_data.data1 + "<br />";

}

</script>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

サーバ側のphpは次のように書きました。
「Content-Type」を「text/event-stream」にしている点と、
出力するデータの名称を「data:」として、値をjson形式にしている点、
それと、「ob_end_flush();」と「flush();」で、出力方法を制御している点に注意が必要です。

<?php

// server_sent_eventデモ

header("Cache-Control: no-store");
header("Content-Type: text/event-stream");

$sample_data = date("Y/m/d H:i:s");

// 返却用のサンプルデータを用意
$result_arr = array(
	"data1" => $sample_data,
	"data2" => "test1234"
);

echo "data: " . json_encode($result_arr) . "\n\n";

ob_end_flush();
flush();

?>

画面にアクセスして動きをみると、画面上に時刻が表示されることがわかります。
(日時はダミーです)

onmessageのテスト
2022/09/07 21:22:21
2022/09/07 21:22:24
2022/09/07 21:22:27
2022/09/07 21:22:30

また、通信が行われるタイミングは約3秒ごとに発生していますが、
これはどこで制御されているのかは不明なので、改めてわかった時点で内容を更新しようと思います。

JSONPについて

Posted コメントするカテゴリー: javascript

JSONPについて

これまで投稿したサンプルでは、同一出身ポリシーに基づき、
同一サーバ、同一ドメインでクライアントとサーバ側の通信をするサンプルを試しました。

ここでは、クライアントから異なるサーバ(ドメイン)に対しての通信方法を試してみます。

異なるサーバ間の通信はJSONPを使用して行います。

具体的にはクライアントからサーバ側に通信を行う際に、単純にjavascriptの
データや、画面上のフォーム情報を送るのではなく、
データを整形したうえでコールバック関数の形にし、コールバック関数名とデータを送信します。

サーバ側では、受信したデータをもとに、指定の処理を行い、クライアント側に
コールバック関数名と、処理結果をセットで返します。

例えば、送信するデータが下記のようなjson形式のデータの場合

[{1:aaa},{2:bbb},{3:ccc}]

以下のようなコールバック関数名をつけた形にして送信します。
(関数名はあえてわかりやすく書いています)

callbackTestFunction([{1:aaa},{2:bbb},{3:ccc}])

この「callbackTestFunction」と書いたコールバック関数名が、サーバ側からの
値が返却され後のjavascript側で動作する関数名になります。

次にサーバ側の処理ですが、phpで以下のように返却します。

// 返却用のサンプルデータを用意
$result_arr = array(
	"sample_value" => "test1",
	"status" => "ok"
);

// ヘッダ情報を確定
header( 'Content-Type: text/javascript; charset=utf-8' );

// クライアント側にコールバック関数名と共にサンプルデータをjsonエンコードして返却する
echo "callbackTestFunction(" . json_encode($result_arr). ")";

のように書きます。
さらにクライアント側では、サーバ側から返却された値を処理する為に
callbackTestFunction用のコールバック関数を書き、その内部でサーバからの返却値を処理します。

実際に動作するHTMLを用意しました。
javascriptを含むHTML全体のソースです。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<form id="testform" enctype="multipart/form-data">
	<div>
		<input type="text" name="text1" id="text1">
	</div>

	<div>
		<input type="button" id="sample_file_upload" value="JSONPのテスト">
	</div>
</form>

<div>通信時の処理内容</div>
<div id="ajax_result"></div>

<script type="text/javascript">

// ファイルアップロードボタンのDOMを取得
let element_sample_file_upload = document.getElementById('sample_file_upload');

element_sample_file_upload.addEventListener('click',  SendXMLHttpRequest, false);

// 通信処理を画面に出力する為、操作用DOMの取得
let result = document.getElementById("ajax_result");

// ボタン押下時の処理
function SendXMLHttpRequest()
{

	// 処理開始
	result.innerHTML += 'ファイルアップロードのテスト start' + '<br />';
	result.innerHTML += '通信処理の開始' + '<br />';

	// フォーム内容の取得
	let testform_data = document.getElementById("testform");

	// 送信用データ
	// フォーム内容を取得して、jsonpでの通信時のパラメータに付与する
	// デモ画面は入力項目が一つなので、URLのパラメータに直接指定する
	let form_data = new FormData(testform_data);
	let params = "text1=" + form_data.get('text1');

	// パラメータの全体を生成
	let url_params = "?" + params + "&callback=callbackTestFunction";

	// 同一出身ポリシーの制限があるので、スクリプトタグを生成して、外部サーバにアクセスする
	let tag_script = document.createElement("script");

	// ファイルタイプを指定する
	tag_script.type = "text/javascript";

	result.innerHTML += 'url_params -> ' + url_params + '<br />';

	// 実行するサーバとは別のドメインを指定
	tag_script.src = "https://propanlab.net/demo/blogsample/js/078/response.php" + url_params;

	document.body.appendChild(tag_script);

	result.innerHTML += '通信処理の終了' + '<br />';

}

// コールバック関数の処理
let callbackTestFunction = function(json)
{
	console.log(json.sample_value);
	console.log(json.status);
	console.log(json.demp_param);

	// 念のため画面にも出力
	result.innerHTML += 'JSONPで取得した値の確認' + '<br />';
	result.innerHTML += 'json.sample_value -> ' + json.sample_value + '<br />';
	result.innerHTML += 'json.status -> ' + json.status + '<br />';
	result.innerHTML += 'json.demp_param -> ' + json.demp_param + '<br />';

}

</script>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

また、サーバ側のphpは以下のようにし、他ドメインのサーバ側に用意しました。

<?php

// コールバック関数名を取得し、そのまま返却時に使用
$demo_callback = $_GET["callback"];

// パラメータ「text1」の値をそのまま返す(デモ用なので)
$demo_param = $_GET["text1"];

// 返却用のサンプルデータを用意
$result_arr = array(
	"demp_param" => $demo_param,
	"sample_value" => "test1",
	"status" => "ok"
);

// ヘッダ情報を確定(javascript指定にします)
header("Content-Type: text/javascript; charset=utf-8");

// クライアント側にコールバック関数名と共にサンプルデータをjsonエンコードして返却する
echo $demo_callback . "(" . json_encode($result_arr). ")";

?>

実際に画面にアクセスすると、まず次のようになります。

次に、テキストエリアにテスト文字列を入力します。

その後に、「JSONPのテスト」ボタンを押下します。

その際の開発者ツールの通信部分は次のようになります。
別ドメインにあるphpプログラムにアクセスし、結果が返り画面が更新されます。

通信部分のサーバからの返却結果を確認すると次のようになります。

また、画面上のHTMLは次のように更新されます。
javascript側で動的に「script」タグを生成しているので、
htmlに出力した部分が

src="https://propanlab.net/demo/blogsample/js/078/response.php?text1=1234&amp;callback=callbackTestFunction

になり、
scriptタグで別サーバへ通信を行っていることがわかります。

ざっと試しましたが、本来の開発では値のチェックや
イレギュラーケースを考慮して実装する必要があります。

リクエストの中止について

Posted コメントするカテゴリー: javascript

リクエストの中止について

要領の大きいファイルを通信中に、処理を中止したい時があります。
通信処理を途中で中止する方法を試してみます。

通信処理中のHTTPリエクトを中止するには、abort()メソッドを呼び出して中止します。

abort()メソッドを呼び出すと、abortイベントが発生し、通信処理を停止します。

通信処理中のリクエストを中止する方法として、下記のようなHTMLを用意しました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<form id="testform" enctype="multipart/form-data">
	<div>
		<input type="file" name="file1" id="file1">
	</div>
	
	<div>
		<label for="progress">進捗</label>
		<div id="percent"></div>
		<progress id="progress" value="0" max="100"></progress>
	</div>
	
	<div>
		<input type="button" id="sample_file_upload" value="ファイルアップロードのテスト">
	</div>

	<div>
		<input type="button" id="stop_file_upload" value="ファイルアップロードの中止">
	</div>

</form>

<div>通信時の処理内容</div>
<div id="ajax_result"></div>

<script type="text/javascript">

// ファイルアップロード中止ボタンのDOMを取得
let element_stop_file_upload = document.getElementById('stop_file_upload');
// ファイルアップロード中止ボタンを非表示にする
element_stop_file_upload.style.display = 'none';


// ファイルアップロードボタンのDOMを取得
let element_sample_file_upload = document.getElementById('sample_file_upload');

element_sample_file_upload.addEventListener('click',  SendXMLHttpRequest, false);

// ボタン押下時の処理
function SendXMLHttpRequest()
{

	// 通信処理を画面に出力する為、操作用DOMの取得
	let result = document.getElementById("ajax_result");
	result.innerHTML += 'ファイルアップロードのテスト start' + '<br />';
	result.innerHTML += '通信処理の開始' + '<br />';

	// フォーム内容の取得
	let testform_data = document.getElementById("testform");

	// 送信用データ
	let form_data = new FormData(testform_data);

	// 通信用XMLHttpRequestを生成
	let req = new XMLHttpRequest();

	// POST形式でサーバ側の「response.php」へデータ通信を行う
	req.open("POST", "response.php");

	// ファイル通信時の進捗確認用にプログレスバーを更新する
	req.upload.addEventListener("progress", function(e) {

		if (e.lengthComputable) {

			// ファイルアップロード中止ボタンを表示する
			// document.getElementById('stop_file_upload').style.display = 'block';
			element_stop_file_upload.style.display = 'block';

			let progress_value = Math.round(e.loaded / e.total * 100);

			// 進捗%表示用に値を更新する
			document.getElementById('percent').innerHTML = progress_value + "%";
			
			// プログレスバーの値にファイル転送値を代入する
			document.getElementById('progress').value = progress_value;

			// 進捗率が100%になったら終了の処理をする
			if (progress_value == 100) {
				// 通信が完了したらレスポンスをコンソールに出力する
				req.addEventListener('readystatechange', () => {
					// ここでレスポンス結果を制御する
					console.log("レスポンス結果");
				});

				result.innerHTML += '通信処理の終了' + '<br />';
				result.innerHTML += 'レファイルアップロードのテスト end' + '<br />';
			}

		}
	});

	// ファイルが選択されたときに処理を実行するようイベントリスナーに登録
	input_file = document.getElementById("file1");
	input_file.addEventListener('change', function(e) {
		form_data.append('file1', e.target.files[0]);
	});

	// ファイルアップロードの中止ボタン
	element_stop_file_upload.addEventListener('click', function(e) {
		req.abort();
	});
	
	// ファイル送信
	req.send(form_data);

}

</script>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

上記の画面にアクセスし、サイズの大きいファイルを転送中に、
「アップロード中止」のボタンを押すと、転送が中止されます。

ポイントとなる箇所は以下の箇所です。

	// ファイルアップロードの中止ボタン
	element_stop_file_upload.addEventListener('click', function(e) {
		req.abort();
	});

通信用XMLHttpRequestを生成した後、XMLHttpRequestオブジェクトに対してabortメソッドを実行しています。

また、一度停止した通信処理は、またファイル送信処理を実行すると
最初からファイルが転送されます。

レジューム機能を伴ったアップロード方法にするには、転送するファイルに
一意なIDを付与し、そのIDをもとにサーバに転送済のファイル要領を確認し、
未転送バイトからのアップロードをする。といった一連の処理が必要になります。
(今回の投稿ではその方法は試しませんが、別な投稿でまとめるかもしれません)

ファイルアップロード時のプログレスイベント

Posted コメントするカテゴリー: javascript

ファイルアップロード時のプログレスイベント

前回投稿したファイルアップロード処理について、ファイル容量が大きい場合に
アップロードの状態を把握する為にプログラムイベントを使って、現在何パーセントの状態かを取得する方法を試します。

アップロード部分の処理は、前回までと同様の処理を書きますが、
ファイルの送信状況を把握する為のイベントとして、次のイベントを使います。

仕組みは、送信処理時(sendした時)にloadstartイベントが発火され、その後にprogressイベントが発生します。
このprogressイベントを使うことにより、ファイルの送信状況を画面に表示することができます。

また、通信の終わりには、XMLHttpRequestオブジェクトのstatusコードを判定し、
値が200の場合には正常に終了した。という送信状態を把握します。

通信時のエラーチェックについて

通信時になんらかの異常があってエラーになる場合があります。
一つはリクエストがタイムアウトした場合、二つ目はリクエストが中止された場合、三つ目はネットワークエラー等でリクエストが完了できない場合、です。

loadstartとloadendについて

さきほど「送信処理時(sendした時)にloadstartイベントが発火され」と書きましたが、
通信処理が終了した際には、ブラウザがload、abort、timeout、errorのイベントを発生させます。

この際、XHR2仕様ではloadendイベントが発生すると定義づけられています。
また、このイベントは、通信処理が成功しても失敗しても発生します。

通信時のXMLHttpRequestオブジェクトについて

通信時のXMLHttpRequestオブジェクトにイベントをaddEventListenerでメソッドを付与し、
プログレスイベント「ProgressEvent(インターフェイス)」にハンドラを登録することができます。
このハンドラに対し、onprogressやonload等のプロパティを設定する方法があります。
このプロパティにはtotal、lengthComputable、loaded、等のプロパティがあります。

それぞれ次のような意味です。

lengthComputable
通信時の進捗が測定可能かどうかを返す(true / false)

loaded
現時点までに転送されたバイト数(64ビット符号なし整数値)が返されます

total
転送するデータの総容量が格納されています。不明な場合は0です。

これらのプロパティを使用し、ファイル転送中の進捗状況を把握することができます。

アップロード処理の実例

実際に送信状態を把握するアップロード処理を確認してみます。

ファイルをアップロードする際、uploadプロパティが使用できます。
また、ProgressEventのプロパティとして、onprogressやonload等のプロパティも定義されています。

下記のようなHTMLを用意しました。
サーバ側のプログラムは、前回と同様の為ここでは記載を省きます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<form id="testform" enctype="multipart/form-data">
	<div>
		<input type="file" name="file1" id="file1">
	</div>
	
	<div>
		<label for="progress">進捗</label>
		<div id="percent"></div>
		<progress id="progress" value="0" max="100"></progress>
	</div>
	
	<div>
		<input type="button" id="sample_file_upload" value="ファイルアップロードのテスト">
	</div>
</form>

<div>通信時の処理内容</div>
<div id="ajax_result"></div>

<script type="text/javascript">

// ファイルアップロードボタンのDOMを取得
let element_sample_file_upload = document.getElementById('sample_file_upload');

element_sample_file_upload.addEventListener('click',  SendXMLHttpRequest, false);

// ボタン押下時の処理
function SendXMLHttpRequest()
{

	// 通信処理を画面に出力する為、操作用DOMの取得
	let result = document.getElementById("ajax_result");
	result.innerHTML += 'ファイルアップロードのテスト start' + '<br />';
	result.innerHTML += '通信処理の開始' + '<br />';

	// フォーム内容の取得
	let testform_data = document.getElementById("testform");

	// 送信用データ
	let form_data = new FormData(testform_data);

	// 通信用XMLHttpRequestを生成
	let req = new XMLHttpRequest();

	// POST形式でサーバ側の「response.php」へデータ通信を行う
	req.open("POST", "response.php");

	// ファイル通信時の進捗確認用にプログレスバーを更新する
	req.upload.addEventListener("progress", function(e) {

		if (e.lengthComputable) {

			let progress_value = Math.round(e.loaded / e.total * 100);

			// 進捗%表示用に値を更新する
			document.getElementById('percent').innerHTML = progress_value + "%";
			
			// プログレスバーの値にファイル転送値を代入する
			document.getElementById('progress').value = progress_value;

			// 進捗率が100%になったら終了の処理をする
			if (progress_value == 100) {
				// 通信が完了したらレスポンスをコンソールに出力する
				req.addEventListener('readystatechange', () => {
					// ここでレスポンス結果を制御する
					console.log("レスポンス結果");
				});

				result.innerHTML += '通信処理の終了' + '<br />';
				result.innerHTML += 'レファイルアップロードのテスト end' + '<br />';
			}

		}
	});

	// ファイルが選択されたときに処理を実行するようイベントリスナーに登録
	input_file = document.getElementById("file1");
	input_file.addEventListener('change', function(e) {
		form_data.append('file1', e.target.files[0]);
	});

	// ファイル送信
	req.send(form_data);

}

</script>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

画面にアクセスすると、まず次のような画面が表示されます。

次に、ファイルを選択します。

「ファイルアップロードのテスト」のボタンを押すと、ファイルのアップロードが開始されます。

この時、アップロードの進捗状態に応じて、プログレスバーの値が更新されていきます。
(同時に画面に進捗率を表示しています)

ファイルのアップロードが完了すると、プログレスバーは100%になり、処理が終わります。
実際には、100%になった後に、ユーザにはなんらかのアクションを求めるので、
継続的な操作をするUIにしたほうが、わかりやすく親切です。

アップロード処理のポイントと注意点

アップロード処理をする際のポイントとなる点は、

	// 通信用XMLHttpRequestを生成
	let req = new XMLHttpRequest();

で、XMLHttpRequestオブジェクト(ここでいうインスタンスのreqです)を生成した後、
reqに対して、progressイベントをaddEventListenerしている箇所です。

	// ファイル通信時の進捗確認用にプログレスバーを更新する
	req.upload.addEventListener("progress", function(e) {

		if (e.lengthComputable) {

			let progress_value = Math.round(e.loaded / e.total * 100);

			// 進捗%表示用に値を更新する
			document.getElementById('percent').innerHTML = progress_value + "%";
			
			// プログレスバーの値にファイル転送値を代入する
			document.getElementById('progress').value = progress_value;

		}
	});

それ以外は、前回ファイルのアップロードを投稿したサンプルと同じ処理にしています。

また、注意点として、実際に大きい容量のファイルをアップロードして試してみるとわかりますが
サーバ側で受信できない容量サイズのファイルは、javascript側でアップロード完了までの
プログレスバーの表示になりますが、サーバ側でファイル受信(容量制限)ができていないケースがあります。

HTML+css+javascriptの作りとして、見かけ上は処理が止まっていないので、
ファイルがアップロード成功したかのように見えますが、サーバサイドで正常にファイルアップロードが
成功したかどうかは、サーバ側プログラムで判断し、容量オーバやなんからのエラーだった場合には
クライアント(javascript)側にエラーのレスポンスを返す必要があります。

XMLHttpRequestを使ったファイルアップロード(multipart/form-data)

Posted コメントするカテゴリー: javascript

XMLHttpRequestを使ったファイルアップロード(multipart/form-data)

前回の投稿ではXMLHttpRequestを使ったファイルアップロードを試しました。
その際、formの属性に「enctype=”multipart/form-data”」を記述しない形で試しました。

では、multipart/form-dataを記述する場合はどのような場合なのかを
今回は試してみようと思います。

multipart/form-dataを付与する必要がある場合というのは、
フォームの項目が、ファイルの選択と、それ以外のテキスト入力や、
チェックボックス、ラジオボタン、セレクトボックス、等の通常よく使われるフォーム項目がある場合、
それらとファイル送信を併用してサーバに通信する時です。

multipart/form-dataを記載することにより、サーバ側でファイル情報の他、
その他フォーム情報を受信して処理することができます。

前回の投稿を少し変更して、下記のようなHTMLを用意しました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<form id="testform" enctype="multipart/form-data">
	<div>
		<input type="text" name="text1" id="text1">
	</div>
	<div>
		<input type="text" name="text2" id="text2">
	</div>
	<div>
		<input type="text" name="text3" id="text3">
	</div>
	<div>
		<input type="file" name="file1" id="file1">
	</div>
	<div>
		<input type="button" id="sample_file_upload" value="ファイルアップロードのテスト">
	</div>
</form>

<div>通信時の処理内容</div>
<div id="ajax_result"></div>

<script type="text/javascript">

// ファイルアップロードボタンのDOMを取得
let element_sample_file_upload = document.getElementById('sample_file_upload');

element_sample_file_upload.addEventListener('click',  SendXMLHttpRequest, false);

// ボタン押下時の処理
function SendXMLHttpRequest()
{

	// 通信処理を画面に出力する為、操作用DOMの取得
	let result = document.getElementById("ajax_result");
	result.innerHTML += 'ファイルアップロードのテスト start' + '<br />';
	result.innerHTML += '通信処理の開始' + '<br />';

	// フォーム内容の取得
	let testform_data = document.getElementById("testform");

	// 送信用データ
	let form_data = new FormData(testform_data);

	// 通信用XMLHttpRequestを生成
	let req = new XMLHttpRequest();

	// POST形式でサーバ側の「response.php」へデータ通信を行う
	req.open("POST", "response.php");

	// ファイルが選択されたときに処理を実行するようイベントリスナーに登録
	input_file = document.getElementById("file1");
	input_file.addEventListener('change', function(e) {
		form_data.append('file1', e.target.files[0]);
	});

	// ファイル送信
	req.send(form_data);

	// 通信が完了したらレスポンスをコンソールに出力する
	req.addEventListener('readystatechange', () => {
		// ここでレスポンス結果を制御する
		console.log("レスポンス結果");
	});

	result.innerHTML += '通信処理の終了' + '<br />';
	result.innerHTML += 'レファイルアップロードのテスト end' + '<br />';
}

</script>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

また、サーバ側のプログラムは、前回同様、下記のphpを用意しました。

<?php

// ファイルのアップロード処理
if (is_uploaded_file($_FILES["file1"]["tmp_name"])) {
	if (move_uploaded_file($_FILES["file1"]["tmp_name"], "./datas/" . $_FILES["file1"]["name"])) {
		chmod($_FILES["file1"]["name"], 0644);
		$ret = true;
		$message = $_FILES["file1"]["name"] . "をアップロードしました。";
	} else {
		$ret = false;
		$message = "ファイルをアップロードできません。";
	}
} else {
	$ret = false;
	$message = "ファイルが選択されていません。";
}

// 処理結果をjson形式用の形にまとめる
$json_value = array(
	"ret" => $ret,
	"message" => $message
);

// ヘッダーの指定と返却値をjsonで返す
header("Content-Type: application/json; charset=UTF-8");
header("X-Content-Type-Options: nosniff");
echo json_encode($json_value, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP);

?>

上記の記述はファイルの受信しか行っていませんが、

実際には、$_FILES の HTTPファイルアップロード変数の他、
$_REQUESTの HTTPリクエスト変数にも「text1」「text2」「text3」の変数の値が渡ってきていることがわかります。

XMLHttpRequestを使ったファイルアップロード

Posted コメントするカテゴリー: javascript

XMLHttpRequestを使ったファイルアップロード

XMLHttpRequestを使ったファイルアップロードを試してみます。

HTML側のファイル参照ボタンは

<input type="file" name="file1">

のように書くものとします。
(場合により、このファイル参照用ボタンにはidやclassを指定して使います)

また、XMLHttpRequestには仕様策定のバージョンがあり、XMLHttpRequest の Level2 の
sendメソッドを使用するとファイルのアップロードができます。

ユーザがブラウザから

<input type="file" name="file1">

を使って選択したデータにはファイル情報が格納されます。

具体的には、参照ボタンからファイルを選択した結果としてFileListオブジェクトを取得し、
また、ドラッグ&ドロップ操作時にはDataTransferオブジェクトのfilesプロパティを使って取得できます。

通常のHTML+php等でファイルアップロードをする場合は、
formに「 enctype=”multipart/form-data”」の属性を記述しますが、
今回試したサンプルでは、上記の属性を記述していません。

簡単なサンプルを書いて試してみます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<form id="testform">
	<div>
		<input type="file" name="file1" id="file1">
	</div>
	<div>
		<input type="button" id="sample_file_upload" value="ファイルアップロードのテスト">
	</div>
</form>

<div>通信時の処理内容</div>
<div id="ajax_result"></div>

<script type="text/javascript">

// ファイルアップロードボタンのDOMを取得
let element_sample_file_upload = document.getElementById('sample_file_upload');

// イベントを付与
element_sample_file_upload.addEventListener('click',  SendXMLHttpRequest, false);

// ボタン押下時の処理
function SendXMLHttpRequest()
{

	// 通信処理を画面に出力する為、操作用DOMの取得
	let result = document.getElementById("ajax_result");
	result.innerHTML += 'ファイルアップロードのテスト start' + '<br />';
	result.innerHTML += '通信処理の開始' + '<br />';

	// フォーム内容の取得
	let testform_data = document.getElementById("testform");

	// 送信用データ
	let form_data = new FormData(testform_data);

	// 通信用XMLHttpRequestを生成
	let req = new XMLHttpRequest();

	// POST形式でサーバ側の「response.php」へデータ通信を行う
	req.open("POST", "response.php");

	// ファイルが選択されたときに処理を実行するようイベントリスナーに登録
	input_file = document.getElementById("file1");
	input_file.addEventListener('change', function(e) {
		form_data.append('file1', e.target.files[0]);
	});

	// ファイル送信
	req.send(form_data);

	// 通信が完了したらレスポンスをコンソールに出力する
	req.addEventListener('readystatechange', () => {
		// ここでレスポンス結果を制御する
		console.log("レスポンス結果");
	});

	result.innerHTML += '通信処理の終了' + '<br />';
	result.innerHTML += 'レファイルアップロードのテスト end' + '<br />';
}

</script>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

このように書くことで、選択したファイルをサーバ側へ送信することができます。
ファイルをアップロード後のサーバサイドでは、ファイルを受信するプログラムを用意します。

言語はなんでもいいのですが、上記のサンプルを動作させる為にphpで下記のように書きました。

<?php

// ファイルのアップロード処理
if (is_uploaded_file($_FILES["file1"]["tmp_name"])) {
	if (move_uploaded_file($_FILES["file1"]["tmp_name"], "./datas/" . $_FILES["file1"]["name"])) {
		chmod($_FILES["file1"]["name"], 0644);
		$ret = true;
		$message = $_FILES["file1"]["name"] . "をアップロードしました。";
	} else {
		$ret = false;
		$message = "ファイルをアップロードできません。";
	}
} else {
	$ret = false;
	$message = "ファイルが選択されていません。";
}

// 処理結果をjson形式用の形にまとめる
$json_value = array(
	"ret" => $ret,
	"message" => $message
);

// ヘッダーの指定と返却値をjsonで返す
header("Content-Type: application/json; charset=UTF-8");
header("X-Content-Type-Options: nosniff");
echo json_encode($json_value, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP);

?>

実際に試してみると、選択したファイルがサーバにアップロードされることがわかります。

また、ファイルアップロード後のレスポンス結果を処理する部分は、何も処理を書いていません。

javascriptのXMLHttpRequestを使ってファイルを送信する方法を試した程度なので、
実際の開発では、レスポンス結果を制御して実装する必要があります。

リクエストボディ の XMLエンコード について

Posted コメントするカテゴリー: javascript

リクエストボディ の XMLエンコード について

前回の投稿は下記のJSON形式のデータ

{
	test1: "aaaa",
	test2: "bbbb",
	test3: "cccc"
}

という「キーと値」のセットをサーバ側へ送信し、通信の確認しました。

今回はJSON形式を、XML形式にしてサーバへ送信する方法を試してみます。
具体的には、上記の値を次の形のXMLデータに置き換えて送信します。

<data_p>
	<data_c id="test1" sample="s1">
		sample_value1
	</data_c>
	<data_c id="test2" sample="s2">
		sample_value2
	</data_c>
	<data_c id="test3" sample="s3">
		sample_value3
	</data_c>
</data_p>

javascript内で上記のXML形式のデータ(この場合、要素が3つのデータ)を、サーバ側に送信するには
xml形式の文字列データを、文字列からDOMのDocumentに変換しています。(下記の箇所です)

// DOMのDocumentに変換する
let ObjParser = new DOMParser();
let xml_datas  = ObjParser.parseFromString(send_xml_strings, "text/xml");

Content-Typeはこの例では省略しています。
ブラウザ側で適切な設定がされる為、必須ではありません。

また、明示的に下記のMIMEタイプを設定して試してみましたが、いずれも動作結果は同じした。

req.setRequestHeader("Content-Type", "application/json");

req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");

req.setRequestHeader("Content-Type", "application/xml");

処理の最後では、sendメソッドでデータ送信をしています。

全体の処理がわかるように、以下のサンプルを用意しました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<input type="button" id="sampleXMLHttpRequest" value="レスポンスボディ(JSONエンコード)のテスト">

<div>通信時の処理内容</div>
<div id="ajax_result"></div>

<script type="text/javascript">

// ボタン要素のDOMを取得
let element_sampleXMLHttpRequest = document.getElementById('sampleXMLHttpRequest');

// イベントを付与
element_sampleXMLHttpRequest.addEventListener('click',  SendXMLHttpRequest, false);

// ボタン押下時の処理
function SendXMLHttpRequest()
{

	// ----------------------------
	// 通信処理用のxmlデータを生成
	// ----------------------------
	// 今回のサンプル用のXML形式のデータを以下のように定義
	let send_xml_strings = '<data_p>';
		send_xml_strings += '<data_c id="test1" sample="s1">sample_value1</data_c>';
		send_xml_strings += '<data_c id="test2" sample="s2">sample_value2</data_c>';
		send_xml_strings += '<data_c id="test3" sample="s3">sample_value3</data_c>';
		send_xml_strings += '</data_p>';

	// DOMのDocumentに変換する
	let ObjParser = new DOMParser();
	let xml_datas  = ObjParser.parseFromString(send_xml_strings, "text/xml");

	// 通信処理を画面に出力する為、操作用DOMの取得
	let result = document.getElementById("ajax_result");

	// 検証の為、画面出力とログ出力をする
	result.innerHTML += 'レスポンスボディのテスト start' + '<br />';


	// ----------------------------
	// ajax通信処理の開始
	// ----------------------------
	let req = new XMLHttpRequest();

	// POST形式でサーバ側の「response.php」へデータ通信を行う
	req.open("POST", "./response.php");

	result.innerHTML += 'xml_datas -> ' + xml_datas + '<br />';
	result.innerHTML += '通信処理の開始' + '<br />';

	// ここまでで整理した送信用データを、サーバ側へ送信する
	req.send(xml_datas);

	// 検証の為、画面出力とログ出力をする
	result.innerHTML += '通信処理の終了' + '<br />';
	result.innerHTML += 'レスポンスボディのテスト end' + '<br />';

}

</script>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

サーバ側(php側)では、以下のプログラムを用意し、結果をログ出力して確かめています。

<?php

// xml形式のまま取得
$xml = file_get_contents("php://input");

// オブジェクトに変換する場合
$obj = simplexml_load_string($xml);

// オブジェクトをjsonエンコードする
$array_encord = json_encode($obj);

// 連想配列に変換する場合
$arrays = json_decode($array_encord, true);

file_put_contents('./log_xml.log', print_r($xml, true));
file_put_contents('./log_obj.log', print_r($obj, true));
file_put_contents('./log_array_encord.log', print_r($array_encord, true));
file_put_contents('./log_arrays.log', print_r($arrays, true));

?>

実際に画面にアクセスしてボタンを押下すると、send_data で送ったデータが
下記の形でログ出力されていることがわかります。

ボタンを押下し、通信処理が終わった後の、ブラウザ上の動作確認用の表示は次のようになります。

出力された内容を確認すると
「xml_datas -> [object XMLDocument]」
という形でXMLDocumentのオブジェクトとして送信を行っていることがわかります。

また、サーバ側のログ出力する内容は4通りの出力を試してみましたので、
以下、全て記載します。

log_xml.log (実際には改行されず、1行で出力)

<data_p>
<data_c id="test1" sample="s1">sample_value1</data_c>
<data_c id="test2" sample="s2">sample_value2</data_c>
<data_c id="test3" sample="s3">sample_value3</data_c>
</data_p>

log_obj.log

SimpleXMLElement Object
(
    [data_c] => Array
        (
            [0] => sample_value1
            [1] => sample_value2
            [2] => sample_value3
        )

)

log_array_encord.log

{"data_c":["sample_value1","sample_value2","sample_value3"]}

log_arrays.log

Array
(
    [data_c] => Array
        (
            [0] => sample_value1
            [1] => sample_value2
            [2] => sample_value3
        )

)

リクエストボディ の JSONエンコード について

Posted コメントするカテゴリー: javascript

リクエストボディ の JSONエンコード について

前回の投稿は

{
	test1: "aaaa",
	test2: "bbbb",
	test3: "cccc"
}

という「キーと値」のセットを3つ用意し、サーバ側へ通信を行いました。

javascriptとブラウザの歴史的理由により、この「キーと値」の形をJSON形式にして送信する方法が多用されています。
前回のサンプルコードをjson形式で送信、受信できるかどうか試してみます。

以下のサンプルを用意しました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div>MIMEタイプが「application/json」の場合</div>
<input type="button" id="sampleXMLHttpRequest" value="レスポンスボディ(JSONエンコード)のテスト">

<div>通信時の処理内容</div>
<div id="ajax_result"></div>

<script type="text/javascript">

// ボタン要素のDOMを取得
let element_sampleXMLHttpRequest = document.getElementById('sampleXMLHttpRequest');

// イベントを付与
element_sampleXMLHttpRequest.addEventListener('click',  SendXMLHttpRequest, false);

// ボタン押下時の処理
function SendXMLHttpRequest()
{

	// 今回のサンプル用の送信データを以下のように定義
	// 実際の実装シーンでは、画面上にフォームを設置し、入力された値等を取得する
	let send_data = {
		"test1": "123",
		"test2": "aaa",
		"test3": "テスト"
	}

	// 通信処理を画面に出力する為、DOM操作
	let result = document.getElementById("ajax_result");

	// 検証の為、画面出力とログ出力をする
	result.innerHTML += 'レスポンスボディのテスト start' + '<br />';

	// ajax通信処理の開始
	let req = new XMLHttpRequest();

	// POST形式でサーバ側の「response.php」へデータ通信を行う
	req.open("POST", "./response.php");

	// リクエストヘッダにMIMEタイプを設定する
	// ここではJSON形式とする
	req.setRequestHeader("Content-Type", "application/json");

	// サーバ送信用の値をJSON形式として送信する
	let send_params = JSON.stringify(send_data);

	result.innerHTML += 'サーバ送信用の配列を&区切りにして文字列にし、send_params 変数へ代入する' + '<br />';
	result.innerHTML += 'send_params -> ' + send_params + '<br />';
	result.innerHTML += '通信処理の開始' + '<br />';

	// ここまでで整理した送信用データを、サーバ側へ送信する
	req.send(send_params);

	// 検証の為、画面出力とログ出力をする
	result.innerHTML += '通信処理の終了' + '<br />';
	result.innerHTML += 'レスポンスボディのテスト end' + '<br />';

}

</script>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

javascriptからjson形式でサーバにデータを渡す際の注意点として
Content-Type を application/json にする場合は、サーバ側の受取り方法が変わる点です。

サーバ側(php側)では、次のように取得しないと、データが受信できません。

<?php

$input_json = json_decode(file_get_contents("php://input"), true);

// 確認の為、ログ出力をする
file_put_contents('./response.log', print_r($input_json, true));

?>

実際に画面にアクセスしてボタンを押下すると、send_data で送ったデータが
下記の形でログ出力されていることがわかります。

Array
(
    [test1] => 123
    [test2] => aaa
    [test3] => テスト
)

XMLHttpRequest リクエストボディについて

Posted コメントするカテゴリー: javascript

XMLHttpRequest リクエストボディについて

リクエストボディについて調べてみます。
AJAX通信時にPOSTリクエストする際、クライアント側からサーバ側への通信内容にリクエストボディが含まれます。
リクエストボティは次の種類があります。

フォームエンコード(HTMLフォーム)

JSON

XML

ファイルアップロード

multipart/form-data

実際にサンプルコードを用意しつつ、一つ一つ動きを見てみます。

リクエストボディ – フォームエンコード(HTMLフォーム) について

ここではPOST形式での送信時に、フォームエンコードがどのように通信されるのかをみてみます。

一般的なHTMLフォームでは、テキストボックスや、チェックボックス、セレクトボックス等が
設置され、そのフォームの内容をサーバへ送信することで、サーバ側の処理に値を渡すことができます。

AJAX通信時にも同様に、HTMLフォームの値を「キーと値」の組み合わせで、サーバ側に送信します。
また、リクエスト時のデータは文字列形式にエンコードしてサーバ側に送信します。

HTMLフォームの要素が複数の場合、例えば要素1、要素2、要素3がある場合、
要素1のフォームの値を「キーと値」のペアとして、次に要素2、要素3の「キーと値」を&で連結してサーバ側に送信されます。

また、送信時に「application/x-www-form-urlencoded」MIMEタイプを、POSTするときのContent-Typeのリクエストヘッダに設定します。

例えば、サーバ側に以下のデータを送信することを考えます。

{
	test1: "aaaa",
	test2: "bbbb",
	test3: "cccc"
}

上記の値をサーバ側に送信する際、encodeURIComponentしたうえで「キーと値」のセットにし、
それぞれの要素(ここではtest1、test2、test3の3つ)を&で連結します。

ここまでの内容を踏まえて、簡単なサンプルを書いてみます。

クライアント側からサーバ側へのフォーム内容の送信方法は3通りありますが、
ここでは原始的な方法を試してみます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<input type="button" id="sampleXMLHttpRequest" value="レスポンスボディのテスト">

<div>通信時の処理内容</div>
<div id="ajax_result"></div>

<script type="text/javascript">

// ボタン要素のDOMを取得
let element_sampleXMLHttpRequest = document.getElementById('sampleXMLHttpRequest');

// イベントを付与
element_sampleXMLHttpRequest.addEventListener('click',  SendXMLHttpRequest, false);

// ボタン押下時の処理
function SendXMLHttpRequest()
{

	// 今回のサンプル用の送信データを以下のように定義
	// 実際の実装シーンでは、画面上にフォームを設置し、入力された値等を取得する
	let send_data = {
		test1: "123",
		test2: "aaa",
		test3: "テスト"
	}

	// 通信処理を画面に出力する為、DOM操作
	let result = document.getElementById("ajax_result");

	// 検証の為、画面出力とログ出力をする
	result.innerHTML += 'レスポンスボディのテスト start' + '<br />';

	// ajax通信処理の開始
	let req = new XMLHttpRequest();

	// POST形式でサーバ側の「response.php」へデータ通信を行う
	req.open("POST", "./response.php");

	// リクエストヘッダにMIMEタイプを設定する
	req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");

	// サーバ側への送信用の配列を用意する
	let urlPairs = [];

	// デモ用のsend_dataオブジェクトを、キーと値のペアにし、
	// 全ての要素を「+」で連結する
	for (key_in in send_data) {

		// キーと値をそれぞれエンコードをして取得する
		let p_key = encodeURIComponent(key_in);
		let p_value = encodeURIComponent(send_data[key_in])

		// キーと値のペアを生成し、サーバ送信用の配列へ格納する
		urlPairs.push(p_key + '=' + p_value);
	}

	// サーバ送信用の配列を&区切りにして文字列にし、send_params 変数へ代入する
	let send_params = urlPairs.join('&');

	result.innerHTML += 'サーバ送信用の配列を&区切りにして文字列にし、send_params 変数へ代入する' + '<br />';
	result.innerHTML += 'send_params -> ' + send_params + '<br />';
	result.innerHTML += '通信処理の開始' + '<br />';

	// ここまでで整理した送信用データを、サーバ側へ送信する
	req.send(send_params);

	// 検証の為、画面出力とログ出力をする
	result.innerHTML += '通信処理の終了' + '<br />';
	result.innerHTML += 'レスポンスボディのテスト end' + '<br />';

}

</script>

</body>
</html>

また、サーバ側のphpはシンプルにリクエスト変数をログファイルに出力するだけの処理にしています。
(ここではjavascript側のレスポンスまでは考慮していません)

<?php

file_put_contents('./response.log', print_r($_REQUEST, true));

?>

サーバ上のHTMLはこちら(test1.html)

画面にアクセスして「レスポンスボディのテスト」のボタンを押下すると、
出力結果は次のようになります。

通信時の処理内容
レスポンスボディのテスト start
サーバ送信用の配列を&区切りにして文字列にし、send_params 変数へ代入する
send_params -> test1=123&test2=aaa&test3=%E3%83%86%E3%82%B9%E3%83%88
通信処理の開始
通信処理の終了
レスポンスボディのテスト end

サーバ側へ送信する値は最初に

{
	test1: "aaaa",
	test2: "bbbb",
	test3: "cccc"
}

という形で用意していましたが、サーバへ通信する際には

send_params -> test1=123&test2=aaa&test3=%E3%83%86%E3%82%B9%E3%83%88

という形にエンコードしたうえで送信していることがわかります。
この時、半角英数時の「123」と「aaa」はエンコード処理されても形は変わらないですが、
日本語文字の「テスト」は

test3=%E3%83%86%E3%82%B9%E3%83%88

という値になっていることがわかります。

また、サーバ側のphpが出力したログファイルは以下のように出力されており、
問題なくデータが受信できていることがわかります。

Array
(
    [test1] => 123
    [test2] => aaa
    [test3] => テスト
)

XMLHttpRequest レスポンスの解釈について

Posted コメントするカテゴリー: javascript

XMLHttpRequest レスポンスの解釈について

XMLHttpRequestを使ったAJAX通信の動きを試していますが、
レスポンス時の処理について、もう少し詳しく調べてみます。

前回の投稿では、サーバ側からのレスポンスの内容を下記の形で取得していました。

// responseレスポンス内容を出力
result.innerHTML += 'レスポンス内容の表示 -> ' + req.response + '<br />';

XMLHttpRequestオブジェクトの response プロパティを使っていますが、
このプロパティ以外にもレスポンスを受取ることができます。
レスポンスの形式の一例として次のものがあります。

文字列 (text)
json (json形式)
配列 (arraybuffer)
バイナリデータ (blob)
ドキュメント (xmlドキュメント)
null

受信時には、上記のデータをjsonで受取るケースが多く、その場合には受信データをパースしてクライアント側で使用します。

また、受信時にはサーバ側からの Content-Type(レスポンスヘッダ) を基にどんなデータなのかの判断をすることもあります。
ただし、実際に受信したデータと、サーバ側から返される Content-Type(レスポンスヘッダ)のデータが保証されているわけではなく、
サーバ側で Content-Type(レスポンスヘッダ) を意図的に実データと異なるものを指定することもできる為、
アプリケーションの設計、実装時に十分な注意が必要になります。

XMLHttpRequest 同期 / 非同期について(注意点あり)

Posted コメントするカテゴリー: javascript

XMLHttpRequest 同期 / 非同期について

前回までの投稿で、XMLHttpRequestを使ったAJAX通信の動きを試してみました。
通常、通信の処理には、同期と非同期という考え方があります。

同期はクライアント側からサーバに対して通信をしている間、リクエストに対してのレスポンスを待ち、
レスポンスが返ってきてからクライアント側の処理を続行します。

また、非同期はクライアント側からサーバに対して通信を行うと、
その結果(レスポンス)を待たず、クライアント側の処理を続行します。(レスポンスはサーバ側の処理が終わり次第に返ってくるイメージです)

では、前回に書いたサンプルソースは同期か非同期のどちらになっているかを確かめます。

クライアント側のjavascriptは、前回に投稿したサンプルと概ね同じですが、
req.send()メソッドの前に、非同期中の動きを確認する文字列を出力するように変更しています。

	result.innerHTML += '同期 / 非同期の確認' + '<br />';
	console.log('同期 / 非同期の確認');

	req.send();

また、サーバ側のphpは、時間のかかる処理を行うものとし、「sleep(2);」という2秒のスリープ時間を入れました。
サンプルの全体は以下のように書いています。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<input type="button" id="sampleXMLHttpRequest" value="レスポンスのテスト">

<div>通信時の処理内容</div>
<div id="ajax_result"></div>

<script type="text/javascript">

// ボタン要素のDOMを取得
let element_sampleXMLHttpRequest = document.getElementById('sampleXMLHttpRequest');

// イベントを付与
element_sampleXMLHttpRequest.addEventListener('click',  SendXMLHttpRequest, false);

// ボタン押下時の処理
function SendXMLHttpRequest()
{

	// 通信処理を画面に出力する為、DOM操作
	let result = document.getElementById("ajax_result");

	// ajax通信処理
	let req = new XMLHttpRequest();

	req.open("GET", "./response.php");

	req.onreadystatechange = function() {

		// 検証の為、画面出力とログ出力をする
		result.innerHTML += 'onreadystatechange start' + '<br />';
		result.innerHTML += 'req.readyState -> ' + req.readyState + '<br />';
		result.innerHTML += 'req.status -> ' + req.status + '<br />';

		console.log('req.readyState -> ' + req.readyState);
		console.log('req.status -> ' + req.status);
		
		// レスポンスの状態 (req.readyState) を判定する
		if (req.readyState == 4) {
			result.innerHTML += 'レスポンスは正常 (readyState)' + '<br />';
			console.log('レスポンスは正常 (readyState)');
		} else {
			result.innerHTML += 'レスポンスは異常の為、処理終了(readyState) ' + '<br />';
			console.log('レスポンスは異常の為、処理終了(readyState) ');
			return false;
		}
		
		// レスポンスの状態 (req.status) を判定する
		if (req.status == 200) {
			result.innerHTML += 'レスポンスは正常 (status)' + '<br />';
			console.log('レスポンスは正常 (status)');
		} else {
			result.innerHTML += 'レスポンスは異常の為、処理終了(status) ' + '<br />';
			console.log('レスポンスは異常の為、処理終了(status) ');
			return false;
		}
		
		// getResponseHeaderを取得する
		let content_type = req.getResponseHeader("Content-Type");
		result.innerHTML += 'content_type -> ' + content_type + '<br />';
		console.log('content_type -> ' + content_type);
		
		// レスポンスの状態 (content_type) を判定する
		// ここでは検証の為、強制的に「text/html; charset=UTF-8」の文字列で判定する	
		if (content_type == "text/html; charset=UTF-8") {
			result.innerHTML += 'レスポンスは正常 (content_type)' + '<br />';
			console.log('レスポンスは正常 (content_type)');
		} else {
			result.innerHTML += 'レスポンスは異常の為、処理終了(content_type) ' + '<br />';
			console.log('レスポンスは異常の為、処理終了(content_type) ');
			return false;
		}
		
		// responseレスポンス内容を出力
		result.innerHTML += 'レスポンス内容の表示 -> ' + req.response + '<br />';

	};

	result.innerHTML += '同期 / 非同期の確認' + '<br />';
	console.log('同期 / 非同期の確認');

	req.send();
}

</script>

</body>
</html>

サーバ側の response.php では、2秒のスリープを追記。

<?php

sleep(2);

echo "response_test_123";

?>

サーバ上のHTMLはこちら(test1.html)

実際にアクセスして、画面上の「レスポンスのテスト」ボタンを押下すると、
まず、画面上に「同期 / 非同期の確認」が表示されます。

その後に、サーバ側の処理が約2秒後に終わり、レスポンスが返りクライアント側に結果が渡され、
画面上にレスポンス結果の文字列が表示されます。

このことから、前回のサンプルプログラムは、サーバ側の処理を待たずに通信後に
javascriptの処理を続行する「非同期」で処理されていることがわかります。

XMLHttpRequest 同期について

では次に、上記のサンプルのソースを調整して、同期通信を試してみます。

同期通信にする為には、openメソッドの第三引数にfalseを設定します。

	// 第三引数にfalseを指定することで、同期で実行する
	req.open("GET", "./response.php", false);

上記のように書くことで、クライアント側からサーバ側へ通信し、
サーバ側での処理結果(レスポンス)を待ってから、クライアント側の処理が続行されます。

サンプル全体は以下のように書きました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<input type="button" id="sampleXMLHttpRequest" value="レスポンスのテスト">

<div>通信時の処理内容</div>
<div id="ajax_result"></div>

<script type="text/javascript">

// ボタン要素のDOMを取得
let element_sampleXMLHttpRequest = document.getElementById('sampleXMLHttpRequest');

// イベントを付与
element_sampleXMLHttpRequest.addEventListener('click',  SendXMLHttpRequest, true);

// ボタン押下時の処理
function SendXMLHttpRequest()
{

	// 通信処理を画面に出力する為、DOM操作
	let result = document.getElementById("ajax_result");

	// ajax通信処理
	let req = new XMLHttpRequest();

	// 第三引数にfalseを指定することで、同期で実行する
	req.open("GET", "./response.php", false);

	req.onreadystatechange = function() {

		// 検証の為、画面出力とログ出力をする
		result.innerHTML += 'onreadystatechange start' + '<br />';
		result.innerHTML += 'req.readyState -> ' + req.readyState + '<br />';
		result.innerHTML += 'req.status -> ' + req.status + '<br />';

		console.log('req.readyState -> ' + req.readyState);
		console.log('req.status -> ' + req.status);
		
		// レスポンスの状態 (req.readyState) を判定する
		if (req.readyState == 4) {
			result.innerHTML += 'レスポンスは正常 (readyState)' + '<br />';
			console.log('レスポンスは正常 (readyState)');
		} else {
			result.innerHTML += 'レスポンスは異常の為、処理終了(readyState) ' + '<br />';
			console.log('レスポンスは異常の為、処理終了(readyState) ');
			return false;
		}
		
		// レスポンスの状態 (req.status) を判定する
		if (req.status == 200) {
			result.innerHTML += 'レスポンスは正常 (status)' + '<br />';
			console.log('レスポンスは正常 (status)');
		} else {
			result.innerHTML += 'レスポンスは異常の為、処理終了(status) ' + '<br />';
			console.log('レスポンスは異常の為、処理終了(status) ');
			return false;
		}
		
		// getResponseHeaderを取得する
		let content_type = req.getResponseHeader("Content-Type");
		result.innerHTML += 'content_type -> ' + content_type + '<br />';
		console.log('content_type -> ' + content_type);
		
		// レスポンスの状態 (content_type) を判定する
		// ここでは検証の為、強制的に「text/html; charset=UTF-8」の文字列で判定する	
		if (content_type == "text/html; charset=UTF-8") {
			result.innerHTML += 'レスポンスは正常 (content_type)' + '<br />';
			console.log('レスポンスは正常 (content_type)');
		} else {
			result.innerHTML += 'レスポンスは異常の為、処理終了(content_type) ' + '<br />';
			console.log('レスポンスは異常の為、処理終了(content_type) ');
			return false;
		}
		
		// responseレスポンス内容を出力
		result.innerHTML += 'レスポンス内容の表示 -> ' + req.response + '<br />';

	};

	result.innerHTML += '同期 / 非同期の確認' + '<br />';
	console.log('同期 / 非同期の確認');

	req.send();
}

</script>

</body>
</html>

サーバ上のHTMLはこちら(test2.html)

画面にアクセスして「レスポンスのテスト」ボタンを押すと、先程のサンプルとは違い
「同期 / 非同期の確認」という文字列は、ボタン押下と同時には画面に出てきません。

サーバ側の処理が終わり、約2秒経過した後に「同期 / 非同期の確認」の文字列と同時に
レスポンス結果の文字列が画面に出力されます。

このようにopenメソッドの第三引数により、同期か非同期かを決定することができます。
また、第三引数を省略すると「非同期」として処理されます。

このことから同期処理を行うと、サーバ側の処理結果に合わせたタイミングで
クライアント側の動作をコントロールすることができます。
実際の開発では、同期/非同期のどちらを使うかはケースによって決めます。

XMLHttpRequest 同期 / 非同期の注意点

ここまででXMLHttpRequestの同期/非同期を試してみましたが、
現在の仕様では同期は廃止(非推奨)となっています。

注: Gecko 30.0 (Firefox 30.0 / Thunderbird 30.0 / SeaMonkey 2.27), 
Blink 39.0, Edge 13 以降では、メインスレッド上での同期リクエストは
ユーザーの使い勝手に悪影響を与えるため、非推奨になっています。

(mdn web dosc [同期と非同期のリクエスト]から引用)

XMLHttpRequest自体、現在の開発手法では使うケースが極端に少なく、
当ブログではXMLHttpRequestの挙動を試す意味で取り上げています。

実際の開発シーンでは、フレームワークやライブラリ側が提供している通信処理の書き方をするケースが多いです。

XMLHttpRequest 動作例、レスポンスについて

Posted コメントするカテゴリー: javascript

XMLHttpRequest 動作例、レスポンスについて

XMLHttpRequestは、javascriptの言語仕様の更新や、フレームワーク事情から考えると、
利用シーンはかなり少なくなっていますが、ここではひととおりのことを試してみようと思います。

前回の投稿ではクライアント側からサーバ側への通信(主にデータの送信)を試しました。

今回は通信時に、サーバ側から返されるレスポンスについて試してみます。

XMLHttpRequestの通信時には、レスポンスとして次のものが取得できます。

ステータスコード
 HTTPのステータスを数値形式とテキスト形式で返す

レスポンスヘッダ
 getResponseHeader()、getAllResponseHeaders()で取得

レスポンスボディ
 responseTextプロパティから取得
 テキスト形式、Document形式で取得可能

ざっと書きましたが、具体的な使い方や書き方、動作等はサンプルを下記ながら
試してみようと思います。

XMLHttpRequest 通信時の同期、非同期について

XMLHttpRequestで通信処理する場合、非同期で処理されます。

前回は下記のサンプルを書きました。

let req = new XMLHttpRequest();
let send_string = "test_post_value=123";
req.open("POST", "./receive.php");
req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
req.send(send_string);

この時、最終行にある「req.send」のsendメソッドは、リクエスト送信後にすぐに続きの処理に戻る為、
サーバからの応答を待つことをしません。

ではサーバからの応答を取得するにはどうするのかというと、
XMLHttpRequestオブジェクトのreadystatechangeイベントの発火をきっかけにして処理を行います。

readystatechangeイベントについては、以前の投稿でサンプルを書いて試しています。

また、readystatechangeイベントとセットで覚えておくとよいものに、XMLHttpRequestオブジェクトのreadyStateプロパティがあります。
readyStateプロパティはHTTPリクエストの通信時に状態を表すステータスの値を持ちます。

readyStateプロパティの各ステータスの定数名は以下のようになります。

UNSET
OPEND
HEADERS_RECEIVED
LOADING
DONE

定数名に対してのreadyStateの値と意味は以下のようになります。

UNSET              0 open()が呼び出されていない
OPEND              1 open()が呼び出された
HEADERS_RECEIVED   2 ヘッダを受信
LOADING            3 レスポンスボディの受信中
DONE               4 レスポンスボディの受信完了

通信前、通信中、通信後に readyStateプロパティの値が変化する際、readystatechangeイベントが発火します。
実際の通信時の処理によっては、全てのreadyStateプロパティの値のケースよって、
readystatechangeイベントの発火の順が保証されている訳ではないことに注意が必要です。
サーバ側からの通信完了の目安として、readyStateの値をプロパティ値の値(DONE、4)を判定すると確実な処理ができると言えます。

AJAXでの通信時に、readystatechangeイベントを受信する方法はonreadystatechangeプロパティにイベントハンドラの関数を登録し、
その関数内でリクエスト完了後のサーバ側からのレスポンスを処理します。
レスポンスの処理は主に以下の内容です。

リクエストが完了したかどうか
レスポンスのステータスコードをチェック
ステータスコードの結果、レスポンスが正しく完了しているかを判定
Content-Typeヘッダを基に、レスポンスの内容をチェック
全てのチェックが問題なければ、レスポンスボディをコールバック関数に渡す

ここまでの内容を確かめる為に、簡単なサンプルを書いてみます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<input type="button" id="sampleXMLHttpRequest" value="レスポンスのテスト">

<div>通信時の処理内容</div>
<div id="ajax_result"></div>

<script type="text/javascript">

// ボタン要素のDOMを取得
let element_sampleXMLHttpRequest = document.getElementById('sampleXMLHttpRequest');

// イベントを付与
element_sampleXMLHttpRequest.addEventListener('click',  SendXMLHttpRequest, false);

// ボタン押下時の処理
function SendXMLHttpRequest()
{

	// 通信処理を画面に出力する為、DOM操作
	let result = document.getElementById("ajax_result");

	// ajax通信処理
	let req = new XMLHttpRequest();

	req.open("GET", "./response.php");

	req.onreadystatechange = function() {

		// 検証の為、画面出力とログ出力をする
		result.innerHTML = 'onreadystatechange start' + '<br />';
		result.innerHTML += 'req.readyState -> ' + req.readyState + '<br />';
		result.innerHTML += 'req.status -> ' + req.status + '<br />';

		console.log('req.readyState -> ' + req.readyState);
		console.log('req.status -> ' + req.status);
		
		// レスポンスの状態 (req.readyState) を判定する
		if (req.readyState == 4) {
			result.innerHTML += 'レスポンスは正常 (readyState)' + '<br />';
			console.log('レスポンスは正常 (readyState)');
		} else {
			result.innerHTML += 'レスポンスは異常の為、処理終了(readyState) ' + '<br />';
			console.log('レスポンスは異常の為、処理終了(readyState) ');
			return false;
		}
		
		// レスポンスの状態 (req.status) を判定する
		if (req.status == 200) {
			result.innerHTML += 'レスポンスは正常 (status)' + '<br />';
			console.log('レスポンスは正常 (status)');
		} else {
			result.innerHTML += 'レスポンスは異常の為、処理終了(status) ' + '<br />';
			console.log('レスポンスは異常の為、処理終了(status) ');
			return false;
		}
		
		// getResponseHeaderを取得する
		let content_type = req.getResponseHeader("Content-Type");
		result.innerHTML += 'content_type -> ' + content_type + '<br />';
		console.log('content_type -> ' + content_type);
		
		// レスポンスの状態 (content_type) を判定する
		// ここでは検証の為、強制的に「text/html; charset=UTF-8」の文字列で判定する	
		if (content_type == "text/html; charset=UTF-8") {
			result.innerHTML += 'レスポンスは正常 (content_type)' + '<br />';
			console.log('レスポンスは正常 (content_type)');
		} else {
			result.innerHTML += 'レスポンスは異常の為、処理終了(content_type) ' + '<br />';
			console.log('レスポンスは異常の為、処理終了(content_type) ');
			return false;
		}
		
		// responseレスポンス内容を出力
		result.innerHTML += 'レスポンス内容の表示 -> ' + req.response + '<br />';

	};

	req.send();
}

</script>

</body>
</html>

また、サーバ側の response.php は、文字列を出力するだけのサンプルにしています。

<?php

echo "response_test_123";

?>

サーバ上のHTMLはこちら(test1.html)

実際にアクセスして、画面上の「レスポンスのテスト」ボタンを押下すると、

通信時の処理内容
onreadystatechange start
req.readyState -> 4
req.status -> 200
レスポンスは正常 (readyState)
レスポンスは正常 (status)
content_type -> text/html; charset=UTF-8
レスポンスは正常 (content_type)
レスポンス内容の表示 -> response_test_123

という通信結果が画面に出力されます。
検証の為、readyState、status、Content-Typeを画面上と、ログ出力をしていますが、
出力された画面と、ログ出力を確認すると、少し違いがあります。

画面への出力は、上記の処理結果が出力されますが、ログには次のように出力されます。

ここでわかることは、画面上のボタンを押下した後、XMLHttpRequestの通信が開始されますが、
ボタンを押下した直後には「req.readyState -> 2」「req.readyState -> 3」等の状態(イベント)が発生していることがわかります。

さきほど「readystatechangeイベントの発火の順が保証されている訳ではない」と書きましたが、
クライアント側からサーバ側へ通信を行う際は、リクエストとレスポンスの状態は保証されていない為、
「レスポンスを確実に受取った」というステータス(ここではreadyStateが4)を判定し、クライアント側の処理を行う必要があることがわかります。

検証の為、画面出力とログ出力をしているので、ソースが長くなりましたが、
詳しく確認する通信状態を細かく考慮する必要があることがわかります。

XMLHttpRequest 動作例(POSTまたはGET)について

Posted コメントするカテゴリー: javascript

XMLHttpRequest 動作例(POST)について

XMLHttpRequestについて、実際に動作サンプルを試してみます。
画面上のボタンを押したら、クライアント側からサーバ側へ文字列を送信してみます。

単純な文字列を、POST形式で送信するサンプルを、以下のHTMLを用意してみました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<input type="button" id="sampleXMLHttpRequest" value="XMLHttpRequest送信テスト(POST)">

<script type="text/javascript">

// ボタン要素のDOMを取得
let element_sampleXMLHttpRequest = document.getElementById('sampleXMLHttpRequest');

// イベントを付与
element_sampleXMLHttpRequest.addEventListener('click',  SendXMLHttpRequest, false);

// ボタン押下時の処理
function SendXMLHttpRequest()
{
	let req = new XMLHttpRequest();

	// 送信する文字列は「キー=値」の形にする
	let send_string = "test_post_value=123";

	req.open("POST", "./receive.php");

	req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");

	req.send(send_string);
}

</script>

</body>
</html>

また、受取り側のreceive.phpでは、受取った値を$_REQUESTで参照し、ログファイルに出力して値を確認しています。
ログ出力の内容は常に通信1回につき、1回の受信のみ出力しています(蓄積はしていません)

<?php

file_put_contents('./receive.log', print_r($_REQUEST, true));

?>

サーバ上のHTMLはこちら(test1.html)

実際に画面上のボタンを押下してボタンを押すと、見た目の変化はありません。
開発者ツールのネットワークで、サーバ側への通信が行われていることが確認できます。

POST形式でサーバ側へ通信する場合は
「キー=値」の形式にしないと正しくサーバ側(この例ではphp側)で値を受信できません。

また、setRequestHeaderの値も、第一引数に「Content-Type」
第二引数に「application/x-www-form-urlencoded」を指定するとXMLHttpRequestを使わない場合の
通常の画面サブミットを同じ形式でサーバ側に値を送ることができます。

サーバ側の受信ログの内容をみてみると、以下のようになります。

Array
(
    [test_post_value] => 123
)

XMLHttpRequest 動作例(GET)について

では次に、GET方式でサーバ側に文字列を送信してみます。

受信側のphpはPOSTの場合と同じです。

HTMLは以下のように書いています。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<input type="button" id="sampleXMLHttpRequest" value="XMLHttpRequest送信テスト(GET)">

<script type="text/javascript">

// ボタン要素のDOMを取得
let element_sampleXMLHttpRequest = document.getElementById('sampleXMLHttpRequest');

// イベントを付与
element_sampleXMLHttpRequest.addEventListener('click',  SendXMLHttpRequest, false);

// ボタン押下時の処理
function SendXMLHttpRequest()
{
	let req = new XMLHttpRequest();
	
	req.open("GET", "./receive.php?test_get_value=123");

	req.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");
	
	req.send();
}

</script>

</body>
</html>

サーバ上のHTMLはこちら(test2.html)

GET形式でサーバ側へ通信する場合は、URLの後に「キー=値」の文字列を追加してサーバ側に送信しています。

画面上のボタンを押下すると、開発者ツールのネットワークは以下のように通信が行われていることがわかります。

また、setRequestHeaderの値も、第一引数に「Content-Type」、第二引数に「text/plain;charset=UTF-8」を
指定しています。

サーバ側の受信ログの内容をみてみると、以下のようになります。

Array
(
    [test_get_value] => 123
)

XMLHttpRequest リクエスト時の各メソッド について

Posted コメントするカテゴリー: javascript

XMLHttpRequest リクエスト時の各メソッド について

XMLHttpRequestについて、さらにまとめます。

前回の投稿で、下記のようにXMLHttpRequestのオブジェクトを生成しました。

let req = new XMLHttpRequest();

このオブジェクトのメソッドを呼び出し、リクエストで必要な情報を引数にセットして
クライアント側(ブラウザ側)から、サーバ側に対してリクエストを呼び出すようにします。
ここで注意が必要なことは「リクエストを呼び出す」という意味は、常にデータを受信するわけではなく、
「データを送信する」リクエストを呼ぶ場合や、
「データを受信する」リクエストを呼ぶ場合など

データが流れる方向はクライアント → サーバの場合、サーバ → クライアントの場合という点で、
一方向ではないことに注意が必要です。

XMLHttpRequest openメソッド について

具体例として「データを受信する」リクエストを呼ぶ場合、の書き方は
次のように記述します。

let req = new XMLHttpRequest();
req.open("GET", 指定したURL);

XMLHttpRequestオブジェクトのメソッドの一つであるopenメソッドを呼び出しています。
第一引数はリクエストメソッドで、REST APIでよく利用される定義を指定します。
第二引数はURLを指定します。
第三引数は省略可能で、asyncの設定を真偽値で指定します。通信時に非同期で処理するかを決めます。
第四引数は省略可能で、userを指定します。認証プロセスで使用するユーザ名を指定します。
第五引数は省略可能で、passwordを指定します。

第四引数、第五引数のuser、passwordはbasic認証がかかっているURLに通信を行う場合に使用できます。

補足ですが、リクエストメソッドには以下の種類があります。

GET
POST
PUT
DELETE
HEAD
CONNECT
OPTIONS
TRACE
PATCH

このopenメソッドを使って通信をする際には、同一出身ポリシーに注意する必要があります。
具体的には、プログラムを実行するサーバから、同一のプロトコル、ホスト、ポート、である必要があり、
同一出身ポリシーのルールから外れると通信エラーになるので注意が必要です。

XMLHttpRequest setRequestHeaderメソッド について

上記の記載で、openメソッドを使い、サーバ側に通信をする例を書きましたが、
この通信を行う際には、HTTPリクエストヘッダを設定する必要があります。

通信時のリクエストメソッドによりますが、
POSTは、クライアント側プログラムからサーバ側にデータを送ることができ、
GETは、サーバ側からクライアント側にデータを受取ることができます。

また、もしPOSTを使う場合には、Content-Typeヘッダを指定し、
MIMEタイプをリクエストボディに付与する必要があります。

Content-Type

リクエストヘッダの説明については、mdn web doscから詳細を引用すると、次のようになります。

リクエストヘッダーは、 HTTP リクエストで使用される HTTP ヘッダーであり、
メッセージの内容には関連しないものです。 

Accept, Accept-*, If-* などのリクエストヘッダーは、
条件付きリクエストを行うことができます。
他の Cookie, User-Agent, Referer などはサーバーが
回答を作成するための文脈を明確にします。

リクエストに現れるすべてのヘッダーがリクエストヘッダーであるとは限りません。

例えば、 POST リクエストの中に現れる Content-Length は、
実際にはリクエストメッセージの本文の長さを表すエンティティヘッダーです。

しかし、これらのエンティティヘッダーもそのような場面では
リクエストヘッダーと呼ばれることがよくあります。

加えて、 CORS では、常に認証が考慮され、プリフライトリクエストへのレスポンスで
明確に列挙されないリクエストヘッダーの一部を単純ヘッダーとして定義しています。

(引用[mdn web dosc 内の Request header (リクエストヘッダー) から])

また、通信時のリクエストヘッダに、次のものは明示的に指定することはできません。

Accept-Charset
Accept-Encoding
Connection
Content-Length
Cookie
Cookie2
Content-Transfer-Encoding
Date
Expect
Host
Keep-Alive
Referer
TE
Trailer
Transfer-Encoding
Upgrade
User-Agent
Via

これらはブラウザが自動的に追加してサーバ側へ送信するようになっています。

具体例として以下のように書きます。

let req = new XMLHttpRequest();
req.open("POST", 指定したURL);
req.setRequestHeader("Content-Type", "text/plain");

XMLHttpRequest sendメソッド について

上記の openメソッド と、setRequestHeaderメソッド で、通信先URLとリクエストヘッダが指定すると、
リクエストボディを指定してサーバへリクエストを送信することができます。

let req = new XMLHttpRequest();
req.open("GET", 指定したURL);
req.setRequestHeader();
req.send();

各メソッドの引数は記載していませんが、通信を行うシーンに応じて引数を指定します。

また、XMLHttpRequest のインスタンスを生成した後、
open → setRequestHeader → send の順でメソッドを呼んでいますが、この順番にしないと
例外エラーが発生するので注意が必要です。

XMLHttpRequest 全メソッド について

XMLHttpRequestオブジェクトの全メソッドは以下のものがあります。
AJAX通信を使ったプログラムで各メソッドを組み合わせて書きますが、ここでは一つ一つの詳細については省略します。

abort()
getAllResponseHeaders()
getResponseHeader()
open()
overrideMimeType()
send()
setRequestHeader()

XMLHttpRequest について

Posted コメントするカテゴリー: javascript

XMLHttpRequest について

XMLHttpRequestについてまとめます。

ブラウザ上でなんらかの操作を行い、そのきっかけ(イベント)でサーバ側に通信を行う時に
XMLHttpRequestの通信方法を使います。

XMLHttpRequestはリクエストとレスポンスの組み合わせです。
リクエスト時に、パラメータをつけたり、遷移先のURLを指定したり、形式を指定したりします。
レスポンス時にはサーバ側でなんらかの処理を行い、その結果をブラウザ側へ返します。

最近のjavascript界隈では、各種フレームワークや、仕様の標準API化、reactやvue等の使用等で
ブラウザ ⇔ サーバの通信の方法(プログラムの書き方)は多種多様にありますが、
ここではjavascriptの言語仕様に基づいたシンプルなものを試してみます。

まず、XMLHttpRequestで通信を行うには、XMLHttpRequestオブジェクトのインスタンスを作ります。

let req = new XMLHttpRequest();

ブラウザ → サーバ への通信時のHTTPリクエストは以下の4点を使います。
・HTTPリクエストメソッド
・リクエスト先URLの指定
・リクエストヘッダ情報の送信
・リクエストボディの送信

また、サーバ → ブラウザ へのHTTPレスポンスは以下の3点です。

・リクエストのステータスコード、数値またはテキスト
・レスポンスヘッダ
・レスポインスボディ

さらに、HTTPリクエスト HTTPレスポンス時には次の点に注意します。

・リクエスト時はHTTPプロトコルでもHTTPSプロトコルでも可能です
(別なプロトコル使用も理論的には可能ですが、現実的には使われない)
・リクエスト後は、サーバ側でどのように処理が行われるか、ブラウザ側での制御外になる
・サーバ側で、リダイレクトされて処理がされることもある
・ブラウザ側でクッキー、キャッシュ、プロキシが使われるケースがある
(実際の実装時、動作にどれくらい影響範囲を考慮する必要がある)

Comet について

Posted コメントするカテゴリー: javascript

Comet について

Cometについてまとめます。
前回の投稿でCometはAjaxの逆の動きをすると書きました。
Ajaxはクライアント側からサーバ側への通信であることに対し、
Cometはサーバ側からクライアント側への通信が可能です。

Cometの通信時の処理は、クライアント側からサーバ側に接続を確立して、
そのまま接続の状態を維持します。
この接続したまま、非同期にメッセージを(サーバ側から)送信します。

例えば、Ajax通信でサーバに接続した後、サーバ側からデータを送信するまで待機し、
データが送信されたタイミングで、接続を一旦クローズする。
その後に、クライアント側で受信したデータを処理し、処理後に再度サーバ側への接続を行い、
同様にデータが(クライアント側から)送信されるまで待機をする。
という仕組みが考えられます。

HTTP通信 について

Posted コメントするカテゴリー: javascript

HTTP通信 について

javascriptでHTTP通信を行う方法をまとめてみます。

HTTP通信を行う時は
・ブラウザがサーバに対してデータを要求する
・ブラウザからサーバに対してフォーム内容を送信する
・サーバにデータを送った後、サーバ側で処理した結果を受取る(送信、受信)
等の通信を行うシーンがあります。

通信を行うタイミングはユーザの操作(ボタンやリンクを押下)によって開始したり、
javascript内で特定のタイミング(一定の間隔でサーバにリクエスト)したり、
プログラムの実行中に処理に必要なデータをサーバから取得する、等々、実装シーンにより通信タイミングは変わります。

AJAX について

javascriptで通信を行うプログラムを書く際、AJAXを利用して通信部分を書くケースが多く、
各javascriptライブラリで書き方は異なりますが、概念的なものは同じです。
AJAXを使うとページをリロードや更新をせずに画面内のDOM構造やデータを変更できることが特徴です。
最近ではSPAでシステムやwebサイトを開発することが増えてきているので、javascriptによるHTTP通信を使うシーンは多いと言えます。

Comet について

AJAXとは逆にCometという技術もあります。
Cometを使うとサーバ側からクライアント側(ここでいうjavascript側)に対して、非同期にデータを送信することができます。
この仕組みはjavascript以外の言語やフレームワークでも使用できるので、javascriptに特化した考え方ではないです。

トランスポート について

クライント側とサーバ側間でAJAXやCometにより通信を行う実装をトランスポートと言うことができます。

例えばimgタグはsrcプロパティで画像ソースの場所を指し示し、ブラウザ表示したタイミングで
クライアント側からサーバ側に画像リソースを取得する為の通信が発生します。
画像を表示する。という一方通行の通信ですが、双方向の通信ではなくてもトランスポートと言えます。

また、iframeタグは、クライアント、サーバ間の双方向のトランスポートと言えます。
iframeタグ内のsrcプロパティに対して、HTTP通信を行い、指定したURLの情報を参照(送信)し、指定URLのデータをiframeタグ内に表示(受信)します。

HTML内に記載するjavascriptを呼び出すscriptタグもsrcプロパティを持ち、HTTP通信を行ってサーバ側のjavascriptプログラムを参照する書き方もあります。
トランスポートと言えます。
ただ、javascriptで通信を行うプログラムの注意として、クロスドメイン時には同一出身ポリシーの制約を考慮してプログラミングする必要があります。
具体的には、サーバからのレスポンスをどのようなデータ形式で受信するか、という点になりますが、詳しくは別な記事で試してみようと思います。

XMLHttpRequest について

AJAX通信を行う際、各ブラウザで共通で使用できるXMLHttpRequestオブジェクトがあります。
XMLHttpRequestオブジェクトはHTTP通信を制御するAPIが定義されており、
GET、POST、PUT、DELETE等のリクエストメソッドの送信を行ったり、
サーバからのレスポンスを様々な形式で受信することができます。
レスポンスはXML形式だけではなく、json、プレーンテキスト、等が使用できます。

キーボードイベント について

Posted コメントするカテゴリー: javascript

キーボードイベント について

前回の投稿で画面からキーボードでテキスト入力する時のイベントについて試しました。

その中でkeypress、input、keydown、keyupイベントについて、
それぞれ取得できるタイミングや値が異なることがわかりました。

また、主にキーボードで入力する際のイベントであるkeydown、keyupについて
keyCodeで押下したキー情報が取得できますが、このプロパティの他にkeyプロパティもあります。
このkeyプロパティは、入力した文字キーをそのまま取得することができます。

以下に簡単なサンプルを用意しました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

テキスト入力テスト
<input type="text" id="test1" name="test1">

<div>取得結果keydown</div>
<div id="keydown_result"></div>

<div>取得結果keyup</div>
<div id="keyup_result"></div>

<script type="text/javascript">
// DOM要素を取得
let test1_text = document.getElementById("test1");

// keydownのイベントハンドラを登録
test1_text.addEventListener('keydown', function(event) {
	console.log("keydown start ------------------");
	console.log("event keyCode -> " + event.keyCode);
	console.log("event key -> " + event.key);

	// コードから文字列を取得する
	let text_str = String.fromCharCode(event.keyCode);
	console.log("event text_str -> " + text_str);

	let disp_text = "event keyCode -> " + event.keyCode + "<br />";
	disp_text += "event key -> " + event.key + "<br />";

	let result = document.getElementById("keydown_result");
	result.innerHTML = disp_text + "<br />";
}, false);

// keyupのイベントハンドラを登録
test1_text.addEventListener('keyup', function(event) {
	console.log("keyup start ------------------");
	console.log("event keyCode -> " + event.keyCode);
	console.log("event key -> " + event.key);

	// コードから文字列を取得する
	let text_str = String.fromCharCode(event.keyCode);
	console.log("event text_str -> " + text_str);

	let disp_text = "event keyCode -> " + event.keyCode + "<br />";
	disp_text += "event key -> " + event.key + "<br />";

	let result = document.getElementById("keyup_result");
	result.innerHTML = disp_text + "<br />";
}, false);

</script>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

画面にアクセスし、テキスト入力欄に「a」と入力してみます。
画面には入力したキー情報として、keyCode情報とkey情報が表示されます。

「a」以外にも他の文字キーを試してみると、それぞれの文字情報が取得できることがわかります。

また、文字入力の他、数値キー(テンキー)や、ファンクションキー、Enter、delete、半角全角キー、等のキー情報も
文字列として取得できることがわかります。