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、半角全角キー、等のキー情報も
文字列として取得できることがわかります。

テキストイベント について

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

テキストイベント について

画面からキーボードでテキスト入力する時のイベントについて調べて試してみます。

キーボード入力関連のイベントは複数ありますが、今回は以下のものを試してみます。

keydown
keyup
textInput
input
keypress

上記のイベントのうち、テキスト入力内容を取得する際に、各イベントごとに
取得できる内容が異なるので注意が必要です。

それぞれのイベントはキーボードで1文字入力したタイミングで、
引数のイベントターゲットに対して、1回の入力されたキーボード情報が取得ができます。

keypressについては、Unicodeの符号位置(数値)を取得します。

イベントターゲットから取得したkeyCodeは、入力文字列に対応したコード番号が取得されます。
コードではなく、文字列で取得する場合は、keyCodeをもとに、
String.fromCharCode()とすることで、文字列に変換することができます。
(firefoxではkeyCodeではなく、charCodeを使うと文字列に変換できます)

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

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

テキスト入力テスト
<input type="text" id="test1" name="test1">

<div>取得結果keypress</div>
<div id="keypress_result"></div>

<div>取得結果textInput</div>
<div id="keypress_result"></div>

<div>取得結果input</div>
<div id="input_result"></div>

<div>取得結果keydown_result</div>
<div id="keydown_result"></div>

<div>取得結果keyup_result</div>
<div id="keyup_result"></div>

<script type="text/javascript">
// DOM要素を取得
let test1_text = document.getElementById("test1");

// keypressのイベントハンドラを登録
test1_text.addEventListener('keypress', function(event) {
	console.log("keypress start ------------------");
	console.log("event type -> " + event.type);
	console.log("event keyCode -> " + event.keyCode);
	console.log("event charCode -> " + event.charCode);
	// コードから文字列を取得する
	let text_str = String.fromCharCode(event.keyCode);
	console.log("event text_str -> " + text_str);

	let disp_text = "event type -> " + event.type + "<br />";
	disp_text += "event keyCode -> " + event.keyCode + "<br />";
	disp_text += "event charCode -> " + event.charCode + "<br />";
	disp_text += "text_str -> " + text_str + "<br />";

	let result = document.getElementById("keypress_result");
	result.innerHTML = disp_text + "<br />";
}, false);

// textInputのイベントハンドラを登録
test1_text.addEventListener('textInput', function(event) {
	console.log("textInput start ------------------");
	console.log("event type -> " + event.type);
	console.log("event keyCode -> " + event.keyCode);
	console.log("event charCode -> " + event.charCode);
	// コードから文字列を取得する
	let text_str = String.fromCharCode(event.keyCode);
	console.log("event text_str -> " + text_str);

	let disp_text = "event type -> " + event.type + "<br />";
	disp_text += "event keyCode -> " + event.keyCode + "<br />";
	disp_text += "event charCode -> " + event.charCode + "<br />";
	disp_text += "text_str -> " + text_str + "<br />";

	let result = document.getElementById("textInput_result");
	result.innerHTML = disp_text + "<br />";
}, false);

// inputのイベントハンドラを登録
test1_text.addEventListener('input', function(event) {
	console.log("input start ------------------");
	console.log("event type -> " + event.type);
	console.log("event keyCode -> " + event.keyCode);
	console.log("event charCode -> " + event.charCode);
	// コードから文字列を取得する
	let text_str = String.fromCharCode(event.keyCode);
	console.log("event text_str -> " + text_str);

	let disp_text = "event type -> " + event.type + "<br />";
	disp_text += "event keyCode -> " + event.keyCode + "<br />";
	disp_text += "event charCode -> " + event.charCode + "<br />";
	disp_text += "text_str -> " + text_str + "<br />";

	let result = document.getElementById("input_result");
	result.innerHTML = disp_text + "<br />";
}, false);

// keydownのイベントハンドラを登録
test1_text.addEventListener('keydown', function(event) {
	console.log("keydown start ------------------");
	console.log("event type -> " + event.type);
	console.log("event keyCode -> " + event.keyCode);
	console.log("event charCode -> " + event.charCode);
	// コードから文字列を取得する
	let text_str = String.fromCharCode(event.keyCode);
	console.log("event text_str -> " + text_str);

	let disp_text = "event type -> " + event.type + "<br />";
	disp_text += "event keyCode -> " + event.keyCode + "<br />";
	disp_text += "event charCode -> " + event.charCode + "<br />";
	disp_text += "text_str -> " + text_str + "<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 type -> " + event.type);
	console.log("event keyCode -> " + event.keyCode);
	console.log("event charCode -> " + event.charCode);
	// コードから文字列を取得する
	let text_str = String.fromCharCode(event.keyCode);
	console.log("event text_str -> " + text_str);

	let disp_text = "event type -> " + event.type + "<br />";
	disp_text += "event keyCode -> " + event.keyCode + "<br />";
	disp_text += "event charCode -> " + event.charCode + "<br />";
	disp_text += "text_str -> " + text_str + "<br />";

	let result = document.getElementById("keyup_result");
	result.innerHTML = disp_text + "<br />";
}, false);

</script>

</body>
</html>

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

画面にアクセスし、テキスト入力欄に「a」と入力してみます。

開発者ツールでも確認可能ですが、取得内容が下記のように画面にも表示されます。

chromeの場合

firefoxの場合

実際に入力して確認すると、各イベントによって、
keyCodeや取得できる文字列が異なることがわかります。
(textInputは取得できていません)

マウスホイール について

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

マウスホイール について

これまでDOM要素をドラッグ&ドロップについて試しましたが、
マウスの操作としてホイールのコントロールについて、javascriptでどのように制御できるのか試してみます。

ホイールのイベントハンドラは、ブラウザごとに完全に同一ではないので、
実装時にはどんなイベントハンドラが、何のブラウザで有効か、無効かに注意する必要があります。

ホイールのイベントハンドラの一つとして「mousewheel」がありますが、これは現在は非推奨になっています。
構文としては以下のように書きます。

addEventListener('mousewheel', (event) => {});

もしくは

onmousewheel = (event) => { };

ただし、このイベントは非推奨であることと、ブラウザによってサポートされていないブラウザ(firefox)があるので、
おおよそ実用的なイベントとは言えません。

また、DOM level3の仕様では「wheel」という名前で提唱されています。

このイベントは構文としては次のように下記ます。

document.addEventListener("wheel", function(event) {
    console.log("wheel start");
});

この簡単な記述を、HTML内に記載し画面表示をすると、マウスのホイールを取得して、
イベントが発火していることがわかります。

console.logで確認する方法でもいいのですが、都度、開発者ツールを開いて確認する手間もあるので、
画面上にイベント内容を出力するようにしました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
<style type="text/css">
body, html {
	overflow: hidden;
}
#disp_mouse_wheel {
	position: absolute;
	height: 500px;
	width: 300px;
	background-color: #c0e5ff;
	overflow-y: scroll;
}
</style>
</head>
<body>

マウスホイールの「wheel」イベント情報を表示する
<div id="disp_mouse_wheel"></div>

<script type="text/javascript">
document.addEventListener("wheel", function(event) {

	// 開発者ツールで表示する
    console.log("event Delta -> " + event.wheelDelta);

	// 画面上のDOM要素を取得
	let disp_mouse_wheel = document.getElementById("disp_mouse_wheel");
	disp_mouse_wheel.innerHTML = event.wheelDelta + "<br />" + disp_mouse_wheel.innerHTML;
});
</script>

</body>
</html>

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

画面にアクセスし、マウスホイールを上下に回転すると、イベントハンドラの wheelDelta プロパティが取得できます。

画面のイベント情報を表示する領域に、このwheelDeltaプロパティの値を出力すると
「120」や「-120」といった数値が出力されます(chrome、firefoxで確認済)。
現在はブラウザのサポート状況は「Safari on iOS」だけは非対応となっています。
「Safari on iOS」以外のブラウザではほとんどのブラウザで動作します。

値とホイールの回転方向については、次のとおりです。

マウスホイールを下方向に回転した時:-120
マウスホイールを上方向に回転した時:120

画面上でマウスホイールを操作すると、上記の値が1スクロール(マウスにもよりますが、1ホイール)ごとに
取得できることがわかります。

ホイールイベントのdeltaX、deltaY、deltaZ、プロパティについて

上記のHTMLを基に、ホイール操作時のイベントのプロパティである

deltaX
deltaY
deltaZ

の値を出力してみて、実際にどのような値が取得できるのかを試してみます。

deltaX  水平方向のスクロール
deltaY  垂直方向のスクロール
deltaZ  Z軸方向のスクロール

上記のプロパティの値を把握しやすいように、簡単なサンプルHTMLを用意しました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
<style type="text/css">
body, html {
	overflow: hidden;
}

.title {
	width: 200px;
	top: 10px;
	margin-left: 5px;
	float: left;
}	

#disp_mouse_wheel {
	position: absolute;
	height: 500px;
	width: 200px;
	top: 30px;
	background-color: #c0e5ff;
	overflow-y: scroll;
}

#disp_mouse_wheel_deltaX {
	position: absolute;
	height: 500px;
	width: 200px;
	top: 30px;
	left: 210px;
	background-color: #c0e5ff;
	overflow-y: scroll;
}

#disp_mouse_wheel_deltaY {
	position: absolute;
	height: 500px;
	width: 200px;
	top: 30px;
	left: 420px;
	background-color: #c0e5ff;
	overflow-y: scroll;
}

#disp_mouse_wheel_deltaZ {
	position: absolute;
	height: 500px;
	width: 200px;
	top: 30px;
	left: 630px;
	background-color: #c0e5ff;
	overflow-y: scroll;
}
</style>
</head>
<body>

<div class="title">wheel</div>
<div id="disp_mouse_wheel"></div>

<div class="title">deltaX</div>
<div id="disp_mouse_wheel_deltaX"></div>

