こちらが今回作る完成形の自動見積フォームです。
HTMLとCSSで静的なページを作成する
今回はHMTLの冒頭でBootstrapを読み込んでいるので、CSSを書かずにフォームを制作しています。
<!DOCTYPE html>
<html lang=”ja”>
<head>
<meta charset=”UTF-8″>
<meta http-equiv=”X-UA-Compatible” content=”IE=edge”>
<meta name=”viewport” content=”width=device-width, initial-scale=1.0″>
<link href=”https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css” rel=”stylesheet” integrity=”sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC” crossorigin=”anonymous”>
<title>自動見積フォーム</title>
</head>
<body>
<div id=”app”>
<div class=”container bg-dark text-white p-5″>
<h2 class=”text-center border-bottom border-white pb-3 mb-5″>
自動見積フォーム
</h2>
<form>
<div class=”form-group row”>
<label class=”col-md-3 col-form-label pt-0″>制作したいムービ
<span class=”badge bg-danger”>必須</span>
</label>
<div class=”col-md-9″>
<div class=”row”>
<!– ムービーの種類 –>
<!– 余興ムービー –>
<div class=”col-md-5″>
<div class=”form-check form-check-inline”>
<input class=”form-check-input” type=”radio” name=”movie_type” id=”type1″ value=”余興ムービー” checked>
<label class=”form-check-label” for=”type1″>余興ムービー</label>
</div>
</div>
<!– サプライズムービー –>
<div class=”col-md-5″>
<div class=”form-check form-check-inline”>
<input class=”form-check-input” type=”radio” name=”movie_type” id=”type2″ value=”サプライズムービー” >
<label class=”form-check-label” for=”type2″>サプライズムービー</label>
</div>
</div>
<!– 生い立ちムービー –>
<div class=”col-md-5″>
<div class=”form-check form-check-inline”>
<input class=”form-check-input” type=”radio” name=”movie_type” id=”type2″ value=”生い立ちムービー” >
<label class=”form-check-label” for=”type2″>生い立ちムービー</label>
</div>
</div>
<!– オープニングムービー –>
<div class=”col-md-5″>
<div class=”form-check form-check-inline”>
<input class=”form-check-input” type=”radio” name=”movie_type” id=”type3″ value=”オープニングムービー” >
<label class=”form-check-label” for=”type3″>オープニングムービー</label>
</div>
</div>
</div><!–row–>
</div><!–class=”col-md-9–>
</div><!–form-group row–>
<!– 挙式日 –>
<div class=”form-group row”>
<label class=”col-md-3 col-form-label pt-0″ for=”wedding_date”>挙式日
<span class=”badge bg-danger”>必須</span>
</label>
<div class=”col-md-9″>
<input class=”form-control” type=”date” id=”wedding_date” placeholder=”日付をお選びください”>
<small class=”form-text”>結婚式のお日にちを選択してください</small>
</div><!–col-md-9–>
</div><!–form-group row–>
<!–DVD仕上がり予定–>
<div class=”form-group row”>
<label class=”col-md-3 col-form-label pt-0″ for=”delivery_date”>DVD仕上がり予定日
<span class=”badge bg-danger”>必須</span>
</label>
<div class=”col-md-9″>
<input class=”form-control” type=”date” id=”delivery_date” placeholder=”日付をお選びください”>
<small class=”form-text”>挙式日の1週間前までにDVDが必要な場合が多いため、仕上がり予定を挙式日の1週間前に設定しております。</small>
</div><!–col-md-9–>
</div><!–form-group row–>
<!– 小計;基本料金 –>
<div class=”form-group row”>
<label class=”col-md-3 col-form-label pt-0″ >基本料金(税込)</label>
<div class=”col-md-9″>
<input type=”text” class=”form-control text-right” id=”sum_base” value=”32,400″ readonly>
</div><!–col-md-9–>
</div><!–form-group row–>
<!– オプションメニュー –>
<div class=”form-group row”>
<label class=”col-md-3 col-form-label pt-0″>オプションメニュー
<span class=”badge bg-info”>任意</span>
</label>
<!– BGM手配 –>
<div class=”col-md-9″>
<div class=”form-check mb-3″>
<input class=”form-check-input” type=”checkbox” id=”opt1″>
<label class=”form-check-label” for=”opt1″>BGM手配 +5,500円</label>
<small class=”form-text”>当社で曲を手配する場合は、1曲あたり5,500円(税込み)がかかります。</small>
</div>
<!– 撮影手配 –>
<div class=”form-check mb-3″>
<input class=”form-check-input” type=”checkbox” id=”opt2″>
<label class=”form-check-label” for=”opt2″>撮影 +5,500円</label>
<small class=”form-text”>当社に撮影を依頼する場合の料金です(税込み)</small>
</div>
<!– DVF盤面印刷 –>
<div class=”form-check mb-3″>
<input class=”form-check-input” type=”checkbox” id=”opt3″>
<label class=”form-check-label” for=”opt3″>DVD盤面印刷 +5,500円</label>
<small class=”form-text”>DVD盤面をデザインさせて頂き場合は、5,500円(税込み)がかかります。</small>
</div>
<!– 写真スキャニング –>
<div class=”form-row mb-3 align-items-center”>
<div class=”col-auto”>
<label class=”form-check-label” for=”opt4″>写真スキャニング +550円</label>
</div>
<div class=”col-auto”>
<div class=”input-group”>
<input class=”form-control” type=”number” id=”opt4″ value=”0″ min=”0″ max=”30″>
<div class=”input^group-append”>
<label class=”input-group-text” for=”opt4″>枚</label>
</div>
</div>
</div>
<small class=”form-text” >プリントアウトした写真のスキャニングをご希望の方は、1枚当たり550円にて承ります。</small>
</div><!–form-row mb-3 align-items-center–>
</div><!–col-md-9–>
</div><!–form-group row–>
<!– 小計、オプション料金 –>
<div class=”form-group row”>
<label class=”col-md-3 col-form-label pt-0″>オプション料金(税込)</label>
<div class=”col-md-9″>
<div class=”input-group”>
<input type=”text” class=”form-control text-right” id=”sum_opt” value=”0″ readonly>
<div class=”input-group-append”>
<label class=”input-group-text”>円</label>
</div>
</div><!–input-group–>
</div><!–col-md-9–>
</div><!–form-group row–>
<!– 合計:基本料金+オプション料金 –>
<div class=”form-group row”>
<label class=”col-md-3 col-form-label pt-0″>合計(税込)</label>
<div class=”col-md-9″>
<div class=”input-group”>
<input type=”text” class=”form-control text-right ” id=”sum_total” value=”33,000″ readonly>
<div class=”input-group-append”>
<label class=”input-group-text “>円</label>
</div>
</div><!–input-group–>
</div><!–col-md-9–>
</div><!–form-group row–>
</form>
</div><!–container–>
</div><!–app–>
</body>
</html>
JavaScriptで自動計算処理を実装する
変数宣言 | |
var app = document.querySelector(‘#app’); | コンポーネントのルートノード |
var taxRate = 0.1; | 消費税率 |
「イベントハンドラの割り当て」 ページの読み込みが完了したタイミングとDVD仕上がり予定日、オプションの入力内容が変化したタイミングに割り当てます。オプションの「写真スキャニング」はチェックボックスではなく、input type=”number”のスピンボタン付き入力です。そのためchangeイベントではなくinputイベントにハンドラを割り当てます。changeイベントだと入力値を変更しただけではイベントが発生しないからです。 | |
window.addEventListener(‘load’,onPageLoad, false); | ページの読み込み完了イベント |
app.querySelector(‘#delivery_date’).addEventListener(‘change’,onInputChanged,false); | 入力内容変更イベント(DVD仕上がり予定日) |
app.querySelector(‘#opt1’).addEventListener(‘change’,onInputChanged,false); | 入力内容変更イベント(BGM手配) |
app.querySelector(‘#opt2’).addEventListener(‘change’,onInputChanged,false); | 入力内容変更イベント(撮影) |
app.querySelector(‘#opt3’).addEventListener(‘change’,onInputChanged,false); | 入力内容変更イベント(DVD盤面印刷) |
app.querySelector(‘#opt4’).addEventListener(‘input’,onInputChanged,false); | 入力内容変更イベント(写真スキャニング) |
イベントハンドラ | |
function onPageLoad(event){ | ページの読み込みが完了したときの呼び出されるイベントハンドラ |
var wedding_date = app.querySelector(‘#wedding_date’); | 挙式日 |
var delivery_date = app.querySelector(‘#delivery_date’); | DVD仕上がり予定日 |
var dt = new Date(); | 今日の日付を取得 |
dt.setMonth(dt.getMonth() + 2); | 挙式日に2か月後の日付を設定 |
wedding_date.value = formatDate(dt); | formatDate関数にてyyyy-mm-ddの書式で返す |
dt.setDate(dt.getDate() -7) | DVD仕上がり予定日に、挙式日の1週間前の日付を設定 |
delivery_date.value = formatDate(dt); | formatDate関数にてyyyy-mm-ddの書式で返す |
delivery_date.setAttribute(‘min’,tommorow()); | DVD仕上がり予定日に翌日以降しか入力できないようにする。後のtommorow関数にて設定。input type=”date”には入力できる最小をしていするmin属性や、最大値を指定するmax属性が使えるので,setAttribute()関数を使って翌日の日付をyyyy-mm-dd書式にした文字列を設定。 |
updateForm(); } | // フォームの表示を更新する |
関数 | |
function incTax(untaxed){ return Math.floor(untaxed * (1 + taxRate)) } | 税抜き金額を税込み金額に変換する関数 |
function number_format(val){ return val.toLocaleString(); } | 数値を通貨書式「#,###,###」に変換する関数 |
function getDateDiff(dateString1,dateString2){ | 日付の差を求める関数 (基本料金は、今日から納期までの残り日数に応じて割増料金が発生するため「今日の日付」と「DVD」仕上がり予定日の入力値)の差) |
var date1 = new Date(dateString1); var date2 = new Date(dateString2); | 日付を表す文字列から日付オブジェクトを生成 |
var msDiff = date1.getTime() – date2.getTime(); | 2つの日付の差分(ミリ秒)を計算 |
return Math.ceil(msDiff / (1000 * 60 * 60 * 24)); } | 求めた差分(ミリ秒)を日付に換算。差分÷(1000ミリ秒×60秒×60分×24時間) |
function taxedBasePrice(){ | 再計算した基本料金(税込み)を返す関数 |
割増料金 | |
var addPrice = 0; | |
var delivery_date = app.querySelector(‘#delivery_date’); | フォームコントロールを取得(DVD仕上がり予定日) |
var dateDIff = getDateDiff(delivery_date.value, (new Date()).toLocaleString()); | 納期までの残り時間を計算 |
割増料金を求める | |
if (21 <= dateDIff && dateDIff <30){ addPrice = 5000; } | DVD納期が1ヶ月未満 |
else if (14 <= dateDIff && dateDIff < 21){ addPrice = 10000; } | 納期が3週間未満 |
else if (7 <= dateDIff && dateDIff < 14){ addPrice = 15000; } | 納期が2週間未満 |
else if (3 < dateDIff && dateDIff < 7){ addPrice = 20000; } | 納期が1週間未満 |
else if (dateDIff == 3){ addPrice = 40000; } | 納期が3日後の場合 |
else if (dateDIff == 2){ addPrice = 45000; } | 納期が2日後の場合 |
else if (dateDIff == 1){ addPrice = 50000; } | 納期が翌日の場合 |
return incTax(30000 + addPrice); } | 基本料金(税込み)を返す |
function taxedOptPrice(){ | 再計算したオプション料金(税込み)を返す関数 |
var optPrice = 0; | |
フォームコントロールを取得 | |
var opt1 = app.querySelector(‘#opt1’); | BGM |
var opt2 = app.querySelector(‘#opt2’); | 撮影 |
var opt3 = app.querySelector(‘#opt3’); | DVD盤面印刷 |
var opt4 = app.querySelector(‘#opt4’); | 写真スキャニング |
if (opt1.checked){optPrice += 5000;} | BGM手配 |
if (opt2.checked){optPrice += 5000;} | 撮影 |
if (opt3.checked){optPrice += 5000;} | DVD盤面印刷 |
if (opt4.value ==”){opt4 = 0;} optPrice += opt4.value * 500; | 写真スキャニング |
return incTax(optPrice) } | オプション料金を返す |
function formatDate(dt){ | 日付をYYYY-MM-DDの書式で返すメソッド |
var y = dt.getFullYear(); | 変数yに年数 |
var m = (’00’ + (dt.getMonth()+1)).slice(-2); | getMonth()は1からでなく0から数えた月を返すので、今月を表示したいときは1を加算しなければいけません。月が常に2桁の文字列で得られるように、月を表す数値の手前に0を文字列として2つ連結し、その結果をslice()関数で後ろから2文字切り取ると03となります。10月の場合の0010は10となり、1桁の月でも2桁の月でも必ず2桁の結果になります。getFullYear()は4桁で年を返すので、このようなかこうは必要ありません。 |
var d = (’00’ + dt.getDate()).slice(-2); | 例;0010とし後ろから2つ目を切り取り10となる |
return (y + ‘-‘ + m + ‘-‘ + d); } | 書式を返す |
function tommorow(){ var dt = new Date(); dt.setDate(dt.getDate() + 1); return formatDate(dt); } | 明日の日付をYYYY-MM-DDの書式で返すメソッド |
function onInputChanged(event){ | 入力内容を変更したとき呼び出されるイベントハンドラ |
updateForm(); } | フォームの表示を更新する |
function updateForm(){ | 金額の表示を更新する関数 |
フォームコントロールを取得 | |
var sum_base = app.querySelector(‘#sum_base’); | 基本料金(税込み) |
var sum_opt = app.querySelector(‘#sum_opt’); | オプション料金(税込み) |
var sum_total = app.querySelector(‘#sum_total’); | 合計(税込み) |
金額を再計算 | |
var basePrice = taxedBasePrice(); | 基本料金 |
var optPrice = taxedOptPrice(); | オプション料金 |
var totalPrice = basePrice + optPrice; | 合計 |
表示を更新(数値を通貨書式に) | |
sum_base.value = number_format(basePrice); | 基本料金(税込み) |
sum_opt.value = number_format(optPrice); | オプション料金(税込み) |
sum_total.value = number_format(totalPrice); } | 合計(税込み) |
この本を参考に学び、完成させることができました。しかし、プログラミング初心者の私が詳しく解説することは、おこがましく、難しく出来ません(ToT) その点、この本では丁寧な解説が載っていますので、解説とともにコードを書き、完成させればより深く学ぶことができます(^.^) 身に付けて消えないスキルがこの値段。買っておいてよかったです。