20200206のJavaScriptに関する記事は27件です。

グループ分けアプリを作って護廷十三隊を分けてみた

はじめに

  • 結構な人数で懇親会をしたい
  • テーブルが別れている店を予約してしまった
  • 同じ部署の人は離れて座ってほしい

こんな条件が揃った時に使うグループ分けアプリを作りました。
試しに最近はまったBLEACHに出てくる護廷十三隊をグループ分けしてみます。(まだ全巻読んでいないので初期メンバーで)

デモ

4ステップでグループ分けしていきます
1. 氏名と部署を入れたCSVファイルを読み込む → csvの内容が下に表示される
2. グループ数を入力 + [表を作成]をクリック → グループの数だけ表が作成される
3. [START]をクリック → ビンゴっぽく動く
4. [STOP]をクリック → 各グループに振り分けられる

demo.gif

CSVを読み込んで画面に表示する

こちらの記事を参考に実装しました。
うまく読み取ってくれなかったので<body onLoad='readCsv();'>でページ読み込み時に関数を呼び出しています。
ファイル選択ボタンを他のボタンとデザインを合わせたかったのでinput要素をdisplay:noneにします。

index.html
<body onload='readCsv();'>
  <!-- CSV読み込みボタン -->
  <label>
    <form name='myform'>
      csvファイルを選択
      <input type='file' name='myfile' style='display:none'>
    </form>
  </label>

  <!-- CSVの内容を表示 -->
  <h5 id='num'>人数: 名</h5>
  <table class='table table-striped'>
    <thead>
      <tr>
        <th>氏名</th>
        <th>部署</th>
      </tr>
    </thead>
    <tbody id='tbody'>
    </tbody>
  </table>

</body>
shuffle.js
function readCsv(){
  document.forms.myform.myfile.addEventListener( 'change', function(e) {
    var result = e.target.files[0];
    var reader = new FileReader();
    reader.readAsText( result );
    reader.addEventListener( 'load', function() {
      var memberArray = reader.result.split('\n');
      memberArray.shift();
      var table = $('.table').children('tbody');
      for(var i = 0; i < memberArray.length; i++){
        memberArray[i] = memberArray[i].split(',');
        table.append('<tr>\
          <td>' + memberArray[i][0] + '</td>\
          <td>' + memberArray[i][1] + '</td>\
          </tr>');
      }
      document.getElementById('num').innerHTML = '人数:' + memberArray.length + '';
    });
  });
}

グループの数分の表を作る

result-leftresult-rightという領域を作って、そこに入力したグループの数だけ表を作ります。

index.html
<input id='group' type='text' placeholder='グループ数(半角数字)'>
<button type='submit' onClick='createArea();'>表を作成</button>

<div id='result-left'></div>
<div id='result-right'></div>
shuffle.js
function createArea(){
  var group = parseInt(document.getElementById('group').value);
  var row = $('#tbody').children().length;
  var perGroup = Math.ceil(row / group);

  for(var i = 0; i < group / 2; i++){
    $('#result-left').append('<ul>\
      <li>' + (2 * i + 1) +'</li>\
      </ul>');
    for(var j = 0; j < perGroup; j++){
      $('#result-left').children('ul').last().append("<li class='shuffle-area'></li>");
    }
  }

  for(var i = 0; i < Math.floor(group / 2); i++){
    $('#result-right').append('<ul>\
      <li>' + 2 * (i + 1)  +'</li>\
      </ul>');
    for(var j = 0; j < perGroup; j++){
      $('#result-right').children('ul').last().append("<li class='shuffle-area'></li>");
    }
  }
}

ビンゴっぽく動かす

こちらの記事を参考にビンゴっぽく動かします。
shuffle-areaというクラス(グループの数だけ表を作るときに<li>に設定したクラス)をもつ要素に値をランダムに入れて動かしてる感を出します。

index.html
<button type='button' onClick='start();'>START</button>
shuffle.js
function start(){
  var memberArray = [];
  var row = $('#tbody').children().length;
  for(var i = 0; i < row; i++){
    memberArray.push($('#tbody').find('td').eq(2 * i).text());
  }
  roulette = setInterval(function(){
    var random = Math.floor(Math.random() * memberArray.length);
    var shuffleArea = $('.shuffle-area');
    var count = shuffleArea.length;
    for(var i = 0; i < count; i++){
      shuffleArea.eq(i).text(memberArray[(random + i) % count]);
    }
  }, 10);
}

グループを分けて表示する

動くものをとりあえず作りましたが正直改善の余地しかない。
「同じ部署の人は違うグループにする」という条件を満たすまでランダムにグループ分けをし続けます。グループ数ニアイコール同じ部署の人数の時にwhile文めっちゃまわります...。

index.html
<button type='button' onClick='stop();'>STOP</button>
shuffle.js
function stop(){
  clearInterval(roulette);
  var status = true;
  var group = parseInt(document.getElementById('group').value);
  var memberArray = [];
  var copyArray = [];
  var groupArray = new Array(group);
  var row = $('#tbody').children().length;

  for(var i = 0; i < row; i++){
    var name = $('#tbody').find('td').eq(2 * i).text();
    var department = $('#tbody').find('td').eq(2 * i + 1).text().replace(/\r?\n/g, '');
    memberArray.push([department, name]);
  }

  while(status){
    status = false;
    memberArray = shuffle(memberArray);
    copyArray = memberArray.concat();
    for(var i = 0; i < group; i++){
      groupArray[i] = copyArray.splice(0, Math.ceil(copyArray.length / (group - i)));
      groupArray[i].sort();
    }

    loop:
    for(var i = 0; i < group; i++){
      for(var j = 0; j < groupArray[i].length - 1; j++){
        if(groupArray[i][j][0] != "" && groupArray[i][j][0] == groupArray[i][j + 1][0]){
          status = true;
          break loop;
        }
      }
    }
  }

  for(var i = 0; i < groupArray.length; i++){
    for(var j = 0; j < groupArray[i].length; j++){
      $('ul').eq(i).children('.shuffle-area').eq(j).text(groupArray[i][j][1]);
    }
    if(j < $('ul').eq(i).children('.shuffle-area').length){
      $('ul').eq(i).children('.shuffle-area').eq(j).text(' ');
    }
  }
}

function shuffle(array) {
  for (let i = 0; i < array.length; i++) {
    let rand = Math.floor(Math.random() * (i + 1));
    [array[i], array[rand]] = [array[rand], array[i]]
  }
  return array;
}

TODO

  • グループ分けのアルゴリズムを改良する
  • グループ数が同じ部署の人数より少ない時にアラートを出す
  • 完成したグループをcsv出力する

GitHub

この記事ではhtmlを部分的に記載しているので全体はこちらに載せています。
https://github.com/rntkym/team-division

おまけ

何度かグループ分けをして遊んでたら花太郎が胃痛で死にそうなグループができた

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初学者がつまずくJavaScript

こんにちは。ばーんです

今回は前回行ったfizzbuzzゲームとズンドコキヨシを実践した感想を纏めていきます。
自身のJSレベルがProgateを終えたところなので、かなり苦戦しながら進めました;;

特に自身の経験でつまずいたところを解説していきたいと思います!
それではいってみましょー!^o^/
(fizzbuzzの記事はこちら。ズンドコキヨシの記事はこちら)

はじめに

今回つまずいた6人衆はこちらっ!
「アロー関数、戻り値、引数、配列、オブジェクト指向、テストコード」

解説する(できる)のは前の4つです。あとの2つは概念程度しか理解できていないので、おいおい試して分かり次第記事にしたいと思います。

正直前の4つはProgateで学習してる時も「これやばそうだな…」という感じがむんむん出てたんですよね…
多分、これが理解できん!jQuery面白い^q^ っていう人は多いんじゃないかと。
大丈夫です。JSも面白いです(今のことろ…)

アロー関数

わりと悪の現況だと思ってます(身勝手な恨み辛み)
アロー関数単品なら大丈夫なんなんですが、ここから派生する省略系がやばいです(語彙力も)

アロー関数の省略について分かり易い記事があったのでこちらを是非読んでみてください!

アロー関数自体はよく見かけるので(というかネットに落ちてるやつでfunctionの方が余り見ない)、覚えていた方がいいかと。
省略しすぎは「コードゴルフ」などでよく見かけますが、これは「こういう表記でもできるのか」程度で良いと思います。

※コードゴルフ→プログラミングコンテストの1種。コードをどれだけ短く表記できるか?を競い可読性や保守性などは考慮しない

やっぱり一番いいのは慣れることかなぁと思います。コードを書いて。
自分が動かしたいコードを書きます。簡単で良いので。
そのfunctionを => に変えて実行してみましょう!

function firstComment(){
  console.log("hello world");
}
let firstComment = () => {
  console.log("hello world");
}

上も下も同じ意味ですね。

しかし、自分も書いていて疑問が湧いてきました。

なんでこんなめんどくさい表記すんの??

と思って調べて出てきたのがこちら

こういう歴史的な背景を知るのはいいことですね!
悩んだらfunctionに置き換えれば良いだけなので、ガンガン使っていきましょー。すぐ慣れます。

戻り値

これが正直分かりませんでした。
そして、今もあやふやなので調べてきました;;

これはあれですね。関数と一緒にご説明した方がいいですね(調べてから気付いた)。
調べ方的には「JS 戻り値 とは」でMDNを読むと
「関数を実行した際に返される値」とありました。

ちなみに関数ってなんや?と思って調べたのですが、ネストが深すぎて惨敗してきました…
(純日本人の僕が、日本語の文を読んで理解できなかった。 な ぜ だ。 )

関数と戻り値

ここのポイントは2つです。
①関数の定義と関数の実行は別物
②戻り値や返す がややこしい。出力するの方が日本語的にはリンクします

1つずつ説明していきます。

①関数の定義と関数の実行は別物

これどういうことかと言うと…

function firstComment(){
  console.log("hello world");
}

これって実行すると「hello world」って実行される気しませんか?僕はむちゃくちゃしてました^^
ちなみに実行結果は…
Screen Shot 2020-02-06 at 19.13.12.png
※一応補足しておくと下から2行目の node zundoko~はこのプログラムを実行するものです。
僕の想像であれば実行結果が出ているはずなのですが…

それもそのはず。これは関数を定義しただけです。
(これが理解できてるようで中々理解できていなかった…)

関数の実行はこの場合
firstComment();
が必要です。
Screen Shot 2020-02-06 at 19.25.09.png

関数を実行したい場合は、関数の定義を先に(コード上の上部で)行って、その後実行処理が必要です。

②戻り値や返す がややこしい。出力するの方が日本語的にはリンクします

もちろん正しい言葉で覚えた方が良いのですが、入り口は分かり易い言葉の方がいいのかなと思ってます。
自分と同じくHTML→CSSの流れでやってきた人はこの処理で???となるはず。

僕の体験談なんですが、

function firstComment(){
  console.log("hello world");
}

firstComment();

これを見てもどんな処理が起きるかピンときてませんでした。
とりあえず実行して「おーハローワールドって出るなぁ」と思ってました。

HTMLやCSSに上見て下見て、上見て下見ていう流れの処理が殆どないので…

引数

関数と戻り値とほぼセットで覚えてもらっていいかと思います。
これも正直名前がよくないと思ってます。すごく内容が入ってこなかったんですよね…この引数っていうワードが(というかしばらく いんすう って読んでましたw)。

僕が理解できたのは中学時代のxとかyみたいなモノじゃないか?と思ってからです。
Screen Shot 2020-02-06 at 20.12.42.png
これ先ほどの式で引数を使っただけなのですが、僕が最初に間違えていたのは
「何が引数で、どれが関数で、メソッドで、配列で、変数なのか?」という点です。

前項でも書いたとおり関数の書き方は数種類です。
=> なのか、 function なおか、 const で宣言されているのか。
そして、関数には引数が必ずあります(空欄の場合もある)。

ですので↑の写真の場合、
①関数 firstComment
②引数 greeting
引数に渡す値→"hello world"

です。僕がxとかyに置き換えていたのは②です。
ようは、引数が指定されていたらまず先に何が入るの?と考えるのではなく、関数が実行されているところまで見にいきます。そこに答えがあるので。
xyがなにか?は考えませんよね?その先に答えがあるので。
僕はxyが何か?を考えてよく躓いてました…

配列

連想配列みたいな言葉もあって、何が何だかっていうのが僕の印象でした。
1つずつ整理すると、どんな形であれ配列は「データを格納する箱です」。

昔懐かしで言うとプレステのメモリーカードみたいなもんですねw
(兄弟いる方はわかると思いますがデータの上書きで殴り合いが始まるやつです)

こちらの記事が大変読みやすいので是非ご一読下さい。
https://www.sejuku.net/blog/31746)

const fruit = ["apple" , "banana" , "orange"];
console.log(fruit[0]);

この出力結果は「apple」です。
いやこれ何に使うの?って話だと思うのですが、前回ズンドコキヨシを使った時は、

// calculatorという関数を定義しています。randomという引数を返します
const calculator = (random) => {
  return random;
  }

// zundokoという変数に「ずん」「どこ」というデータを格納しています(配列)
const zundoko = ["ずん" , "どこ"];
// 0か1をランダムで出力します。この値はcalculatorに引き渡されます
var random = Math.floor( Math.random() * 2 );
// 0=ずん、1=どこなのでこれで「ずん」「どこ」のどちらかが出力されます
console.log(zundoko[random]);

といった形で使用しました!(コメントいただき編集しました!ありがとうございます)

他にも配列の中を追加したり、削除したり、比較したりと色々できます!

さいごに

いかがでしたでしょうか?
今回は自分がつまずいて、挑戦して理解が深まった部分を記事にしました。
やはり何か目標を持って作ることは大事ですね。

少しでも初学者の方のお役に立てれば幸いです。

自分で書いていても「違うかな?」「あってるかな?」と思いながら書いていきました。
違うよーというものがあれば遠慮なく教えていただけると助かりますm_ _m

ばーん。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

何も知らないところからAjaxを学んだのでまとめてみた

Ajaxとは何か

AjaxはAsynchronous JavaScript + XMLの略。
非同期でjavascriptとXMLを使ってサーバーとやりとりをするって理解。
サーバーとのやりとり?ってなる方はまずHTTPの仕組みを理解する必要があると思います。(実際のところ僕も大枠しかわかってないので偉そうなことは言えませんが)
このサイトがとても分かりやかったです。
https://app.codegrid.net/entry/ajax-1#toc-5
本なら「WEBを支える技術」です。(デザイナーの僕には難しかったですが)

非同期通信ってなに?

非同期通信は、一部の情報をwebブラウザからサーバーにリクエストを行なってレスポンシブがくることです。
よく言われるのが、サイトで何かアクションをした時に画面が一度白くなるのが同期通信。
サイトが一度も白くならずアクションによって一部変化するのが非同期通信と説明しています。
しかしこれだけだとなぜ白くなるのかよく分かりませんでした。
クライアントとサーバーのやり取りの中でクライアントからHTTPリクエストを送った時に、HTTPレスポンスがサーバーから返ってくるまでずっと待機する必要があります。この状態が真っ白になると言うことです。

その「白くなる」ことがなく、ページの一部が変化させることができるのがAjaxです。
この言い方をすると誤解を招きますが、Ajaxは言語ではなく手法です。

Ajaxのメリットってなんなん?

表示速度が速い

皆さんも経験したことがあると思いますが、ページで何かアクションをした際にずっと真っ白な状態・・・長いな・・・って思うことがあります。それは先ほどお話しした同期通信です。
一部分だけしか変わらないのに同期通信はページ全体を(再)読み込みするため、ページ全体のコードを全て読み込む必要があります。
非同期通信はリクエストを送った後、待機せずに別の処理を進めていて、レスポンスが返ってきたら処理を再開することができるし、ページ全体ではなくアクションをした時の一部分だけを読み込むため表示速度が速い。

自分の言葉で伝えると
僕の言葉でまとめるとAjaxを使用することで操作性が向上し、ユーザーにストレスを与える機会を減らせるものだと思っています。ユーザーの体験をよりよくし、「使いやすいサイトだな」と思ってもらえることは再訪やLTVにも繋がる重要なものだと思っています。
GoogleMapがいい例で、今では非同期通信のためマップの位置をずらしてもなんの変化もありませんが、昔はマップの位置を変更すると一度真っ白になってから読み込みを行い表示されます。めちゃストレスですよね。
このように、Ajaxを利用することは操作性を向上させ、ユーザーにストレスを与える機会を減らせることができるの1つがAjaxだと思います。

ここで感じた疑問を @sho0211 に投げかけてみた

Ajaxに全く知識がなかった中で疑問に感じたことはこの2点です。

・一部の情報をwebブラウザからサーバーへリクエストするのは同期通信も一緒。
 なぜ真っ白にならないの?ロジックが知りたい。
・じゃあ全部非同期にすればええやん

こちらはあとで記事にしれくれるそうなので、完成したら貼っておきます。

Ajaxの仕組み

こちらに関しては@hisamura333さんのhttps://qiita.com/hisamura333/items/e3ea6ae549eb09b7efb9
記事を参考にしています。とても分かりやすいのでぜひご覧ください。

色々調べる中で僕がそうなんだ!と思ったのは、Ajaxは「昔ながらの技術、そして新しい工夫」だと言うこと。
なんかかっこいいですよね。笑 最近の技術だと思っていたら昔ながらの技術を工夫してできているのかと言う真実は自分の中で強く印象に残っています。

さて、Ajaxの仕組みは大きく分けて4つの技術が合わさってできています。

・XMLHttpRequest
・JavaScript
・DOM
・XML

XMLHttpRequest

XMLHttpRequestはこのような形で作成できます。

<script language="javascript" type="text/javascript">
 var xmlHttp = new XMLHttpRequest();
</script>

IBM Developerより