<div class="title">deltaY</div>
<div id="disp_mouse_wheel_deltaY"></div>

<div class="title">deltaZ</div>
<div id="disp_mouse_wheel_deltaZ"></div>

<script type="text/javascript">
document.addEventListener("wheel", function(event) {

	// 開発者ツールで表示する
    console.log("event Delta -> " + event.wheelDelta);
    console.log("event deltaX -> " + event.deltaX);
    console.log("event deltaY -> " + event.deltaY);
    console.log("event deltaZ -> " + event.deltaZ);

	// 画面上のDOM要素を取得
	let disp_mouse_wheel = document.getElementById("disp_mouse_wheel");
	disp_mouse_wheel.innerHTML = event.wheelDelta + "<br />" + disp_mouse_wheel.innerHTML;

	let disp_mouse_wheel_deltaX = document.getElementById("disp_mouse_wheel_deltaX");
	disp_mouse_wheel_deltaX.innerHTML = event.deltaX + "<br />" + disp_mouse_wheel_deltaX.innerHTML;

	let disp_mouse_wheel_deltaY = document.getElementById("disp_mouse_wheel_deltaY");
	disp_mouse_wheel_deltaY.innerHTML = event.deltaY + "<br />" + disp_mouse_wheel_deltaY.innerHTML;

	let disp_mouse_wheel_deltaZ = document.getElementById("disp_mouse_wheel_deltaZ");
	disp_mouse_wheel_deltaZ.innerHTML = event.deltaZ + "<br />" + disp_mouse_wheel_deltaZ.innerHTML;

});
</script>

</body>
</html>

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

画面にアクセスして、マウスホイールを操作すると、「wheel」プロパティと「deltaY」プロパティの
値が取得できることがわかります。(deltaXとdeltaZはマウスの形状により、取得できます)

一般的なマウスの形状は、縦(垂直)方向のスクロールなので、上記のうち deltaY の値が取得できることになります。
注意する点は wheelとdeltaYとでは、取得できる符号が逆になっている点です。
wheelが正の値を出力する時は、deltaYは負の値を出力しています。

また、ブラウザごとに取得できるdeltaYの値は異なります。

実際にchromeで試してみると次のような画面になります。

同じ操作をfirefoxで試すと次のような画面になります。

「deltaY」の値を確認すると、
chromeでは「166.66665649414062」「-166.66665649414062」等の値ですが、
firefoxでは「120」「-120」となり、ブラウザごとに値が違うことがわかります。

実際のプログラム実装時にはこの違いについて注意が必要です。

マウスドラッグ、マウスドロップ時の dataTransferプロパティ について

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

マウスドラッグ、マウスドロップ時の dataTransferプロパティ について

前回の投稿では、マウスでDOM要素をドラッグ&ドロップすることを試してみました。

このドラッグ&ドロップには単にDOM要素をドラッグして、特定のDOM要素にドロップするという
動作だけではなく、ドラッグ時、ドロップ時にデータを転送する。という利用方法があります。

このデータを転送する処理については

移動
コピー
リンク

という諸方法があり、ドロップ時にそれらの方法を選択した上で処理を決定する。
という方法がアプリケーションの質を向上させるポイントにもなります。

また、最近のブラウザではドラッグ&ドロップを標準化したAPIも整備されてきました。

次の例では、ドラッグ時とドロップ時に、データ転送をする方法として
dataTransferプロパティを使った例を試してみます。

dataTransferプロパティは、DataTransferオブジェクトのプロパティです。

dataTransferを試してみる

画面上のとあるDOM要素をドラッグする際、dragstartイベントが発動されます。

このイベントハンドラの実行時に、dataTransferプロパティに対して、データをセットする
ことができます。

前回の投稿を例にすると、

// ドラッグ開始したとき
element_drag_dom.addEventListener('dragstart', function(e){
	display_status('DRAG DOM : dragstart', 'drag_c');
});

上記の箇所の「ドラッグを開始したとき」の dragstart イベントの箇所です。

このイベントの処理に対して、引数のイベントターゲットに、dataTransferプロパティの
setDataメソッドを使って、値を設定します。

具体的には、以下のように記述します。

// ドラッグ開始したとき
element_drag_dom.addEventListener('dragstart', function(e){
	display_status('DRAG DOM : dragstart', 'drag_c');
	
	e.dataTransfer.setData("text/plain" , e.target.textContent);
	
	e.stopPropagation();
});

上記サンプルの「dataTransfer.setData」の箇所がデータをセットしている箇所です。

この「setData」メソッドは、第一引数にデータタイプを指定し、第二引数に格納するデータを指定します。

上記の例でいうと、データタイプは text/plain 、データは e.target.textContent(ドラッグ中のDOM要素のテキスト「ドラッグ用DOM」)です。

上記のdataTransferプロパティに文字列が格納されている前提として、
今度はドロップ側のDOM要素に対して、dropイベントの中でdataTransferプロパティにgetDataメソッドを使って
中の値を取り出すことができます。

前回のドロップ時の処理は以下のように書きました。

// ドラッグしている要素をドロップした時
element_drop_dom.addEventListener('drop', function(e){
	e.preventDefault();
	display_status('DROP DOM : drop', 'drop_c');
	
	// ドラッグ用DOMを削除する
	element_drag_dom.parentNode.removeChild(element_drag_dom);
	
});

このドロップ時の処理に、「let droptxt = e.dataTransfer.getData(“text/plain”);」を追記し、
dataTransferの値を取り出します。

取り出した値がわかりやすいように、一旦droptxtの変数に格納し、
その変数を「element_drop_dom.innerHTML = droptxt;」に出力します。

処理としては、次のように変更しました。

// ドラッグしている要素をドロップした時
element_drop_dom.addEventListener('drop', function(e){
	e.preventDefault();
	display_status('DROP DOM : drop', 'drop_c');
	
	// ドラッグ用DOMを削除する
	element_drag_dom.parentNode.removeChild(element_drag_dom);
	
	let droptxt = e.dataTransfer.getData("text/plain");
	element_drop_dom.innerHTML = droptxt;

});

このように書くことにより、ドラッグ用のDOMをドロップ領域用のDOMにドロップしたタイミングで
ドラッグ時にdataTransferに格納した文字列が、ドロップ時にドロップ領域用のDOMに出力されることが確認できます。

この初期画面の右下のドロップ要素のDOMの文字列が

この文字列に変更されます。

HTMLの全体は以下のようになります。

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

#drag_dom {
	position: absolute;
	height: 100px;
	width: 150px;
	top: 10px;
	left: 10px;
	background-color: #c0e5ff;
}

#drop_dom {
	position: absolute;
	height: 200px;
	width: 300px;
	top: 300px;
	left: 500px;
	padding: 8px;
	border: 5px solid;
	border-color: #ffbaba;
}

#drop_dom_hover {
	background-color: #998877;
}

#dnd_status_area {
	position: absolute;
	top: 120px;
}

.drag_c {
	color: #28a4fb;
}

.drop_c {
	color: #ff5050;
}

</style>
</head>
<body>

<div id="drag_dom" draggable="true">
	ドラッグ用DOM
</div>

<div id="drop_dom">
	ドロップ領域用DOM
</div>

<div id="dnd_status_area">
	<div>ドラッグ&ドロップの状態【日時 - ステータス】</div>
	<div id="dnd_status"></div>
</div>

<script type="text/javascript">

// ドラッグするDOM要素を取得
let element_drag_dom = document.getElementById("drag_dom");

// ドロップするDOM要素を取得
let element_drop_dom = document.getElementById("drop_dom");

// ステータス表示用配列を用意
let status_queue = [];
let status_i = 0;

//------------------------------------------------
// ドラッグ対象に登録するイベントハンドラ
//------------------------------------------------
// 要素をドラッグしている時
element_drag_dom.addEventListener('drag', function(e){
	display_status('DRAG DOM : drag', 'drag_c');

	// ドラッグ中は、文字列を変更
	element_drag_dom.innerHTML = "ドラッグ中です";

});

// ドラッグ開始したとき
element_drag_dom.addEventListener('dragstart', function(e){
	display_status('DRAG DOM : dragstart', 'drag_c');
	
	e.dataTransfer.setData("text/plain" , e.target.textContent);
	
	e.stopPropagation();
});

// ドラッグが終了した時
element_drag_dom.addEventListener('dragend', function(e){
	display_status('DRAG DOM : dragend', 'drag_c');

	// ドラッグが終わったら、文字列、cssを戻す
	element_drag_dom.innerHTML = "ドラッグ用DOM";
	element_drop_dom.style.backgroundColor = "#ffffff";
});

//------------------------------------------------
// ドロップ対象に登録するイベントハンドラ
//------------------------------------------------
// ドラッグ中の要素がドロップできる場所の上に入ったとき
element_drop_dom.addEventListener('dragenter', function(e){
	display_status('DROP DOM : dragenter', 'drop_c');
});

// dragexitは「dragleave」と同義イベントなので、ここではコメントアウト
//element_drop_dom.addEventListener('dragexit', function(e){
//	display_status('dragexit');
//});

// ドラッグしている要素がドロップできる場所から離れたとき
element_drop_dom.addEventListener('dragleave', function(e){
	display_status('DROP DOM : dragleave', 'drop_c');

	element_drop_dom.style.backgroundColor = "#ffffff";

});

// ドラッグ中の要素がドロップできる場所の上にあるとき
element_drop_dom.addEventListener('dragover', function(e){
	e.preventDefault();
	display_status('DROP DOM : dragover', 'drop_c');
	
	// ドロップできる場所の上にあるとき、メッセージとcssを変更する
	element_drag_dom.innerHTML = "離すと消えるよ";
	element_drop_dom.style.backgroundColor = "#ff5050";
});

