nodejs: ローカルサーバでのlisten実装には気をつけろ

やあ子供たち。
今日はこれも書いとかないとおじさんは忘れてしまうので書いておくだけのメモだよ。
「server.listen()で、ローカルサーバーだからといって、うっかり"127.0.0.1"を指定してしまうとそこには罠があるぞ」っていう話だ。
そう、これをやってしまうと他のPCやスマホ等からそのサーバにアクセスしようとしても拒否されてしまう。
つまり、ローカルブラウザにしか応答しないサーバになってしまうんだな。
なので、そんなときはちゃんとそのサーバが走っているPCに割り当てられたIPアドレスを指定するようにすると、外部機器のブラウザからのリクエストにも応答するようになるぞ。
今日はそれだけのネタだよ。チャオ!

JavaScript の concat() について

やあ子供たち。今日はconcat()関数についてだよ。
concat()関数は配列と配列とを繋げてくれるよ。
以下の例では、配列arrの末尾に配列brrを結合するよ。

      let arr = [1,2,3,4];
      let brr = [5,6,7];
      let crr = arr.concat(brr);
      console.log( crr);

でねーおじさんが確認したかったのはこの呼び方をしたときに、arr自体は更新されるのかということだったんだけど、結論から言うとされないね。つまり

   arr.concat(brr);

これだけでは、arr、brr、いずれの内容も何も更新されない。上記のように、concat()関数の仕事の結果は、concat()関数の返り値として受け取る必要があるってことだね。
じゃ今日はこれだけの話なので。チャオ!

CSS GRID始めました

CSS GRIDをExcelふうに言えばこんな感じか。
①どんな表なのかを定義する
 (行数と各行の高さ、列数と各列の幅、という感じで指定。)
②結合セルを、あるだけ定義する。
 (結合セルなければ不要)
③要素リストを用意する。
 (結合セルに入れたい要素には、②で定義した結合セルのマークをつけておく。)
③要素リストを入力する。
 (→入力要素順に、①で定義した表の中に値や文字列が流し込まれる)

Form使わない!送信後も画面更新させずに表示処理継続可能な実装方法

やあ子供たち。寒いけど元気にしているかい。
ファイルのアップロードが出来るページを作る際に、基本として紹介されているのが、

  1. Formを作成する。
  2. その中にinputボタン(ファイル選択)(type="file")を作成
  3. その中にinputボタン(送信)(type="submit")を作成

といった流れですが、上述の実装方法では以下のような問題があります。

  • ユーザーがsubmitボタンを押した途端に、画面がリフレッシュされてしまい、サーバーがレスポンスとして返してくる画面に切り替わってしまう                           
  • ファイルの送信後にサーバーとsocket.io 経由の送受信処理をしたいのだが、肝心なsocket.idも一緒に送りたいということがあるのにこの方法だと送れない(socket.io接続はインタラクティブなWebページがサーバを双方向通信する上での命綱です!)

といったことが、どうしても起きてしまいます。
しかしながら、場合によっては

  • ファイル送信後もページの表示や動きを停めたくない
  • ユーザーがフォーム入力した情報と一緒に何らかのシステム関連情報も送りたい。

といったケースもあるでしょう。
そういう場合に一つの選択肢となり得る、XMLHttpRequestを使った実装方法を紹介したいと思います。
Formは一切使わずに、以下のようなシンプルなHTMLを用意し、

    <span>Type some words to upload</span>
    <input type="text" id="edit1" value="some words..." />
    <br>
    <span>Select files to upload</span>
    <input type="file" id="fileSelector1" name="file1" value="Select..." />
    <br>
    <button id="submit" onclick="onClickedUpdateGo()"> UPLOAD GO </button>

上記の送信用ボタンのコールバックとして、以下のようなコードを置きます。

  function onClickedUpdateGo()
  {
      var req = new XMLHttpRequest();       
      req.open("POST", "/upload_post");
      var form = new FormData();
      {
        let text1 = document.getElementById("edit1").value;
        let file1 = document.getElementById("fileSelector1").files[0];
        form.append("key1", text1);
        form.append("key_file1", file1, file1.name);
      }
      req.send(form);  // formの内容を送信
  }

はい、実はもう、これだけなんですね。

少しだけ上記コードの解説ですが、動的に生成したXMLHttpRequestを使って、POST送信を行います。送信内容はFormDataであり、ユーザーが入力したテキストや、アップロード用に選択したファイルをその中に詰め込んでいます。一つ分かりにくいのが、file1変数でしょうか。これはblob型とされるものであり、それが具体的に何かを考える必要はなく、つまりこれがユーザーが選択したファイルを送信するために必要なとなる情報なのだと理解しましょう。file1.nameは、パスを含まないファイル名となっています。

はい、これでサーバーが上がっている状態で、上記HTMLを実行し、適当な文字列をテキストボックスに打ち込みファイルセレクタで任意のファイルを選択した後に、「送信」ボタンを押してみましょう。ファイルは問題なくアップロードされていながら、ページのリロードなど一切発生せず、socket.ioも問題なく受信し続けられることが確認できるかと思います。

