20211203のHTMLに関する記事は6件です。

【Themeleaf初心者向け】formにおけるth:fieldの挙動まとめ

はじめに Spring+Thymeleafで開発を行っていて、 formの作成の際、 <input type="radio" th:value="○" th:text="○" th:field="○"> のように、複数Thymeleafタグを使っています。 これって全部書く意味があるのか?必要ないものはないのか? と思ったので、自分で検証してみた結果を以下にまとめます。 概要 inputのtypeによって、th:fieldの挙動が違うので、記載すべきものが違います。 text,number,range th:fieldは、「id」「name」「value」の役割を果たします。 th:fieldとth:valueが重複している時、th:objectで指定したformの値がある場合は、th:fieldが優先されます。 textarea th:fieldは、「id」「name」「text」の役割を果たします。 th:fieldとth:textが重複している時、th:objectで指定したformの値がある場合は、th:fieldが優先されます。 radio,checkbox,select th:fieldは「id」「name」の役割を果たします。 つまり、他2パターンのように「value」や「text」を補うことはできません。 th:valueとth:textは別途指定する必要があります。 前提 ・コントローラークラス コード説明は後述します。 ThFieldTestController.java public class ThFieldTestController { // import文省略 @ModelAttribute private ThFieldTestForm setUpForm() { ThFieldTestForm thFieldTestForm = new ThFieldTestForm(); thFieldTestForm.setGender("2"); return thFieldTestForm; } @RequestMapping("") public String index(Model model) { Map<Integer, String> genderMap = new LinkedHashMap<>(); genderMap.put(1, "woman"); genderMap.put(2, "man"); model.addAttribute("genderMap", genderMap); model.addAttribute("gender", "1"); // ${"gender"}で1を呼べるように return "th-field-test"; } setUpForm()メソッドでは、 thFieldTestForm.setGender("2"); でフォームのgenderの初期値に2を渡しています。 index()メソッドでは、リクエストスコープにデータを格納し、 <○ th:each="gender:${genderMap}" th:○="${gender.key}"> <th:○="${gender}"> のどちらでも値を呼べるようにしています。 この時、マップでないgenderの値は1を渡しています。 ・フォームクラス ThFieldTestForm.java public class ThFieldTestForm { private String gender; public void setGender(String gender) { this.gender = gender; } public String getGender() { return gender; } } 詳細 textの場合 th-field-test.html <div class="text"> <h4>text</h4> リクエストスコープのgender:1<br> formの初期値:2<br> 【text】th:field="*{gender}"<br> <form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}"> <input type="text" th:field="*{gender}"><br> <button>送信</button> </form> 【text】th:field="*{gender}" th:value="${gender}"<br> <form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}"> <input type="text" th:field="*{gender}" th:value="${gender}"><br> <button>送信</button> </form>   th:value="${gender}"が優先されるなら1<br> th:field="*{gender}"が優先されるなら2<br> →th:fieldの入力値保存のほうが強く働いている<br> 【text】th:value="${gender}" <br> <form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}"> <input type="text" th:value="${gender}"><br> <button>送信</button> </form> 【text】th:text="${gender}" <br> <form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}"> <input type="text" th:text="${gender}"><br> <button>送信</button> </form><br> </div> ブラウザだと・・・ コンソールで確認すると・・・ →th:fieldがあると、id,name,valueが入力されている。 (th:fieldとth:valueが重複している時、th:objectで指定したformの値がある場合は、th:fieldが優先される。) (numberとrangeも同じ挙動のため割愛します。) textareaの場合 th-field-test.html   <div class="textarea"> <h4>textarea</h4> リクエストスコープのgender:1<br> formの初期値:2<br> 【textarea】th:field="*{gender}"<br> <form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}"> <textarea th:field="*{gender}"></textarea><br> <button>送信</button> </form> 【textarea】th:id="gender" th:name="gender" th:text="${gender}"<br> <form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}"> <textarea th:id="gender" th:name="gender" th:text="${gender}"></textarea><br> <button>送信</button> </form> 【textarea】th:field="*{gender}" th:value="${gender}" <br> <form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}"> <textarea th:field="*{gender}" th:value="${gender}"></textarea> <button>送信</button> </form> 【textarea】th:value="${gender}" <br> <form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}"> <textarea th:value="${gender}"></textarea> <button>送信</button> </form> 【textarea】th:text="${gender}" <br> <form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}"> <textarea th:text="${gender}"></textarea> <button>送信</button> </form><br> 【textarea】th:field="*{gender}" th:text="${gender}" <br> <form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}"> <textarea th:field="*{gender}" th:text="${gender}"></textarea> <button>送信</button> </form> th:text="${gender}"が優先されるなら1<br> th:field="*{gender}"が優先されるなら2<br> </div> ブラウザだと・・・ コンソールだと・・・ →th:fieldがあると、id,nameが入力されており、textを指定したときと同じ挙動をしている。 (th:fieldとth:textが重複している時、th:objectで指定したformの値がある場合は、th:fieldが優先される。) radioの場合 th-field-test.html <div class="radio"> <h4>radio</h4> リクエストスコープのgender:1<br> formの初期値:2<br> 【radio】th:field="*{gender}" <br> → エラー(org.attoparser.ParseException: Attribute "value" is required in "input(radio)" tags)<br><br> <!-- <form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}"> --> <!-- <span th:each="gender:${genderMap}"> --> <!-- <input type="radio" th:field="*{gender}"><br> --> <!-- </span> --> <!-- <button>送信</button> --> <!-- </form><br> --> 【radio】th:field="*{gender}" th:value="${gender.key}<br> <form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}"> <span th:each="gender:${genderMap}"> <input type="radio" th:field="*{gender}" th:value="${gender.key}"><br> </span> <button>送信</button> </form><br> 【radio】th:value="${gender.key}<br> <form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}"> <span th:each="gender:${genderMap}"> <input type="radio" th:value="${gender.key}"><br> </span> <button>送信</button> </form><br> 【radio】th:value="${gender.key} th:text="${gender.value}"<br> → 送れるけどデータとしてgenderのデータとして受け取られない<br> <form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}"> <span th:each="gender:${genderMap}"> <input type="radio" th:value="${gender.key}" th:text="${gender.value}"><br> </span> <button>送信</button> </form><br> 【radio】th:field="*{gender}" th:text="${gender.value}"<br> → エラー(org.attoparser.ParseException: Attribute "value" is required in "input(radio)" tags)<br><br> <!-- <form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}"> --> <!-- <span th:each="gender:${genderMap}"> --> <!-- <input type="radio" th:field="*{gender}" th:text="${gender.value}"><br> --> <!-- </span> --> <!-- <button>送信</button> --> <!-- </form><br> --> 【radio】th:field="*{gender}" th:value="${gender.key} th:text="${gender.value}"<br> <form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}"> <span th:each="gender:${genderMap}"> <input type="radio" th:field="*{gender}" th:value="${gender.key}" th:text="${gender.value}"><br> </span> <button>送信</button> </form><br> </div> ブラウザだと・・・ コンソールだと・・・ →th:fieldがあると、id,nameが入力されていますが、 別途valueを指定しないとエラーになる。また、textを指定しないとボタンの横に文字が現れません。 th:valueとth:textは別途指定する必要があります。 (checkboxとselectも同じ挙動のため割愛します。) まとめ th:fieldは以下図のように挙動していました。 ・検証に使用したコードは以下に格納しておりますので、よろしければこちらからご確認ください。 https://github.com/MasayukiFurumoto-rks/th-field-test 参考 以下記事を参考にさせていただきました。 ありがとうございました。 ・Thymeleafのth:fieldの意味とSpring Frameworkの値の受け渡しの仕組み (Qiita) ・th:field と th:object によるフォームバインディング機能(inputタグ・基本入力系編) (Hatena Blog) 所感 最後までご覧下さりありがとうございます。 今回、初めてQiita記事を投稿させていただきました。 マークダウン記法もままならないところからのスタートでしたが、アウトプットしていく中で理解が深まることもあり、ぜひまた投稿しようと思います。 もし訂正・加筆すべき箇所がございましたら、コメントにてご教示いただけますと幸いです。 何卒よろしくお願いいたします。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

A-Frameを使ってクリスマスっぽいことをする

はじめに 今回、初めてのアドベントカレンダーに参加します。 ということで 今回は学び始めたA-Frameを使って クリスマスっぽいことをしようと思います! 方法 MDSドキュメントを使おうと思いましたが、 HTMLがうまく実装できず 下記サイトを参考にしました! 画面左「Hello WebVR」>画面右上「VIEW SOURCE」>(少し時間がかかります)画面が表示されたら「view source」を押すと、 ソースコードを取得できます。 ■物体の形 ■背景 ■色の選択 ■プラグイン:夜空の背景を使用するために下記を使用 <script src="https://aframe.io/releases/1.0.3/aframe.min.js"></script> <script src="https://cdn.rawgit.com/matthewbryancurtis/aframe-star-system-component/db4f1030/index.js"></script> ・ターゲットの使用 <script src="https://unpkg.com/aframe-event-set-component@3.0.3/dist/aframe-event-set-component.min.js"> ■円錐 <a-cone position="-1 0.5 -3" color="tomato" radius-bottom="2" radius-top="0.5"></a-cone> ■円柱 <a-cylinder position="-1 3.0 -3" color="#583822" height="1.3" radius="0.3"></a-cylinder> ■正方形 <a-box position="-1 4.0 -3" color="#ba2636" depth="1.0" height="1.0" width="1.0"></a-box> ■背景・星を散らせる <a-sky color="#001e43"></a-sky> <a-entity star-system></a-entity> ■ターゲット作成 <a-camera><a-cursor></a-cursor></a-camera> ■動的処理:下記サイトを参考  今回は、照準にあたると、「文字の出力」と「色の変更」がされるようにしました。 完成 <!DOCTYPE html> <html> <head> <title>Tree</title> <script src="https://aframe.io/releases/1.0.3/aframe.min.js"></script> <script src="https://cdn.rawgit.com/matthewbryancurtis/aframe-star-system-component/db4f1030/index.js"></script> <script src="https://unpkg.com/aframe-event-set-component@3.0.3/dist/aframe-event-set-component.min.js"></script> </head> <body> <a-scene> <!-- ツリー本体 --> <a-cone position="-1 1.9 -3" color="#2f5d50" radius-bottom="0.5" radius-top="0" event-set__enter="_event: mouseenter; _target: #cone-S; visible: true" event-set__leave="_event: mouseleave; _target: #cone-S; visible: false"> <a-text id="cone-S" value="Happy" align="center" color="#FFF" visible="false" position="2 -0.75 0.55" geometry="primitive: plane; width: 0.75" material="color: #333"></a-text> </a-cone> <a-cone position="-1 1.3 -3" color="#2f5d50" radius-bottom="0.8" radius-top="0" height="1.5" event-set__enter="_event: mouseenter; _target: #cone-M; visible: true" event-set__leave="_event: mouseleave; _target: #cone-M; visible: false"> <a-text id="cone-M" value="Merry" align="center" color="#FFF" visible="false" position="2 0.0 0.55" geometry="primitive: plane; width: 1.25" material="color: #333"></a-text> </a-cone> <a-cone position="-1 0.5 -3" color="#2f5d50" radius-bottom="1.1" radius-top="0" height="2.0" event-set__enter="_event: mouseenter; _target: #cone-L; visible: true" event-set__leave="_event: mouseleave; _target: #cone-L; visible: false"> <a-text id="cone-L" value="Christmas" align="center" color="#FFF" visible="false" position="2 0.75 0.55" geometry="primitive: plane; width: 1.75" material="color: #333"></a-text> </a-cone> <a-cylinder position="-1 -0.7 -3" color="#583822" height="2.5" radius="0.3"></a-cylinder> <a-box position="-1 -2.1 -3" color="#ba2636" depth="1.0" height="1.0" width="1.0" event-set__enter="_event: mouseenter; color: yellow" event-set__leave="_event: mouseleave; color: #ba2636"> <a-animation attribute="visible" dur="2000" to="false" repeat="indefinite"></a-animation> </a-box> <!-- 背景と星 --> <a-sky color="#001e43"></a-sky> <a-entity star-system></a-entity> <a-camera> <a-cursor></a-cursor> </a-camera> </a-scene> </body> </html> 詰まった点 ・プラグインしたバージョンの問題で  背景の実装に時間がかかりました。   https://www.npmjs.com/package/aframe-particle-system-component これを参考に npm i aframe-star-system-componentをターミナルで実行。 上記サイトに記載されているものは 古いバージョンでないと反応しませんでした。 おわりに クリスマスツリーは賑やかですが 出し入れが面倒と感じる場合もありますよね そんなときに役立つのではないでしょうか。 いまは少し味気ないクリスマスツリーです。 みんなでわいわい飾りつけしましょ。笑 是非Forkして飾り付けてみてください! Tips ■星を降らせる めちゃめちゃ映えるけど めちゃめちゃ重いため、今回は実装しませんでした。 星を降らせる場合のプラグインは下記の通りです。 これを採用すると<a-box>タグや<a-text>タグが反映されていませんでした... <script src="https://aframe.io/releases/0.7.0/aframe.min.js"></script> <script src="https://unpkg.com/aframe-particle-system-component@1.0.x/dist/aframe-particle-system-component.min.js"></script>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

form内でbuttonのtype属性を書いていなかったら、挙動ではまった話

この記事はビビッドガーデン Advent Calendar 2021の7日目です。 こんにちは、食べチョクを開発している遠藤です。 今回はbuttonのtype属性を書いていないおかげで、謎の挙動にハマりました。 発生していた事象 何が起きていたかを話します。 弊社では、Reactを利用して管理画面を作成しています。 サンプルコードは、以下の通りです。 const handleOnChange = (e) => {   ... } const handleOnClick = (e) => { alert("Enter押すとこいつが反応するんだよな") } ... ... <form> <input type="text" ... onChange={handleOnChange} /> ... ... ... <button ... onClick={handleOnClick}> 追加する </button> </form> text型でEnterキーを押すとなぜか、handleOnClick呼ばれてしまいます? 正直全然見当違いの場所でイベントが発火しているために、e.preventDefault()で対応する方針で行っていましたが、どうもうまくいかなかったです。 調べていくと、本家のReactでもIssueが報告がされていました。 原因 さて、原因です。 buttonのtype属性を書いていないために起こる挙動が原因です。 ここからはリファレンスを参照します。 type このボタンの既定の動作です。以下の値が指定可能です。 submit: このボタンはフォームのデータをサーバーへ送信します。これはこの属性が に関連付けられたボタンに指定されていない場合、またはこの属性が空であったり不正な値であったりした場合の既定値です。 reset: このボタンはすべてのコントロールを初期値に初期化します。 と同様です。 (この動作はユーザーを困らせる傾向があります。) button: ボタンには既定の動作がなく、既定では押されても何も行いません。この要素のイベントを待ち受けし、イベントが発生すると起動されるクライアント側スクリプトを設定することができます。 そう、この挙動です。 submit: このボタンはフォームのデータをサーバーへ送信します。これはこの属性が に関連付けられたボタンに指定されていない場合、またはこの属性が空であったり不正な値であったりした場合の既定値です。 つまり、form内の場合は、Enterを押した場合の送信ボタンになります。 ボタンがサーバーにデータを送信するためのものでない場合は、 type 属性を button に設定することを忘れないでください。さもないと、フォームデータを送信して (存在しない) レスポンスを読み込み、文書の現在の状態を破棄してしまうおそれがあります。 注意文があるぐらい、罠な挙動になっています。 対策 弊社では同じ過ちを繰り返さないためにも、対策を行います。 今回はeslintでbuttonのtype属性がない場合は、指摘するルールがあったので、それを適用しました。 これでこの話は終わりです。 最後に 弊社では一度問題が発生したら、対策していきます。 問題が発生したら、解決策を練ることが好きな人のための求人があります。 よろしくお願いします!!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

マークダウン=HTMLだと思っていた

今まで#とかではなくて全部タグで書いていた こちらの記事は今年のはじめに書いた記事なのですが、マークダウンというものを知らずに、「ああ、HTMLを書くのか」とか思いながら記事を書いていました。 なので、Qiitaで記事書くのめんどくさいなぁなんて思っていたのですが、最近Zennでマークダウンのチュートリアルを見た時に、マークダウン記法便利やなぁとか思ってZennで書いていたのですが、これってQiitaでもできるのではと気づいたのがここ数日の話です。 connpassのページもHTMLタグで書いていたのですが、こちらもマークダウンでいけると気づいた時は発狂しかけました…。 マークダウン、めっちゃ便利よね… HTMLだと文字を挟む必要がありますが、マークダウンだと片方だけで済む!!! この感動は、N予備校のプログラミングコース、Expressを使ったWebアプリ制作の時に導入したpugというテンプレートエンジンを初めて使った時の感動に似ています。(pugもめっちゃ簡単に書けて本当に好き) まとめ マークダウンは便利。 P.S. pug最近使ってないので使いたい。(動く仕組みがわかっていない)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ウェブアクセシビリティ達成基準の覚書

はじめに はじめまして。 オークファン開発部のWEBデザイナーの@af_etoです。 最近ウェブアクセシビリティ界隈の記事やツイートを目にする機会が多く、 様々な視点やアプローチの手法など大変興味深く拝読しております。 その多くは概念的でもあり、断片的な実例だったりするので、 実際に行われる検証基準に沿ってウェブアクセシビリティを覗いていきたいと思います。 JIS X 8341-3 2016 JIS X 8341-3 2016の正式名称は、 「高齢者・障害者等配慮設計指針-情報通信における機器,ソフトウェア及びサービス-第3部:ウェブコンテンツ」。 すべての利用者が、端末やブラウザや支援技術などに関係なく、 ウェブコンテンツを利用できることを目的としています。 8341は「やさしい」の語呂合わせです。 JIS X 8341-3 2016では61の達成基準と3つのレベルがあります。 レベル A:25の達成基準 アクセシビリティ確保に最低限必要なレベル レベル AA:13の達成基準 諸外国でも公的機関に求められるレベル レベル AAA:23の達成基準 レベルAAAを目標とすることは推奨しない 総務省「みんなの公共サイト運用ガイドライン(2016年版)」における 公的機関に対して推奨している「レベルAAに準拠」する達成基準をご紹介したいと思います。 今回は時間の都合上、達成基準の一部を省略して記載しております。 恐れ入りますがご理解のほどよろしくお願いいたします 非テキストコンテンツに関する達成基準 状況A: 短い説明によって、非テキストコンテンツと同じ目的を果たし、同じ情報を提示できる場合 img要素のalt属性を用いる alt属性が記述されているか。 <img src="christmas_tree.png" alt="クリスマスツリー"> object要素のボディに代替テキストを記述する object要素に代替画像や代替テキストが提供されているか。 <object type="application/pdf" data="request_list.pdf"> <p>サンタさんお願いリスト</p> </object> イメージマップのarea要素に代替テキストを提供する area要素に代替テキストが提供されているか。 <img src="scandinavia.png" usemap="#map" alt="北欧"> <map id="map" name="map"> <area shape="rect" coords="300, 0, 450, 30" href="finland.html" alt="フィンランド"> <area shape="rect" coords="150, 0, 300, 30" href="norway.html" alt="ノルウェー"> </map> 非テキストコンテンツに対して、それと同じ目的を果たし、同じ情報を提供する、簡潔な代替テキストを提供する 簡潔にわかりやすくaltが記述されているか。 検索フォームでsubmitボタンが画像(非テキストコンテンツ)の場合、 画像の代替テキストは「虫眼鏡」ではなく「検索」というように、 代替テキストが画像(非テキストコンテンツ)の目的を果たしているか。 隣り合った画像とテキストリンクを同じリンクの中に入れる スクリーンリーダーでは二度読まれることになる。 <a href="/"><img src="santa.png" alt="サンタクロース"></a><a href="/">サンタクロース</a> <!-- ひとつのリンクにする --> <a href="/"><img src="santa.png" alt="">サンタクロース</a> ASCII アート、絵文字、及びリート語に代替テキストを提供する 特殊な文字に代替テキストが提供されているか。 リート語とは「Chopper」を「Ch0pp3r」にしたり「for」を「4」、「to」を「2」にしたりするもの。 <abbr title="ごめんなさい">m(_ _)m</abbr> 画像のグループにある一つの画像に代替テキストを提供して、そのグループのすべての画像を説明する 複数の画像を組み合わせてひとつの意味になるような場合、支援技術が無視できるようにaltは空白にする。 <p>評価: <img src="star_active" alt="5つ星のうちの2つ星"> <img src="star_active" alt=""> <img src="star_disabled" alt=""> <img src="star_disabled" alt=""> <img src="star_disabled" alt=""> </p> 状況B: 短い説明によって、非テキストコンテンツと同じ目的を果たし、同じ情報を提示できない場合 いずれかの方法を用いて、非テキストコンテンツの簡単な説明を提供する、簡潔な代替テキストを提供する a. 非テキストコンテンツに対して、それと同じ目的を果たし、同じ情報を提供する長い説明を提供する b. 短い説明の中で長い説明のある場所を示して、非テキストコンテンツの近くにあるテキストで長い説明を提供する c. 非テキストコンテンツのすぐ隣に別の場所へのリンクを置き、その別の場所で長い説明を提供する 複雑なグラフなどの付近に長い説明文、または説明ページのリンクが提供されているか。 状況C: 非テキストコンテンツがコントロールである、又は利用者の入力を受け入れる場合 送信/実行ボタンとして用いる画像のalt属性を使用する 送信/実行ボタンが画像の場合、代替テキストが記述されているか。 いずれかの方法を用いる a. label要素を用いて、テキストのラベルとフォーム・コントロールを関連付ける b. label要素を用いることができないとき、title属性を用いてフォーム・コントロールを特定する 視覚デザイン上、label要素を用いることができない場合はtitle属性を用いる。 <input type="radio" name="chimney" value="ある" id="yes"><label for="yes">ある</label> <input type="radio" name="chimney" value="ない" id="no"><label for="no">ない</label> <fieldset>電話番号 <input type="text" name="tel01" title="市外局番"> <input type="text" name="tel02" title="市内局番"> <input type="text" name="tel03" title="加入者番号"> </fieldset> <input type="text" title="ここに検索語を入力"> <input type="submit" value="検索"> 状況D: 非テキストコンテンツが時間の経過に伴って変化するメディアである場合 以下のいずれかの方式で、非テキストコンテンツの内容が分かるラベルを提供する a. コンテンツの内容が分かるラベルを提供し、ライブの音声しか含まないコンテンツ及びライブの映像しか含まないコンテンツの目的を説明する b. 非テキストコンテンツの一般に認められた名前又は内容が分かる名前を提供する 音声・映像ファイルが設置されている場合に、内容がわかりやすい名前や説明があるか。 状況E: 非テキストコンテンツが CAPTCHA である場合 代替テキストを提供して、CAPTCHAの目的を説明する 「CAPTCHA」画像にある文字を代替テキストとして記述すると、 ロボット(プログラム)が読みとることができ、本来の目的を果たせなくなってしまうので、 alt="画像に表示された文字列をタイプ入力してください。この後に音声版もあります。のように、 その目的と別形式の「CAPTCHA」の代替バージョンがあることが説明されているか。 同じ目的を果たす、異なる感覚モダリティを用いたもう一つのCAPTCHAがウェブページにあることを確認する 視覚に対応した画像版と聴覚に対応した音声版、複数のバージョンを提供しているか。 状況F: 非テキストコンテンツを支援技術が無視するようにしなければならない場合 支援技術が無視すべき画像のimg要素は、alt属性値を空にして、title属性を付与しない 装飾のためのimg要素にalt属性は空になっているか。title属性が付与されていないか。 <img src="hr.png" alt=""> CSSで指定する画像は、装飾的なものだけである 背景画像などに文字はないか。 収録済みの音声しか含まないコンテンツの場合 状況A: 収録済みの音声しか含まないコンテンツの場合 いずれかの方法を用いる a. 時間の経過に伴って変化するメディアの収録済みの音声しか含まないコンテンツに対して代替コンテンツ(書き起こしテキスト)を提供する b. 時間の経過に伴って変化するメディアの収録済みの音声コンテンツがテキストの代替メディアである場合は、代替メディアであることを明確にラベル付けする 状況B: 収録済みの映像しか含まないコンテンツの場合 いずれかの方法を用いる a. 時間の経過に伴って変化するメディアの映像しか含まないコンテンツに対して代替コンテンツ(書き起こしテキスト)を提供する b. 重要な映像コンテンツを説明する音声を提供する c. 時間の経過に伴って変化するメディアの収録済みの映像コンテンツがテキストの代替メディアである場合は、代替メディアであることを明確にラベル付けする 収録済み音声コンテンツのキャプションに関する達成基準 いずれかの方法を用いる a. オープン・キャプション(常に表示)を提供する b. クローズド・キャプションを提供する c. 時間の経過に伴って変化するメディアの収録済みの音声コンテンツがテキストの代替メディアである場合は、代替メディアであることを明確にラベル付けする オープンキャプション:常時表示される字幕。 クローズドキャプション:表示/非表示を切り替えることができる字幕。 収録済みの映像コンテンツの代替コンテンツ又は音声ガイドに関する達成基準 いずれかの方法を用いる a. 時間の経過の伴い変化するメディアに対して代替コンテンツを提供する b. 音声ガイドを含んだ、利用者が選択可能な副音声トラックを提供する c. 時間の経過に伴って変化するメディアの収録済みの映像コンテンツがテキストの代替メディアである場合は、代替メディアであることを明確にラベル付けする ※映像トラックにある情報のすべてが音声トラックですでに提供されている場合には、音声ガイドを必要としない。 情報及び関係性に関する達成基準 状況A: ウェブコンテンツ技術が、表現によって伝えている情報及び関係性をプログラムが解釈可能にするセマンテックな構造を提供している場合 セマンティックな要素を用いて、構造をマークアップする a. 強調、又は視覚的な装飾個所が特別な意味を持つ場合、その情報がセマンティックなマークアップによって伝えられている。 b. 引用箇所に、blockquote要素が使われている。 c. 参照箇所に、cite要素が使われている。 d. 下付き文字、上付き文字が、sub、sup要素でマークアップされている。 <strong class="fontColorRed">重要なお知らせ</strong> <blockquote cite="https://ja.wikipedia.org/wiki/クリスマス"> <p>イエス・キリストの降誕を記念する祭</p> </blockquote> <span>エタノールの化学式は CH<sub>3</sub>CH<sub>2</sub>OH</span> テキストを用いて、テキストの表現のバリエーションによって伝えている情報を伝達する ローストターキーを太字で表示すると視覚のみで情報が提供されてしまう。 ローストターキー ローストチキン ローストポテト 「オススメ」を表記すれば聴覚での情報も提供される。 ローストターキー(オススメ) ローストチキン ローストポテト(売り切れ) 視覚的なバリエーションが何らかの情報を伝達している場合は、 コンテンツのどこか他の場所でテキストによっても入手可能である必要がある。 情報と構造を表現から分離して、異なる表現を可能にする(CSSを用いて構造と表現を分離する) <p>・ローストターキー<br> ・ローストチキン<br> ・ローストポテト</p> <!-- リストで書く --> <ul> <li>ローストターキー</li> <li>ローストチキン</li> <li>ローストポテト</li> </ul> 色の手がかりを用いる場合には、セマンティックにマークアップする 重要という意味で色を使用するならばマークアップもそれに従う。 <p>赤色で<strong style="color: #ff0000;">必須</strong>と示されている項目は、必須項目ですので必ず入力してください。</p> テーブルのマークアップを用いて、表の情報を提示する tableがあり、table, tr, th, tdを用いてマークアップされているか。 レイアウトに使用されるテーブルの場合は、th要素、又はcaption要素などは必要ない。 <table> <tr> <td class="head">お名前</td> <td class="item"><input type="text"></td> ... <tr> </table> caption要素を用いて、データテーブルの表題とデータテーブルを関連付ける table要素にcaption要素が記述されているか。 <table> <caption>表のタイトル</caption> <tbody> <tr> ... </tr> </tbody> </table> データテーブルに列方向か行方向かあいまいな見出しセルがある場合、scope属性を用いて、見出しセルとデータセルを関連付ける scope=”row” 同じ行(水平方向)のセルを対象とする。 <table> <tr> <th scope="row">くだもの</th> <td>りんご</td> <td>ぶどう</td> </tr> <tr> <th scope="row">野菜</th> <td>トマト</td> <td>玉ねぎ</td> </tr> </table> scope=”col” 同じ列(垂直方向)のセルを対象とする。 <table> <tr> <th scope="col">くだもの</th> <th scope="col">野菜</th> </tr> <tr> <td>りんご</td> <td>トマト</td> </tr> <tr> <td>ぶどう</td> <td>玉ねぎ</td> </tr> </table> 見出し構造が複雑で、scope属性だけでは見出しセルが指定できないデータテーブルでは、id属性及びheaders属性を用いて、データテーブルのデータセルを見出しセルと関連付ける 複雑なテーブルであればheaders属性を使う。 <table> <tr> <th>年度</th> <th id="y2021">2021年</th>... </tr> <tr> <th id="sales">売上</th> <td headers="sales y2021">1億</td> ... </tr> </table> label要素を用いて、テキストのラベルとフォーム・コントロールを関連付ける フォームにはlabel要素が書かれているか。 各要素の前にlabel要素があるか label要素のfor属性値がidと一致しているか label要素のラベルが視覚的に認識できる状態であるか フォームのコントロールがあるグループを形成している場合、fieldset要素及び legend要素を用いて、フォーム・コントロールのグループに関する説明を提供する fieldset要素はフォームの入力項目をグループ化する場合に使用。 グループの先頭には、legend要素でタイトルをつける。 <form> <fieldset> <legend>お客様情報</legend> <label for="name">お名前</label> <input type="text" id="name" name="name"> ... </fieldset> <fieldset> <legend>このサイトをどこで知りましたか?</legend> <input type="radio" id="google" name="google" value="1"> <label for="google">Google</label><br> <input type="radio" id="yahoo" name="yahoo" value="2"><br> <label for="yahoo">Yahoo!</label> ... </fieldset> </form> optgroup要素を用いて、select要素内の option要素をグループ化する <select> <optgroup label="関東"> <option value="東京都">東京都</option> <option value="神奈川県">神奈川県</option> <option value="千葉県">千葉県</option> <option value="埼玉県">埼玉県</option> <option value="茨城県">茨城県</option> <option value="栃木県">栃木県</option> <option value="群馬県">群馬県</option> </optgroup> </select> リストに、ol要素、ul要素、dl要素を用いる、リストのマークアップを用いて、リストの情報を提示する 順序リストはol要素で記述されているか。 順不同リストはul要素で記述されているか。 定義される言葉とその説明はdl要素で記述されているか。 意味のある順序に関する達成基準 コンテンツを意味のある順序で並べる cssを排除しコンテンツをリニアライズ(線形化)し、 順序で並べたコンテンツが、リニアライズする前のコンテンツと同じ意味を示しているか。 単語の文字間にスペースやタグを用いない 単語(日本語だけでない)の文字間にスペース、タグを挿入して整形していないか。 <ul> <li>神無月</li> <li>霜 月</li> <li>師 走</li> </ul> 感覚的な特徴に関する達成基準 理解すべき情報を感覚的にだけ伝えることのないように、テキストでも情報を伝える 「フォームをすべて入力完了後、ページ左側にある四角形の『送信』ボタンを押してください。」などの説明文で、形、大きさ、または位置を使って説明しているすべての言及箇所が、ページ左側にある四角形という情報が無い場合でもボタンを見つけられるように情報が表記されているか。 色の使用に関する達成基準 状況A: ウェブコンテンツ技術が、表現によって伝えている情報及び関係性をプログラムが解釈可能にするセマンテックな構造を提供している場合 色の違いで伝えている情報をテキストでも入手可能にする 異なる色を使用することで違いを表現していないか。 色が消えても違いがわかるようにテキストを表記する。 テキストの色の違いで情報を伝える際は、視覚的な手がかりを補足する 周辺の情報との差別化のために太字やイタリック、下線を使用しているか。 色の違いだけで示されているリンク又はコントロールは、その文字色と周囲にあるテキストとのコントラスト比を 3:1 以上にして、フォーカスを受け取ったときには視覚的な手がかりを補足して強調する リンクの色のコントラスト比は 3:1 以上か。 状況B: 情報を伝える画像の中で色を用いている場合 色とパターンを併用する 画像の中で色の違いを用いている場合には、 色に依存せずに同じ情報を伝達するためのパターン(模様)を併用しているか。 色の違いで伝えている情報をテキストでも入手可能にする フォームに緑の「次へ」と赤の「キャンセル」ボタンある場合、 「入力完後、緑の[次へ]ボタンを押してください」と表記する。 音声制御に関する達成基準 音声の再生を3秒以内に自動的に停止する ウェブページをロードし、自動的に再生される全ての音が3秒、またはそれより早く停止されるか。 スクリーンリーダーの利用者が、音が聞こえないためにコントロールを探せないという問題が起こらないようにする。 自動的に再生される音声を停止するコントロールを、ウェブページの先頭付近で提供する 自動的に再生するメディアを設置している場合、停止ボタンはあるか。 利用者の要求に応じてのみ、音声を再生する (例)ウェブページのBGMが設定されているが自動再生はされず、ヘッダー付近に再生・停止ボタンがあり、 再生ボタンには「BGMを再生する」停止ボタンには「BGMを停止する」と表記する。 キーボード操作に関する達成基準 キーボードがトリガーとなるイベント・ハンドラを提供する ドラッグアンドドロップでしか機能しないコンテンツはないか。 すべての機能がキーボードだけを使ってアクセスできるか。 フォーカスに関する達成基準 ユーザーがコンテンツ内に閉じ込められないようにする Tabキーでコンテンツ内を最初から最後まで移動してみてどこかのコンテンツに閉じ込められてはいないか。 閉じ込められた場合、ぬけ出す方法を説明したヘルプがキーボードで利用できるか。 3回の閃光又は閾値以下に関する達成基準 どの1秒間においても、コンテンツに3回よりも多く閃光を放つコンポーネントがないことを確認する 光感受性発作を引き起こす恐れがあるため。 閃光を放つエリアを十分に小さくする 閃光を放つ領域が視野10度の25%未満であればいい。 1024 x 768ピクセルの中で言えば、341 x 256ピクセル程度。 ブロックスキップに関する達成基準 コンテンツの各セクションの開始位置に見出し要素を提供する 段落を訴える見出しタグが正しく表記されているか。 構造を示す要素を用いて、リンクをグループ化する ナビゲーションがページ上部にある場合、スクリーンリーダーの利用者がナビゲーションバーを飛ばしたり、 そこに含まれる全てのリンクの読み上げを回避したりできるようにする。 <!-- リンクの羅列ではなく --> <a href="">ホーム</a> <a href="">事業内容</a> <a href="">会社概要</a> <!-- グループ化する --> <ul> <li><a href="">ホーム</a></li> <li><a href="">事業内容</a></li> <li><a href="">会社概要</a></li> </ul> フォーカスに関する達成基準 コンテンツ内の順番及び関係に従った順序で、インタラクティブな要素を配置する タブ移動がコンテンツの順序に従っているか。 プログラムが解釈可能な識別名、役割及び設定可能な値に関する達成基準 マークアップを用いて、名前及び役割をユーザーエージェントに提供し、利用者が設定可能なプロパティを直接設定可能にし、変化を通知する ユーザーインタフェース要素が、識別名と役割を決定することができるような適切なマークアップがされているか。 また、全て支援技術から操作することができるように適切にマークアップされているか。 例えば、ハンバーガーメニューやタブはdivやspanで実装しない。 キーボード利用者はそれらにフォーカスを当てることができないので buttonを使用するか、aで実装してrole="botton"を付与する。 おわりに ウェブアクセシビリティ検証基準の一部をご紹介してきました。 その他にもフォームの制限時間やテキストサイズに関する基準などがあります。 さらに見てみたい方は下記リンクが参考になるかと思います。 JIS X 8341-3:2016 試験実施ガイドライン ユーザーに的確に情報を伝えることは時に難しく、視覚的な美しさでは足りず、 その手段と表現方法に最善の解を見い出すことがとても大事なことだと思います。 私もWEBデザイナーとしてそれを体現できているか確信はありませんが、 常に自らの常識を疑いながら、ユーザーへの想像力を育んでいきたいと思っています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iPhoneからでもクロネコヤマトの配達状況をAPIで取得したいんじゃ!~SwiftでHTMLを解析~

初めに M1 ProのMacBook Proを買いました。性能には120%の満足なのですが、デザインだけはどうも受け入れ難いです、どうもこんにちはTOSHです。 でもなんやかんやPS5とは違って、MacBookは抽選販売にならなかったので、よかったです? さて、近年はコロナの影響もあり、ネットで物を買うことが多くなってきているのではないでしょうか? そうなってくると、いつ配達されるのかは気になりますよね?そこで、クロネコヤマトの配達状況のWebページなんかを利用すると思います。しかし、RestAPIのような形式で使えるものが欲しいですよね? では、そういったAPIはないのでしょうか? 業者用のAPIは用意されているようですが、こちらに関しては法人としての登録が必要だったり使用が難しいです。 また、本家クロネコヤマトのアプリが使用してるAPIはどうでしょう? クロネコヤマトのセキュリティに関することになってしまうので、あまり詳しく解説はしないのですが、配達IDを暗号化してクエリに追加しているような形式になっているため、鍵を知らない一般ユーザーがこのAPIを使用するのもどうやら難しそうです。 荷物お問い合わせシステム そうなったら、荷物お問い合わせシステムを使用するしかありません。 このようなシステムを使用して、おり、送り状番号を入力すると、その商品の配達状況を確認することができます。 では、具体的にはこのサービスはどのようにしてリクエストを送信しているのでしょうか? "endpoint": "https://toi.kuronekoyamato.co.jp/cgi-bin/tneko" "methodType": "Post" "query": ["number00": Int, "number01": Int, "number02": Int, "number03": Int, "number04": Int, "number05": Int, "number06": Int, "number07": Int, "number08": Int, "number09": Int, "number10": Int] このようにリクエストをすると、配達状況のデータを持ったHTMLが帰ってきます。クエリはnumber00には1を入れてあげ、その後number01~10まで最大10個の伝票番号を同時に問い合わせることができます。 また、クエリを付与する際には、number00=1&number01=伝票番号のような形式に変更してあげる必要があります。 コードで書くとこんな感じです。 extension Dictionary where Key == String, Value == Int { func equalEncode() -> String { return map { key, value in return key + "=" + String(value) } .joined(separator: "&") } } レスポンスの処理方法 さて、さっきまでの方法で無事リクエストをすることができるようにはなりましたが、問題はレンポンスの形式です。実際に叩いてみるとわかるのですが、これは、jsonが帰ってくるわけではなく、HTML形式でレスポンスが帰ってきます。 なので、クライアント側でそのHTMLを解析して、使いやすい形に変換してあげる必要があります。 まずはModelの作成です。 public struct Tneko: Codable { public var deriveryList: [DeliveryList] public struct DeliveryList: Codable { public var deliveryID: Int public var statusList: [DeliveryStatus] public init(deliveryID: Int, statusList: [Tneko.DeliveryList.DeliveryStatus]) { self.deliveryID = deliveryID self.statusList = statusList } public struct DeliveryStatus: Codable { public var status: String public var date: String public var time: String public var shopName: String public init(status: String, date: String, time: String, shopName: String) { self.status = status self.date = date self.time = time self.shopName = shopName } } } } 先ほども述べた通り、JSON形式でレスポンスが帰ってくるわけではないので、Codableに準拠する必要はないのですが、あくまで、Codableで定義できる型以外を定義しないという意味や他のエンドポイントとの統一性、JSON形式でmockを生成した際のことなどを考えてCodableに準拠しておくことをおすすめします。 では、ここからが鬼門です。HTMLをこのような形のModelへと変更する必要があります。 HTMLは容易にattributedStringへと変換できるので、attributedStringへと変換してしまうのが良いでしょう。 let task = URLSession.shared.dataTask(with: urlRequest) { data, response, error in if let attributedString = try? NSAttributedString(data: data!, options[.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil) { let tneko = Tneko( idList: request.idList(), response: attributedString.string ) completion(.success(tneko)) } else { completion(.failure(.decodeErrror("This is not HTML"))) } } このようにして形式を変更しておくと、TnekoのModelではStringからModelを作成すれば良いということになります。 StringからModelへの変更 先ほどの、HTMLをStringに変更したものを一部抜粋するとこのような形式になっています。 配達完了 このお品物はお届けが済んでおります。 お問い合わせはサービスセンターまでお願いいたします。 お受け取り日時・場所変更をする ※このお荷物は対象外です お届け完了のメール通知を受け取る ※このお荷物は対象外です • 商品名:
宅急便
 • お届け予定日時:
-
 1. 荷物受付
10月27日 11:27
ZOZO支店
 2. 発送済み
10月27日 11:27
ZOZO支店
 3. 輸送中
10月28日 01:35
〇〇ベース店
 4. 配達完了
10月28日 11:30
〇〇センター
 詳細印刷 Yahoo!地図からもお荷物の状況が確認できます。 ※推奨環境はこちら ▲上部に戻る 上記のStringはすべて一行に収まっているので、\nで区切ってあげて、Stringの配列へと変更すると良いでしょう。 ここからは力技です。HTMLの中からルールを見つけ出し、Modelの要素へと変換していきます。変換するコードのイメージは以下の通りです。 extension Tneko { public init(idList: [Int], response: String) { self.deriveryList = idList.enumerated().map { initialIndex, id in let stringList = response.components(separatedBy: "\n") var newStatusList: [Tneko.DeliveryList.DeliveryStatus] = [] var indexCounter = 0 stringList.enumerated().forEach { index, str in if str.contains("お届け予定日時:") { if initialIndex == indexCounter { var counter = 0 let initialStatusGroup = stringList[index + 1].split(separator: "
") if var statusCode = initialStatusGroup[0].split(separator: "\t")[safe: 1]?.description { while statusCode.isValidStatusCode { let newStatusGroup = stringList[index + counter + 1].split(separator: "
") let date = newStatusGroup[1].split(separator: " ")[0].description.replacingOccurrences(of: "月", with: "/").replacingOccurrences(of: "日", with: "") let time = newStatusGroup[1].split(separator: " ")[1].description let shopName = newStatusGroup[2].description let status = Tneko.DeliveryList.DeliveryStatus( status: statusCode, date: date, time: time, shopName: shopName ) newStatusList.append(status) counter += 1 statusCode = stringList[index + counter + 1].split(separator: "
")[0].split(separator: "\t")[safe: 1]?.description ?? "" } } } indexCounter += 1 } } return DeliveryList(deliveryID: id, statusList: newStatusList) } } } extension String { var isValidStatusCode: Bool { return self == "荷物受付" || self == "発送済み" || self == "輸送中" || self == "配達中" || self == "配達完了" || self == "持戻(ご不在)" || self == "配達完了(宅配ボックス)" } } だいぶ、力技の解析にはなってしまっていますが、このようにすると、Modelへと落とし込むことができます。 まとめ HTMLを力技で解析して、クロネコヤマトのWebページから情報を持ってくる方法を紹介しました! 実際に、これを使って作成したのでよかったら使ってみてください!すべてSwiftUIで作成しております。 https://apps.apple.com/jp/app/%E3%82%AF%E3%83%AD%E3%83%8D%E3%82%B3%E9%85%8D%E9%81%94%E7%8A%B6%E6%B3%81/id1585504785
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む