// ドラッグしている要素をドロップした時
element_drop_dom.addEventListener('drop', function(e){
	e.preventDefault();
	display_status('DROP DOM : drop', 'drop_c');
	
	// ドラッグ用DOMを削除する
	element_drag_dom.parentNode.removeChild(element_drag_dom);
	
	let droptxt = e.dataTransfer.getData("text/plain");
	element_drop_dom.innerHTML = droptxt;

});

/**
 * 処理ステータスを画面出力する
 */
function display_status(status, dd_class)
{
	// 画面上のDOM要素を取得
	let dnd_status = document.getElementById("dnd_status");

	// 現在日時を取得
	let date_tmp = new Date();
	let date_disp = date_tmp.getFullYear() + "/"
	 + ('0' + (date_tmp.getMonth() + 1)).slice(-2)  + "/"
	 + ('0' + date_tmp.getDate()).slice(-2) + " "
	 + ('0' + date_tmp.getHours()).slice(-2) + ":"
	 + ('0' + date_tmp.getMinutes()).slice(-2) + ":"
	 + ('0' + date_tmp.getSeconds()).slice(-2) + "."
	 + ('00' + date_tmp.getMilliseconds()).slice(-3);

	// ステータスを10個づつの表示用配列へ格納する
	status_queue[status_i] = status;
	status_i++;
	if (status_i >= 30) {
		status_i = 0;
	}

	// キューの内容を画面へ描画する
	dnd_status.innerHTML = '';
	for (let i = 0; i < status_queue.length; i++) {
		dnd_status.innerHTML += date_disp + " - <span class='" + dd_class + "'>" + status_queue[i] + "</span><br />";
	}
}

</script>

</body>
</html>

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

画面にアクセスして、ドラッグ用DOMをドロップ領域用のDOMへドラッグ&ドロップすると、
ドロップ側のDOMの表示が変わることが確認できます。

dataTransferプロパティに格納できるデータについて

dataTransferプロパティに格納できるデータは文字列だけはないです。

例えば、ブラウザ経由でファイルがドロップされた場合はdataTransfer.filesプロパティにファイル内容がオブジェクトとして格納されます。

また、画面上の単純なDOM要素だけではなく、リスト表示をしている

<ul>タグ内の<li>タグ

に対して、
リストの順をドラッグ&ドロップで並び変える。等の動作も可能になります。

マウスドラッグ、マウスドロップについて

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

マウスドラッグ、マウスドロップについて

前回の投稿では、マウスでDOM要素をドラッグすることを試してみましたが、
要素をドラッグしたあと、ドロップする方法も試してみます。

ドラッグ&ドロップに関するイベントの種類は次のものがあります。

drag

dragstart

dragend

dragenter

dragleave

dragexit

dragover

drop

それぞれ、イベントハンドラの意味は下記のようになります。

要素をドラッグしている時
drag

ドラッグ開始したとき
dragstart

ドラッグが終了した時
dragend

ドラッグ中の要素がドロップできる場所の上に入ったとき
dragenter

ドラッグしている要素がドロップできる場所から離れたとき
dragleave

上記の「dragleave」と同義
dragexit

ドラッグ中の要素がドロップできる場所の上にあるとき
dragover

ドラッグしている要素をドロップした時
drop

上記のイベントハンドラの意味を踏まえ、シンプルなドラッグ&ドロップのサンプルを試してみます。
下記のHTMLを用意しました。

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

#drag_dom {
	position: absolute;
	height: 100px;
	width: 150px;
	top: 10px;
	left: 10px;
	background-color: #9ed7ff;
}

#drop_dom {
	position: absolute;
	height: 200px;
	width: 300px;
	top: 300px;
	left: 500px;
	padding: 8px;
	border: 5px solid;
	border-color: #ffbaba;
}

#drop_dom_hover {
	background-color: #998877;
}

#dnd_status_area {
	position: absolute;
	top: 120px;
}

.drag_c {
	color: #28a4fb;
}

.drop_c {
	color: #ff5050;
}

</style>
</head>
<body>

<div id="drag_dom" draggable="true">
	ドラッグ用DOM
</div>

<div id="drop_dom">
	ドロップ領域用DOM
</div>

<div id="dnd_status_area">
	<div>ドラッグ&ドロップの状態【日時 - ステータス】</div>
	<div id="dnd_status"></div>
</div>

<script type="text/javascript">

// ドラッグするDOM要素を取得
let element_drag_dom = document.getElementById("drag_dom");

// ドロップするDOM要素を取得
let element_drop_dom = document.getElementById("drop_dom");

// ステータス表示用配列を用意
let status_queue = [];
let status_i = 0;

//------------------------------------------------
// ドラッグ対象に登録するイベントハンドラ
//------------------------------------------------
// 要素をドラッグしている時
element_drag_dom.addEventListener('drag', function(e){
	display_status('DRAG DOM : drag', 'drag_c');

	// ドラッグ中は、文字列を変更
	element_drag_dom.innerHTML = "ドラッグ中です";
	element_drag_dom.style.backgroundColor = "#d3edff";

});

// ドラッグ開始したとき
element_drag_dom.addEventListener('dragstart', function(e){
	display_status('DRAG DOM : dragstart', 'drag_c');
});

// ドラッグが終了した時
element_drag_dom.addEventListener('dragend', function(e){
	display_status('DRAG DOM : dragend', 'drag_c');

	// ドラッグが終わったら、文字列、cssを戻す
	element_drag_dom.innerHTML = "ドラッグ用DOM";
	element_drag_dom.style.backgroundColor = "#9ed7ff";
	element_drop_dom.style.backgroundColor = "#ffffff";
});

//------------------------------------------------
// ドロップ対象に登録するイベントハンドラ
//------------------------------------------------
// ドラッグ中の要素がドロップできる場所の上に入ったとき
element_drop_dom.addEventListener('dragenter', function(e){
	display_status('DROP DOM : dragenter', 'drop_c');
});

// dragexitは「dragleave」と同義イベントなので、ここではコメントアウト
//element_drop_dom.addEventListener('dragexit', function(e){
//	display_status('dragexit');
//});

// ドラッグしている要素がドロップできる場所から離れたとき
element_drop_dom.addEventListener('dragleave', function(e){
	display_status('DROP DOM : dragleave', 'drop_c');

	element_drop_dom.style.backgroundColor = "#ffffff";

});

// ドラッグ中の要素がドロップできる場所の上にあるとき
element_drop_dom.addEventListener('dragover', function(e){
	e.preventDefault();
	display_status('DROP DOM : dragover', 'drop_c');
	
	// ドロップできる場所の上にあるとき、メッセージとcssを変更する
	element_drag_dom.innerHTML = "離すと消えるよ";
	element_drop_dom.style.backgroundColor = "#ff5050";
});

// ドラッグしている要素をドロップした時
element_drop_dom.addEventListener('drop', function(e){
	e.preventDefault();
	display_status('DROP DOM : drop', 'drop_c');
	
	// ドラッグ用DOMを削除する
	element_drag_dom.parentNode.removeChild(element_drag_dom);
	
});

/**
 * 処理ステータスを画面出力する
 */
function display_status(status, dd_class)
{
	// 画面上のDOM要素を取得
	let dnd_status = document.getElementById("dnd_status");

	// 現在日時を取得
	let date_tmp = new Date();
	let date_disp = date_tmp.getFullYear() + "/"
	 + ('0' + (date_tmp.getMonth() + 1)).slice(-2)  + "/"
	 + ('0' + date_tmp.getDate()).slice(-2) + " "
	 + ('0' + date_tmp.getHours()).slice(-2) + ":"
	 + ('0' + date_tmp.getMinutes()).slice(-2) + ":"
	 + ('0' + date_tmp.getSeconds()).slice(-2) + "."
	 + ('00' + date_tmp.getMilliseconds()).slice(-3);

	// ステータスを30個づつの表示用配列へ格納する
	status_queue[status_i] = status;
	status_i++;
	if (status_i >= 30) {
		status_i = 0;
	}

	// イベントハンドラの状態を画面へ描画する
	dnd_status.innerHTML = '';
	for (let i = 0; i < status_queue.length; i++) {
		dnd_status.innerHTML += date_disp
		 + " - <span class='" + dd_class + "'>"
		 + status_queue[i] + "</span><br />";
	}

}

</script>

</body>
</html>

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

単純にドラッグ&ドロップの部分だけでも動作はしますが、
ドラッグ用のDOMと、ドロップ領域のDOMに対して、ドラッグ&ドロップをする際の
それぞれのイベントが見えるように、
画面上に「イベント発生時の日時」と「イベントハンドラの状態(ステータス)」を出力してみました。

上記の出力は、サンプルの動作がわかりやすいように出力してます。
実際にはこのような出力はせず、必要な処理のみを書くように注意します。

余計な画面表示を行わず、シンプルな動作例を確認する場合は、
ソース上の「display_status」を全て削除し、下記のメソッドごと削除しても動作します。

/**
 * 処理ステータスを画面出力する
 */
function display_status(status, dd_class)
{
	// 画面上のDOM要素を取得
	let dnd_status = document.getElementById("dnd_status");

	// 現在日時を取得
	let date_tmp = new Date();
	let date_disp = date_tmp.getFullYear() + "/"
	 + ('0' + (date_tmp.getMonth() + 1)).slice(-2)  + "/"
	 + ('0' + date_tmp.getDate()).slice(-2) + " "
	 + ('0' + date_tmp.getHours()).slice(-2) + ":"
	 + ('0' + date_tmp.getMinutes()).slice(-2) + ":"
	 + ('0' + date_tmp.getSeconds()).slice(-2) + "."
	 + ('00' + date_tmp.getMilliseconds()).slice(-3);

	// ステータスを30個づつの表示用配列へ格納する
	status_queue[status_i] = status;
	status_i++;
	if (status_i >= 30) {
		status_i = 0;
	}

	// キューの内容を画面へ描画する
	dnd_status.innerHTML = '';
	for (let i = 0; i < status_queue.length; i++) {
		dnd_status.innerHTML += date_disp + " - <span class='" + dd_class + "'>" + status_queue[i] + "</span><br />";
	}
}