これがサーバーとの通信全てを処理するもの(HTTP通信)でAjaxの基幹技術です。
WEB上で実行されるJavaScriptからwebサーバーへHTTPリクエストを送信して、サーバーから返ってきたHTTPレスポンスをページ上に反映させるものです。
XMLHttpRequestを行うことで、JavaScriptエンジンからサーバーへリクエストが行えるため非同期通信ができます。
ちなみにこれはJavaScriptのAPIです。

JavaScript

XMLHttpRequestはJavaScriptの組み込みオブジェクトです。
AjaxとはAsynchronous JavaScript + XMLの略といったように、JavaScriptを使わないと実装できません。
Ajaxのソースコードは基本JavaScriptで構成されています。

DOM

Ajaxを使用して動的なWebページを作成するときにHTML・XML上のどの要素を変更するか指定するために使われます。
DOMはHTML・XMLをツリー構造として展開して、アプリケーション側に文章の情報を伝え、加工や変更をしやすくします。

XML

XMLは、Extensible Markup Languageの略。
タグを自由に設定して、そのタグに対して意味づけを行うものです。
HTMLもXML形式なので似たようなものと説明されますが、HTMLタグにも意味があるようにXMLも同じです。

<お魚料理>
 <レシピ>
  <魚>鯖</魚>
  <調味料>塩</調味料>
 </レシピ>
</お魚料理>

データのやりとりでXMLを使えば、複数のデータを同時にやりとりしてもどのデータがどの要素なのか一発で判定できます。
今ではXMLよりJSONがよく使われており、XMLじゃないといけないと言うわけではありません。
余談ですが、僕の記事では魚料理がよく出るのは釣りが大好きだからです。

Ajaxのまとめ

・ユーザーがアクションをする(イベント)
・Javascript+XMLHttpRequestでサーバーに対してリクエストを送信①
・サーバー処理が走る②
・処理結果をJSONやXMLなどの形式でレスポンス
・その情報をもとにDOMを操作しページを更新する③

①サーバーに対してリクエストをするときに必要なものだけリクエストをしていること
②サーバーからレスポンスがくるまで、違う処理を実行できる
③必要な部分だけ更新するので白くならない

と言うことです。
同期通信はブラウザからサーバーへリクエストをしてその処理が返ってきたタイミングでページ全てを新しい内容に置き換えますが非同期通信は、必要な情報をブラウザからサーバーへリクエストして必要な情報だけJavaScriptが受け取りDOMを変えます。
加えて、リクエストを送ってサーバーが処理している間は同期通信は待機するけど、非同期通信は待機せず別の処理を行います。

概要を理解したので書いてみました(jQuery)