以上、「勝手に画面リロードが始まらない、socket.io接続も維持され続け」ながら、ユーザーが入力したテキストや、ユーザーが指定したアップロードファイルを、サーバに送信するだけの処理を実装させることが実現できたので、ご紹介でした。

Javascriptのsortがstableであることの確認

やあ子供たち。
「最近のブラウザ上で走るjavascriptのsort()関数は、すっかりStableな sort になっている」ということのようなので、実験してみたよ。ま、それだけの内容のつまらないポストだけれども、中盤あたりの、比較関数定義(comp_*という名前)のところで、「arrow演算子におけるreturnの省略」というものに挑戦してみたよ!コメントアウトしたコードブロックはすべてその直下の、{}やreturnを省略した形にかけるので、是非みんなもこれは覚えてしまってほしい。
今日実はそのようなコードを見つけてしまってね、もしやと思って「javascript omit return」などで検索してみるといろいろ出てきたよ!やっぱりそうだったんだ!ってことでこれは便利なのでどんどん使いたい。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <meta http-equiv="Pragma" content="no-cache">
  <meta http-equiv="Cache-Control"content="no-cache">
  <title>This is Tab TItle</title>
<style>
  /* スタイルシートコードを記述 */
  #placeholder1
  {
    display: flex;
  }
  .phc
  {
    background-color: lightgreen;
    margin: 5px;
  }
</style>
</head>
<body>
  <!-- HTML部品設置を記述 -->
  <h4 id="hello">Hello Stable Sort (ECMA Script 2019).</h4>
  <div id="placeholder1"></div>
  <script type="text/javascript">
    // ***javascriptのプログラムを記述***
    window.addEventListener("DOMContentLoaded",function(){
    });
    let arr=[
      {name: "grape", price: 350},
      {name: "acai berry", price: 700},
      {name: "apple", price: 150},
      {name: "apricot", price: 400},
      {name: "melon", price: 1280},
      {name: "banana", price: 150},
      {name: "blueberry", price: 350},
      {name: "melon", price: 800},
      {name: "banana", price: 90},
      {name: "guava", price: 700},
      {name: "banana", price: 590},
      {name: "orange", price: 150},
      {name: "muscat", price:1280},
      {name: "mango", price: 700},
    ];
    function dump_arrow()
    {
      let phc = document.createElement("div");
      {
        phc.textContent = "→";
      }
      document.getElementById("placeholder1").appendChild(phc);
    }
    function dump( title, arr, name_colorId, price_colorId )
    {
      let phc = document.createElement("div");
      phc.className = "phc";
      {
        phc.textContent = title;
        phc.appendChild( document.createElement("hr"));
      }
      {
        let table = document.createElement("table");
        for( let item of arr )// for..ofループ(Isn't it Handy!)
        {
          let tr = document.createElement("tr");
          {
            let td1 = document.createElement("td");td1.textContent=item.name;
            td1.style = "background-color: "+name_colorId.get(item.name);
            let td2 = document.createElement("td");td2.textContent=item.price;
            td2.style.backgroundColor = price_colorId.get(item.price);
            tr.appendChild(td1);
            tr.appendChild(td2);
          }
          table.appendChild(tr);
        }// item
        phc.appendChild(table);
      }
      document.getElementById("placeholder1").appendChild(phc);
      return;
    }
    function uniq(array) {
      return Array.from(new Set(array));
    }
    // let for_name=(a)=>{return a.name;}
    // let for_price=(a)=>{return a.price;}
    // let comp = (a,b,f)=>{return f(a)<f(b)?-1:1;}
    // let comp_by_name = (a,b)=>{return comp(a,b,for_name);}
    // let comp_by_price = (a,b)=>{return comp(a,b,for_price);}
    // let comp_by_name_i = (a,b)=>{return -1*comp(a,b,for_name);}
    // let comp_by_price_i = (a,b)=>{return -1*comp(a,b,for_price);}    
    let for_name= a=>a.name;
    let for_price= a=>a.price;
    let comp = (a,b,f)=>f(a)<f(b)?-1:1;
    let comp_by_name = (a,b)=>comp(a,b,for_name);
    let comp_by_price = (a,b)=>comp(a,b,for_price);
    let comp_by_name_i = (a,b)=>-1*comp(a,b,for_name);
    let comp_by_price_i = (a,b)=>-1*comp(a,b,for_price);
    let name_colorId;
    let price_colorId;
    {
      function calc_colorId(arr,f)
      {
        let tmparr=[];
        for( let ipr of arr )
          tmparr.push( f(ipr) );
        tmparr = uniq(tmparr);
        let cnt=0;
        let result =[];
        for(let ival of tmparr)
        {
          result.push(
            {
              key: ival, 
              color_id: cnt, 
              rgb: grads(cnt/(tmparr.length-1)),
            });
          console.log( ival+":"+cnt+","+tmparr.length+"--"+cnt/(tmparr.length-1)+"--"+grads(cnt/(tmparr.length-1)));
          ++cnt;
        }
        const map1 = new Map(
          result.map(object => {
            return [object.key, object.rgb];
          }),
        );        
        return map1;
      } 
      let arr2 = [...arr];
      arr2.sort(comp_by_name);
      name_colorId = calc_colorId( arr2, for_name);
      arr2.sort(comp_by_price);
      price_colorId = calc_colorId( arr2, for_price);
      console.log( name_colorId );
      console.log( price_colorId );
    }
    dump( "初期状態リスト", arr, name_colorId, price_colorId  );
    dump_arrow();
    arr.sort(comp_by_name);    
    dump( "フルーツ名で順ソート", arr, name_colorId, price_colorId  );
    dump_arrow();
    arr.sort(comp_by_price);
    dump( "フルーツの価格で順ソート", arr, name_colorId, price_colorId  );
    dump_arrow();
    arr.sort(comp_by_name);
    dump( "フルーツ名で順ソート", arr, name_colorId, price_colorId  );
    dump_arrow();
    arr.sort(comp_by_price);
    dump( "フルーツの価格で順ソート", arr, name_colorId, price_colorId  );

    // 参考URL
    // ① https://lab.syncer.jp/Web/JavaScript/Snippet/60/
    // ② https://lab.syncer.jp/Web/JavaScript/Snippet/67/
    function rgb2hex ( rgb ) {
      return "#" + rgb.map( function ( value ) {
        return ( "0" + value.toString( 16 ) ).slice( -2 ) ;
      } ).join( "" ) ;
    }
    function grads( val ) {
      let ival = parseInt(val*255);
      return rgb2hex( [ival, 255-ival, ival] );
    }
  </script>