画面にアクセスするとわかるように、まず最初に、以下のような
ドラッグ用のDOMと、ドロップ領域用のDOMが画面に表示されます。

この状態から、ドラッグ用のDOM(画面上の青い領域)をマウスでドラッグ開始します。

そうすると、ドラッグ中の状態として、下記のステータスが画面に出力されます。
(実際にはマウスの挙動に応じて、出力される内容が変わりますので、色々と操作して確かめてみていただければと思います)

要素をドラッグしている時
ドラッグ開始したとき
ドラッグが終了した時
ドラッグ中の要素がドロップできる場所の上に入ったとき
ドラッグしている要素がドロップできる場所から離れたとき
ドラッグ中の要素がドロップできる場所の上にあるとき
ドラッグしている要素をドロップした時

ここでプログラム上の注意が必要な点としては、

各イベントハンドラの登録は「ドラッグ用のDOMに対して登録するイベント」と「ドロップ領域用のDOMに対して登録するイベント」がそれぞれ異なっている点です。

「ドラッグ用のDOMに対して登録するイベント」に対しては

drag
dragstart
dragend

であることに対し、

「ドロップ領域用のDOMに対して登録するイベント」に対しては

dragenter
dragexit(実際には未登録)
dragleave
dragover
drop

としている点です。

ドラッグ元のDOMに対して、ドロップ領域用のイベントハンドラを登録しても意図しない動作になるので、
「何をドラッグ」して、「どこにドロップ」するのか、という点に注意が必要になります。

マウスドラッグイベントについて

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

マウスドラッグイベントについて

マウスでDOMの要素をドラッグするイベントを試してみます。

DOMの要素をドラッグするイベントは、documentやHTMLElementのdragイベントを使う方法がありますが、
ここではシンプルな例として、DOM要素をクリックした際のonmousedownイベントを使い、
その中でDOM要素の座標を制御する方法を試してみます。

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

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
<style type="text/css">
#test1 {
	position:absolute;
	height:100px;
	width:150px;
	top:10px;
	left:10px;
	background-color:rgb(0 74 207 / 15%);
}
</style>
</head>
<body>

<div id="test1">
	ドラッグテスト
</div>

<script type="text/javascript">

let element_test1 = document.getElementById("test1");

element_test1.onmousedown = function(event) {

	// DOM要素の表示順をあげる (他のDOM要素がある場合、考慮する)
	element_test1.style.zIndex = 999;

	// マウスダウン時、マウスアップ時のイベントハンドラを登録する
	document.addEventListener('mousemove', mousemovehandler, false);
	document.addEventListener('mouseup', mouseuphandler, false);

	// マウスダウン時 (マウス移動時) のイベントハンドラ
	function mousemovehandler(event)
	{
		// イベントオブジェクトから座標情報を取得し、移動に使用する
		element_test1.style.left = event.pageX - element_test1.offsetWidth / 2 + 'px';
		element_test1.style.top = event.pageY - element_test1.offsetHeight / 2 + 'px';
	}

	// マウスアップ時のイベントハンドラ
	function mouseuphandler(event)
	{
		// DOM要素の表示順を下げる
		element_test1.style.zIndex = 0;

		document.removeEventListener('mousemove', mousemovehandler, false);
		document.removeEventListener('mouseup', mouseuphandler, false);
	}

};

</script>

</body>
</html>

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

実際に画面にアクセスすると、画面上に「ドラッグテスト」というDOM要素が見えます。

このDOMをマウスでクリックすると、mousemovehandler のイベントハンドラが実行され、
DOM要素の座標がマウス位置の座標に代入され、DOM要素がドラッグできるようになります。

課題としては、マウスクリック時の座標が、DOM要素の座標の2分の1の箇所を指しているので、
要素内のどの位置をクリックしても常にDOM要素の中心をドラッグする動きになる点があります。(この制御は別途、計算処理を入れると解消できます)

また、マウスをドラッグした後、マウスをアップすると mouseuphandler のイベントハンドラが実行され、
それまでドラッグしていたDOM要素の座標の計算、およびマウスアップ時のイベントをそれぞれ 削除(remove)しています。
こうすることでDOM要素をドロップした位置にその要素を固定することができます。

この簡単な例の他、documentやHTMLElementのdragイベントを使った例については、別途書いてみようと思います。

マウスクリック時の座標を計算し、修正

上記の例で課題としてあげた「マウスクリック時のDOM要素の位置がずれる」という点に対して、
修正版のHTMLを用意しました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
<style type="text/css">
#test1 {
	position:absolute;
	height:100px;
	width:150px;
	top:10px;
	left:10px;
	background-color:rgb(0 74 207 / 15%);
}
</style>
</head>
<body>

<div id="test1">
	ドラッグテスト
</div>

<script type="text/javascript">

let element_test1 = document.getElementById("test1");

element_test1.onmousedown = function(event) {

	// DOM要素の表示順をあげる (他のDOM要素がある場合、考慮する)
	element_test1.style.zIndex = 999;

	let margin_x = event.pageX - element_test1.getBoundingClientRect().left;
	let margin_y = event.pageY - element_test1.getBoundingClientRect().top;

	// マウスダウン時、マウスアップ時のイベントハンドラを登録する
	document.addEventListener('mousemove', mousemovehandler, false);
	document.addEventListener('mouseup', mouseuphandler, false);

	// マウスダウン時 (マウス移動時) のイベントハンドラ
	function mousemovehandler(event)
	{
		// イベントオブジェクトから座標情報を取得し、移動に使用する
		element_test1.style.left = event.pageX - margin_x + 'px';
		element_test1.style.top = event.pageY - margin_y + 'px';
	}

	// マウスアップ時のイベントハンドラ
	function mouseuphandler(event)
	{
		// DOM要素の表示順を下げる
		element_test1.style.zIndex = 0;

		document.removeEventListener('mousemove', mousemovehandler, false);
		document.removeEventListener('mouseup', mouseuphandler, false);
	}

};

</script>

</body>
</html>

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

画面にアクセスして「ドラッグテスト」のDOM要素のどこをドラッグしてもマウス位置がずれないようになります。

ポイントとしては、マウスクリック時の座標をあらかじめマージンのx座標、y座標として取得しておき、

	let margin_x = event.pageX - element_test1.getBoundingClientRect().left;
	let margin_y = event.pageY - element_test1.getBoundingClientRect().top;

それを、移動時の座標として計算に入れている点です。

		element_test1.style.left = event.pageX - margin_x + 'px';
		element_test1.style.top = event.pageY - margin_y + 'px';

pageX、pageY、を使わずに、clientX、clientY、でも同様の動きになりますが、
画面上での座標計算と、DOM上での座標計算の違いがあり、ページをスクロールした上でドラッグする際(つまり、画面の初期表示外へドラッグする際)に挙動が違ってきます。

実際の開発シーンでは、どんなDOM上で何をどこまでドラッグするのか、という設計次第で使うプロパティを分けるとよいと思います。

マウスイベントとキーボードの修飾について

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

マウスイベントとキーボードの修飾について

前回は、マウスイベントについての投稿をしましたが、
サンプルではマウスイベントのみを試してみました。

実際のアプリケーションではマウスイベントと同時に、キーボードの修飾キーを組み合わせて
アプリケーションの挙動を変えるシーンがあります。

修飾キーは「Ctrlキー」「Shiftキー」「Altキー」「メタキー」あります。
メタキーは、windowsでいうとウィンドウズキー、macでいうとコマンドキーです。

具体的にマウスイベントと修飾キーの組み合わせをどのように書くのか、試してみます。
下記のHTMLを用意しました。

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

<body>

<div id="test1" style="width: 1000px; height: 900px;">
	<div style="position:fixed; top:10px;">

		<div>clientX :<span id="client_x"></span></div>
		<div>clientY :<span id="client_y"></span></div>
		<div>screenX:<span id="screen_x"></span></div>
		<div>screenY:<span id="screen_y"></span></div>
		<div>pageX:<span id="page_x"></span></div>
		<div>pageY:<span id="page_y"></span></div>
		<div>offsetX:<span id="offset_x"></span></div>
		<div>offsetY:<span id="offset_y"></span></div>

		修飾キー
		<div>Ctrl:<span id="ctrl"></span></div>
		<div>Shift:<span id="shift"></span></div>
		<div>Alt:<span id="alt"></span></div>
		<div>Meta:<span id="meta"></span></div>

	</div>
</div>

<script type="text/javascript">

	let element_test1 = document.getElementById("test1");

	element_test1.onmousemove = function(event) {

		// イベントオブジェクトから座標を取得
		let client_x = event.clientX;
		let client_y = event.clientY;
		let screen_x = event.screenX;
		let screen_y = event.screenY;
		let page_x = event.pageX;
		let page_y = event.pageY;
		let offset_x = event.offsetX;
		let offset_y = event.offsetY;

		// キー情報を取得する
		let ctrl = event.ctrlKey;
		let shift = event.shiftKey;
		let alt = event.altKey;
		let meta = event.metaKey;

		// x座標出力用の要素を取得
		let ele_client_x = document.getElementById("client_x");
		let ele_client_y = document.getElementById("client_y");
		let ele_screen_x = document.getElementById("screen_x");
		let ele_screen_y = document.getElementById("screen_y");
		let ele_page_x = document.getElementById("page_x");
		let ele_page_y = document.getElementById("page_y");
		let ele_offset_x = document.getElementById("offset_x");
		let ele_offset_y = document.getElementById("offset_y");

		let ele_ctrl = document.getElementById("ctrl");
		let ele_shift = document.getElementById("shift");
		let ele_alt = document.getElementById("alt");
		let ele_meta = document.getElementById("meta");

		// 要素を更新する
		ele_client_x.innerHTML = client_x;
		ele_client_y.innerHTML = client_y;
		ele_screen_x.innerHTML = screen_x;
		ele_screen_y.innerHTML = screen_y;
		ele_page_x.innerHTML = page_x;
		ele_page_y.innerHTML = page_y;
		ele_offset_x.innerHTML = offset_x;
		ele_offset_y.innerHTML = offset_y;

		// キー情報を表示する
		ele_ctrl.innerHTML = ctrl;
		ele_shift.innerHTML = shift;
		ele_alt.innerHTML = alt;
		ele_meta.innerHTML = meta;

	};