利用させていただくのは、郵便番号検索APIです。(http://zipcloud.ibsnet.co.jp/doc/api)
イメージとしては、7桁の番号を記入して検索ボタンを押すと外部APIへリクエストを送り返ってきたデータ(住所や郵便番号)を反映させます。
ということでまずはinputと反映した時のHTMLを作ります。

事前準備

  <h1>郵便番号</h1>
  <input type="text" id="zipcode" value="" maxlength="7">
  <input type="button" id="search_btn" value="検索">
  <p id="postCode">郵便番号:[ここに返ってきたデータを入れる]</p>
  <p id="address">住所:[ここに返ってきたデータを入れる]</p>
  <p id="caution"></p>
  <p id="always"></p>

スクリーンショット 2020-02-11 15.29.33.png
コードと見た目はこんな感じです。
ここまでできたらjQueryでAjax処理を書いていきます。

jQueryでAjax処理を書いていく

<script>
    //search_btnをクリックしてAjax通信を行う
    $("#search_btn").on('click', function () {
      $.ajax({
        url: 'http://zipcloud.ibsnet.co.jp/api/search', //通信先
        type: 'GET', //HTTPメソッド
        data: { //送信するデータ
          zipcode: $("#zipcode").val()
        },
        dataType: "jsonp", //応答データの種類
      }).done(function (data) { //通信成功時の処理
        var results = data.results[0];
        $("#address").append(results.address1 + results.address2 + results.address3);
        $("#postCode").append(results.zipcode);
      }).fail(function (data) { //通信失敗時の処理
        $("#caution").html(data.message);
      }).always(function (data) { //通信成功・失敗に関わらない処理
        $("#always").html('通信完了');
      })
    });
  </script>

コードは綺麗じゃないと思います。すみません・・・
どのような内容かはコメントに残してあります。
これを実行すると・・・

実行した時の表示

スクリーンショット 2020-02-14 0.41.52.png

画面が白くならず反映させることができました!
まだまだ複雑な処理はできませんが、Ajaxの概要から操作することはできたかなと思います。

Ajaxでのまとめ

Ajaxのような操作性を向上させてユーザーにストレスを与える機会を減らす技術は本当にすごいなと思います。
また、昔ながらの技術で新しい工夫という点も自分自身見つめ直すいい機会だったと思います(すぐ新しいものに手をだす癖があるので・・・笑)
自分のような初歩的な部分から分からない方の参考になればと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者によるプログラミング学習ログ 231日目

100日チャレンジの231日目

twitterの100日チャレンジ#タグ、#100DaysOfCode実施中です。
すでに100日超えましたが、継続。

100日チャレンジは、ぱぺまぺの中ではプログラミングに限らず継続学習のために使っています。

231日目は

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VS Code で ESLint × Prettier のベストかも知れないプラクティス

前書き

餅は餅屋と言う事で、コードフォーマットは Prettier に任せ、ESLint には静的解析だけを担当させると言う棲み分けが昨今の風潮ではないかと思います。
しかし VS Code には拡張機能でも ESLintPrettier が用意されており、npm 等でインストールされるそれらとの関連がややこしい。
と言うわけで、 「いろいろ突き詰めればいろいろあるけどとりあえずこれで良くね?」 と言うやり方をご紹介します。

チーム・組織や案件によってニーズやルールは異なるのは当然ですが、ごくごくシンプルに決まったルールでフォーマットと lint をやってくれればそれでいいんだ。と言う人向けです。

前提

前提として、VS Code に

がインストール済みで、保存時に自動フォーマットが掛かるよう設定済みであることが条件です。

.vscode/settings.json
{
  (省略),
  "editor.formatOnSave": true
}

設定

最終的には以下の措置だけで良いです。

まず、ESLint と Prettier の競合をよしなに取り計らってくれる設定をインストールします。

npm install -D eslint-config-prettier
# もしくは
yarn add -D eslint-config-prettier

それを ESLint が参照できるよう設定。

.eslintrc.js
module.exports = {
  env: {
    es6: true
  },
  extends: [
    (省略),
    "prettier"
  ]
};

extends の最後に prettier を追加するようにしてください。
最初とか途中ではダメです。

あとは必要に応じて .prettierrc で任意のフォーマット設定を指定すれば良いです。
特段の理由がなければデフォルトで良いと思いますが、"singleQuote": true くらいはしておきたいかもですね。

これで簡単快適に素敵なコーディングライフが送れます!

参考

VSCode でファイル保存時に Prettier と ESLint でフォーマットをかけても、1 回でフォーマットしきらないときの対処法

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JSONの基礎の基礎

JSONは、Java Script Object Notationのこと。テキストベースのデータフォーマット。

JavaScriptのオブジェクトとほぼ同じ形式であり、例えば下記のJavaScriptオブジェクト。

var schoolOne = {

  hensachi: 60,
  students: 260,
  name: "ichi-koukou"

}

これを、JSONにすると


{"hensachi": 60, "students": 260, "name": "ichi-koukou"}

こんな感じになります。簡潔に短くなりました。

JavaScriptのオブジェクトをJSONに変換する方法はこちら。

JSON.stringify(schoolOne);

これで、JSONに変換されました。

逆に、JSONをJavaScriptオブジェクトに変換する場合は

//まずJSONに変換したものを変数に入れる

var jsonSchoolOne = JSON.stringify(schoolOne);

//これで変数にはschoolOneのJSON版が入っている

//次にJSON → JavaScriptオブジェクトにする
JSON.parse(jsonSchoolOne);


まとめ

つまり、APIとかでJSONスタイルでデータを取得した際は、必ずJSON.parseでJavaScriptオブジェクトの形式に戻してから、値を取得するようにしましょう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby、PHP、Java、JSでの書き方の違い

はじめに

「Rubyでプログラミングの基礎を学んだから、次はjsやってみようかな!」

「書き方ごちゃごちゃになって、何が何だかよくわかんねー!!」

こんな人もいるのでは?

僕自身も以前、詰め込みすぎでパンパンになりましたので、書き方の違いをわかりやすくまとめてみました。
一つ一つ覚える必要はありませんので、この記事を、ストックしていただければと思います。
オブジェクト指向の理解も多少深まると思いますので、最後までご覧ください!

文字出力

基本となる文字の出力。言語によって処理のされ方が違うので難しいところですが、基本なのでしっかり押さえましょう。Javaはやっぱり記述が長いですねw

  #Ruby
  puts "こんにちは"
  #PHP
  echo "こんにちは";
  //Java
  System.out.println("こんにちは");
  //JavaScript
  console.log("こんにちは");

変数代入

プログラミングの超基本、変数代入です。

  #Ruby
  name = "Tom"
  #PHP
  $name = "Tom";
  //Java(String、intなどの型を指定する)
  String name = "Tom";
  //JavaScript
  let name = "Tom";

変数展開、文字列+変数

これもよく使いますね。全部似てますが、違いがあります。※書き方はあくまで一例です

  #Ruby
  "私の名前は#{name}です。"
  #PHP
  "私の名前は{$name}です。"
  //Java
  "私の名前は"+ name + "です。"
  //JavaScript(''ではなく``(バッククオーテーション)で囲む)
  `私の名前は${name}です。`

if文

処理の代表と言えばif文でしょう。elsifのところの違いに注意です。

  #Ruby
  if age >= 20
    #処理
  elsif age >= 10
    #処理
  else
    #処理
  end
  #PHP
  if (age>= 20){
    #処理
  }elseif (age >= 10){
    #処理
  }else{
    #処理
  }
  //Java & JavaScript
  if (age>= 20){
    //処理
  }else if (age >= 10){
    //処理
  }else{
    //処理
  }

配列と取り出し方

配列の取り出しもよく使いますね、1つづつ取り出す方法も言語によっていろんなのがあります。

  #Ruby
  names = ["Tom","Kenta","John"]
  names[0]
  #PHP
  $names = array("Tom","Kenta","John");
  $names[0];
  //Java
  String names[] = {"Tom","Kenta","John"};
  names[0];
  //JavaScript
  const names = ["Tom","Kenta","John"];
  names[0];

ハッシュ、連想配列、オブジェクトと取り出し方

言語によって呼び方が違うので、検索する際には注意してください。

  #Ruby(ハッシュ)
  user = {name: "Tom",age: 20}
  user[:name]
  #PHP(連想配列)
  $user = array("name" => "Tom","age" => 20)
  $user["name"]
  //JavaScript(オブジェクト)
  const user = {name: "Tom",age: 20};
  user.name

通常のメソッド、関数

ここからが重要なところです。今回は2つの値を合計するaddメソッド(関数)を作りました。
戻り値も重要なので、しっかり押さえましょう。

  #Ruby
  def add(a, b)
    return a + b
  end
  sum = add(5, 2)
  #PHP
  function add($a, $b){
    return $a + $b;
  }
  $sum = add(5, 2);
  //Java
  public static int add(int a, int b){
  //staticの後のintは戻り値の型を指定、戻り値がないメソッドの場合はvoidを使用
    return a + b; 
  }
  int sum = add(5, 2);
  //JavaScript
  const add = function(a, b){
    return a + b;
  };
  let sum = add(5,2);

  //または
  const add = (a, b) => {
    return a + b;
  };
  let sum = add(5,2);

  //または
  function add (a, b){
    return a + b;
  }
  let sum = add(5,2);

jsはバージョンによって推奨されている記述が違います。

クラスとインスタンス作成

さあ、待ちに待ったオブジェクト指向の始まりです!
Menuクラスを作って、そのクラスをもとに、インスタンスを作成し変数menu1に代入しています。

  #Ruby
  class Menu
    ##処理
  end
  menu1 = Menu.new
  #PHP
  class Menu{
    ##処理
  }
  $menu1 = new Menu();
  //Java
  class Menu{
    //処理
  }
  Menu menu1 = new Menu();
  //JavaScript
  class Menu{
    //処理
  }
  const menu1 = new Menu();

インスタンス変数、インスタンスフィールド、プロパティの定義

メニューには名前や値段などの"情報"が含まれます。それをクラス内で事前に宣言しておきます。

  #Ruby
  attr_accessor :name
  attr_accessor :price
  #PHP
  private $name;
  private $price;
  //Java
  private String name;
  private int price;
  //JavaScript
  //事前定義は不要?

初期メソッドでのインスタンス変数、プロパティへの代入

さあ、さらに訳わからないところにやってきました!
初期メソッドとはnewされた時(一番最初)に呼ばれるメソッドになります。
今回はnewの引数に指定された値が初期メソッド内で、インスタンスの情報として変数に入ります。

  #Ruby
  def initialize(name, price)
    self.name = name
    self.price = price
  end
  menu1 = Menu.new("ハンバーガー",300)
  #PHP
  public function __construct($name,$price){
    this->name = $name;
    this->price = $price;
  }
  $menu1 = new Menu("ハンバーガー",300);
  //Java
  //クラス内でクラスと同名のメソッドを定義する
  Menu(String name, int price){
    this.name = name;
    this.price = price;
  }
  Menu menu1 = new Menu("ハンバーガー",300)
  //JavaScript
  constructor (name, price){
    this.name = name;
    this.price = price;
  }
  const menu1 = new Menu("ハンバーガー",300)

インスタンスメソッドと呼び出し

さて最後になります。インスタンスは"情報"の他に"処理"を持っています。
今回はその処理の定義方法と呼び出し方です。
変数menu1にはMenuクラスから作られたインスタンスが代入されているので、
出力は"こちらのハンバーガーは300円です"となります。

  #Ruby
  def show
    puts "こちらの#{self.name}#{self.price}円です"
  end
  menu1.show
  #PHP
  public function show(){
    echo "こちらの{$this->name}{$this->price}円です";
  }
  $menu1->show();
  //Java
  public void show(){
    System.out.println("こちらの"+this.name+"は"+this.price+"円です");
  }
  menu1.show();
  //JavaScript
  show(){
    console.log(`こちらの${this.name}${this.price}円です`);
  }
  menu1.show();

終わりに

最後までご覧いただきありがとうございました。
抜けや間違いがあるかもしれませんが大目に見てください!!

この世の中では浅く広く学ぶのはあまり良くないとされていますが、知識を深めるために別言語に挑戦してみるのもいいと私は思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Nuxt で description をデフォルト設定していないのに、謎の値が入る

解決策

nuxt.config.jsplugin にて meta プロパティをオフにする

 ...,
 ['@nuxtjs/pwa', { meta: false }],
 ...,

もしくは、 @nuxtjs/pwa を使わない。

前提

  • Nuxt.js v2.8.1
  • @nuxtjs/pwa

起こったこと

nuxt.config.js で、metaタグの description を設定していないのに以下のようなタグが append される

<meta data-n-head="true" data-hid="description" name="description" content="---">

原因

@nuxtjs/pwa が README.md の値を append していた

この時の README.md--- の値が使われていた。。

# Project title

---

some text...

@nuxtjs/pwa の挙動

1. package.jsondescription が設定されているとき

 package.jsondescription の値をつかって、自動的にmetaタグを挿入
 
 ※ただし、 nuxt.config.js でmetaタグが指定されていた場合は、そちらが overwrite される

2. package.jsondescription が設定されていないとき

 なぜか README.md の2行目の値が使われる ???

 中を見ると process.env.npm_package_descriptionREADME.md の値が入っていた(原因は未調査)

まとめ

PWAが要件にないのに、 @nuxtjs/pwaplugin として使われている場合は注意しましょう
 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GASがV8エンジンに対応したのでドキュメント読んだ

GASがV8エンジンに対応したのでドキュメント読んでみた

公式のV8移行ドキュメント
Migrating scripts to the V8 runtime
を読んだのでメモ

だいぶテキトーです。

経緯

2020/02/05 、Release Notesに更新がありました。

February 5, 2020
Apps Script now supports the V8 runtime. This enables modern JavaScript features and syntax in Apps Script. You can migrate existing scripts to use V8 and its features.

「V8ランタイム対応!GASをモダンJavaScriptで書けるよ!」

varじゃなくconstで変数宣言したり、アロー関数を使ったりできます。

G Suite の今後のリリース


適用方法

現在、デフォルトエンジンはRhinoなので、V8に変更してあげる必要があります。

方法は以下の2つがあるのですが、前者は現在未リリース

  • メニューから切り替える方法 (実行タブに追加される予定)
  • スクリプトマニフェストでエンジンを指定する方法

後者のやり方は以下のツイートがわかりやすい。

https://twitter.com/howdy39/status/1225168177996320768?s=20


移行

ここから公式ドキュメントMigrating scripts to the V8 runtime の翻訳です

モダンなJavaScriptで書けるようになったが、既存のスクリプトをそのままV8エンジンに変更すると動かなくなるかも。

今までのGASとV8ベースのGASは非互換性が少しあるので移行にあたって少し注意が必要。

V8移行手順

  1. スクリプトでV8エンジンを有効にする (適応方法参照)
  2. 非互換性を知って注意深くスクリプトを修正してね
  3. 「その他の差異」についても調査してスクリプトを修正してね。
  4. 「非互換性」「その他の差異」を修正し終わったら、モダンな書き方で変更を始められるよ
  5. 書き終わったらちゃんと想定どおり動くかテストしてね
  6. スクリプトをWebアプリや拡張機能として公開しているなら、新しいバージョンで再公開してね

 非互換性と対応

ShiroベースのこれまでのGASは現在のJavaScriptの標準仕様と非互換性を持っています。

V8エンジンに移行する際はまず非互換性のある記法を修正しましょう。

for each (variable in object) から for (variable in object)に変える

これはfor eachで検索してeachを削除するだけなので楽ですね。

Date.prototype.getYear()からDate.prototype.getFullYear()に変える

getFullYear()は4桁で年を返す。

予約語を避ける

ECMAScriptの予約語を変数や関数名に使用しているなら、名前を変更します。

RhinoではECMAScriptの予約語のいくつかが許可されていて、既存のスクリプトでclass,import,exportなどで命名しているかもしれないので注意が必要です。

constで宣言した変数に再代入しないように書き換える

GASでconstは以前から使えたが、以下のような挙動だった

// Rhino runtime
const x = 1;
x = 2;          // No error
console.log(x); // Outputs 1

V8からは標準的な挙動になる

// V8 runtime
const x = 1;
x = 2;          // Throws TypeError
console.log(x); // Never executed

そもそもGASでconst使えることを知らなかった。

XMLリテラルやXMLオブジェクトはパースする

今までは非標準機能としてXMLを直接扱うことができました。

V8では代わりにXmlServixeクラスのparseを使ってください。

// V8 runtime
var incompatibleXml1 = <container><item/></container>;             // Don't use
var incompatibleXml2 = new XML('<container><item/></container>');  // Don't use

var xml3 = XmlService.parse('<container><item/></container>');     // OK

__iterator__関数でカスタムイテレータを作っていたら、ECMAScript 6 iteratorsで書き換える

Iteration protocols - JavaScript | MDN

グローバル変数を避ける

V8エンジンではスクリプトは呼び出される際に解析されるので、解析されていないスクリプトで宣言している変数を使おうとするとエラーになる。

スクリプトファイルが解析される順序はHTMLのscriptタグと同じで、上から順。

ファイルの順序を入れ替えれば対応できるが、それは望ましくない。

移行の際はグローバル変数を関数内に移動して、最初はundefinedで持つことで同じ機能を維持しながらエラーを回避できる。

// First.gs
var globalVar = calculate();

function myFunction() {
  Logger.log("globalVar = %s", globalVar);
}

///////////////////////////////////////

// Second.gs
function calculate() {
  return Math.random();
}

// First.gs
function myFunction2() {
  var localVar = calculate();
  Logger.log("localVar = %s", localVar);
}

////////////////////////////////////////

// Second.gs
var cachedValue;

function calculate() {
  if (cachedValue == undefined) {
    cachedValue = Math.random();
  }
  return cachedValue;
}

のように書き換える

条件付きcatch句を避ける

// Rhino runtime

try {
  doSomething();
} catch (e if e instanceof TypeError) {  // Don't use
  // Handle exception
}

このような書き方でif条件を使えないので、以下のようにする

// V8 runtime
try {
  doSomething();
} catch (e) {
  if (e instanceof TypeError) {
    // Handle exception
  }
}

Object.prototype.toSource()を使っていたら削除してください

初耳学
JavaScript 1.3のメソッドですがV8ではサポートされていません。

その他の違い、注意点

DateクラスのLocal関連メソッドの出力形式の違い

toLocalString(),toLocalDateString(),toLocalTimeString()の出力形式がRhinoV8で違うので注意

Error.fileNameError.lineNumberがサポートされていない

依存している処理があるなら削除する。
Error.prototype.stackで代替できるが、これも非推奨

enumの扱いの違い

enumオブジェクトにJSON.stringify()した際Rhinoでは{}を返すのみだったがV8ではオブジェクト名を返すようになった。

引数にundefinedを渡す際の扱いの違い

Rhinoでは文字列の"undefined"として扱われたが、V8ではnullを渡したことと同等になる。


 感想

web版VBAもJavaScript(typescript)ベースだし、IEももうすぐお亡くなりになるのでモダンなJSを気軽に書ける環境が整ってきた感。
Excel操作をJavaScriptで記録 ~Microsoft、“Office Scripts”をパブリックプレビュー - 窓の杜

V8エンジンとstrictモードはデフォになって欲しい


 参考

V8 Runtime Overview  |  Apps Script  |  Google Developers

Migrating scripts to the V8 runtime  |  Apps Script  |  Google Developers

google/clasp

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

一瞬でJavaScriptのfor文が爆速になった話

AtCoder初心者のアクマちゃん(@akumachanit)デモ!for文を早くする方法を見かけたので試してみたデモ!

三行で

  • i++
  • i=(i+1)|0に変えると
  • 爆速になったデモ

実演

// これを
for (i = 0; i <= H; i++) {
  // 省略...
}

// こうしただけ
for (i = 0; i <= H; i=(i+1)|0) {
  // 省略...
}

爆速
えっ...
メモリも...

なんで?

なんでも処理系に型を認識させることで速度が上がるらしいデモ。
詳しいことはこれから調べるからワガハイには聞かないでくれデモ...笑
環境によって差異とかあっても当悪魔は一切関知しないデモ!(こっそりコメントで教えてくれデモ)

仲良くして欲しいデモ

Twitterやってるデモ!仲良くしてくださいデモ!@akumachanit

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vueとaxiosを使って外部APIからデータを取得する

new Vue({
  el: '#fetchUsers',
  data: {
    users: []
  },
  mounted() {
    axios.get('/users')
      .then(response => (this.users = response.data))
    }
});
<div id="fetchUsers">
  {{users}}
</div>

結果

{userId: 1, userName: sato}, {userId: 2, userName: tanala}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[js]配列の中身がObjectのとき、特定の要素があるかチェックするうまい方法

配列の中に特定の要素があるかチェックする方法はいくつかあります。
ここではpersonsというオブジェクト要素を複数もつ配列で考えてみます。

まずは、まあよくある書き方

例1

sample.js
const persons = [
    {name: 'takada'},
    {name: 'yamada'},
    {name: 'tanaka'},
    ]
const target = 'takada'

let hasTargetName = false
persons.map((person) => {
    if (person.name === target) {
        hasTargetName = true
    }
})

console.log(hasTargetName) //trueが表示される

for文でもいいですが、mapを使って配列の要素を見ていって、targetと一致するものがあるかを調べています。でもなんか、すごく原始的な感じがして嫌だ。。。

例2

調べてみるとincludesというのがあった。使い方はどれどれ・・・

const array = ['a' , 'b', 'c']
const target = 'a'
console.log(array.includes(target)) //trueが表示される

なるほど、簡単ですね。でも今回は配列の中身がオブジェクトだからこう書けばいいのか?

const persons = [
    {name: 'takada'},
    {name: 'yamada'},
    {name: 'tanaka'},
    ]
const target = 'takada'
const hasTargetName = persons.includes({name: target})
console.log(hasTargetName) //falseが表示される

おいおいおい、falseになったぞ!!
どうやらincludesはオブジェクト比較のときは参照元が一致しているかを見てるようです。
試しにこう書いたらtrueになった。ふーん。

const persons = [
    {name: 'takada'},
    {name: 'yamada'},
    {name: 'tanaka'},
    ]
const target = persons[0] //ここを変えてみた
const hasTargetName = persons.includes(target)
console.log(hasTargetName) //trueが表示される

とりあえず、includesはこの場合には使えないってのは分かりました。

例3 じゃあどうするの!

どうやら、someというナイスガイがいるらしい。使い方は、ほぼincludesと同じだが比較する値を渡すのではなく、booleanを返す関数を渡してあげればいいようだ。

const persons = [
    {name: 'takada'},
    {name: 'yamada'},
    {name: 'tanaka'},
    ]
const target = 'takada'
const hasTargetName = persons.some((person) => {return person.name === target})
console.log(hasTargetName) //trueが表示される

いいじゃないか!!!使いやすい!!!
ついでに、冗長な書き方を修正して・・・

const persons = [
    {name: 'takada'},
    {name: 'yamada'},
    {name: 'tanaka'},
    ]
const target = 'takada'
const hasTargetName = persons.some(person => person.name === target)
console.log(hasTargetName) //trueが表示される

はい、スッキリです☆彡
今後使っていこう~!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】フォームにフォーム外のファイルを追加しようとしたらJavaScriptをめっちゃ書くことになった

概要

Railsのフォーム送信は色々いい感じにやってくれるが、少し変えたい時もある。
僕はフォーム外からファイルを追加したかった。

そういう時は、rails-ujsのajaxイベントハンドラを使うとフォーム送信に要所で割り込める。
Working with JavaScript in Rails#3.5 Rails-ujs event handlers ※ Rails 5.1以降の機能

以下、これを使ってフォーム外のファイルをフォーム内容と同時送信する例を紹介する。

やりたいことの例

本来フォーム内のnameだけ送信されるところ、フォーム外からファイルを追加送信したい。
ここでは仮に、フォーム外のnewImageFilesに画像ファイルを入れてあり、フォーム内容と一緒に送信したいという前提で話を進める。

<%= form_with model: [:user, @config] do |f| %>
  <div>
    <%= f.label :name, 'お名前' %>
    <div>
      <%= f.text_field :name %>
    </div>
  </div>
  <div>
    <%= f.submit '保存' %> <%#= 通常、フォームの内容(今回はname)のみ送信される %>
  </div>
<% end %>
<script>
  var newImageFiles = [] // ← フォーム外のファイルを送信に含めたい
</script>

form_withlocalオプションを付けないでください。
参考:【Rails】form_withのlocalオプション

実装

// ...前略
<script>
  var newImageFiles = [] // ← フォーム外のファイルを送信に含めたい
  document.body.addEventListener('ajax:beforeSend', function(event) { // [1]
    var detail = event.detail
    var xhr = detail[0], options = detail[1]
    // [2]コールバック
    options.success = function() {}
    options.error = function() {}
    options.complete = function() {}
    newImageFiles.forEach(image => {
      options.data.append('config[images][]', image, image.name) // [3]
    })
    xhr.onreadystatechange = function () { // [4]
      if (this.readyState === XMLHttpRequest.DONE) {
        if (this.status === 200) { // [4-1] 200の時、リロード
          window.location.href = location.href
        } else { // [4-1] 200以外の時、失敗表示
          alert(`フォーム送信に失敗しました`)
        }
      }
    }
    xhr.send(options.data) // [5]
    event.preventDefault() // [6] その後の通常処理を行わない
  })

[1] ajax:beforeSendイベントから、フォームから送信予定のxhrとoptionsがとれる。これをカスタマイズする

  document.body.addEventListener('ajax:beforeSend', function(event) { // [1]
    var detail = event.detail
    var xhr = detail[0], options = detail[1]

[2] optionsにコールバック設定がある。そのまま利用してもよいが今回は雑に全部削除

    // [2]コールバック
    options.success = function() {}
    options.error = function() {}
    options.complete = function() {}

[3] options.dataにフォーム外から含めたいデータをappend。

    newImageFiles.forEach(image => {
      options.data.append('config[images][]', image, image.name) // [3]
    })

[4] onreadystatechangeを改めて書く。これも今回は最低限。

    xhr.onreadystatechange = function () { // [4]
      if (this.readyState === XMLHttpRequest.DONE) {
        if (this.status === 200) { // [4-1] 200の時、リロード
          window.location.href = location.href
        } else { // [4-1] 200以外の時、失敗表示
          alert(`フォーム送信に失敗しました`)
        }
      }
    }

[5] xhrを送信。

    xhr.send(options.data) // [5]

[6] その後の通常処理をキャンセル。Rails5以前のjquery-ujsではこういうところでreturn falseと書くらしいので注意

    event.preventDefault() // [6] その後の通常処理を行わない

感想

Railsの話だけどJavaScriptしか書いていない。
rails-ujsのこの機能は、ほかにも色々な用途で使えると思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavascriptでDOM生成したフォームは別の関数スコープで作成しないと要素を取得できない

選択ボックスの値を取得したり、操作するために使われるgetElementByIdの解説が充実しているサイトは数多くありましたが、基本的にhtmlファイルにベタ打ちで書かれたフォームに対する操作を前提としたものです。

getElementByIdのおさらい

例えば

form.html
<form>
  <select id='musume5'>
    <option value='001'>紺野</option>
    <option value='002'>小川</option>
    <option value='003'>高橋</option>
    <option value='004'>新垣</option>
  </select>
</form>

と言う形でプルダウンリストを作成することが出来るが、以下のjavascriptでリストの中身を取得することが可能である。

form.js
var elm = document.getElementById('musume5');

ここまではどこにでも書かれている。

リストをDOM生成したとき

しかし、リストをJavascriptやJQueryでDOM生成して書かせた際は、同じスコープで全部の作業を一気にやったときにうまくいかなかった。
恐らく関数内の巻き上げとDOMへの値の挿入タイミングの都合でgetElementByIdが値を取得できなかったと思われる。

まず、以下のhtmlファイルを用意する。
initialize関数で起動してIDがhello-morning内のdivタグの中にフォームを入力する方針を取る。

form.html
<!-- 中略 -->
  <body>
    <div id='hello-morning'></div>
    <!-- 中略 -->
    <script>initialize()</script>
  <body>
<html>

うまくいかなかったやり方

form.js
  var initialize = function () {
    // 中略
    insertMusume();
    // 中略
  };

  var insertMusume = function() {
    var block = $('<div'>)
    var musumeForm = $('<form>');
    var select = $('<select>').attr({ 'id': 'musume5' });
    select.appendTo(musumeForm);
    $('<option>').attr({'value': '001' }).text('紺野').appenTo(select);
    $('<option>').attr({'value': '002' }).text('小川').appenTo(select);
    $('<option>').attr({'value': '003' }).text('高橋').appenTo(select);
    $('<option>').attr({'value': '004' }).text('新垣').appenTo(select);
    musumeForm.appendTo(block);
    // ここではクラッシュしないが値が取得できない
    console.log(document.getElementById('musume5'));
    return block;
  }

同一関数内でDOM生成及び挿入を行うと、フォームはブラウザに表示されるがgetElementByIdで値を取得できなかった。
ブラウザ上のコンソールでgetElementByIdを実施するときちんと取得できたことから、値の挿入タイミングが合わなかったことが考えられる。

そこで、以下のようにやってみるとうまく取得できた。
コード設計時には「DOM生成」と「値取得・処理」は別スコープで行う必要があることが分かった。

うまくいったやり方

form.js
  var initialize = function () {
    // 中略
    insertMusume();
    // 中略

    // ここでなら取得できた
    console.log(document.getElementById('musume5'));
  };

  var insertMusume = function() {
    var block = $('<div'>)
    var musumeForm = $('<form>');
    var select = $('<select>').attr({ 'id': 'musume5' });
    select.appendTo(musumeForm);
    $('<option>').attr({'value': '001' }).text('紺野').appenTo(select);
    $('<option>').attr({'value': '002' }).text('小川').appenTo(select);
    $('<option>').attr({'value': '003' }).text('高橋').appenTo(select);
    $('<option>').attr({'value': '004' }).text('新垣').appenTo(select);
    musumeForm.appendTo(block);
    return block;
  }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JestでHTML5 FileAPI を使ったテストをする方法

概要

  • HTML5 FileAPIのFileオブジェクトを使ったテストをJestで行う方法について書きます

前提

  • Jestではフロントエンド アプリのテストもテスト時にNode.js用にコード変換して実行する
  • この特性のため、Node.js用にブラウザ用のAPIをがんばって再現してる(JSDOM等つかって)
  • なるべくNode.jsに媚びたコードを書かずフロントエンドのお作法だけでテストを書きたい

FetchしたファイルをHTML5 Fileとして取り扱う方法

やりたいこと

Fetch APIを使って、あるURLからファイルをダウンロードしてきて HTML5 File APIのFileオブジェクトとして取り扱い、それをJestしたい。

node-fetchをインストールする

フロントエンドでおなじみのFetch API (window.fetch) はNode.jsには無いので、似た機能を提供するnode-fetchをインストールする

npm install --save-dev node-fetch

fetchしたデータをHTML5 FileAPIのFileに変換して、それをJestする

コードは以下のとおり

import fetch from 'node-fetch';

describe('Fetch on Node', () => {

    test('Get HTML5 "File" on Node ', (done) => {

        const path = 'https://riversun.github.io/img/riversun_256.png';

        fetch(path)
            .then(res => {

                return res.arrayBuffer().then(buffer => ({
                    contentType: res.headers.get('Content-Type'),
                    buffer: buffer
                }));

            })
            .then(data => {
                return new File([data.buffer], path, {type: data.contentType});
            })
            .then(file => {

                console.log(`fileName=${file.name} fileSize=${file.size} fileType=${file.type}`);

                //Write some tests
                expect(true).toBe(true);

                done();

            });
    });
});

ポイントは以下のあたり。

node-fetchで取得したres.arrayBuffer()をバッファとしてnew File([バッファ])Fileを生成することができる。

(以前、こちらの記事で、ブラウザ上でフェッチしてFileを紹介したが、この方法はnode-fetchではうまくいかなかった。node-fetchblob()実装に何かありそう。)

 .then(res => {

                return res.arrayBuffer().then(buffer => ({
                    contentType: res.headers.get('Content-Type'),
                    buffer: buffer
                }));

            })
            .then(data => {
                return new File([data.buffer], path, {type: data.contentType});
            })

参考情報

ソースコード

紹介したコードを含む全ソースコードは https://github.com/riversun/jest_html5_file にあります

ソースコードの実行方法
npm clone https://github.com/riversun/jest_html5_file.git
npm instlal
npm test

Jestの設定ファイルなど

Jestの設定ファイルなどはこちらの記事と同様です

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【2020年2月】direnv+nvmでプロジェクトごとのnode.jsのバージョンを【強制】【固定】

プロジェクトごとにNode.jsのバージョンが異なって辛みマシマシ

そんな時は以下の処方箋がおススメ。
例えば node.js 10 を強制的に使用する場合は以下のようにプロジェクトのフォルダ直下に.envrcファイルを作成し、nvm で 10 を指定しておきます。

.envrc
[[ -s ~/.nvm/nvm.sh ]] && . ~/.nvm/nvm.sh
nvm use 10

で、コマンドから direnv を有効にします。

$ direnv allow .

以上!

direnv、nvm のリンク

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【2020年2月】プロジェクトごとのnode.jsのバージョンを、direnv+nvmで【強制】【固定】

プロジェクトごとにNode.jsのバージョンが異なって辛みマシマシ

そんな時は以下の処方箋がおススメ。
例えば node.js 10 を強制的に使用する場合は以下のようにプロジェクトのフォルダ直下に.envrcファイルを作成し、nvm で 10 を指定しておきます。

.envrc
[[ -s ~/.nvm/nvm.sh ]] && . ~/.nvm/nvm.sh
nvm use 10

で、コマンドから direnv を有効にします。

$ direnv allow .

以上!

direnv、nvm のリンク

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

中間テーブルとは??

 プログラミング初学者が学習する中間テーブルについて

  • Railsなどのフレームワークを学習していて『中間テーブル』という言葉がよくわからなかったので備忘録も兼ねて解説します! (初投稿なので温かく見守ってください泣)

  • 中間テーブルってなんなの??

  • 具体的な使い方

中間テーブルってなんなの??

中間テーブルとは・・・データベースで、テーブルとテーブルの多対多の関係を表すテーブルのこと。

これだけ言われてもパッとしないかもしれません。イメージしてみましょう!

データベースにuserテーブルとgroupテーブルというのが存在すると仮定します。

userテーブル

id name
1 山田
2 佐藤
3 鈴木
4 田中

groupテーブル

id group-name
1 Aグループ
2 Bグループ
3 Cグループ
4 Dグループ

以上二つのテーブルから誰がどのグループに属しているかを表現してみると、、

id name group
1 山田 Bグループ
2 佐藤 Cグループ
3 鈴木 Aグループ
4 田中 Cグループ

このようにuserテーブルにgroupカラムを追加することで解決できます。

しかし!

userがいくつものgroupに所属できるようにしたい!と考えました
*例えば通話アプリの『LINE』でも複数の『グループ』に所属できますよね!

すると、、

userテーブル

id name group1 group2 group3 group4
1 山田 Bグループ Cグループ Aグループ
2 佐藤 Cグループ Aグループ Dグループ Bグループ
3 鈴木 Aグループ
4 田中 Cグループ Bグループ
これじゃ、空のカラムが大量発生するじゃないか!!!(親父ギャグ)
  • カラムどんだけ追加しないとダメなんだ・・・
  • DB設計時にどれだけカラムを用意しなければならないんだろう・・・予測できない・・
  • 空のカラムはエラーの元じゃないか・・・

ここで登場するのが 中間テーブル です

中間テーブルの具体的な使い方

userテーブル

id name
1 山田
2 佐藤
3 鈴木
4 田中

groupテーブル

id group-name
1 Aグループ
2 Bグループ
3 Cグループ
4 Dグループ

新たに以下のテーブルを追加!

group_userテーブル

id user group
1 山田 Bグループ
2 山田 Cグループ
3 山田 Aグループ
4 佐藤 Cグループ
5 佐藤 Aグループ
6 佐藤 Dグループ
7 佐藤 Bグループ
8 鈴木 Aグループ
  1. このように中間テーブルを追加することで空のカラムもなくなり、その都度追加する必要があったカラムも追加せずに済むことができます。さらに誰がどのグループに所属しているかが見やすくなりました。

  2. データベースの設計をしていく際には空のカラムが発生しないか、チームとして開発を進めていく中で見やすいDBができているかを考える必要があると思うので覚えておきましょう!!!

初投稿となりましたが、何かわかりにくい点や誤って理解してしまっている点などあればご教授願います。では!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

HTML+CSSコーディングの言語化

はじめに

HTML+CSSコーディングにおける制作者の思考や判断、アプローチ、コード化に至るまでの流れなどを分解し、コードの状態なども含めてそれぞれを短い言葉で言語化しました。

以下のような効果が期待できます。

  • HTMLとCSSを使っておこなってきた事を客観視・再認識できる
  • 始原的な動機を把握することで、手法を別の視点で捉えられるようになる
  • 認識合わせや熟練度確認に利用し、制作時のコミュニケーションを円滑にする

場合によっては、ぼんやりと捉えていたことや、詰まりやすかったポイントなどがハッキリと認識できるようになるかもしれません。

言語化の概要

まずは、全体像が分かる概要図を掲載します。
この記事は、以下のステップと各項目について順に説明するものになります。

HTML+CSSコーディング 制作者の行動 言語化.png

前提事項・環境について

言語化するにあたって前提となる考え方や環境について記載します。

  1. HTMLとCSSの役割
    • HTML → データに意味を持たせるためにマークアップした、情報表示の基礎となるもの
    • CSS → HTMLでマークアップされた情報を、よりよく伝達するために装飾や機能を持たせるためのもの
  2. 業務フロー
    • コーディングをおこなう以前にデザインやWFなどを作成する
      • これらは、Webページという納品物をつくるための「完成予想図」と言える
      • 完成予想図は成果物がプロジェクトの目的を達成できるかどうかを事前に検討・確認するためのもの
    • 業務上ではコーディングから開始するケースは稀であるため、何らかの「図」や「絵」が存在しているものとする
  3. コーディングのゴールと目的
    • 何らかの「完成予想図」に求められる視覚情報や動きや働きをコードに変換しつくすこと
    • 情報や機能などを、Webページとして利用者に提供可能な状態にすること

コーディング行動の流れ

ここから、HTML+CSSコーディングの言語化に関する具体的な説明です。

コーディングを制作者(人間)中心に捉えると、コード化までには一連の流れがある事に気づきます。
最終的にコードを記述するまでの流れを分解すると以下のようになります。

コーディング行動の直線ステップ.jpg

制作者はこれらを一瞬、もしくは短時間で連続的に流れるようにおこなっているため、恐らく普段はこのような事を意識する事は少なく、また、実際には全てが直線的な訳ではありません。

例えば「完成予想図」を確認した直後であれば、頭の中でHTML構造を組み立てながら、CSSの設計のために共通部分を見つけようとするかもしれません。
HTMLコードを記述している時であれば、同時にCSSの実装を考えるでしょうし、CSSコードの記述中にHTMLの修正に移る事もあります。
また、その視野についても、「広い範囲」と「狭い範囲」の両視点を切り替えながら進める事が多く、本来の思考や行動は放射状や螺旋、ジグザグといった形に近くなります。
上記のステップは、こういった行動を分解・細分化し、直線に組み替えたときの基本の流れとなります。

以下、このステップに準じて説明を記載します。

【STEP-1】 関心の対象

関心の対象」とは、制作者が心の焦点をあてたもの、意識したものです。
すべての思考や行動は、対象が定まることから始まります。

その対象は、例えば完成予想図を確認した時の大きな面や塊、その中の小さな部品など様々です。ほかにも、既にコード化し終わったものであったり、何らかの補助用classそのもの、あるいは何らかのアニメーションや動きといったものが対象になることもあります。
こういったものを全く意識することなく「文字列を入力し、それから対象を考える」といったことは、少なくとも業務効率を意識している制作者であればおこなわないはずです。

ここから先、「対象」と記載しているものは、常に制作者の「関心の対象」を意味します。

【STEP-2】 対象への欲求

対象が定まると、次に「それをどうしたいか」という欲求が発生します。
コーディングにおける対象への欲求は、大きく以下の4つに分類できます。

対象への4つの欲求@2x.png

1. 新たな部品や役割・働きをつくりたい
まだコード化されていないものを新たに作成したいという欲求
2. 類似した部品や役割・働きをつくりたい
既に存在している対象の「似て非なるもの」を新たに作りたいという欲求
3. 対象を変化させたい
既に存在している対象を何らかの方法によって変化させたいという欲求
4. 現状の問題をよりよく改善したい
既に存在している対象の問題を修正したり、改善したいという欲求

これらはどれか1つだけの事もあれば複数同時に発生する事もありえます。
例えば、何かを新規作成したいと思ったと同時に、類似したものが必要になると判断した場合や、新たなものを作るには、既存で影響を受ける対象の問題解決が必要。といったようにです。

いずれにしても、コード化に向けて次の「確認・想定」に移行します。

【STEP-3】 確認・想定

「対象への欲求」が発生したら、次はそれを実現するために以下の内容を判定・確認・予測します。

確認・想定.png

以下、それぞれについての詳細説明です。

A-1. 規模・粒度

規模・粒度」は、対象の表示面積や範囲、コードの行数・情報量といった、それそのものの規模感です。

A-1・規模・粒度-詳細@2x.png

最終の表示が「面」を持つスクリーンである場合、そこに映し出されるものには「面積」や「範囲」といったものが伴います。
制作者は、大きいものなら他への影響、小さいものなら受ける影響があるのを知っており、また、HTMLコードの行数、情報量が必ずしも表示面積に比例するとは限らない事も知っているため、これらを含めた「規模・粒度」を意識しています。

HTMLコードの記述前、つまり「完成予想図」の特定箇所に関心を寄せている段階であるなら、その対象をどれくらいのコード量で再現できるかを予測・想定するでしょうし、記述後のHTMLコードが対象の場合は、それはどこで区切られたもので、どんな表示になるのか。といったことを、レンダリング結果と照らし合わせながら確認することになります。

規模・粒度の例@2x.png

もし既に、規模感を定義したルールを使っているなら、上記の語句や判定・認識は別のものになるかもしれません。

A-2. 構造

構造」は、HTMLコードのネスト状態を指します。

A-2・構造-詳細@2x.jpg

HTMLには文書構造のほかに、コード自体のネスト構造が存在します。
対象である視覚情報をはじめてコードに置き換えていく時には、CSSを想定しながらHTMLのネスト構造を設計する事になるでしょう。HTMLを記述した後のスタイリング段階では、CSSセレクタの記述のために構造の把握が必要となります。
これらも、HTMLとCSSを扱う制作者であれば自然と判定・認識・予測しているものであり、その状態はいずれか1つ、もしくは複数にあてはまる場合もあります。

以下は、コードの状態を言語化したものの説明です。

構造の例@2x.png

A-3. CSS読解

CSS読解」は、現在のCSSのセレクタやプロパティがどのような状態になっているのかを把握することです。

A-3・CSS読解-詳細@2x.jpg

CSS読解の目的は、そのコードを記述したのが自分自身であれば「再度確認」といった意味になるでしょう。
他者が作成したコードの場合、かつ情報が何も無いところからのスタートであれば、影響がありそうな箇所のすべての状況を調査・把握して「仕様のマップ」を頭の中に作成する事になります。
いずれにしても、既存コードの影響や状況把握のための行動で、必要に応じて随時実行することになります。

A-4. JS読解

JS読解」は、現在のJavaScriptのコードがどのような状態になっているのかを把握することです。

A-4・JS読解-詳細.png

JS読解の目的は、JavaScriptのコードが「何に対してどのような影響を与えているか」を把握するためです。
直接的なアニメーションをおこなっているのか、classを追加する事でCSS側で何かを変化させているのか、直接スタイルを付与しているのか。などを確認することになります。
JSコードの作成に関する言語化はおこないませんが、リファクタリングの時などにJSコードが関わっている場合は、必ず確認することになるでしょう。

【STEP-4】 コード化のための思考

対象の状況確認や想定が終われば、コード化のために以下のような思考(設計)が必要となります。

このうち上段の2つ(B群)は「【STEP-2】 対象に対する欲求」で挙げた、「類似した部品や役割・働きをつくりたい」と「対象を変化させたい」の2つにそれぞれ対応し、下段の3つ(C群)は共通しておこなう思考内容となります。

コード化のための思考@2x.png

以下、1つずつ個別に説明します。

B-1. 類似複製

類似複製」は、とある部品や役割、働きの「似て非なるもの」が必要となった時に検討する内容です。

B-1・類似複製-詳細@2x.jpg

Webページやサイトには、類似した部品が多数必要となることも少なくありません。例えば「形状は同じだが色が異なる」「形状のみが少しが異なる」といったケースです。
対象の「バリエーション」が必要な状況であり、どのように実装するのかを検討する事になります。
類似複製の例@2x.png

これらの表示変化は、以下の二つのアプローチによって実現可能です。

別名複製

別名複製」は、複製元に類似した見栄え・役割・働きのものを新たに作成するために、複製した対象に対して単一の別の英単語や文字列(※多くの場合はシングルクラス)を与え、似て非なるものを作成する方法です。
複製物の名前を、複製元の名前と類似させる事によって、制作者は二つの関連性を予測できるようになりますが、機械からすれば全く別のものになるため、CSSプロパティは同一のものや似たコードが多くなります。

HTML例

※似て非なる部品を作成した場合

  • 複製元:<a class="btn-primary">
  • 複製物:<a class="btn-secondary">

文字列追加

文字列追加」も類似品を作成する行動です。
複製対象に新たな単語や文字列を追加(※多くの場合はマルチクラス)することによって、対象のCSSプロパティを追加・キャンセル・上書きし、意図した表示を完成させる方法です。

対象そのものに直接語句を追加する方法と、親要素や先祖要素に語句を追加して間接的に対象をスタイリングする方法があり、併用するパターンも合わせれば合計3つのパターンがあります。

A. 「直接」のHTML例

.btnが複製元で、複製したものを似て非なる見栄えにする場合。直接btn-largeという文字列を付与している。

  • 複製元:<a class="btn">
  • 複製物:<a class="btn btn-large">

B. 「間接」のHTML例

.innerがスタイリングの対象の場合。対象の親要素にbox-pattern1という文字列を追加している。

  • 複製元:<div class="box"><div class="inner"> … </div></div>
  • 複製物:<div class="box box-pattern1"><div class="inner"> … </div></div>

C. 「直接・間接の両方」のHTML例

.box.innerがスタイリングの対象の場合。HTMLはBと同じ(CSSで目的の状態にしている)

  • 複製元:<div class="box"><div class="inner"> … </div></div>
  • 複製物:<div class="box box-pattern1"><div class="inner"> … </div></div>

B-2. 状態変化

状態変化」は、対象に何らかの変化を与えたい場合に検討する内容となります。

B-2・状態変化-詳細@2x.jpg

Webページやサイトは、人の行動(クリックやタップ、スクロールなど)と共に利用されます。
このため、その動作に対する色や形状の変化、アニメーションなど、行動に対する反応も必要とされる事が多いと言えます。

※ JavaScriptなどの外部プログラムによって直接対象を操作する場合もありますが、こちらはプログラムの領域となるため、CSSによる変化の場合を対象として説明します。

状態変化の例@2x.png

効果付与

効果付与」は、目的の対象を変化させるために、対象そのもの、もしくは対象の親や先祖要素に語句を追加する方法です。
アプローチそのものは「類似複製」の「語句追加」と同一であり、パターンも同様に以下の3つがあります。※ これらは静的に追加(記述)することもあれば、JavaScriptなどによって動的に追加する事もあります。

A. 「直接」のHTML例

.boxを直接変化させたい場合。直接is-closeという文字列を付与している。

  • 変化前:<div class="box"> … </div>
  • 変化後:<div class="box is-close"> … </div>

B. 「間接」のHTML例

変化させたい対象が、内部のpの場合。親要素にis-activeの文字列が付与され、それを起点にセレクタを記述するパターン。

  • 変化前:<div class="text_box"><p>テキスト</p></div>
  • 変化後:<div class="text_box is-active"><p>テキスト</p></div>

C. 「直接・間接の両方」のHTML例

変化させたい対象が、.box.inner両方の場合。is-fixedの文字列を加え、両方をスタイリング。

  • 変化前:<div class="box"><div class="inner"> … </div></div>
  • 変化後:<div class="box is-fixed"><div class="inner"> … </div></div>

「効果付与」と「文字列追加」との違い

状態変化の「効果付与」と、類似複製の「文字列追加」は、HTML要素に対する命名のアプローチが同じです。

「効果付与」の欲求は対象に状態変化を与えることであり、「類似複製」の「文字列追加」は、似て非なるものを作成したいという欲求が起点となっています。二つの違いを言い表すと、「表示要素が増える前提であったかどうか」と言えます。

効果付与と文字列追加の違いの例@2x.png

「効果付与」した後のものを、単独かつ別用途で再利用する事になれば「類似複製」とも言えますし、「効果付与」したものにCSSプロパティを書き足していけば、変化に伴うバリエーションを作成した事にもなりえます。
つまり「元々どう望んでいたか」は後でどうとでも変容し、曖昧になりがちで、変容しても画面上にエラーが表示される訳ではありません。

どのように使っているのかは、HTML上からは命名から予測するしかなく、実装方法はCSSコードを見る事でしか判断できません。この2つを区分けして制御したりルール化するかどうか。といった事も制作者の意思によるものと言えます。

C-1. 活用方法

活用方法」とは、その対象を全体の中でどのように扱いたいのか、制作者の意思を指します。

C-1・活用方法-詳細@2x.jpg

Webページの表示部品は、ページ間で再利用するものや、その場所でしか使用しないもの、その場でしか使用しないと思っていても後々再利用の必要が発生した。などの様々なケースがあります。
このため、その対象を成果物(Webページ・サイト)の全域でどのように活用するのかを定めることが必要になります。

HTMLタグそのままの状態とclass属性で名前を与えたものは、どちらも「仕様上は」すべて共用として扱える状態になりますが、制作者がその仕様通りに望んでいるかどうかは別の話です。
HTMLとCSSの仕様は自由度が高く「絶対に表示されない」といった制約が少ないため、制作者の意思による制御が必要となります。

C-2. 命名判断

命名判断」は、対象のHTMLタグに独自の名前を与えるかどうかの判断を指します。

C-2・命名判断-詳細@2x.jpg

名前を与えると、何も命名しなかった時と比べCSSからその要素を指定するのが容易になり、さらに、その単語は制作者の予測・判断のための「道しるべ」としての役割も兼用することになります。

対象の表示を考える時、「どんな名前を与えるか」の前に「名前を与えるかどうか」を先に判断しており、「無名」の場合は、何らかの判断により「命名しない事を選んだ」という事になります。

C-3. 特定方法

特定方法」は、CSSのセレクタの記述方法を指します。
最終的なセレクタ記述は様々な意図や意思、複数の影響を踏まえて総合的に決定する事になります。

C-3・特定方法-詳細@2x.jpg

公共的
「公共的」なアプローチとは、第一セレクタにその対象を指定することによってCSSの詳細度を低く抑える方法です。命名の法則によって「活用方法」を「一意」に扱うという意図がある場合は、「公共的」な記述をしたとしても「一意」になります。(BEMなどの単語連結規則による命名一意化がこれにあたります)
詳細的
「詳細的」なアプローチとは、対象のCSSの詳細度を上げるため、もしくは他の要素へのスタイリングとの影響を分離するために、子セレクタや子孫セレクタを利用する方法です。
条件的
「条件的」なアプローチとは、隣接セレクタや以降のセレクタ、記述の順番などのセレクタ、クラスや属性のマッチなど、HTMLの記述条件をセレクタに利用する方法です。
局所的
「局所的」なアプローチとは、HTMLのid属性による指定方法です。
HTMLのid属性の名前は仕様上ページの中に1つと定められていますが、実際のブラウザレンダリングでは複数のid属性を利用していても意図した通りに表示される事も多くあります。 しかし、与えられた役割そのものと、役割違いの状態をソフトウェアが柔軟にレンダリングする事とは別の話といえます。 id属性をCSSのセレクタに使用するかどうかはその環境やプロジェクトのルール次第となります。

【STEP-5】 コードの記述

最終的に物理的なファイルにコードを記述します。
大抵の場合はデータ表示の基礎となるHTMLコードを記述する所からはじまります。

Webページとしてスクリーンに表示するにはHTMLとCSSの両方のコードが必要であり、この二つは同時には記述できないため、実際には「確認・想定」と「コード化のための思考」を行き来しながら記述する事になります。

実際にどのようなコードを記述するのかは制作者の役割であり、この記事の目的とは異なるため割愛します。

さいごに

この試みは「CSS設計手法に振り回されない、何か不変的なものは無いのか」という疑問からはじまりました。
そのための方法として選んだのが今回の「コーディングを短い言葉で言い表してみよう」というものです。2019年初頭に完成した原文には用例と思考パターンによる裏付け情報もあります(※こちらはまた機会があれば公開します)

そして、これらの情報を背景として構築した「HTML+CSSコーディングの標準化」のノウハウがあるのですが、一連の情報を「Ultimate Coding」と題して、今後順次、情報を公開していく予定です。

最後になりましたが、この記事の内容に対し、仕事として貴重なレビューを下さった次の方々に感謝いたします。

レビュワー(※五十音順)

以下は、レビュー頂いた内容の一部です。

  • 普段やっている事が言語化されている
  • 頭の中で起こっている事と遜色なし
  • 初学者と読み合わせして反応を見れば、どこで詰まっているかが分かる
  • 書き方を変えれば教育用に使えるのでは
  • コーディングを公平かつ中立的な視点で捉えたコンテンツ
  • (原文の状態では)図が無く、情報がぱっと頭に入ってこない
    ※ ⇒ これを受け、今回、図を追加して公開

クレジット

Ultimate Coding

  • 設計・考案/構築/記事投稿
  • 技術検証/設計パートナー
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

お前はもう死んでいる。とか言ってる俺の脳みそが一番死んでいたのかもしれない。~JavaScript 即時関数~

JavaScriptに苦手意識のない世界を実現したい!!で以下の様に述べたのですが、"終えた"は言い過ぎだったような気がして修正しました。失礼しました。

即時関数という仕組みがありましたが、役割を終えた感があります。

先日こんなものを見つけました。kintone JavaScript APIのイジりかたというページの説明です。cybozuさんのkintoneというアプリケーションです。
kintone標準.PNG

kintoneとは

説明が難しいのですが、業務アプリをノンプログラミングで作成できるアプリケーションです。恐らく、この説明ではピンとこないと思うのですが、多分やれることが多すぎて一言で説明するのが難しいのだと思います。きっと触ってみるのが一番なんだと思います。

人手が足りないのでバックオフィス業務を極力効率化したいねって話がありまして、試してみることにしました。

競合にはSalesforce Lightning Platformなどがあるそうです。使ったことがないので実際のところどうかはわかりませんが。

kintoneは、ノンプログラミングでアプリを作れるのがウリなのですが、APIを公開しておりプログラムが書ければ更にカスタマイズの自由度があがるという代物です。

今回UIで気なったことがあり、カスタマイズしてみようと思い立ったわけです。

やったこと

デフォルトで利用できる「クレーム管理」というアプリがあるのですが、こうだったものを、
修正前.PNG

こうしました。(コメント入力欄に影を付けて浮き立たせただけ)
修正後.PNG

パッと見わかりにくい(と私は感じた)のですが、下図の赤い線を境にして、左と右は意味的に別物の領域となっています。ただこれが、フラットで、線が揃っており、色味も似ているデザインだったせいか一体化している印象を受け、結果、相沢(仮称)は混乱しました。

修正前2.PNG

でも、もう誰かのせいにするのはやめよう。わからないならば、わかるようにすればいい。そうだろ?

「ノンプログラミングで」というコアバリューからは脱線してしまいましたが、使いやすく作り直せるのはうれしいし楽しいですね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

KintoneとJavaScript 即時関数

JavaScriptに苦手意識のない世界を実現したい!!で以下の様に述べたのですが、"終えた"は言い過ぎだったような気がして修正しました。失礼しました。

即時関数という仕組みがありましたが、役割を終えた感があります。

先日こんなものを見つけました。kintone JavaScript APIのイジりかたというページの説明です。cybozuさんのkintoneというアプリケーションです。
kintone標準.PNG

kintoneとは

説明が難しいのですが、業務アプリをノンプログラミングで作成できるアプリケーションです。恐らく、この説明ではピンとこないと思うのですが、多分やれることが多すぎて一言で説明するのが難しいのだと思います。きっと触ってみるのが一番なんだと思います。

人手が足りないのでバックオフィス業務を極力効率化したいねって話がありまして、試してみることにしました。

競合にはSalesforce Lightning Platformなどがあるそうです。使ったことがないので実際のところどうかはわかりませんが。

kintoneは、ノンプログラミングでアプリを作れるのがウリなのですが、APIを公開しておりプログラムが書ければ更にカスタマイズの自由度があがるという代物です。

今回UIで気なったことがあり、カスタマイズしてみようと思い立ったわけです。

やったこと

デフォルトで利用できる「クレーム管理」というアプリがあるのですが、こうだったものを、
修正前.PNG

こうしました。(コメント入力欄に影を付けて浮き立たせただけ)
修正後.PNG

パッと見わかりにくい(と私は感じた)のですが、下図の赤い線を境にして、左と右は意味的に別物の領域となっています。ただこれが、フラットで、線が揃っており、色味も似ているデザインだったせいか一体化している印象を受け、結果、相沢(仮称)は混乱しました。

修正前2.PNG

でも、もう誰かのせいにするのはやめよう。わからないならば、わかるようにすればいい。そうだろ?

「ノンプログラミングで」というコアバリューからは脱線してしまいましたが、使いやすく作り直せるのはうれしいし楽しいですね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

円グラフを描画したらラベルが被ってムカついた話。

HTML上に円グラフを描画する必要があって、描画してみたらしてみたで偏ったデータでラベルが重なってストレスがたまったのでその記録。

【やりたかったこと】
円グラフを描画する。
とりあえず、chart.jsで円グラフを描画してみる。
(前任者がこれを採用してたのがすべての元凶なのだろうか)
cahrt.js :https://github.com/chartjs/Chart.js/releases/tag/v2.9.3
Labelのプラグイン:https://github.com/emn178/chartjs-plugin-labels

【実装①】

drowChart.js
var data = {
    datasets:[{
        data:[90,1,1,1],
            backgroundColor: ["#009900","#009990","#909990","#300030"]
        }],
        labels: ["一人ぼっちは…","寂しいもんな","いいよ","一緒にいてやるよ"],
    }
    var ctx = document.getElementById('myChart').getContext('2d');
    var myPieChart = new Chart(ctx,{
        type: 'pie',
        data: data,
        options: {
            legend: {
                display: false
            },
            plugins: {
                labels: {
                    render: 'label',
                    position:'outside'
                }
            }
        }    
    });
index.html
<canvas id="myChart"></canvas>

【実装①の結果】
①.png

ラベルがめっちゃ被った・・・

ということで、色々と検索。
やっぱり同じようなことで悩んでる方はいるようだった。
http://danlec.com/st4k#questions/45352194
質問者が「パーフェクトだ!」みたいな返事をしている回答があったので、
さては天才だな?って思ってこの通りに実装してみた。

【実装②】

drowChart.js
angular.module("app", ["chart.js"]).controller("ChartCtrl", function($scope) {
    $scope.labels = ["一人ぼっちは…","寂しいもんな","いいよ","一緒にいてやるよ"];
    $scope.data = [90,1,1,1];
    $scope.options = {
        pieceLabel: {
            render: 'label',
            fontColor: '#000',
            position: 'outside',
            segment: true
        }
    };
});
index.html
<div ng-app="app" ng-controller="ChartCtrl">
        <canvas id="pie" class="chart chart-pie" chart-data="data" chart-labels="labels" chart-options="options"></canvas> 
</div>

【実装②の結果】
②.png

ラベル消えたが(#^ω^)ピキピキ

【解決法】
chart.jsを切り捨てて、d3.js + d3pie.js を使用する。

D3.js :https://d3js.org/
d3pie:https://github.com/benkeen/d3pie

drowChart.js
var pie = new d3pie("myChart", {
    data: {
        content:[
            {label:"一人ぼっちは…",value:90},
            {label:"寂しいもんな",value:1},
            {label:"いいよ",value:1},
            {label:"一緒にいてやるよ",value:1}
        ]
    },
    labels:{
        percentage: {
            fontSize: 0
        }
    },
    tooltips:{
        enabled: true,
        type: "placeholder",
        string: "{label}: {value}"
    }
});
index.html
<div id="myChart"></div>

【結果】

③.png

できたー(^^♪

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

highlight.js で一部の言語のみに対応して webpack のビルドサイズを減らす方法

こんにちは、フロントエンジニアの ku6ryo です。
コード文字列をハイライトしてくれる highlight.js を使う機会があったのですが、highlight.js のライブラリ全体のファイルサイズが大きくて webpack ビルドのサイズを大きくしていたので必要な言語のみ対応して、ビルドサイズを大幅に削減する方法を紹介します。

まず、highlight.js の大きさは webpack で build した場合、Gzip しないとサイズが 746.69 KB、Gzip すると 246.18 KB のサイズがあります。

Screen Shot 2020-02-06 at 1.46.50.png

いずれにしても、とても大きなサイズなことは代わりありませんね(汗)
すべての対応言語を読み込んでしまうと、このようなサイズになります。
必要な言語をのみを読み込むには、以下のように必要な言語のファイルのみを読み込む設定ファイルを作ります。例えば、Javascript, CSS, JSON のみに対応するには、以下のようなファイルを作ります。

// my-highlight.js
import hljs from 'highlight.js/lib/highlight'

hljs.registerLanguage('css', require('highlight.js/lib/languages/css'))
hljs.registerLanguage('javascript', require('highlight.js/lib/languages/javascript'))
hljs.registerLanguage('json', require('highlight.js/lib/languages/json'))

export default hljs

そして、このファイルを highlight.js を呼ぶときと同じように使えば OK です。

// 通常
import hljs from 'highlihgt.js'

// 必要なものだけ読み込む
import hljs from './my-highlight.js'

上記の言語セットの場合、Gzip 前 19KB、Gzip 後 7.14 KB になります。Gzip 前 700 KB 以上、Gzip 後 200 KB 以上のサイズ削減になります。これはだいぶ大きですよね! highlight.js 使っている方はぜひやってみてくださいね〜

Screen Shot 2020-02-06 at 2.01.41.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Angular]ページ遷移にアニメーションをつける

概要

  • ページ遷移する時にスライドするようなアニメーションを入れてみます
  • Angularにアニメーションの機能も搭載されているのでそれを使います

サンプル

ベースのアプリ作成

  • まずは適当な3つのページを用意します
% tree src/app
src/app
├── animations.ts
├── app-routing.module.ts
├── app.component.ts
├── app.module.ts
├── page1
│   └── page1.component.ts
├── page2
│   └── page2.component.ts
└── page3
    └── page3.component.ts
  • 各ページは背景色が違うだけでほとんど同じ内容です
src/app/page1/page1.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-page1',
  template: `
    <style>
      main {
        height: 100vh;
        width: 100vw;
        background-color: lightblue;
        display: flex;
        justify-content: center;
        align-items: center;
        flex-direction: column;
      }
    </style>
    <main>
      <p>page1 works!</p>
      <p>
        <span>Prev</span> | <a routerLink="/page2">Next</a>
      </p>
    </main>
  `,
})
export class Page1Component {}
  • styleが多いですが真ん中中央寄せにしているだけです

before.gif

アニメーションを追加

  • app.module.tsにアニメーションを使うためのBrowserAnimationsModuleを追加します
src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
// importを追加
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { Page1Component } from './page1/page1.component';
import { Page2Component } from './page2/page2.component';
import { Page3Component } from './page3/page3.component';

@NgModule({
  declarations: [AppComponent, Page1Component, Page2Component, Page3Component],
  // BrowserAnimationsModuleを追加
  imports: [BrowserModule, BrowserAnimationsModule, AppRoutingModule],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}
  • routingの設定に識別子を追加します
  • これはこの後で、どのページからどのページに遷移した時にどのアニメーションを適用する、といった感じの定義をする時に使います
src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { Page1Component } from './page1/page1.component';
import { Page2Component } from './page2/page2.component';
import { Page3Component } from './page3/page3.component';

const routes: Routes = [
  { path: '', redirectTo: 'page1', pathMatch: 'full' },
  // それぞれdataプロパティを追加
  { path: 'page1', component: Page1Component, data: { animation: 'Page1' } },
  { path: 'page2', component: Page2Component, data: { animation: 'Page2' } },
  { path: 'page3', component: Page3Component, data: { animation: 'Page3' } },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
})
export class AppRoutingModule {}
  • <router-outlet>を定義している部分に処理を追加します
src/app/app.component.ts
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';

@Component({
  selector: 'app-root',
  // <router-outlet>をdivで囲ってアニメーションの設定を追加
  template: `
    <div [@routeAnimations]="prepareRoute(outlet)">
      <router-outlet #outlet="outlet"></router-outlet>
    </div>
  `,
})
export class AppComponent {
  title = 'ng-animation-sample';

  prepareRoute(outlet: RouterOutlet) {
    return (
      outlet && outlet.activatedRouteData && outlet.activatedRouteData.animation
    );
  }
}
  • routeAnimationsはこの後定義します
  • prepareRoute実行時に<router-outlet>の情報を渡すことで今表示しているページの情報を取得できます
    • outlet.activatedRouteData.animationapp-routing.module.tsで設定したanimation: 'Page1'といったデータを取り出してreturnしています
  • 最後にanimation.tsにアニメーションの情報を定義します
src/app/animation.ts
import {
  trigger,
  animate,
  transition,
  query,
  style,
  group,
  animateChild,
} from '@angular/animations';

export const slideInAnimation = trigger('routeAnimations', [
  transition('Page1 => Page2, Page2 => Page3', [
    style({ position: 'relative' }),
    query(':enter, :leave', [
      style({
        position: 'absolute',
        top: 0,
        right: 0,
        width: '100%',
      }),
    ]),
    query(':enter', [style({ right: '-100%' })]),
    query(':leave', animateChild()),
    group([
      query(':leave', [animate('300ms ease-out', style({ right: '100%' }))]),
      query(':enter', [animate('300ms ease-out', style({ right: '0%' }))]),
    ]),
    query(':enter', animateChild()),
  ]),
  transition('Page3 => Page2, Page2 => Page1', [
    style({ position: 'relative' }),
    query(':enter, :leave', [
      style({
        position: 'absolute',
        top: 0,
        left: 0,
        width: '100%',
      }),
    ]),
    query(':enter', [style({ left: '-100%' })]),
    query(':leave', animateChild()),
    group([
      query(':leave', [animate('300ms ease-out', style({ left: '100%' }))]),
      query(':enter', [animate('300ms ease-out', style({ left: '0%' }))]),
    ]),
    query(':enter', animateChild()),
  ]),
]);
  • この内容は公式サイトの内容をベースにしています
  • transitionの定義が2つあります
    • transition('Page1 => Page2, Page2 => Page3'はPage1からPage2への遷移かPage2からPage3への遷移の際に発動します
    • transition('Page3 => Page2, Page2 => Page1'はPage3からPage2への遷移かPage2からPage1への遷移の際に発動します
    • これは先程の[@routeAnimations]="prepareRoute(outlet)"の部分でページの情報を随時拾っているので判断できるというわけです
  • 最後にこの定義を反映させます
src/app/app.component.ts
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
// importを追加
import { slideInAnimation } from './animations';

@Component({
  selector: 'app-root',
  template: `
    <div [@routeAnimations]="prepareRoute(outlet)">
      <router-outlet #outlet="outlet"></router-outlet>
    </div>
  `,
  // アニメーションの設定を追加
  animations: [slideInAnimation],
})
export class AppComponent {
  title = 'ng-animation-sample';

  prepareRoute(outlet: RouterOutlet) {
    return (
      outlet && outlet.activatedRouteData && outlet.activatedRouteData.animation
    );
  }
}
  • animations: [slideInAnimation]の部分でアニメーションを適用しています
  • これで完成しているはずなので動かしてみます

after.gif

  • うまくいきました!

感想

  • Angular以外のライブラリを使っている場合アニメーションをやろうとするとライブラリ選定から悩むことになりますが、3rdパーティライブラリを使わずできちゃうところがいいですね
  • 公式サイトのドキュメントも親切でした
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

jQueryの次にJavaScriptを学びはじめたキミのための記事!

記事の目的

この記事は、プログラミング初学者の中でも、
HTML → CSS → jQuery と学んできて、次のステップとしてJavaScriptを学び始めた人が、
技術的な話の前に知っておいたほうが助かりそうな事情や背景をまとめた記事です。

JavaScriptをちょっと初めてみたけど、よくわからん!と混乱しているタイミングで、読んでいただけたらと思います。

「そんなことわかってるよ」と言いたくなるような初歩的な説明もあると思うので、読み飛ばしていただければと思います。

執筆者もプログラミング歴1年程度の初心者なので、知識の浅い部分があります。
また、初心者ながらJavaScriptを学んでしばらくたっているので、当事者目線からずれた説明をしてしまう部分もあるかもしれません。
そのため、この記事のコメント欄で、ターゲットとなる当事者の方や、執筆者よりも博識な方などから、
いろいろな意見を頂きながら成長していくコミュニティにしたいと思っていますので、ご協力よろしくおねがいします!

jQueryの立ち位置

そもそも、HTML、CSS、jQuery、JavaScriptというのは、何が違くて、どういう風に活躍する言語たちなんでしょうか。

HTML・CSSの立ち位置

HTMLは、WEBサイトの構成を表すための言語です。
<head>では主に、WEBサイトの構成に必要な設定の記述や、ファイルの読み込みを行い、
<body>では主に、どんな構成で要素を配置していくかを表します。

CSSは、WEBサイトのデザインを表すための言語です。
HTMLで配置した要素を、どんなデザインでユーザーに見せたいか記述します。

この2つは、jQueryやJavaScriptと違ってプログラミング言語ではありません。
プログラムというのは、機能的な処理をするものの事ですが、
この2つは書いたとおりに表現をしてくれるものですので、マークアップ言語と呼ばれ、違うものとして考えるのが一般的です。

ただし近年では、HTMLやCSSだけでもある程度機能的な部分を実装できるようになりつつあります。
例えば、HTMLだけで入力欄入力された文字のチェックをすることも出来ますし
CSSだけでハンバーガーメニューの実装をすることなど出来ますね

このように、HTMLやCSSだけで出来ることについて深く知ることは、
過度にJavaScriptやjQueryへ依存してしまわないためには必要なので、
どんどん情報収集した方がよいと思います。

JavaScriptの立ち位置

JavaScriptは、WEBサイト上(=ブラウザ上)で動かすことができるプログラミング言語です。
プログラミング言語って、つまりどんな事ができるの?という話をしだすとキリがないのですが、
要は、HTMLとCSSだけでは出来ない事は、全てプログラミングで実装していると理解していればオッケーです。
そして、そのように何でもできる技術だからこそ、何でもできるようになるためには、沢山の知識が必要になります。
(ブラウザ上で動くJavaScriptの他にも、サーバー上で動くサーバーサイドのプログラミング言語も存在するので、
 全てJavaScriptという訳ではないのですが、それはもう少し先のお話になります。)

jQueryの立ち位置

jQueryは「JavaScriptでWEBサイトの見た目や動き方を指定したいけど、あまり深い知識まで学んでいられない人」とか、
「できるだけわかりやすく書きたい人」とか、「パパっと仕上げたい人」のために作られた仕組みです。
HTMLとCSSだけでは出来ない事を、簡単に実装したい人のための仕組み、と考えてもいいです。

実は、見た目や動き方だけではなく、もっと機能的な処理も簡単に実装できるようにと作られている機能もありますが、
本記事を読みたいと思う方で、そういうところまで使いこなしている方は少ないと思うので、深くは触れません。

jQueryって何なんだっけ?

jQueryとはどういうものか、についての説明です。
jQueryを学んだ際にも同じような話を聞いたかと思いますが、再び思い出してみて、より深く理解しましょう。

jQueryは、JavaScriptのライブラリ

jQueryは、JavaScriptのライブラリです。
ライブラリとは、まとまった機能を切り出して、再利用できる形に保存してあるコードのことです。

CSSで例えるならば

ライブラリ.css
.button{
    borde: 1px solid black;
    border-radius: 5px;
}

という内容のファイルを作成して、これをHTMLの<link>タグで読み込んで、
このデザインを適用したいボタンにclass="button"と指定してあげれば、
いつでもボタンを同じデザインに統一することができますよね。
JavaScriptで、これと同じような事をしたものがライブラリです。

つまり…

jQueryでやっている事は、JavaScriptでやっている。

jQueryはJavaScriptのライブラリという言葉の真意は、
jQueryの中身はJavaScriptで書かれているということであり、
jQueryでやっている事は、JavaScriptでやっている。
という事になります。

jQueryの中でJavaScriptも使える。

違う言い方をすると
jQueryの中でJavaScriptをバリバリ使ったプログラムを書いていくことも可能ということです。
というよりも、なんなら既にバリバリ使っていると思います。
簡単なところで言うとvarletを使った変数宣言や、=を使った変数への代入、
forforEachなどを使ったループ処理、ifをつかった条件分岐などもそうですね。
jQueryのメソッドではない部分の処理は、すべてJavaScriptの処理だということです。

そうは言っても、jQueryとJavaScriptが同じ書き方には見えない、という方も居るかもしれませんね。

jQueryの独特な書き方について

jQueryが本来のJavaScriptと違った書き方をしているように見えるなら、それはjQueryが以下の仕組みを積極的に使用しているからだと思います。

  1. 「\$」という名付け
  2. メソッドチェーン
  3. コールバック関数

これらの詳しいところは、JavaScriptについて分かってきた後に、やっと理解できるようなレベルの話だったりしますが、
現時点で知っておいた方がいい要点だけ、説明させていただきます。
(わりと長い説明になってしまうので、理解できなければ飛ばしても構いません。)

「$」という名付け

jQueryの使用を開始するために使う$( function() { ...という書き方や、
jQueryでHTMLの要素を操作するために使用する$('セレクタ')という書き方など、
jQueryを使用する場合では頻繁に目にする「\$」という文字があります。
$()ではなくjQuery()と書くことでもjQueryを使う事ができるので、
 そちらの書き方のイメージが強い方であれば、飛ばしていただいて構いません。)

この「\$」はjQuery独特の書き方に感じるかもしれませんね。
しかし、JavaScriptのプログラムにおいて「\$」という文字に特別な意味はないという事を覚えといてください。
「a」「b」「c」と同じように、普通に使えるただの文字です。

そうは言っても、人間から見れば特殊な文字なので、
大半のプログラマーは、特別な理由がない限り「\$」という文字を名前に使うことはありません。

jQueryを使っているという、特別な理由がある場合じゃないと使わないような文字なので、
かなり独特に見えるとおもいますが、要はただの変わった名前でキラキラネームみたいなものです。

メソッドチェーン

$('ooo').hide()$('xxx').val()のように、処理を続けて書くような書き方をメソッドチェーンと呼びますが、
これもJavaScriptの学び始めではなかなか見かけないかもしれません。
しかし紐解いて分析してみると、なんてことのないJavaScriptの標準的な使い方だと気づくことが出来ます。

JavaScriptにはオブジェクト型というデータ型がありますね。複数のデータをまとめて保管できる便利なデータ型です。
(学習環境によっては連想配列だとかObjectオブジェクトという呼び方で学んだ人もいるかもしれませんが、どれも同じものを指しています。)

オブジェクト型のデータが持っている中身のデータから、あなたが選んだ一つのデータ(=任意のキーの値)を取り出したい場合、
オブジェクト.データのように、.で区切って記述します。
(本来、オブジェクトというのは、もっと色んなものを指す言葉なので、そこは誤解されないよう注意してください。)
そして、このデータに入る値が、文字列や数値などではなく関数だった場合、
実行するにはオブジェクト.データ() と書きます。

そうです。jQueryで使われる$('引数')がオブジェクトなら、
$('引数').データで、中身の関数を実行することができるのです。

え?
$()はオブジェクトじゃなくて関数だろうって?
鋭いですね。

次に必要になる知識が関数の戻り値(返り値)という概念です。
関数を実行すると、関数の中で設定した戻り値(=returnの値)が、その関数の位置に置き換わります。
(JavaScriptの学びはじめだとreturnは処理を終了させるための記述だと教えられる人もいるかもしれませんが、
 それはあくまで副作用です。本来は、戻り値を指定することが目的の記述になります。)

では、$という名前の関数を作って、戻り値にshowという名前の関数が入っているオブジェクトにした場合は…?
そう、$().show();と書いてあげることで、$()の戻り値に入っているshowという関数データが実行される事になります。

では、$()という関数に、引数を渡してあげると…

$('引数').show();

なんということでしょう。
一見、見慣れないプログラミング言語かのようだったJavaScriptが、
匠の手によって、jQueryと同じような記述に生まれ変わりました。

jQueryは、引数として渡された文字列(セレクタ)を、いい感じに処理した上で、
jQueryメソッドと呼ばれる関数を持っているオブジェクトを戻り値にしてくれているんだと、
なんとなくイメージできたでしょうか?
(ちなみに、メソッドというのは、オブジェクトや関数などの、何かの中に入ってる関数という意味です。)

jQueryが、きちんとJavaScriptで出来ていることがわかってきたかと思います。

コールバック関数

最後に説明するのは、
jQueryをはじめる際に現れる
$( function(){ ... という書き方についてです。

これはコールバック関数と呼ばれる使い方で、調べると結構ややこしい情報がでてきたりしますが
今回の説明に限っていえば簡単なことです。

JavaScriptは、基本的には上から順番に処理を実行してくれるので、
関数の中の、特定のタイミングで別の処理を実行させたい場合の書き方は、下のようになります。

function(){
  //事前の処理
  ooo();
  //事後の処理
}

しかしこの書き方には、この関数を定義するよりも前にooo()を定義しておかなければならないという弱点があります。
それを回避するために、引数に関数を渡すという策があります。

function( xxx ){
  //事前の処理
  xxx();
  //事後の処理
}

こうする事で、引数に渡された関数が、jQueryの都合のいいタイミングで実行されてくれるのです。
つまり、$()の引数に、関数を渡しているのが、$( function(){ ...の正体だということです。

まとめ

ちょっと長い説明になってしまいましたが、結局何が言いたいかというと、
jQueryは、JavaScript学び始めの段階ではまだまだ使い慣れていない仕組みを積極的に使用しているせいで、
なんだかJavaScriptとは違う書き方をしているように見える事があると思いますが、
全てJavaScriptでも使われる書き方だと言うことです。

jQueryが皆に愛された理由

次は「とりあえずjQuery」と言われるほどに、
jQueryが皆から愛された理由について説明します。
これは、次の章にも関連する内容になります。

ブラウザ間の動作の違い

JavaScriptはブラウザ用のプログラミング言語です。
ブラウザとはInternetExplorerEdgeSafariGoogleChromeFirefoxなどのインターネットを見るためのアプリですが、
これらのアプリの中身は別々の人達が作っているのでJavaScriptはブラウザごとに細かい動作の違いがありました。
(ブラウザ用の言語でなくても、WindowsMacなどのOSによって動作が違う言語はあるようなので、JavaScript特有の問題というわけではないかもしれませんが。)

「細かい動作の違い」と言いましたが、jQueryが流行った当時は、無視できないくらい大きな違いだったそうです。
単純な処理を実行したいだけなのに、ブラウザの数だけ条件分岐した処理を書かなければいけないとしたらどうでしょう?
趣味でやるなら「頑張れ〜!」で済む話かもしれませんが、仕事であれば時間に対して給料が発生していますので、
小さな処理でも何か書く度に何パターンも考えなければいけないなんて、たまったものじゃありませんね。

それをjQueryは、あらかじめそれぞれのブラウザで狙った処理になるようにまとめてくれているそうです。

以前のJavaScriptは、今より不便だった。

JavaScriptを最近学びはじめた人であっても「ES5」と「ES6」のバージョンの違いがあることはご存知かと思うので、
想像に容易いかと思いますが、
JavaScriptは日々進化し続けています。

それは裏を返すと、少し前の時代のJavaScriptはものすごく不便だったということです。
JavaScriptを駆使する「フロントエンドエンジニア」という職種も最近現れた職種だということからわかるように、
以前はJavaScriptで色々やるなんて想像もできなかったというか、
そこまでのパワーは無かったものだと思います。

jQueryから皆が離れはじめた理由

前章を受けて、そんなに愛されたjQueryから、なぜ人々が離れ始めてしまったのかについて説明します。

ファイルサイズの肥大化

JavaScriptと同じように、jQueryも時代とともに進化しています。
JavaScriptは元々ブラウザに組み込まれているものですが、
jQueryはライブラリという一つのファイルですので、
jQueryが進化するということは、jQueryのファイルサイズが肥大化するということです。
サイトで読み込むファイルのサイズが肥大化するということは、
サイトを表示するスピードが遅くなるということです。

読み込むだけで30秒とか1分とか遅くなるっていうことはないのですが、
jQueryの処理ひとつひとつもそんなに処理速度が早いわけでは無いようで、
jQueryの読込と処理の遅さに加えて、
低スペックスマホや通信速度の遅いフリーWi-Fiなどの環境問題、
さらにGoogleがページ表示速度の高速化を推奨しているなど、
いろんな要素が合わさることによって、ページの表示速度がかなり気にされる要素となってきています。

InternetExplorer離れ

先程の章でブラウザ間の動作の違いについて説明しましたが、
最近学びはじめた方からすると「そんなに違うか?」と思われたかもしれません。
これはなぜかというと、
ブラウザ間で最も動作が違っていたのが、InternetExplorerだからです。
今となってはWindowsの標準ブラウザはEdgeに変わったので、ほとんどの人は使っていないかと思います。

このInternetExplorerはCSSでも色々面倒な問題を起こしてくれていたブラウザですが、
同じようにJavaScriptにおいても、色々面倒な存在だったみたいです。

そのInternetExplorerが主要ブラウザの座から引退してくれたおかげで、
InternetExplorerの動作の違いを解決するためにやっていた色々なことが不要になり、たくさんのエンジニアが救われたそうです。
ただ、そうなると、その問題の解決をしたくて流行っていたjQueryは、必要のない処理が沢山記述されている重いライブラリ、という立ち位置になってしまうわけですね。
世知辛い世の中です…。

DOM操作系の充実

先程の章で以前のJavaScriptは、今より不便だった。と説明しましたが、
その最たるものが、皆様がjQueryで一番よく使われていると思われるDOM操作についての機能です。

DOM操作というのは、JavaScriptでHTMLの要素をいじったり、値を取得したりする処理のことです。
(HTMLをJavaScriptで扱えるデータに変換したものをDOMと呼ぶのですが、
 難しければ「DOMというのはHTMLのことだ」と思っていても問題ないかと思います。)

これ、jQueryじゃないと出来ないと思っていませんでしたか?
これは余談になるのですが、筆者が使っていた、とある学習サービスでは、
JavaScriptのレッスン中にDOM操作の方法を教えてくれるレッスンが存在していなくて、
最初はJavaScriptを学んでも「だから何?」という状態がしばらく続いていました。
jQueryを学ぶことで、はじめて「あ、これをうまく使えばサイトをいい感じに動かせるんだな」と理解したのです。
そのサービス自体は愛用させていただいてるので名前は伏せますが、同じサービスを使って勉強されている方は、
JavaScriptでDOM操作ができるという事実を知らないかもしれませんね…。

おっと、話が脱線しました。
以前のJavaScriptは、今より不便だったと言いましたが、
特に衝撃だったのがdocument.querySelectorAll()という関数の誕生だと思います。
(おそらくjQueryをリスペクトして作られた関数ではないかと予想します。)
「JavaScript DOM操作」とググっていただければ色々な記事がヒットするのでサラッとしか説明しませんが、
この関数の引数に、jQueryの$()と同じようにセレクタを指定してあげることで、指定した要素のDOMが取得できる関数です。

あとは、取得したDOMのプロパティを取得したり書き換えたりすれば、HTMLに関する処理はできちゃうというわけですね。
ぶっちゃけ、このDOM操作を普通に扱えるようになれば、
大体のアプリはjQueryを使わずに作成することができますし、
それが出来るようになれば「なんでjQuery使ってたんだっけ?」とも思うようになります。笑

仮想DOM操作ライブラリの誕生

と、ここまでjQuery離れをしている理由をいろいろ挙げてみましたが、実はこちらが大本命です。
この理由に該当しない案件であれば、まだまだjQueryが現役で使われまくっている事も珍しくはないです。

jQueryや、ライブラリを使わないJavaScript(=バニラJS)でDOM操作をする場合、
当然、なにかデータを書き換えたらHTMLに反映、という処理を繰り返しています。
細かく言うと、JavaScriptで1つ処理をする→HTMLに反映する→JavaScriptで1つ処理をする…という処理を延々繰り返しています。

しかし、近年現れたAngularReactVue.jsHyperappなどのライブラリは、
仮想DOMという仕組みを利用しており、JavaScriptの処理をまとめてする→HTMLに反映するという処理になっています。
これによって、もちろん処理速度が向上するといった裏側のメリットももちろんあるのですが、
仮想DOMで操作している最中に通常のDOM操作をしてしまうと、処理がおかしなことになる危険性を孕んでいます。

もちろん、HTMLに処理を反映しないような処理(テキストの取得など)であればjQueryを使用しても問題はないですが、
その程度の処理であればバニラJSのDOM操作で十分対応できますし、
上記ライブラリたちの特徴であるデータバインディング(JavaScriptのデータとHTMLの内容をリンクしてくれる機能)のおかげで、そもそもDOMを操作する必要すらない場面も増えました。

(さきほどから仮想DOM操作のライブラリと言っていますが、正確には一部はフレームワークと呼ばれています。
 この2つはJavaScriptにおいては規模感が違うだけなので、今は意識しなくて大丈夫です。)

実際に置き換えてみる

さて、ここまでjQueryを使う理由、使わない理由、JavaScriptとの違いなどの話をしてみましたが、
最後は実践的にjQueryをJavaScriptに置き換えて使ってみたいと思います。
これをマスターすれば、jQueryを使わなくても簡単にいろんな事が出来るようになりますよ!

エレメントを取得する

エレメントというのは、実際に取得したDOMひとつひとつのデータのことです。

document.querySelector系の関数

querySelector系の引数には、jQueryと同じように、CSSセレクタと同じ形の文字列を渡します。

document.querySelector();

セレクタにヒットするHTML要素がいくつあっても、最初の1つだけ取得します。
基本的には、1つしかヒットしないはずのものを取得したい時に使います。
例: document.querySelector('body'); document.querySelector('#id');

document.querySelectorAll();

セレクタにヒットするHTML要素が入っている配列のようなものを取得します。
注意点として、要素がひとつしかなかったとしても、取得するのは配列のようなものなので、
実際にHTMLの処理をしたいときは、取得したデータの中身にアクセスして処理する必要があります。
例: document.querySelectorAll('div'); document.querySelectorAll('.class');

document.getElement系の関数

document.getElement系の関数は、id属性やclass属性など、HTMLのどの属性から探して取得してくるかは予め決まっているので、
引数としては文字の部分のみを渡します。

document.getElementById();

指定した文字列と一致するid属性を持っている要素を取得します。
idは固有のものにするというルールがあるため、関数名はgetElementと単数形になっています。
例: document.getElementById('id'); document.getElementById('menu');

document.getElementsByClassName();

指定した文字列と一致するclass属性を持っている要素が入っている配列のようなものを取得します。
classをはじめとする、id以外のものは複数ヒットする可能性があるため、関数名はgetElementsと複数形になっています。
例: document.getElementsByClassName('class'); document.getElementsByClassName('icon');

類似する関数に、name属性の値で取得するgetElementsByNameや、
HTMLタグの名前で取得するgetElementsByTagNameもあります。

「配列のようなもの」について

先程がら太字にしている配列のようなものについてですが、なぜこのような言い方をしているかというと、
実際に配列と言っていいのは配列型のデータのみだからです。
(学習環境によってはArrayオブジェクトという呼び方で学んだ人もいるかもしれませんが、同じものを指しています。)

複数のデータが入っているとか、[]を使って任意のindex番号にアクセスできるところは配列と同じなのですが、
配列では使えるような、特有の関数が使えなかったりします。

また、querySelectorAllgetElements系の関数では
取得してくる配列のようなものの正体が違います。
querySelectorAll が取得してくる配列にはNodeListという名前がつけられており、
getElements系 が取得してくる配列にはHTMLCollectionという名前がつけられています。

この2つの違いについて調べると、HTMLCollectionは中身のデータがHTMLと連動しており、
NodeListは中身のデータは取得した時点で内容が固定されているという話が出てきますが、
こちらの環境でテストしてみたところ、NodeListの中身も、しっかりとHTMLと連動していたので、
最近は改善されたのか、もしくは実行環境によって動作が違うのかもしれません。

DOM操作してみる

お疲れさまでした。おもったより長文になってしまいました。
ここまで読み切ったあなたは、立派なJavaScript入門者です!

つぎは、実際にDOM操作してみましょう。
プログラミング言語を学ぶ上で大事なのは、実践です。

ブラウザの検証ツールのconsoleから、DOM操作系の処理を試してみてください。
この記事でできることの一例を挙げてみようと思ったのですが、出来ることが多すぎるので、ぜひGoogle先生に聞いてみてください。
プログラミング言語を使う上で大事なのは、検索です。笑

「JavaScript DOM操作」や「JavaScript DOM操作 やりたいこと」などで検索したら沢山ヒットするかと思いますが、
最終的に全てが載っているのはMDNのドキュメントになります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Bookmarkletを作ろう(ウェブページのハック手順編)

この記事は

2年前に書いた「ブックマークレット準備編」の記事へのアクセスが、じわじわと増えてきました。ありがとうございます。

さて、ブックマークレットを作る手順が分かっても、上手にブックマークレットは作れません。先人が作ったウェブアプリケーション(画面)をハックする折れない心が必要です。ブックマークレット自体に制約が多いので、制約の範囲の中でハックする必要があります。

ということで、今回は実用的なものを作るハック手順を紹介しようと思います。私がウェブアプリケーションをハックするときに、見ているポイントや気を付けるポイントを紹介しながら、ブックマークレットに仕上げを仕上げていきます。

はじめに

一番大事なのは要件を決めること

いきなりストレートの直球を投げ込みました。別にブックマークレットに限ったことではないですね。プログラムを作る上でとても大事なことです。

ブックマークレットは単独で動くアプリケーションではないです。誰かが作ったウェブアプリケーション上で、自分のコードを動かすものです。ちょい足しの要素が強いです。完全な自動化まではできませんので、画面を操作をする上で面倒なところを削減するようなところがポイントです。

私が良く作るブックマークレットは、
:broken_heart:表示されている画面から1つ1つデータをコピペしないといけない
:arrow_right:HTMLからデータを抜き出して、コピペしやすい形でConsoleのログに書き出す

:broken_heart:社内の申請などでたくさんのテキストボックスや選択肢を埋めないといけないけど、いっつも決まった値で埋める必要がある
:arrow_right:自動で初期値を入力するか、入力補完の機能を付け足す

:broken_heart:ボタンやリンクを押すだけの操作だけど、何度も押さないといけないので面倒
:arrow_right:画面のボタンを押す作業を自動化する

などです。

別の手段も考えよう

本当にブックマークレットでいいですか?

  • 自分だけが使うならまだしも、誰かに使わせるにはインストール面倒ですよ。
  • バージョンアップも面倒ですよ。
  • データ登録系は苦手ですよ。ローカルホストで動くバッチプログラムのほうがよくないですか?
  • PythonでHTTPしてHTMLをほじくり返したほうがよくないですか?
  • なんなら、SeleniumやRPAのブラウザ自動操作でもよくないですか?

:sunflower: ブックマークレットの特性と限界を知った上で、実現できるものを上手に設計しましょう。

ハック手順の紹介

何はともあれ、まずは敵を知りましょう。
対象となるウェブアプリケーションの画面を決めたら、ページ内で起きていることを調べていきます。

1.まずは操作、操作

第一段階は、人間として画面操作をしてみて、何が起きているのかを感じましょう。といっても、適当に見ていてもしょうがないので、見るポイントはこの辺です。

  1. 画面表示時にデータが全部あるのか、遅れて表示されるタイプか?
  2. 入力に応じて画面が変わったり、モーダルが出たりするのか?
  3. 入力に対しバリデーションのチェックがあるのか?
  4. 1つの画面で完結するのか、画面遷移をするのか?

特に4の画面遷移するアプリケーションをハックするのは苦手です。

2. HTMLソースの調査

画面を見たあとは、HTMLのソースを確認します。この段階ではブラウザの「ページのソースの表示」機能で見える範囲でやります。DOMを見るというよりは、HTMLの癖を見抜く感じです。

ポイントは、

  1. HTMLソース内にjavascriptの変数(配列やオブジェクト)がどれくらいあるか?
  2. HTMLにidやclassがしっかり振ってあるか?
  3. HTMLのdisplay:noneなどの隠し要素がどれくらいあるか?
  4. HTML上に必要なデータが全部落ちているか?

1の部分は、Google Analytics用の変数として落ちていたり、schema.orgの変数として落ちていたり、いろいろあります。結構、大事なデータがHTMLに埋まっていることがあります。
2の部分は、jQueryでの操作感にかかわってきます。idやclassが少なめだと、最悪はbody.innerHTMLからの正規表現でのぶっこ抜くのも考えます。

3. HTMLのDOM要素を調べる

2と似ていますが、見るポイントが異なります。

この部分では、画面の初期表示後に何かしらの操作をして、変更されたDOMを状態を確認します。例えば、最初は表示されていないけどクリックしたらリンクが表示されるような画面では、表示された状態のDOMを見ます。

ChromeのDev ToolのElementsパネルで調べます

見るポイントは、
1. 要素そのものか、上位の要素がブロック要素タブ(<div>など)に囲まれているか
2. 繰り返し要素に同じclassがついているのか
3. クリックしたあとにネットワーク通信をしているか?

何度か操作をして、display:noneなどのCSS要素が消えるイベントなども把握します。APIを呼んでそうな場合は、次の手順4で確認します。

4.画面内でAPIを呼び出しているか

SPA系で画面遷移が伴わない場合、裏側でAPI通信をしていることが多いです。どのタイミングどのAPIを呼びだしているかを調べます。APIの呼び方が分かれば、ブックマークレットから勝手に呼び出すことが選択肢として入ってきます。

Chrome Dev ToolのNetworkパネルで調べています。
APIのエンドポイント、リクエストヘッダー、パラメータの詳細を知ることができます。

5. 使っているjavascriptのライブラリ

HTML表示完了時に読み込みされているjavascriptのライブラリは、ブックマークレットからでも使うことができます。どんなライブラリが使われているか、HTMLソースから<script src="">を検索して調べます。最近は避けられがちですがjQueryが読み込まれていれば、ブックマークレットを作るのはかなり簡単になります。

:sunflower: というのが、ウェブページをハックするときに見ているポイントです。この手順とポイントに沿って、最近作ったブックマークレットを紹介します。

実際にブックマークレットを作ってみる

LOHACOのショッピングカートにたくさんの商品を入れて、「カート内の割引が効いているか、割引の組み合わせが間違えていないか」を確認するお仕事がありました。
100個くらいの商品を入れて確認をした後に、別の商品を入れて確認するためにカートを空っぽにする必要があります。しかし、一発で空にする機能は提供されていないので、1つずつ丁寧に心を込めて削除ボタンを押していく必要があります:frowning2:

困りました。削除を100回押すとか、目まいがする作業です。

  • お金に関わる部分なので、できれば目視確認はしたい
  • でも、全削除に時間がかかり、仕事に時間がかかりすぎる
  • どうにか効率よくやりたい

こんな背景でした。

この作業を辞めてしまうというのは選択肢にないようで、そもそも論はできませんでした。
あなたの仕事は、カートの価格が正しいことを確認することであり、カートの商品を消すことではない!一刻も早く削除クリック作業から解放してあげなければ。。。

:warning: 2020年2月時点のHTMLで動作確認しています。将来にわたり動作するコードではないです。

まずは対象の調査から

まずは、カートに商品を詰め込みます。詰め詰めしたら、カート画面を開いてみます。
URLはここっぽいですね。https://lohaco.jp/sf/cart/

image.png

動作を体感する

削除するのが大変ということなので、何も考えずに削除を押してみましょう。

2020-02-02_13h19_33.gif

くるくる回って、消えるタイプですか。画面遷移が伴わないときは、DOMで非表示にするか、APIかなぁ。と想像だけしておきます。

くるくるが終わったら、次の削除を押す。1商品あたり3秒くらいかかりそうです。100商品だと、、、

HTMLソースを見る

一覧系の画面は、<div><table>でデータを表示することが多いので、比較的データを特定しやすいです。Body周辺を眺めてみましょう。
おや、Bodyタグの中に商品名や価格の情報が見当たりません。その代わりに、途中に長~いjavascriptの変数があります。

この辺です
image.png

LOHACO.Env.PAGEという変数が怪しいですね。
これを目で解読するのは大変だし、JSONっぽい変数なので、Chrome Dev ToolのConsoleで変数を覗いてみます。F12でConsoleを開いて、変数名を入力してENTER:arrow_down:するだけでよいです。

image.png
きれいなデータ構造だ。

マウスで▼を下っていきましょう。

image.png
image.png

なるほど、この辺に商品があるっぽいですね。

Console開いたついでに、$.fn.jqueryも入力して、jQueryが使えるか確認しておきます。
image.png

jQueryの3.4系が入ってるらしい。ラッキーですね。

DOMを確認

Bodyタグに表組(データ)が含まれていなかったので、完成した画面のHTMLを確認します。これはChrome Dev ToolのElementパネルで見ます。

削除リンクを右クリックして、検証
image.png

image.png

リンクはAでもButtonでもなくp.deleteでしたね。
ついでに周辺のHTML構造も見ておきましょう。例えば、商品名はp.titleにあるようです。

APIの確認

画面遷移をせず、くるくるして商品が消えているので、裏でAPIを呼んでいる可能性が高いですね。このケースは、とりあえず、Chrome Dev ToolのNetworkパネルで調べてみます。

Networkを開いて、表示されている内容をクリアしてから、削除リンクを押す。
2020-02-02_13h54_19.gif

ずらずら出てきました。
一番最初でfetchしていますね、fetchの詳細を確認させていただきましょう。

image.png

APIを呼び出してました。
Request MethodDELETEですね、Bodyにパラメータは無くてURIでリソースを指定、ですか。Rest Fullな感じでかっこいいですね。

調査段階では、URLやHTTPメソッド、リクエストBodyやリクエストHeaderを確認し、可能であればPostManなどのHTTPクライアントで疑似的に投げて、受け取ってもらえるのか確認しておくのがいいです。

ここまでの調査結果

ここまでの結果をまとめると

  • このページは、削除をクリックしたら画面遷移するタイプじゃないこと
  • データはHTML上から取るのではなく、scriptの変数で取れそうなこと
  • 削除リンクをクリックしたらAPIを呼んでいること
  • jQueryは使えること

ということが分かりました。ブックマークレットとして実現するものは、

  1. カートにある商品のキーになりそうなコードを探してきて、
  2. 削除リンクで呼び出しているAPIを呼んであげる

となります。言葉にすると簡単ですね。

たいていは、商品データを探すところや、集めるところに苦労しますが、javascriptの変数が落ちていたので、比較的簡単に終われそう。

コーディング

javascriptでコードを書くだけです。書いたコードはこちらです。全部で100行くらいです。

clear_cart.js
// ==ClosureCompiler==
// @output_file_name default.js
// @compilation_level SIMPLE_OPTIMIZATIONS
// @language_out ECMASCRIPT_2017
// ==/ClosureCompiler==
javascript:(
    async function(){

        //0.画面チェック
        if( !location.href.match(/lohaco\.jp\/sf\/cart\//)){
            alert("対象のページではないようです。カゴのページに移動します");
            window.location.href='https://lohaco.jp/sf/cart/';
            return;
        }

        var deletedCount = 0;
        var targetItems = [];

        //1. 普通の商品
        targetItems = targetItems.concat(LOHACO.Env.PAGE.lohacoItems);

        //2. discountItem
        for(var i=0; i<LOHACO.Env.PAGE.setDiscounts.length; i++){
            var setDiscount = LOHACO.Env.PAGE.setDiscounts[i];
            console.log('start %s-%s', setDiscount.name, setDiscount.lohacoItems.length );
            targetItems = targetItems.concat(setDiscount.lohacoItems);
        }

        //3. sellerItem
        for(var i=0; i<LOHACO.Env.PAGE.sellers.length; i++){
            var seller = LOHACO.Env.PAGE.sellers[i];
            console.log('start %s-%s', seller.name, seller.items.length );
            targetItems = targetItems.concat(seller.items);
        }

        //4. supplierItem
        targetItems = targetItems.concat(LOHACO.Env.PAGE.supplierItems);

        //5. 商品を消す
        console.log('target Items = %s', targetItems.length);
        deletedCount += await processItems(targetItems);

        //6. ここで終わり
        alert(deletedCount + '商品を消しました。F5でリロードしてね。');

    //======
        //Itemを繰り返してdelete呼ぶ関数
        async function processItems(items){
            var cnt = 0;
            for(var i=0; i<items.length; i++){
                //投げすぎないためのやさしさ。2回目以降はちょっと止まろう
                if(i>0){
                    await new Promise(resolve => setTimeout(resolve, 500)) // 0.5秒待つ
                }

                var code = items[i].catalogCode;
                var result = await deleteItem(code);
                cnt+=result;
                if(result == 1){
                    console.log('%s is OK', code);
                    deletedEffect(code);
                }else{
                    console.log('%s is NG', code);
                }
            }
            return cnt;
        }
        //単品Itemを削除するAPIを実行する関数
        async function deleteItem(itemcd){
            var url = 'https://lohaco.jp/sf/api/b/cart/items/' + itemcd;
            var response = await fetch(url, 
                {
                    method: "DELETE", 
                    headers: {
                        "Content-Type": "application/json", 
                        "x-csrf-token":LOHACO.Env.CSRF_TOKEN },
                    body:"{}"
                });
            var json = await response.text();
            // console.log(json);

            if(response.status=='200'){
                return 1;
            }else{
                return 0;
            }
        }
        //削除が終わったら表示を変える関数(動いている感じが出ないから)
        //商品名に取り消し線を入れて、自動スクロール
        function deletedEffect(itemcd){
            var selector = 'p.title > a[href^="/product/' + itemcd + '"]';
            var a = $(selector);
            if( a.length == 1){
                a.html( '<s>' + a.html() + '</s>' );
                var y = a.offset().top - 100;
                $(window).scrollTop(y);
            }
        }
    }
)();


https://github.com/kanaxx/bookmarklet/blob/master/lohaco/clear_cart.js

プログラムの解説

商品を探す部分

コメントの1,2,3,4の部分です。
LOHACO.Env.PAGEの変数から商品情報から取り出しています。どうやら商品種類ごとに4種類のキーの中に、配列で入っているので丁寧に取り出します。

商品データ(商品コードっぽいやつ)は、targetItemsに貯めこんでおきます。

サイトがバージョンアップすると、値の取り方が変わってしまうので、その時はあきらめて作り直しです。

削除のAPIを呼ぶ

targetItemsの中を順番に、DELETEでAPIを実行します。fetchは非同期な関数なので普通に呼び出すと、結果を待たずにAPIにリクエストがバンバン飛んでしまいます。攻撃っぽく扱われると問題なので、awaitを入れて、商品1つが終わったら次の商品に行くようにしています。CSRF防止のトークンも必要っぽいので、一緒に送っておきます。

削除した感じを出す

無理やりAPIだけ呼ぶと画面に変化が起きないので、うまくいっているのか、いっていないのか利用者に伝わりません。(Console見ればわかるのだけど、エンジニア相手の仕事ではないので)
ということで、削除が終わったときに商品名部分に取り消し線を入れる処理を加えました。ついでに削除した商品が画面で見える位置にスクロールする処理も入れました。

この処理は、ほんのお気持ちです。

動かしてみる

では、実際に動かしてみます。

ブラウザを開き、カートに商品を入れ、F12キーを押し、Dev ToolのConsoleを開き、Consoleにソースを張り付けてENTER

動作の様子
2020-02-02_19h29_10.gif

F5を押すと、空っぽになるはずですね。

全体的に上手くいったら、Closure Compilerで短縮化して、ブラウザにセットします。この手順は前の記事を読んでください。省略します。

その他、補足

今回は上手くいきましたが、APIがうまく呼べないときは削除リンクを$('a').click()して押すケースもあります。とりあえず動くものが欲しいだけなので、その辺は臨機応変な感じで解決の引き出しを持っておくといいです。

まとめ

今回は、HTMLの画面をどの観点でハックすれば実用的なブックマークレットが作れるか、僕なりの手順を紹介しました。Amazonのカートに自動投入したり、楽天の検索結果をハックしたり、他にもいろいろあるのですが、今日はここまでです。

何でそんなことを?みたいなエンジニア視点だと目まいがするような作業を、人間がやっていたりします。全てが不要な作業だとは言いませんが、効率よくやる方法はたくさんあるので、プログラムの力で助けてあげましょう。

今回のやつは、商品を消す作業が5分→1分くらいになったのではないでしょうか?4分の短縮も大きいですが、心が平和でいられるので、そっちのほうが大事です。

たった100行のスクリプトで、人助けができるなんて、素晴らしいことです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Bookmarkletを作ろう(ウェブページのハック手順)

この記事は

2年前に書いた「ブックマークレット準備編」の記事へのアクセスが、じわじわと増えてきました。ありがとうございます。

さて、ブックマークレットを作る手順が分かっても、上手にブックマークレットは作れません。先人が作ったウェブアプリケーション(画面)をハックする折れない心が必要です。ブックマークレット自体に制約が多いので、制約の範囲の中でハックする必要があります。

ということで、今回は実用的なものを作るハック手順を紹介しようと思います。私がウェブアプリケーションをハックするときに、見ているポイントや気を付けるポイントを紹介しながら、ブックマークレットに仕上げを仕上げていきます。

はじめに

一番大事なのは要件を決めること

いきなりストレートの直球を投げ込みました。別にブックマークレットに限ったことではないですね。プログラムを作る上でとても大事なことです。

ブックマークレットは単独で動くアプリケーションではないです。誰かが作ったウェブアプリケーション上で、自分のコードを動かすものです。ちょい足しの要素が強いです。完全な自動化まではできませんので、画面を操作をする上で面倒なところを削減するようなところがポイントです。

私が良く作るブックマークレットは、
:broken_heart:表示されている画面から1つ1つデータをコピペしないといけない
:arrow_right:HTMLからデータを抜き出して、コピペしやすい形でConsoleのログに書き出す

:broken_heart:社内の申請などでたくさんのテキストボックスや選択肢を埋めないといけないけど、いっつも決まった値で埋める必要がある
:arrow_right:自動で初期値を入力するか、入力補完の機能を付け足す

:broken_heart:ボタンやリンクを押すだけの操作だけど、何度も押さないといけないので面倒
:arrow_right:画面のボタンを押す作業を自動化する

などです。

別の手段も考えよう

本当にブックマークレットでいいですか?

  • 自分だけが使うならまだしも、誰かに使わせるにはインストール面倒ですよ。
  • バージョンアップも面倒ですよ。
  • データ登録系は苦手ですよ。ローカルホストで動くバッチプログラムのほうがよくないですか?
  • PythonでHTTPしてHTMLをほじくり返したほうがよくないですか?
  • なんなら、SeleniumやRPAのブラウザ自動操作でもよくないですか?

:sunflower: ブックマークレットの特性と限界を知った上で、実現できるものを上手に設計しましょう。

ハック手順の紹介

何はともあれ、まずは敵を知りましょう。
対象となるウェブアプリケーションの画面を決めたら、ページ内で起きていることを調べていきます。

1.まずは操作、操作

第一段階は、人間として画面操作をしてみて、何が起きているのかを感じましょう。といっても、適当に見ていてもしょうがないので、見るポイントはこの辺です。

  1. 画面表示時にデータが全部あるのか、遅れて表示されるタイプか?
  2. 入力に応じて画面が変わったり、モーダルが出たりするのか?
  3. 入力に対しバリデーションのチェックがあるのか?
  4. 1つの画面で完結するのか、画面遷移をするのか?

特に4の画面遷移するアプリケーションをハックするのは苦手です。

2. HTMLソースの調査

画面を見たあとは、HTMLのソースを確認します。この段階ではブラウザの「ページのソースの表示」機能で見える範囲でやります。DOMを見るというよりは、HTMLの癖を見抜く感じです。

ポイントは、

  1. HTMLソース内にjavascriptの変数(配列やオブジェクト)がどれくらいあるか?
  2. HTMLにidやclassがしっかり振ってあるか?
  3. HTMLのdisplay:noneなどの隠し要素がどれくらいあるか?
  4. HTML上に必要なデータが全部落ちているか?

1の部分は、Google Analytics用の変数として落ちていたり、schema.orgの変数として落ちていたり、いろいろあります。結構、大事なデータがHTMLに埋まっていることがあります。
2の部分は、jQueryでの操作感にかかわってきます。idやclassが少なめだと、最悪はbody.innerHTMLからの正規表現でのぶっこ抜くのも考えます。

3. HTMLのDOM要素を調べる

2と似ていますが、見るポイントが異なります。

この部分では、画面の初期表示後に何かしらの操作をして、変更されたDOMを状態を確認します。例えば、最初は表示されていないけどクリックしたらリンクが表示されるような画面では、表示された状態のDOMを見ます。

ChromeのDev ToolのElementsパネルで調べます

見るポイントは、
1. 要素そのものか、上位の要素がブロック要素タブ(<div>など)に囲まれているか
2. 繰り返し要素に同じclassがついているのか
3. クリックしたあとにネットワーク通信をしているか?

何度か操作をして、display:noneなどのCSS要素が消えるイベントなども把握します。APIを呼んでそうな場合は、次の手順4で確認します。

4.画面内でAPIを呼び出しているか

SPA系で画面遷移が伴わない場合、裏側でAPI通信をしていることが多いです。どのタイミングどのAPIを呼びだしているかを調べます。APIの呼び方が分かれば、ブックマークレットから勝手に呼び出すことが選択肢として入ってきます。

Chrome Dev ToolのNetworkパネルで調べています。
APIのエンドポイント、リクエストヘッダー、パラメータの詳細を知ることができます。

5. 使っているjavascriptのライブラリ

HTML表示完了時に読み込みされているjavascriptのライブラリは、ブックマークレットからでも使うことができます。どんなライブラリが使われているか、HTMLソースから<script src="">を検索して調べます。最近は避けられがちですがjQueryが読み込まれていれば、ブックマークレットを作るのはかなり簡単になります。

:sunflower: というのが、ウェブページをハックするときに見ているポイントです。この手順とポイントに沿って、最近作ったブックマークレットを紹介します。

実際にブックマークレットを作ってみる

LOHACOのショッピングカートにたくさんの商品を入れて、「カート内の割引が効いているか、割引の組み合わせが間違えていないか」を確認するお仕事がありました。
100個くらいの商品を入れて確認をした後に、別の商品を入れて確認するためにカートを空っぽにする必要があります。しかし、一発で空にする機能は提供されていないので、1つずつ丁寧に心を込めて削除ボタンを押していく必要があります:frowning2:

困りました。削除を100回押すとか、目まいがする作業です。

  • お金に関わる部分なので、できれば目視確認はしたい
  • でも、全削除に時間がかかり、仕事に時間がかかりすぎる
  • どうにか効率よくやりたい

こんな背景でした。

この作業を辞めてしまうというのは選択肢にないようで、そもそも論はできませんでした。
あなたの仕事は、カートの価格が正しいことを確認することであり、カートの商品を消すことではない!一刻も早く削除クリック作業から解放してあげなければ。。。

:warning: 2020年2月時点のHTMLで動作確認しています。将来にわたり動作するコードではないです。

まずは対象の調査から

まずは、カートに商品を詰め込みます。詰め詰めしたら、カート画面を開いてみます。
URLはここっぽいですね。https://lohaco.jp/sf/cart/

image.png

動作を体感する

削除するのが大変ということなので、何も考えずに削除を押してみましょう。

2020-02-02_13h19_33.gif

くるくる回って、消えるタイプですか。画面遷移が伴わないときは、DOMで非表示にするか、APIかなぁ。と想像だけしておきます。

くるくるが終わったら、次の削除を押す。1商品あたり3秒くらいかかりそうです。100商品だと、、、

HTMLソースを見る

一覧系の画面は、<div><table>でデータを表示することが多いので、比較的データを特定しやすいです。Body周辺を眺めてみましょう。
おや、Bodyタグの中に商品名や価格の情報が見当たりません。その代わりに、途中に長~いjavascriptの変数があります。

この辺です
image.png

LOHACO.Env.PAGEという変数が怪しいですね。
これを目で解読するのは大変だし、JSONっぽい変数なので、Chrome Dev ToolのConsoleで変数を覗いてみます。F12でConsoleを開いて、変数名を入力してENTER:arrow_down:するだけでよいです。

image.png
きれいなデータ構造だ。

マウスで▼を下っていきましょう。

image.png
image.png

なるほど、この辺に商品があるっぽいですね。

Console開いたついでに、$.fn.jqueryも入力して、jQueryが使えるか確認しておきます。
image.png

jQueryの3.4系が入ってるらしい。ラッキーですね。

DOMを確認

Bodyタグに表組(データ)が含まれていなかったので、完成した画面のHTMLを確認します。これはChrome Dev ToolのElementパネルで見ます。

削除リンクを右クリックして、検証
image.png

image.png

リンクはAでもButtonでもなくp.deleteでしたね。
ついでに周辺のHTML構造も見ておきましょう。例えば、商品名はp.titleにあるようです。

APIの確認

画面遷移をせず、くるくるして商品が消えているので、裏でAPIを呼んでいる可能性が高いですね。このケースは、とりあえず、Chrome Dev ToolのNetworkパネルで調べてみます。

Networkを開いて、表示されている内容をクリアしてから、削除リンクを押す。
2020-02-02_13h54_19.gif

ずらずら出てきました。
一番最初でfetchしていますね、fetchの詳細を確認させていただきましょう。

image.png

APIを呼び出してました。
Request MethodDELETEですね、Bodyにパラメータは無くてURIでリソースを指定、ですか。Rest Fullな感じでかっこいいですね。

調査段階では、URLやHTTPメソッド、リクエストBodyやリクエストHeaderを確認し、可能であればPostManなどのHTTPクライアントで疑似的に投げて、受け取ってもらえるのか確認しておくのがいいです。

ここまでの調査結果

ここまでの結果をまとめると

  • このページは、削除をクリックしたら画面遷移するタイプじゃないこと
  • データはHTML上から取るのではなく、scriptの変数で取れそうなこと
  • 削除リンクをクリックしたらAPIを呼んでいること
  • jQueryは使えること

ということが分かりました。ブックマークレットとして実現するものは、

  1. カートにある商品のキーになりそうなコードを探してきて、
  2. 削除リンクで呼び出しているAPIを呼んであげる

となります。言葉にすると簡単ですね。

たいていは、商品データを探すところや、集めるところに苦労しますが、javascriptの変数が落ちていたので、比較的簡単に終われそう。

コーディング

javascriptでコードを書くだけです。書いたコードはこちらです。全部で100行くらいです。

clear_cart.js
// ==ClosureCompiler==
// @output_file_name default.js
// @compilation_level SIMPLE_OPTIMIZATIONS
// @language_out ECMASCRIPT_2017
// ==/ClosureCompiler==
javascript:(
    async function(){

        //0.画面チェック
        if( !location.href.match(/lohaco\.jp\/sf\/cart\//)){
            alert("対象のページではないようです。カゴのページに移動します");
            window.location.href='https://lohaco.jp/sf/cart/';
            return;
        }

        var deletedCount = 0;
        var targetItems = [];

        //1. 普通の商品
        targetItems = targetItems.concat(LOHACO.Env.PAGE.lohacoItems);

        //2. discountItem
        for(var i=0; i<LOHACO.Env.PAGE.setDiscounts.length; i++){
            var setDiscount = LOHACO.Env.PAGE.setDiscounts[i];
            console.log('start %s-%s', setDiscount.name, setDiscount.lohacoItems.length );
            targetItems = targetItems.concat(setDiscount.lohacoItems);
        }

        //3. sellerItem
        for(var i=0; i<LOHACO.Env.PAGE.sellers.length; i++){
            var seller = LOHACO.Env.PAGE.sellers[i];
            console.log('start %s-%s', seller.name, seller.items.length );
            targetItems = targetItems.concat(seller.items);
        }

        //4. supplierItem
        targetItems = targetItems.concat(LOHACO.Env.PAGE.supplierItems);

        //5. 商品を消す
        console.log('target Items = %s', targetItems.length);
        deletedCount += await processItems(targetItems);

        //6. ここで終わり
        alert(deletedCount + '商品を消しました。F5でリロードしてね。');

    //======
        //Itemを繰り返してdelete呼ぶ関数
        async function processItems(items){
            var cnt = 0;
            for(var i=0; i<items.length; i++){
                //投げすぎないためのやさしさ。2回目以降はちょっと止まろう
                if(i>0){
                    await new Promise(resolve => setTimeout(resolve, 500)) // 0.5秒待つ
                }

                var code = items[i].catalogCode;
                var result = await deleteItem(code);
                cnt+=result;
                if(result == 1){
                    console.log('%s is OK', code);
                    deletedEffect(code);
                }else{
                    console.log('%s is NG', code);
                }
            }
            return cnt;
        }
        //単品Itemを削除するAPIを実行する関数
        async function deleteItem(itemcd){
            var url = 'https://lohaco.jp/sf/api/b/cart/items/' + itemcd;
            var response = await fetch(url, 
                {
                    method: "DELETE", 
                    headers: {
                        "Content-Type": "application/json", 
                        "x-csrf-token":LOHACO.Env.CSRF_TOKEN },
                    body:"{}"
                });
            var json = await response.text();
            // console.log(json);

            if(response.status=='200'){
                return 1;
            }else{
                return 0;
            }
        }
        //削除が終わったら表示を変える関数(動いている感じが出ないから)
        //商品名に取り消し線を入れて、自動スクロール
        function deletedEffect(itemcd){
            var selector = 'p.title > a[href^="/product/' + itemcd + '"]';
            var a = $(selector);
            if( a.length == 1){
                a.html( '<s>' + a.html() + '</s>' );
                var y = a.offset().top - 100;
                $(window).scrollTop(y);
            }
        }
    }
)();


https://github.com/kanaxx/bookmarklet/blob/master/lohaco/clear_cart.js

プログラムの解説

商品を探す部分

コメントの1,2,3,4の部分です。
LOHACO.Env.PAGEの変数から商品情報から取り出しています。どうやら商品種類ごとに4種類のキーの中に、配列で入っているので丁寧に取り出します。

商品データ(商品コードっぽいやつ)は、targetItemsに貯めこんでおきます。

サイトがバージョンアップすると、値の取り方が変わってしまうので、その時はあきらめて作り直しです。

削除のAPIを呼ぶ

targetItemsの中を順番に、DELETEでAPIを実行します。fetchは非同期な関数なので普通に呼び出すと、結果を待たずにAPIにリクエストがバンバン飛んでしまいます。攻撃っぽく扱われると問題なので、awaitを入れて、商品1つが終わったら次の商品に行くようにしています。CSRF防止のトークンも必要っぽいので、一緒に送っておきます。

削除した感じを出す

無理やりAPIだけ呼ぶと画面に変化が起きないので、うまくいっているのか、いっていないのか利用者に伝わりません。(Console見ればわかるのだけど、エンジニア相手の仕事ではないので)
ということで、削除が終わったときに商品名部分に取り消し線を入れる処理を加えました。ついでに削除した商品が画面で見える位置にスクロールする処理も入れました。

この処理は、ほんのお気持ちです。

動かしてみる

では、実際に動かしてみます。

ブラウザを開き、カートに商品を入れ、F12キーを押し、Dev ToolのConsoleを開き、Consoleにソースを張り付けてENTER

動作の様子
2020-02-02_19h29_10.gif

F5を押すと、空っぽになるはずですね。

全体的に上手くいったら、Closure Compilerで短縮化して、ブラウザにセットします。この手順は前の記事を読んでください。省略します。

その他、補足

今回は上手くいきましたが、APIがうまく呼べないときは削除リンクを$('a').click()して押すケースもあります。とりあえず動くものが欲しいだけなので、その辺は臨機応変な感じで解決の引き出しを持っておくといいです。

まとめ

今回は、HTMLの画面をどの観点でハックすれば実用的なブックマークレットが作れるか、僕なりの手順を紹介しました。Amazonのカートに自動投入したり、楽天の検索結果をハックしたり、他にもいろいろあるのですが、今日はここまでです。

何でそんなことを?みたいなエンジニア視点だと目まいがするような作業を、人間がやっていたりします。全てが不要な作業だとは言いませんが、効率よくやる方法はたくさんあるので、プログラムの力で助けてあげましょう。

今回のやつは、商品を消す作業が5分→1分くらいになったのではないでしょうか?4分の短縮も大きいですが、心が平和でいられるので、そっちのほうが大事です。

たった100行のスクリプトで、人助けができるなんて、素晴らしいことです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む