この記事は「HTMLでトーナメントの作成」をテーマに、連載のような形でいくつかの記事で一つになるようにしています。
まとめページとして以下の記事をご参照ください。
リンク:トーナメントの作成まとめ記事
DOM要素の配置のしかた
DOM(Document Object Model)要素とは、
要するにHTMLに記述するページのパーツのこと。
本ページではトーナメント表の名前を表示する
テキストボックス ・セレクトボックス を作成できるようにする。
縦書きは難しいので横書きをベースに、左が山の下で
勝ち進むごとに右に上っていくような形にする。
絵にすると下のような感じ(参加者8人の場合)
これが作れたら今回のゴール。
まずはトーナメント一番下のテキストボックスを作る。
本章の目標をいったん一番下のテキストボックスだけにし、
セレクトボックス以降を次回に回します。
テキストボックスの配置
テキストボックスを作るには以下の手順で処理する。
- 入力画面から参加者の情報を受け取る
- 参加人数から作成する個数を求める
- 要素の定義を個数分作成する
- 作成した要素を反映する
テキストボックスの間隔はテキトーに決めといて、
後で調整でいいのでまずは並べていきましょう。
まずは入力画面を整備します。
入力画面
入力画面は参加者のリストと、人数指定でメイン画面に飛べるようにします。
参加者の入力をするために、入力欄を増減できるようにします。
<body>
<div class="inputBlock">
<div class="nameList">
<div class="inputSet">
<input type="text" name="names" value="" placeholder="参加者名" />
<input type="button" value="-" class="del btn">
</div>
<div class="inputSet">
<input type="text" name="names" value="" placeholder="参加者名" />
<input type="button" value="-" class="del btn">
</div>
</div>
<div class="edit">
<input type="button" value="+" class="add btn">
<button class="btn" onclick="openPage();">トーナメントを作る</button>
</div>
</div>
<div id="readBlock">
<label for="numOfPart">参加人数</label>
<input type="text" id="numOfPart" value="" placeholder="参加人数" />
<button class="btn" onclick="openPage();">空欄でトーナメントを作る</button>
</div>
</body>
しかしまだ「+」ボタンを押しても、欄は増えません。
増減ができるように、ボタンに動きを実装しましょう。
<head>
~省略~
<script type="text/javascript">
var fileParam = "";
$(document).on("click", ".add", function () {
var target = $('.inputSet').last();
var copy = target.clone(true);
copy.children('input[type="text"]').val('');
copy.insertAfter(target);
});
$(document).on("click", ".del", function () {
var target = $(this).parent();
if (target.parent().children().length > 2) {
target.remove();
}
});
~省略~
</script>
</head>
.add
クラスをクリックしたときの動きを追加します。
$('.inputSet').last()
でinputSetクラス直下の、一番最後の要素を選択します。
copy
変数にテキストだけ消した=空欄のテキストボックスをクローンし、最後の要素の後ろに追加します。
また、.del
クラスの時は、最低限の入力欄要素が存在するときだけ消すように、要素数のチェックをしています。
表面上の機能はつけたので、メインページに引数付きで移動できるようにしましょう。
後で作りこむので、ここではいったん「3人でトーナメントを作る」設定で
固定にしておきます。
<head>
~省略~
<script type="text/javascript">
~省略~
function openPage() {
window.location.href = './main.html?num=3';
}
</script>
</head>
?num=3
を付け足しました。これで移動後のページでnum
という名前で
3
という値が取得できます。
メイン画面
前回、Canvasのお試しで使ったmain.htmlですが、
今回ちゃんとメイン画面として作成します。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<title></title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" type="text/css" href="./style.css">
<script src="./tournamentJs.js"></script>
<script>
$(window).on('load', function () {
var url = location.href;
readParam(url);
});
</script>
</head>
<body>
<div id="main1" style="position:relative">
<canvas id="canvas1" width="300" height="300">
</canvas>
</div>
</body>
</html>
htmlとしては空のCanvasがあるだけのページを作ります。
var url = location.href;
で自分のURLを読み込んで、
パラメータを取得できるようにします。
readParam
という関数をjsファイルに実装して、必要な機能を実現します。
ここからはJavaScriptがメインになります。
JavaScriptの実装
jsファイルは関数を書いていき、htmlから呼び出すことができます。
関数が動くベース環境は呼び出しているhtmlになるので、
DOM要素もそのページが基準になります。
まずは最初にページを開いたときに動かす関数readParam
を作ります。
// URL分解
// 起動方法判定用
function readParam(url) {
var params = (new URL(url)).searchParams;
if (Array.from(params).length < 1) {
window.location.href = './input.html';
}
// 人数指定
var num = calcExpof2(params.get("num"));
var arr = [];
init(addEmptyToArray(arr, num));
return false;
}
URLを分解して読み込めるようにしています。
引数の数が足りなければ(例えばこっちのHTMLを先に開いたときなど)、入力ページに戻します。
calcExpof2
、addEmptyToArray
、init
という関数を使っています。
それぞれ実装します。
calcExpof2
:引数の数字以上の、最初の2の累乗を計算
addEmptyToArray
:与えられたリストに、引数の個数になるまで空要素追加
init
:参加者のリストから、トーナメント表を初期作成
// 2の累乗計算
// トーナメントの山数計算用
function calcExpof2(num) {
num = Number(num);
if (!(Number.isInteger(num))) {
return -1;
}
var exp = 2;
while (exp < num) {
exp = exp * 2;
}
return exp;
}
// 空要素の追加
// トーナメントの山数調整用
function addEmptyToArray(arr, num) {
var len = arr.length;
for (var i = len; i < num; i++) {
arr.push("");
}
return arr;
}
// 初期処理
// 順次同期をとる(DOM要素を確実に参照するため)
function init(names) {
var dfd = new $.Deferred();
var area = $("#main1");
var cvs = $("#canvas1").get(0);
var cvsArea = getCanvasArea(names.length);
cvs.width = cvsArea[0];
cvs.height = cvsArea[1];
var context = cvs.getContext('2d');
var textboxs = makeTextBox(names);
var defs = [];
textboxs.forEach((value, index, array) => {
defs.push(appendElement(area, value));
});
// $.when.apply($, defs).done(function () {
// makeSelectBox(area, names.length, 1).done(function () {
// initLine(context, names.length, 1);
// var initDef = initSelectBox(names.length);
// $.when.apply($,initDef).done(function () {
// dfd.resolve();
// });
// });
// });
return dfd.promise();
}
init
関数えぐいね、、、
前半部分はCanvasの設定です。
var textboxs ~
から要素を配置しています。
init
関数では以下の関数を使っています。
getCanvasArea
、makeTextBox
、appendElement
、
makeSelectBox
、initLine
、initSelectBox
上3つまででテキストボックスを作ります。
下3つは次回のためコメントアウトしました。
では上の3つを実装します。
getCanvasArea
:Canvasの広さをトーナメントの大きさに合わせて変更します。
makeTextBox
:テキストボックスを表す文字列(の配列)を作ります。
appendElement
:DOM要素を同期をとれるようにして追加します。
// Canvasの広さを計算
function getCanvasArea(length) {
var height = length * 40;
var i = 1;
var t = 0;
while (i < length) {
i = i * 2;
t += 1;
}
var width = (t * 120) + 350;
return [width, height];
}
// トーナメント最下段のテキストボックスを作成
function makeTextBox(names) {
var num = 0;
var arr = [];
names.forEach((val, index, array) => {
num += 1;
var label = $("<label>", {
for: "1-" + num,
text: num,
css: {
width: "50px",
position: "absolute",
left: "10px",
top: ((num - 1) * 40 + 10) + "px"
}
});
var text = $("<input>", {
type: "text",
id: "1-" + num,
class: "member",
size: "20",
value: val,
css: {
position: "absolute",
left: "60px",
top: ((num - 1) * 40 + 10) + "px"
}
});
arr.push(text);
arr.push(label);
});
return arr;
}
// 要素の追加(同期)
function appendElement(main, elem) {
var dfd = new $.Deferred();
main.append(elem);
$(elem).ready(function () {
dfd.resolve();
});
return dfd.promise();
}
getCanvasArea
で設定する広さは、好きに調節してください。
ただ、小さいと線が引けなくなりますし、大きすぎると上限に引っ掛かるらしいです。
makeTextBox
の大きさなどに関わる設定値も、好みでいいです。
ただ、これをもとに線を引く処理を作るので値自体はしっかり把握しておいてください。
appendElement
では”同期処理”というものを取り入れています。
これは何かというと、Javascriptの性質上「命令を出したらその結果を待たないで次の処理に行ってしまう」という"非同期"な部分があります。
これをそのままにするとこの後の処理で
「前の処理で作ったもの・・・出して」
「わ、わかんないっピ・・・」となってしまう可能性があります。
それを事前に防ぐため、処理結果としてひとまず'promise'を渡しておいて、
同期をとる場面になったらそれを見るようにさせます。
すると、その'promise'に対して前の処理がresolve
を実行するまで、
後続を待機させることができるようになります。
※同期についてはセレクトボックスを作るときにまた解説します。
試験動作
では動かしてみましょう。
入力画面での動作を「3人で固定」にしているので、入力値に関わらず「4人の空のテキストボックス」が出てくるはずです。
範囲拡大
動くのが確認出来たら、今度は任意の人数で動くようにします。
URLのnum
に入力した値が入るようにします。
<head>
~省略~
<script type="text/javascript">
~省略~
function openPage() {
var params = "?num=" + $("#numOfPart").val();
window.location.href = './main.html' + params;
}
~省略~
</script>
</head>
input.htmlで、参加人数のidをnumOfPart
としていたので、そのidで値を取るようにして、
文字列として"?num={入力値}"になるようにしてあげます。
では動かしましょう。
参加人数に5といれて動かせば、8人分のテキストが作られるはずです。
これで人数分のトーナメントの足場が作れるようになりました。
今回はこれにて終了とします。
名前を入れて反映させるのは、セレクトボックスが作れてからにします。
(その方が見栄えがいいので(主観))
次回セレクトボックスを上に積んでいくところを作ります。
それができればだいぶトーナメントっぽくなってきます。
以上です。
まとめリンク:トーナメントの作成まとめ記事