</script>

</body>
</html>

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

画面にアクセスして、マウスイベントが発生した際に、
キーボードの「Ctrlキー」「Shiftキー」「Altキー」「メタキー」を押すと修飾キーの
画面上に「true」と表示されます。(何も押していないキーについてはfalse表示されます)

Ctrlキーを押した状態の出力 (Firefoxの表示)

Ctrlキーを押した状態の出力 (Chromeの表示)

この時、メタキーについては、OSやその他のブラウザ以外のアプリケーションで使われていることが多く、
Chromeではキー情報が取得できていますが、Firefoxではキー情報が取得できておりません。

また、複数のキーを同時に押しても、押したキーについて「true」と表示されることがわかります。

Ctrlキー + Shiftキー + Altキー + メタキー を同時に押した状態の出力 (Firefoxの表示)

Ctrlキー + Shiftキー + Altキー + メタキー を同時に押した状態の出力 (Chromeの表示)

ブラウザの違いで、メタキーの取得ができないことがあるので、
アプリケーションを開発する際には注意が必要です。

マウスイベント

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

マウスイベント

マウスイベントについて試してみます。

マウスイベントの種類には以下のものがあります。

click
contextmenu
dblclick
mousedown
mouseup
mousemove
mouseover
mouseout
mouseenter
mouseleave

それぞれイベント伝播はバブリングします(mouseenter、mouseloaveは例外)。
マウスイベントの内容は以下になります。

click
マウスボタンを押した後、要素をアクティブ(活性化)した時に発生

contextmenu
右クリック時のコンテキストメニューを表示

dblclick
ダブルクリック時に発生

mousedown
マウスボタンを押した時に発生

mouseup
マウスボタンを離した時に発生

mousemove
マウスを動かした時に発生

mouseover
マウスが要素の上に入ったときに発生

mouseout
マウスが要素の上から離れたときに発生

mouseenter
マウスが要素の上から離れたときに発生(バブリング無し)

mouseleave
マウスが要素の上から離れたときに発生(バブリング無し)

イベントハンドラの引数のイベントオブジェクトには以下のプロパティが定義されています。

ブラウザ全体のX座標、Y座標
clientX
clientY

スクリーン(ディスプレイ)全体のX座標、Y座標
screenX
screenY

ページ全体のX座標、Y座標
  縦や横に長いページで、ブラウザのスクロールが最下部にある場合でも、
  ページの左上を基点として座標を取得します。
pageX
pageY

要素のX座標、Y座標(※)
offsetX
offsetY

※…要素が親+子+孫でネストされている場合は、
    親要素に登録したイベントハンドラがイベント伝播します。
    マウスが孫要素の上にある場合は、その孫要素のX座標、Y座標になります。

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

イベントの登録は「DOM要素プロパティにイベントハンドラを登録する」方法で書いていますが、
これは実際には他の方法でイベントハンドラを登録する方法もあります。(後述します)

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

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

<body>

<div id="test1" style="width: 5000px; height: 5000px;">
	<div style="position:fixed; top:10px;">
		<div>clientX :<span id="client_x"></span></div>
		<div>clientY :<span id="client_y"></span></div>
		<div>screenX:<span id="screen_x"></span></div>
		<div>screenY:<span id="screen_y"></span></div>
		<div>pageX:<span id="page_x"></span></div>
		<div>pageY:<span id="page_y"></span></div>
		<div>offsetX:<span id="offset_x"></span></div>
		<div>offsetY:<span id="offset_y"></span></div>
	</div>
</div>

<script type="text/javascript">

	// 方法① DOM要素プロパティにイベントハンドラを登録する
	let element_test1 = document.getElementById("test1");

	element_test1.onmousemove = function(event) {

		// イベントオブジェクトから座標を取得
		let client_x = event.clientX;
		let client_y = event.clientY;
		let screen_x = event.screenX;
		let screen_y = event.screenY;
		let page_x = event.pageX;
		let page_y = event.pageY;
		let offset_x = event.offsetX;
		let offset_y = event.offsetY;

		// x座標出力用の要素を取得
		let ele_client_x = document.getElementById("client_x");
		let ele_client_y = document.getElementById("client_y");
		let ele_screen_x = document.getElementById("screen_x");
		let ele_screen_y = document.getElementById("screen_y");
		let ele_page_x = document.getElementById("page_x");
		let ele_page_y = document.getElementById("page_y");
		let ele_offset_x = document.getElementById("offset_x");
		let ele_offset_y = document.getElementById("offset_y");

		// 要素を更新する
		ele_client_x.innerHTML = client_x;
		ele_client_y.innerHTML = client_y;
		ele_screen_x.innerHTML = screen_x;
		ele_screen_y.innerHTML = screen_y;
		ele_page_x.innerHTML = page_x;
		ele_page_y.innerHTML = page_y;
		ele_offset_x.innerHTML = offset_x;
		ele_offset_y.innerHTML = offset_y;

	};

</script>

</body>
</html>

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

画面にアクセスすると、画面上には

clientX :130
clientY :190
screenX:594
screenY:545
pageX:130
pageY:690
offsetX:122
offsetY:682

※・・・数値は、サンプル用のダミーです。

のように各プロパティに応じた、マウスカーソル位置のX座標、Y座標が表示されます。

この例では、pageX、pageYの値を確認しやすいように、サンプル用にHTMLドキュメントのdivタグを

<div id="test1" style="width: 5000px; height: 5000px;">

というように大きく領域をとっています。

画面の下、あるいは右方向に大きくスクロールをして、
pageXとpageYを確認すると、「ページ全体から」のX座標、Y座標を取得していることがわかります。
(offsetX、offsetYもそうですが、これは要素を基準にしているので、別な考え方をします)

また、clientX、clientY、screenX、screenYの値がスクロールしても、
常に「ブラウザ全体」「スクリーン(ディスプレイ)全体」からの座標を取得していることがわかります。

プロパティの違いにより、取得できる値が異なるのでプログラミングする際には注意が必要です。

また、addEventListenerでイベントハンドラを登録する方法も試してみます。
以下のHTMLを用意しました。

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

<body>

<div id="test1" style="width: 5000px; height: 5000px;">
	<div style="position:fixed; top:10px;">
		<div>clientX :<span id="client_x"></span></div>
		<div>clientY :<span id="client_y"></span></div>
		<div>screenX:<span id="screen_x"></span></div>
		<div>screenY:<span id="screen_y"></span></div>
		<div>pageX:<span id="page_x"></span></div>
		<div>pageY:<span id="page_y"></span></div>
		<div>offsetX:<span id="offset_x"></span></div>
		<div>offsetY:<span id="offset_y"></span></div>
	</div>
</div>

<script type="text/javascript">

	// 方法② addEventListenerメソッドでイベントハンドラを登録する
	document.getElementById('test1').addEventListener('mousemove', function(e) {

		// イベントオブジェクトから座標を取得
		let client_x = event.clientX;
		let client_y = event.clientY;
		let screen_x = event.screenX;
		let screen_y = event.screenY;
		let page_x = event.pageX;
		let page_y = event.pageY;
		let offset_x = event.offsetX;
		let offset_y = event.offsetY;

		// x座標出力用の要素を取得
		let ele_client_x = document.getElementById("client_x");
		let ele_client_y = document.getElementById("client_y");
		let ele_screen_x = document.getElementById("screen_x");
		let ele_screen_y = document.getElementById("screen_y");
		let ele_page_x = document.getElementById("page_x");
		let ele_page_y = document.getElementById("page_y");
		let ele_offset_x = document.getElementById("offset_x");
		let ele_offset_y = document.getElementById("offset_y");

		// 要素を更新する
		ele_client_x.innerHTML = client_x;
		ele_client_y.innerHTML = client_y;
		ele_screen_x.innerHTML = screen_x;
		ele_screen_y.innerHTML = screen_y;
		ele_page_x.innerHTML = page_x;
		ele_page_y.innerHTML = page_y;
		ele_offset_x.innerHTML = offset_x;
		ele_offset_y.innerHTML = offset_y;

	});

</script>

</body>
</html>

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

画面にアクセスすると、画面上にはtest1.htmlと同様の結果になることがわかります。

注意が必要な点は、
addEventListenerの時には、イベントハンドラ登録は「mousemove」と書きましたが、
DOM要素へのイベントハンドラ登録は「onmousemove」と書いている点です。

DOMContentLoadedイベント、loadイベント、readystatechangeイベント

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

DOMContentLoadedイベント

DOMContentLoadedイベントはドキュメントの読み込み、HTMLのパース、DOMの解釈(DOMツリーの構築)、
遅延実行スクリプトの実行が完了したら発生します。

プログラムからDOMContentLoadedを利用する方法はjavascriptのソース内でaddEventListenerの登録で使用できます。
ここで注意が必要なのは、DOMContentLoadedイベントはdocumentオブジェクトのイベントです。

loadイベント

loadイベントはwindowオブジェクトのイベントです。
DOMContentLoadedイベントと似ていますが、このloadイベントはページ全体が画像、CSSを含めて読み込まれた時に発生するイベントです。

readyStateプロパティ

ドキュメントをロードするとき、document.readyStateプロパティはドキュメントの状態を示します。
プログラム内では下記のように書いてその状態を取得します。

let state = document.readyState

この状態については、

loading        読み込み中
interactive    HTMLパース、DOMの解釈は完了、画像、css等のリソースは読み込み中
complete       HTMLパース、DOMの解釈、その他リソースの読み込みが完了