</body>
</html>

今どきのJavaScript 配列は便利なfor .. of 構文で楽しく回そう!

やあ子供たち。今日はこんなプログラムを書いて遊んでみたので紹介するよ。
結論から言うと「配列は、for.. of 構文で回そう。for .. in 構文は、オブジェクトの各フィールドを巡れるが、あまり使わなさそうだな」だ。
(みてほしいのはもちろん以下の「script」節の中身だぞ。)

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <meta http-equiv="Pragma" content="no-cache">
  <meta http-equiv="Cache-Control"content="no-cache">
  <title>This is Tab TItle</title>
</head>
<body>
  <!-- HTML部品設置を記述 -->
  <h4 id="hello">Hello HTML.</h4>
  <script type="text/javascript">
    // ***javascriptのプログラムを記述***
    window.addEventListener("DOMContentLoaded",function(){
    });
    let arr=[
      {name: "apple", price: 150},
      {name: "apricot", price: 400},
      {name: "orange", price: 150},
      {name: "muscat", price:1280},
      {name: "melon", price: 1280},
    ];
    function textout( str )
    {
      let h5 = document.createElement("textContent");
      h5.textContent = str;
      let hello = document.getElementById("hello");
      hello.appendChild(h5);
      hello.appendChild( document.createElement("br"));
      return;
    }
    function dump( arr)
    {
      textout(" -- ");
      for( let item of arr )// for..ofループ(Isn't it Handy!)
        textout( item.name + ", " + item.price );
      textout(" -- ");
      return;
    }
    textout();
    dump( arr );
    let cnt=0;
    for( let item of arr )// for..ofループ(Isn't it Handy!)
    {
      ++cnt;
      item.name = "tako"+cnt;
      let str="";
      str += item.name + " ";
      str+="{";
      for( key in item )// for..ofループ(might be useful in some cases..)
        str+= "  "+ key + ": "+ item[key];
      str+="}";
      textout(str);
    }
    dump( arr );
  </script>
</body>
</html>

さあ、上記コードをa.htmlとして保存してChromeブラウザで見て見ると以下のような出力が見れるぞ。果物の名前が全部takoに書き換わっていることから、循環変数を更新することで実際の配列要素自体を更新できていることが確認できるね。

Hello HTML. --
apple, 150
apricot, 400
orange, 150
muscat, 1280
melon, 1280
--
tako1 { name: tako1 price: 150}
tako2 { name: tako2 price: 400}
tako3 { name: tako3 price: 150}
tako4 { name: tako4 price: 1280}
tako5 { name: tako5 price: 1280}
--
tako1, 150
tako2, 400
tako3, 150
tako4, 1280
tako5, 1280
--

じゃあ今日はこんなところで。チャオ!

findOne

ドキュメントの中のある配列フィールドについて、
その中の要素がある条件(存在するかどうかや、その値などについて)を満たすかどうかを調べ、
その結果に応じて夫々、別の操作(挿入や削除、変更など)をその要素について行いたい場合、
そういう場合は、
findOne()を1回呼び出すだけでは、それは実現できません。