readystatechangeイベント

上記のイベントとは別に、HTML5ではreadystatechangeイベントを標準化しています。
readystatechangeはdocument.readyStateの値が変更したタイミングで発火します。
loadイベントの直前に発生します。

サンプル

実際の動きをHTMLを用意して試してみます。

まず、DOMContentLoadedイベントの動きをみてみます。

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

<script type="text/javascript">

	console.log("DOMContentLoaded state -> " + document.readyState);

	// DOMContentLoadedイベント
	document.addEventListener('DOMContentLoaded', function() {
		console.log("DOMContentLoaded イベント発火");
		console.log("画面の読み込み完了(DOMContentLoaded)");
		console.log("DOMContentLoaded state -> " + document.readyState);
	}, false);
</script>

<body>

<div>
	<div>
		DOMContentLoadedテスト<br />
	</div>
</div>

</body>
</html>

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

上記のHTMLにアクセスし、console.logを確認すると、次のように出力されます。

DOMContentLoaded state -> loading
DOMContentLoaded イベント発火
画面の読み込み完了(DOMContentLoaded)
DOMContentLoaded state -> interactive

では、次にloadイベントの動きをみてみます。

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

<script type="text/javascript">

	console.log("load state -> " + document.readyState);

	// loadイベント
	window.addEventListener('load', function() {
		console.log("load イベント発火");
		console.log("画面の読み込み完了(load)");
		console.log("load state -> " + document.readyState);
	}, false);
</script>

<body>

<div>
	<div>
		loadテスト<br />
	</div>
</div>

</body>
</html>

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

上記のHTMLにアクセスし、console.logを確認すると、次のように出力されます。

load state -> loading
load イベント発火
画面の読み込み完了(load)
load state -> complete

最後にreadystatechangeイベントの動きをみてみます。

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

<script type="text/javascript">

	console.log("readystatechange state -> " + document.readyState);

	// readystatechangeイベント
	document.addEventListener('readystatechange', function() {
		console.log("readystatechange イベント発火");
		console.log("readystatechange state -> " + document.readyState);
	}, false);

	// DOMContentLoadedイベント
	document.addEventListener('DOMContentLoaded', function() {
		console.log("DOMContentLoaded イベント発火");
		console.log("画面の読み込み完了(DOMContentLoaded)");
		console.log("DOMContentLoaded state -> " + document.readyState);
	}, false);

	// loadイベント
	window.addEventListener('load', function() {
		console.log("load イベント発火");
		console.log("画面の読み込み完了(load)");
		console.log("load state -> " + document.readyState);
	}, false);

</script>

<body>

<div>
	<div>
		readystatechange テスト<br />
	</div>
</div>

</body>
</html>

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

上記のHTMLにアクセスし、console.logを確認すると、次のように出力されます。

readystatechange state -> loading
readystatechange イベント発火
readystatechange state -> interactive
DOMContentLoaded イベント発火
画面の読み込み完了(DOMContentLoaded)
DOMContentLoaded state -> interactive
readystatechange イベント発火
readystatechange state -> complete
load イベント発火
画面の読み込み完了(load)
load state -> complete

ここでわかることは、DOMContentLoadedとloadイベントの処理順がわかります。

最初にloadイベントは、HTMLドキュメント内の画像やCSS等のリソース情報を「完全に」読み込んだ後に実行されます。
DOMContentLoadedはリソース情報の読み込みの前に、HTMLドキュメントのパース、DOMツリーの解析が終わった段階で実行されるので、loadイベントより先に実行されます。

また、readystatechangeについては、各イベントの状態(document.readyState)が変化したタイミングでそれぞれ呼ばれていることがわかります。

イベントのキャンセル

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

イベントのキャンセル

イベントのキャンセルについて試してみます。

イベントのキャンセルは以下の3つの書き方(メソッド)があり、それぞれイベント伝播がキャンセルされます。

preventDefault
stopPropagation
stopImmediatePropagation

キャンセルの方法はそれぞれ異なる動作仕様にもとづいてキャンセルされます。

それぞれのメソッドを詳しくみていきます。

preventDefaultについて

preventDefaultはデフォルトのイベントをキャンセルします。

ここでいうデフォルトとは、「aタグ」の場合、リンクを押下した時の動き(画面遷移)をキャンセルできます。

実際に下記のサンプルを書いて試してみます。

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

<div id="test1l">
	<div>
		<a href="./test1linktest.html" target="blank" id="test1link">リンクテスト1</a>
	</div>
	<div>
		<a href="./test1linktest.html" target="blank" id="test2link">リンクテスト2</a>
	</div>
</div>

<script type="text/javascript">

	// リンク要素のDOMを取得
	let element_test1link = document.getElementById('test1link');

	// 外部関数化せず無名関数で書いてもOK
	element_test1link.addEventListener('click',  testclick1link, false);

	function testclick1link(event)
	{
		// preventDefaultの検証
		alert('testclick1link preventDefault START');
		
		event.preventDefault();
		
		alert('testclick1link preventDefault END');
	}

</script>

</body>
</html>

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

HTMLにアクセスすると、画面上には

<a href="./test1linktest.html" target="blank" id="test1link">リンクテスト1</a>

<a href="./test1linktest.html" target="blank" id="test2link">リンクテスト2</a>

が表示されます。

「リンクテスト1」のほうには、イベントを登録してあり、その中で「preventDefault」メソッドを呼んでいます。
「リンクテスト2」には特にイベントを登録していません。

「リンクテスト1」と「リンクテスト2」をそれぞれクリックしてみると、
「リンクテスト1」のほうはアラートが2回表示され、その後は何も変化しません。(a hrefの機能が抑止されている)

「リンクテスト2」のほうは、イベントは登録していないので、そのまま確認用のテスト用HTMLへ画面遷移することがわかります。

このようにpreventDefault()はDOM要素のデフォルトの動きを抑止する働きがあります。
aタグ以外にもデフォルトのイベントを持つDOM要素は全て同様に抑止されます。

stopPropagationについて

イベントの伝播を抑止します。

同じイベントハンドラが他のDOMに定義されている場合(ネストされている場合)、子要素のイベントの処理で、stopPropagationを呼び出した後は、親要素のイベントハンドラは呼び出されません。
伝播については、キャプチャリングでもバブリングでも自分自身でも抑止されます。

具体的なサンプルを書いて試してみます。
前回の投稿「イベントの伝播(2022/06/10)」では、下記のHTMLを用意しました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
 
<body>
 
<div id="test1p">
    テスト親
    <div id="test1c">
        テスト子
    </div>
</div>
 
<script type="text/javascript">
// 親要素にクリックイベントを登録
let element_test1p = document.getElementById('test1p');
element_test1p.addEventListener('click',  testclick1p, false);
function testclick1p() {
    alert('on p !');
}
 
// 子要素にクリックイベントを登録
let element_test1c = document.getElementById('test1c');
element_test1c.addEventListener('click',  testclick1c, false);
function testclick1c() {
    alert('on c !');
}
 
</script>
 
</body>
</html>

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

画面上の「テスト子」をクリックすると、子要素のクリックイベントが動き、その後に親要素のクリックイベントも動きます。

ではこのサンプルの 子要素側の処理に stopPropagation を呼ぶように変更してみます。
以下のHTMLを用意しました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
 
<body>
 
<div id="test1p">
    テスト親
    <div id="test1c">
        テスト子
    </div>
</div>
 
<script type="text/javascript">
// 親要素にクリックイベントを登録
let element_test1p = document.getElementById('test1p');
element_test1p.addEventListener('click',  testclick1p, false);
function testclick1p() {
    alert('on p !');
}
 
// 子要素にクリックイベントを登録
let element_test1c = document.getElementById('test1c');
element_test1c.addEventListener('click',  testclick1c, false);
function testclick1c(event) {
    alert('on c !');
    event.stopPropagation();
}
 
</script>
 
</body>
</html>

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

テスト子のメソッド内で

event.stopPropagation();

という追記しています。

アクセスして「テスト子」をクリックすると、先程のサンプルとは違い、「on c !」のアラートのみが表示され、
「on o !」のアラートは表示されません。
stopPropagation()が実行されて親要素にイベントが伝播されなくなったことがわかります。

stopImmediatePropagationについて

同じDOM要素に対して、複数の同じイベントハンドラ(処理内容は別)が定義されている場合、
任意のイベント処理で stopImmediatePropagation を呼び出すと、その他のイベントハンドラは呼び出されません。
伝播についてもstopPropagationと同様に抑止されます。

具体的に次のHTMLを用意して試してみます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
 
<body>
 
<div id="test1p">
    テスト親
</div>
 
<script type="text/javascript">

// 要素を取得
let element_test1p = document.getElementById('test1p');

// 要素にクリックイベント1を登録
element_test1p.addEventListener('click',  testclick1p, false);
function testclick1p(event) {
    alert('on p 1 !');
}

// 要素にクリックイベント2を登録
element_test1p.addEventListener('click',  testclick2p, false);
function testclick2p(event) {
    alert('on p 2 !');
}

// 要素にクリックイベント3を登録
element_test1p.addEventListener('click',  testclick3p, false);
function testclick3p(event) {
    alert('on p 3 !');
}

// 要素にクリックイベント4を登録
element_test1p.addEventListener('click',  testclick4p, false);
function testclick4p(event) {
    alert('on p 4 !');
}

</script>
 
</body>
</html>

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

画面にアクセスして「テスト親」をクリックすると「on p 1 !」から「on p 4 !」まで、4回アラートが表示されます。

では、このHTMLに stopImmediatePropagation を追加してみます。
わかりやすいように3回目のアラート後に追記します。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
 
<body>
 
<div id="test1p">
    テスト親
</div>
 
<script type="text/javascript">

// 要素を取得
let element_test1p = document.getElementById('test1p');

// 要素にクリックイベント1を登録
element_test1p.addEventListener('click',  testclick1p, false);
function testclick1p(event) {
    alert('on p 1 !');
}

// 要素にクリックイベント2を登録
element_test1p.addEventListener('click',  testclick2p, false);
function testclick2p(event) {
    alert('on p 2 !');
}

// 要素にクリックイベント3を登録
element_test1p.addEventListener('click',  testclick3p, false);
function testclick3p(event) {
    alert('on p 3 !');
    event.stopImmediatePropagation();
}

// 要素にクリックイベント4を登録
element_test1p.addEventListener('click',  testclick4p, false);
function testclick4p(event) {
    alert('on p 4 !');
}

</script>
 
</body>
</html>

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

クリックイベント3の処理内で

    event.stopImmediatePropagation();

と書くことでイベントの伝播を抑止しています。

画面にアクセスして「テスト親」をクリックすると「on p 1 !」「on p 2 !」「on p 3 !」までアラート表示され、
「on p 4 !」のイベント処理が抑止されていることがわかります。

イベントの伝播

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

イベントの伝播

前回の投稿でイベントの伝播(バブリングとキャプチャリング)について書きました。

バブリングはDOMツリーの各要素でイベントハンドラが呼び出された後、ツリーの親要素のイベントハンドラが呼び出されます。
親要素で登録したイベントハンドラは、その子要素でもイベントハンドラとして機能します。

実際にどんな動きになるのか、試してみます。
下記の簡単なサンプルを用意しました。

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

<body>

<div id="test1p">
	テスト親
	<div id="test1c">
		テスト子
	</div>
</div>

<script type="text/javascript">
// 親要素にクリックイベントを登録
let element_test1p = document.getElementById('test1p');
element_test1p.addEventListener('click',  testclick1p, false);
function testclick1p() {
	alert('on p !');
}

// 子要素にクリックイベントを登録
let element_test1c = document.getElementById('test1c');
element_test1c.addEventListener('click',  testclick1c, false);
function testclick1c() {
	alert('on c !');
}

</script>

</body>
</html>

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

画面上には2つのDOM要素「テスト親」と「テスト子」があります。
親要素には「test1p」、子要素には「test1c」のidを割り当てています。

この時、「テスト親」をクリックすると「on p !」のアラートだけが表示されます。
次に「テスト子」をクリックすると「on c !」のアラートが表示され、続いて「on p !」のアラートだけが表示されます。
(呼び出される順としては 子要素 → 親要素 でした)

このように、親要素で登録したイベントハンドラはネストされている子要素のDOMにも同時に登録されます。

イベントのバブリングについて注意点

バブリングする際の注意点は
・forcus、blur、scrollはDocumentまでバブリングされ、それ以上のDOMにはバブリングされない
・Windowsオブジェクトのloadイベントはドキュメント全体がロードされた時のみに発生する

イベントのキャプチャリングについて

イベントのキャプチャリングは、イベントのバブリングとは伝播する順番が異なります。
Windowオブジェクト→Dobumentオブジェクト→bodyオブジェクト→HTMLのDOM構造(親要素→子要素→孫要素…)、の順でのキャプチャリングハンドラが呼び出されます。
DOM構造がネストされている場合は、最後の末端のDOM要素まで順に呼び出されます。

イベントキャプチャリング

キャプチャリングの際にイベントターゲットにイベントが伝播する前にイベント情報を参照することができます。
具体的な例は別な投稿で試す予定です。

イベントの呼び出し順序

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

イベントの呼び出し順序

要素に対して、複数のイベントハンドラを登録した場合に、その要素にイベントが発生した場合、
全てのイベントハンドラが呼び出されます。

その呼び出し時の動きとしては下記の仕様で順序で呼び出されます。(DOM level 3)

まず、オブジェクトプロパティやHTML属性を設定する方法で登録したイベントハンドラが存在すればそのイベントを呼び、
次にaddEventListener()で登録したイベントハンドラがあれば、登録した順で呼び出されます。

イベントハンドラの引数について

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

イベントハンドラの引数について

イベントハンドラが呼び出される際、引数にイベントオブジェクトが渡されます。
イベントオブジェクトのプロパティは、発生したイベントの種類によって異なります。

イベントの種類によらず共通したプロパティは下記のものがあります。
非推奨、廃止したプロパティは除き、それぞれの詳細な意味は書きません。(実装時に調べる等します)

Event.bubbles
Event.cancelable
Event.composed
Event.currentTarget
Event.defaultPrevented
Event.eventPhase
Event.target
Event.timeStamp
Event.type
Event.isTrusted

イベントハンドラのスコープについて

イベントハンドラはスコープを持ちます。
定義された時のスコープ内で実行されます。

イベントハンドラの戻り値について

オブジェクトプロパティ、HTML属性にイベントハンドラを登録した場合、戻り値が重要です。
submitボタンのonclickイベントに戻り値でfalseを指定するとサブミットの動作を無効化することができます。

addEventListener

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

addEventListener

イベントの登録をaddEventListener()を使って行うことができます。

基本構文は次のようになります。

対象の要素.addEventListener(イベントタイプ, 関数, イベントの伝播の判断(論理値))

対象の要素は、画面上のDOMを指定します。(通常はDOMに記述したidやclassです)

第一引数はイベントタイプを指定します。
イベントタイプは以下のものがあります。

load
DOMContentLoaded
click
mousedown
mouseup
mousemove
keydown
keyup
keypress
change
submmit
scroll

第二引数はイベントが発生した時に実行する関数を書きます。

第三引数は論理値なのでtrueかfalseを指定します。
デフォルトはfalseですが、明示的にfalseを書きます。

まずは簡単な例を試してみます。
下記のHTMLを用意しました。

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

<div id="test1">テスト</div>

<script type="text/javascript">
let element_test = document.getElementById('test1');

// addEventListenerの使用
addEventListener(element_test, testclick, false);

function testclick() {
	alert('on !');
}
</script>

</body>
</html>

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

上記の書き方は第二引数の関数を別途記載した関数名にしていますが、
第二引数の関数を無名関数にしてaddEventListenerの構文の中に記載する方法もあります。

具体的に次のhtmlを用意しました。

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

<div id="test1">テスト</div>

<script type="text/javascript">
let element_test = document.getElementById('test1');

// addEventListenerの使用(その2)
element_test.addEventListener('click', function() {
	// 無名関数として定義、処理
	alert('on !');
}, false);

</script>

</body>
</html>

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

画面上の「テスト」の文字列をクリックすると、最初のサンプルと同様の動きをしてアラートが表示されます。

また、ES2015の書き方としてアロー関数を使う方法もあります。
基本的には無名関数の書き方と似ていますが、関数を呼び出す箇所の書き方がアロー関数の書き方に変わります。

具体的に次のサンプルを書いて試してみました。

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

<div id="test1">テスト</div>

<script type="text/javascript">
let element_test = document.getElementById('test1');

// addEventListenerの使用(その3)
element_test.addEventListener('click', () => {
	// アロー関数として定義、処理
	alert('on !');
}, false);

</script>

</body>
</html>

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

アロー関数を使って書いた場合でも同じ動きをすることがわかります。

addEventListenerの重複登録について

addEventListenerの書き方と動作については上記の通りですが、
イベントを2重に登録する場合は、または一つの要素に複数の処理(関数)をつけたい場合は、
意図した動きと違う結果になるので、注意が必要です。

実際にaddEventListenerを2重に登録すると、どのような動きになるのか試してみます。
上記の例の1番目の登録方法で2重にイベントを登録してみます。

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

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

<div id="test1">テスト</div>

<script type="text/javascript">
let element_test = document.getElementById('test1');

// addEventListenerの使用
element_test.addEventListener('click', testclick, false);

// 2重に登録する
element_test.addEventListener('click', testclick, false);

function testclick() {
	alert('on !');
}
</script>

</body>
</html>

要素「element_test」に対して、関数「testclick」を2回登録しています。

実際のサンプルはサーバ上のHTMLはこちら(test4.html)です。

実際に動かしてみるとわかりますが、画面上の「テスト」の文字列をクリックしても
1回のアラートしか表示されません。

では次に、同じサンプルを改造して、2回目に登録する関数名を変えて別名の関数を書いてみます。

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

<div id="test1">テスト</div>

<script type="text/javascript">
let element_test = document.getElementById('test1');

// addEventListenerの使用
element_test.addEventListener('click', testclick, false);

// 2重に登録する
element_test.addEventListener('click', testclick_sub, false);

function testclick() {
	alert('on !');
}

function testclick_sub() {
	alert('on sub !');
}
</script>

</body>
</html>

実際のサンプルはサーバ上のHTMLはこちら(test5.html)です。

この画面の「テスト」をクリックすると、2つアラートが起動することがわかります。

それでは、上記のサンプル4とサンプル5を、無名関数で書いてみます。

サンプル6として次のHTMLを用意しました。

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

<div id="test1">テスト</div>

<script type="text/javascript">
let element_test = document.getElementById('test1');

// addEventListenerの使用(その2)
element_test.addEventListener('click', function() {
	// 無名関数として定義、処理
	alert('on !');
}, false);

// addEventListenerの使用(その2) 重複登録
element_test.addEventListener('click', function() {
	// 無名関数として定義、処理
	alert('on 2 !');
}, false);

</script>

</body>
</html>

実際のサンプルはサーバ上のHTMLはこちら(test6.html)です。

このサンプル6のテストをクリックすると、無名関数で登録した関数が2回呼び出されることがわかります。

以上のことをまとめると、addEventListenerは、

・同じ要素に、addEventListenerで同じ関数を重複登録しても、1つの処理(関数)のみ動作する
・同じ要素に、addEventListenerで違う関数を重複登録すると、2つの処理(関数)が動作する
・同じ要素に、無名関数で同一の処理を割り当ててると、2つの処理(関数)が動作する

ということが言えます。

イベントハンドラの登録と実行

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

イベントハンドラの登録と実行

ここからは実際にサンプルを書いて、動きを試してみます。

イベントハンドラについて、まずはイベントハンドラの登録をします。
登録方法はaddEventListener()のメソッドか、attacheEvent()のメソッドです。
後者は古いブラウザ(IE)でサポートされており、時期に使われることがなくなる為
原則的にaddEventListenerメソッドを使います。

イベントハンドラ登録の簡単な例を書いて試してみます。
具体的には以下のように書きます。

let element_test = document.getElementById('test1');

element_test.onclick = function() {
	alert('on !');
}

DOM要素である「test1」を参照し、その参照結果を変数に代入したうえで、onclickイベントを登録しています。

HTML全体は以下のようになります。

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

<div id="test1">テスト</div>

<script type="text/javascript">
let element_test = document.getElementById('test1');

element_test.onclick = function() {
	alert('on !');
}
</script>

</body>
</html>

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

アクセスして動きを確認してみると、画面上の「テスト」の文字列をクリックするとダイアログが表示されます。

このイベントハンドラの登録方法は、簡単な書き方ですが、実際に開発を進めるとイベント処理が複雑になってくるケースがあるので別な書き方でイベントハンドラを登録します。(後に投稿します)

イベントハンドラを直接HTML DOM要素内に記述する方法

イベントハンドラの登録方法は、上記サンプルの他、HTMLタグ内に記述する方法もあります。
以下のサンプルを試してみます。

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

<div id="test1" onclick="alert('test1');">テスト1</div>

</body>
</html>

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

動作をみてみると、「テスト1」という文字をクリックするとtest1.htmlと同様に、onclickイベントが動いていることがわかります。

ブラウザ全体に対して作用するイベントハンドラ

これまでのサンプルでは、画面上のDOM要素に対してイベントハンドラを登録して動かしてみました。
DOM要素ではなく、画面(ブラウザ)全体に対して登録するイベントハンドラもあります。

具体的にはwindowに

onafterprint
onbeforeprint
onbeforeunload
oncancel
onhashchange
onmessage
onoffline
ononline
onpagehide
onpageshow
onpopstate
onresize
onshow
onstorage

試しに「onresize」について、サンプルを書いて動かしてみます。

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

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

<div id="test1">テスト</div>

<script type="text/javascript">
// ウィンドウ取得
let window_test = window;

window_test.onresize = function() {
	alert('onresize !');
}
</script>

</body>
</html>

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

実際にアクセスして、ブラウザのサイズを変更するとわかるのですが、
上記のサンプルでは、onresizeのイベントが発生する間隔が短く、ウィンドウサイズをいろいろと変更すると
何度もonresizeイベントが呼ばれていることがわかります(アラートのダイアログが何度も表示されます)。

最近の開発では、実際にはネイティブのjavascriptコードでイベントハンドラの登録や、制御を行うことは非常にすくなく、
外部javascirptライブラリやフレームワークの利用がメインなので、上記の書き方は参考程度にしておいたほうがよいかと思います。

DOMイベント

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

DOMイベント

DOMイベントはW3Cが仕様を策定しており、現在はLevel1からLevel4まで勧告されています。(※)


※・・・DOMとHTMLの仕様策定については、歴史的な背景があり、
W3C(World Wide Web Consortium(ワールド・ワイド・ウェブ・コンソーシアム))という標準化団体がDOM level1、level2、level3を策定していましたが、
その団体とは別にWHATWG(Web Hypertext Application Technology Working Group)という関連技術の開発をするためのコミュニティがW3Cとは別な形でDOMとHTMLの仕様を策定していました。
2019年にW3CとWHATWGが合意し、新しくDOM Living Standard という形で仕様を策定しました。(今現在はこれが事実上の最新バージョンと言えます)

javascriptから操作できるイベントはDOMと密接な関係があり、Levelが増える毎に新しく使えるイベントが増えています(同時に廃止された仕様もあります)

また、各Levelごとに使えるイベントが増える代わりに、それまで書いていた命令より、
新しいLevelの書き方を推奨するケースがあります。

例えばforcusを書く代わりにforcusinにしたり、blurの代わりにforcusoutにするという具合です。
マウスホイールのイベントのwheelや、テキスト入力時のイベントtextinput等も新しく策定されたlevelの命令を書くように推奨されています

HTML5イベント

HTML5と関連するイベントも仕様追加されています。

audioの操作系イベントは下記のメソッドとプロパティがあります。

プロパティ
src
controls
volume
muted
defaultMuted
loop
currentTime
autoplay
ended

メソッド
play()
pause()
pause()

videoの操作系イベントは下記のメソッドとプロパティがあります。

プロパティ
src
poster
width
height
controls
volume
muted
defaultMuted
loop
currentTime
autoplay
ended

メソッド
play()
pause()
pause()

ドラッグ&ドロップもHTML5で追加されたAPIで、定義されているイベントは7つです。

dragstart // ドラッグ開始時
dragend   // ドラッグ継続時
drag      // ドロップ要素に入った時
dragenter // ドロップ要素から出た時
dragleave // ドロップ要素に重なっている時
dragover  // ドラッグ終了時
drop      // ドロップ時

また、ドラッグ操作の際、データを「DataTransferオブジェクト」にセットしています。
このオブジェクト格納するプロパティとしてDataTransferプロパティがイベントオブジェクトとして定義されています。

その他のHTML5イベント

その他のHTML5イベントとしては
オフラインWebアプリケーション、WebStorage(クライアントサイドでjavascriptアプリケーション内の情報を保持する方法)、FileAPI等があります。
今後の投稿で実際に動作を試していきます。

イベントの分類

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

イベントタイプ

webブラウザのバージョンアップに伴い、javascriptで取り扱えるイベントの種類は増えてきました。
また、DOMの仕様を策定するW3Cの活動や、HTML5の策定、
スマートフォン上でのタッチインタフェースの登場等に伴い新しいイベントが増えています。

注意が必要なのは、全てのイベントが全てのブラウザに標準搭載されているわけではなく、
動作するブラウザ環境やデバイスにより、javascriptで書けるイベントが異なる点です。

イベントの分類

イベントは、その内容により大まかに分類ができます。
ここでは分類ごとにざっと把握します。

■デバイス依存の入力系イベント
keydown、keypress、keyup、等のキーボード系イベント、
mousedown、mousemove、mouseup、等のマウス系イベント、
touchmove、等のスマートフォン系イベント
等があげられます。

■デバイス独立の入力系イベント
textinputイベント、等のテキストボックスへ文字入力時に発生する入力イベント

■ユーザインタフェースイベント
HTMLフォーム要素で発生します。
前述のtextinputイベントもそうですが、フォームのセレクトボックスの切り替え時に発生するchangeイベント、
フォームのボタン押下時のsubmitイベント等

■状態変化イベント
ブラウザにオブジェクトがロードされた際のロードイベント、ブラウザの履歴系イベント、サーバ間通信時のイベント、ローカルファイルの読み書きで使用するイベント等

■API固有イベント
HTML5により登場した、ドラッグ&ドロップイベント、映像(video)や音声要素(audio)をとりあつかうイベント

■タイマー、エラー
javascriptの非同期イベント、タイマーやエラー処理等

レガシーイベント

レガシーイベントは古くから実装されているマウス、キーボード、HTMLフォーム、Windowオブジェクト関連のイベントです。
以降、ひとつひとつイベントを試して理解を進めます。

レガシーイベント:フォームイベント

フォームはwebページ内のフォーム要素に対してのイベントです。
HTML要素としてフォームは

<form>

</form>

タグとしてフォーム領域を定義していますが、フォームイベントはフォームに対してユーザが操作したイベントを実行することができます。
具体的にはフォームの送信時にsubmitイベント、フォームリセット時にresetイベント、フォーム要素を操作した時にはclickイベント、チェックボックスやセレクトボックスの操作でchangeイベント、テキストボックスには文字列を入力状態にした時にfocusイベント、入力状態を解除した際にはblurイベント、等のイベントがあります。

レガシーイベント:windowイベント

次にwindowイベントですが、ブラウザに関連したイベントが定義されています。
ブラウザにDOM構造を読み込み終わった(画面に表示された)際に発生するloadイベントや、その反対を意味するunloadイベント、
javascriptの実行時エラーに呼ばれるonerrorイベント、
また、ブラウザ全体ではなく、imgタグの単体のドキュメントに対してもloadイベントがを呼ぶことができます。

また、ブラウザの大きさを変更した場合のreziseイベント、ブラウザ上でスクロールをした時のscrollイベント、等もあります。

レガシーイベント:マウスイベント

画面上でマウスを動かしたり、クリックした際にマウスイベントが発生します。

マウスイベントが発生する箇所(DOM要素)は、マウスポインタのある箇所の要素で発生します。
ネストされている場合は、ポインタが指し示す一番深いDOM要素になります。
イベントオブジェクトにはマウスの座標等の情報が定義されています。

マウス操作のイベントには

mousemove
mousedown
mouseup
mouseover
mousewheel

等のイベントがあります。
上記のうち、mousedownとmouseupはclickイベントも発生します。
また、画面上のDOM要素の同じ箇所を2回clickするとdblclick(ダブルクリック)イベントが発生します。

画面上のDOMに対して、マウスが要素の上に移動した時にはmouseoverイベントが発生します。

mousewheelイベントは、マウスのホイールを回したときのイベントを発生させます。

レガシーイベント:キーイベント

ブラウザに対してキーボードがアクティブになっている場合、キーボードの何かのキーを押下したり離した時にイベントが発生しています。

キーボードイベントは主に以下のものがあります。

keydown
keyup
keypress

keydownはキーボードを押下した時、keyupはキーボードを離した時、keypressはキーボードを押しっぱなしにした時に発生するイベントです。
イベントが発生した時に渡されるイベントオブジェクトは、keyCodeを持ち、どのキーを押したのかという情報が入っています。