震災後

十年来、行って手を合わせたかった場所にやっと行くことができた。
往復に700km。
宮城県の大川小学校だ。
津波で死んだ人たちに会いたかったからだ。
周りは工事中で騒がしい場所ではあったが、近くに慰霊の石碑があった。
足が悪いので雪道は勘弁。車の中から目を閉じて黙祷。
色々な情景が浮かんでは消えたが、とてもとても静かだった。
震災からの10年という月日がなければ、今の僕はその場所に立ち向かえなかった。

震災後2週間目で岩手県山田町に向かった。
教科書の写真でしかみたことのない空襲の跡のような映像がカラーで目の前にあった。
自衛隊のおかげで道があり、目的地まで行くことができた。
その途中金縛りにもあった。

その後、何もくなった河原で手を合わせた夢を見た。
ドライブの途中、現実にその場所が実際にあり、再度手を合わせて帰ってきたこともある。

そしてやっとの今日だ。
遅くなったことに申しわけなく思う。

マウスストーカー

<script>

class P2{
  constructor(x=0,y=0){this.x=x;this.y=y}
  get clone(){return new P2(this.x,this.y)}//複写
  add({x=0,y=0},{x:X,y:Y}=this){this.x=X+x;this.y=Y+y;return this}//加算
  sub({x=0,y=0},{x:X,y:Y}=this){this.x=X-x;this.y=Y-y;return this}//減算
  mul({x=0,y=0},{x:X,y:Y}=this){this.x=X*x;this.y=Y*y;return this}//乗算
  div({x=0,y=0},{x:X,y:Y}=this){this.x=X/x;this.y=Y/y;return this}//除算
  sMul(n=0,{x:X,y:Y}=this){this.x=X*n;this.y=Y*n;return this}//スカラー倍
}
//__

//追いかける素となるもの(マウスの座標)
class MousePointer extends P2 {
  constructor (x, y) {
    super (x, y);
  }

  handleEvent (event) {
    this.x = event.clientX + window.pageXOffset;
    this.y = event.clientY + window.pageYOffset;
  }

  static create (view = document) {
    let obj = new this ();
    view.addEventListener ('mousemove', obj, false);
    return obj;
  }
}

//__
//追いかけるもの
class Stalker extends P2 {
  constructor (target, images, pointer, option = {}) {
    super (pointer.x, pointer.y);
    this.target   = target;
    this.images   = images;
    this.pointer  = pointer;
    this.option   = Object.assign ({}, this.constructor.getDefaultOption (), option);
    this.disabled = false;

    this.chase ();
  }


  chase () {
    const { PI, floor:int, atan2 } = Math;
    if (this.disabled) return;
    let
      dp = this.pointer.clone.sub (this),
      n = 360 / this.images.length,
      ang = atan2 (dp.x, -dp.y) * 180 / PI;

    this.add (dp.sMul (this.option.accelerator));
    this.locate ();
    this.target.src = this.images[int ((360 + ang) % 360 / n)].src;
    requestAnimationFrame (this.chase.bind (this));
  }


  locate () {
    const int = Math.floor;
    let p = this.option.offset.clone.add (this);
    Object.assign (this.target.style, { left: int (p.x) + 'px', top: int (p.y) + 'px'});
    return this;
  }


  static getDefaultOption () {
    return {
      offset: new P2 (0, 0),
      accelerator: .9
    };
  }


  static create (imageList, pointer, option = { }) {
    let
      target = new Image,
      images = imageList.map (src=> Object.assign (new Image, {src}));
    
    target.src = images[0].src;
    Object.assign (target.style, {position: 'absolute', left: '0px', top: '0px'});
    document.body.appendChild (target);

    return new Stalker (target, images, pointer, option);
  }
}

//__ 
  
const src = [
  'data:image/gif;base64,R0lGODlhIAAgAJEAAPwAAYcAAf///wAAACH5BAEAAAIALAAAAAAgACAAAAJklI+py33gYgsAymsCrVjqzXXMB4YiQpbmmaor1rpvFMvzWFf6bjm1xgv6frYdjhhMKoifpJKWerKiwpOgJRVhq1NStrPlWa9UsTVsPJfT3SZXu9aN0XK19w2O3zz6HvxuNiZYAAA7',
  'data:image/gif;base64,R0lGODlhIAAgAJEAAPwAAYcAAf///wAAACH5BAEAAAIALAAAAAAgACAAAAJklI+py+0Po5xUhIuzziDuz3UeuAHmRIYnmppuRbovBcqzVNvrWOr7k/OJgB/hrRE0EnvGnyLZRBabMgbUJsA+p8JDdauJIo6IsFLhLF/Ei+EiwK4YzvKxrt7+4tHpvdcPGChRAAA7',
  'data:image/gif;base64,R0lGODlhIAAgAJEAAPwAAYcAAf///wAAACH5BAEAAAIALAAAAAAgACAAAAJXlI+py+0Po2Rh2lBtxFk73nkLGIoIWZoCmh7sC1PwTI70jSn4nuy868MdAMSi8YhMEg3KpnPJfEqNiKk1YZUqss4FV6kaIsNiKjlaPJcB6nJb/B7G56oCADs=',
  'data:image/gif;base64,R0lGODlhIAAgAJEAAPwAAYcAAf///wAAACH5BAEAAAIALAAAAAAgACAAAAJjlI+py+0Po5zUhFDhvbnt3SnfFx4AMILheaacx7Csiy1vIsu0eJu57kKQFD9gyjIkFlu0UWPJbPaUS+mUWrRCoKhmhOv9QncS8HFiTpbH6i23e32+o+s5U2yfufOykv8P6FAAADs=',
  'data:image/gif;base64,R0lGODlhIAAgAJEAAPwAAYcAAf///wAAACH5BAEAAAIALAAAAAAgACAAAAJnlI+pywkPQZi02QhptQw/PXGdBwbi4kngqaQl65ArfLgzLdgabugb7gvxgqahbGfEvJKRJfD4a6SIpSJiqqwiW9iPVjjCfq1SsZZldp6mVWbm9mxueTk5mN57k/F5FR8l8dchSChQAAA7',
  'data:image/gif;base64,R0lGODlhIAAgAJEAAPwAAYcAAf///wAAACH5BAEAAAIALAAAAAAgACAAAAJmlI+py+0PE4j0gFvj3dnt3y3fGCLjGHjN+QXuOins5tYMZ85AzYu4oNvxbDJQcEgsBmnI1G15aTqfS+kDar0emxpdFuLlUmbfLkpcOUmn49aaDWa+4Z7oPKm9DzV6ZOkPGCg4yFAAADs=',
  'data:image/gif;base64,R0lGODlhIAAgAJEAAPwAAYcAAf///wAAACH5BAEAAAIALAAAAAAgACAAAAJXlI+py+0PAZwJSErtxU9vzngfWInkIlpnmbbuaLxyqsy2xt4zoutH7xMAg4KA8YhMKpcBBPMJbTqj1KOiWl1go4zts7FcXZPisbWcQKLN63H7+o7L54cCADs=',
  'data:image/gif;base64,R0lGODlhIAAgAJEAAPwAAYcAAf///wAAACH5BAEAAAIALAAAAAAgACAAAAJflI+py+0PYwO02nthwDzr3XFSQIZeRJYmIBmp2rXuC8YtDYsjXusfb0LxcpjfMOgYvpAM5TLUcD5tCtoMx0S8tEBoIlXtFsNNcaWZNMu+3XUY517A43IwvXzP6/d8fQEAOw=='
];

let
  pointer0 = MousePointer.create (),
  pointer1 = Stalker.create (src, pointer0, {offset: new P2(20,20), accelerator: .1}),
  pointer2 = Stalker.create (src, pointer1, {offset: new P2(40,40), accelerator: .09}),
  pointer3 = Stalker.create (src, pointer2, {offset: new P2(60,60), accelerator: .08});
  pointer4 = Stalker.create (src, pointer3, {offset: new P2(80,80), accelerator: .07});
  pointer5 = Stalker.create (src, pointer4, {offset: new P2(100,100), accelerator: .06});
  pointer6 = Stalker.create (src, pointer5, {offset: new P2(120,120), accelerator: .05});
  pointer7 = Stalker.create (src, pointer6, {offset: new P2(140,140), accelerator: .04});

</script>

```

```

万年カレンダーJavaScriptで作る

万年カレンダーJavaScriptで作る

<!DOCTYPE html>
<meta charset="utf-8">
<title></title>
<style>
table {
  margin: 1em 0;
}
thead td {
  font-weight: bold;
}
thead td, tbody td {
  text-align: center;
  border: 1px transparent solid;
}
tbody td:hover:not(.pDay):not(.nDay) {
  border: 1px #f80 solid;
}
tr td:first-of-type {
  color: red;
}
tr td:last-of-type {
  color:#099;
}
thead td { padding: 0 .5ex;}
caption label {
  padding: 0 .5ex;
  font-size: large;
  font-weight: bold;
}
caption button {
  background: transparent;
  border-style: none;
  font-size: large;
  padding: 0;
}
caption .hide {
  display: none;
}
tbody td.pDay, tbody td.nDay {
  color: silver;
  font-size: small;
  vertical-align: top;
}
tbody td.nDay {
  color: silver;
  font-size: small;
  vertical-align: bottom;
}
</style>


<table></table>
<table></table>
<table></table>

<script>

class Calendar {

  constructor (table, date, option = { }) {
    this.table = table;
    this.date = date;
    this.option = Object.assign ({ }, this.constructor.defaultOption (), option);
    this.view ();
  }


  view () {
    const
      {table, option, constructor: c } = this,
      {remove, getYMD, setNo, splice, toTBody, append} = c.tools ();

    let
      tbody = document.createElement ('tbody'),
      [y, m] = getYMD (this.current),//日付
      [,, pd, pw] = getYMD (new Date (y, m, 0)),//先月末日
      [,, nd, nw] = getYMD (new Date (y, m + 1, 0)),//今月末日
      pn = (pw + 1) % 7,//先月の繰越す日数
      nn = 6 - nw,//来月の取入れ日数
      days = [
        ...setNo (pn, pd - pn + 1),//連番(先月末)
        ...setNo (nd),//連番(今月)
        ...setNo (nn)//連番(翌月)
      ];

    remove (table.children);//子要素を削除
    append (table.createCaption (), option.caption);//キャプションをDOMで展開
    toTBody (table.createTHead (), option.weekName);//週名を展開
    toTBody (table.appendChild (tbody), [...splice (days, 7)]);//7日で区切る

    [...table.querySelectorAll ('caption label')]
      .forEach ((e, i)=> e.textContent = [y, option.monthName[m]][i]);

    let es = [...table.querySelectorAll ('tbody td')];
    if (pn) es.slice (0, pn).forEach (e=> e.classList.add ('pDay'));
    if (nn) es.slice (-nn).forEach (e=> e.classList.add ('nDay'));

    return this;
  }


  offsetMonth (n = 0) {
    this.current.setMonth (this.current.getMonth () + n);
    this.view ();
    return this;
  }


  set date (dt) {
    const {getYMD} = this.constructor.tools ();
    let [y, m] = getYMD (dt);
    this.current = new Date (y, m, 1);
    this._date = dt;
  }


  getDate (td) {
    const {zp, getYMD} = this.constructor.tools ();
    let
      [y, m, _, w] = getYMD (this.current),
      d = Number (td.textContent),
      ymd = [y, m+1, d],
      str = ymd.map ((a,b)=>zp(a,[4,2,2][b])).join ('-');
    return [new Date (y, m, d), str, ...ymd, w];
  }


  event (td) {
    if (td) {
      let
        cbFunc = this.option.cbFunc,
        args = this.getDate (td);
      if ('function' === typeof cbFunc)
        cbFunc.apply (this, args);
      if (this.option.clipboard)
        navigator.clipboard.writeText (args[1]).then(()=> console.log (args));
    }
  }


  handleEvent (event) {
    let e = event.target, p;

    switch (n.nodeName) {
    case 'BUTTON' :
      if (p = e.closest ('caption')) {
        let btNo = [...c.querySelectorAll('button')].indexOf (e);
        return this.offsetMonth ([-12, -1, 1, 12][btNo]);
      }
      break;

    case 'TD' :
      if (p = e.closest ('tbody'))
        return this.event (e);
      break;
    }
  }


  static tools () {
    return {
      zp: (a,b=2)=>String(a).padStart(b,'0'),
      remove: a=>[...a].map(a=>a.remove()),
      getYMD: a=>['FullYear','Month','Date','Day'].map(b=>a['get'+b]()),
      setNo:  function*(a,b=1,c=1,d=0){for(;d<a;d+=c)yield b+d},
      splice: function*(a,b=1){while(a.length)yield a.splice(0,b)},
      append: ((f=(a,b,c,{tag:T,child:C,...O}=b)=>Array.isArray(b)?b.reduce((a,b)=>f(a,b),a):(Object.assign(a.appendChild(c=document.createElement(T)),O),C&&f(c,C),a))=>f)(),
      toTBody: (a,ary)=>{
        const
          reg = /^(#?)(?:\[(\d+)?(?:\,(\d+)?)?\])?\s*(?:(.+)\s)*\s*(?:([+-]?(?:[1-9][0-9]{0,2}(?:\,?[0-9]{3})*)?(?:0?(?:\.\d*))?)|(.+))?$/,
          setAttr = (a,b,O=Object)=>O.assign(a,O.fromEntries(O.entries(b).filter(c=>'undefined'!==typeof c[1])));

        for (let row of ary) {
          let tr = a.insertRow ();
          for (let cell of row) {
            let
              [,thd, colSpan, rowSpan, className, num, text] = reg.exec (cell),
              td = tr.appendChild (document.createElement (thd ? 'th': 'td')),
              attr = {colSpan, rowSpan, className, textContent: text || num || ''};
            if (num != null)
              className += 'num'; 
            setAttr (td, attr);
            tr.appendChild (td);
          }
        }
      }
    };
  }


  static defaultOption () {
    return {
      weekName: [['Sun','Mon','Tue','Wed','Thu','Fri','Sat']],
      monthName: ['January','February','March','April','May','June','July','August','September','October','November','December'],
      caption: [
        { tag: 'button', type: 'button', textContent: '⏪', className: 'hide' },
        { tag: 'button', type: 'button', textContent: '<' },
        { tag: 'label', className: 'hide' },
        { tag: 'label'},
        { tag: 'button', type: 'button', textContent: '>'},
        { tag: 'button', type: 'button', textContent: '⏩', className: 'hide' }
      ],
      cbFunc: null,
      clipboard: true,
    };
  }


  static create (table = document.createElement ('table'), date = new Date, option = { }) {
    const calendar = new this (table, date, option);
    table.addEventListener ('click', calendar, false);
    return calendar;
  }
}


const
  TABLE = document.querySelectorAll ('table'),
  [a, b, c] = Array.from (TABLE, t=> Calendar.create (t));

b.offsetMonth (+1);
c.offsetMonth (+2);

</script>


アロー関数で再帰

JavaScript アロー関数で再帰

有ろうことか!アロー関数で再帰ができるとは知らなかった。
let func = ((cbFunc = arg=> { ... }) => cbFunc)()

const fibonacci = ((fb= n=> n > 1 ? fb(n-2) + fb(n-1): n)=> fb)();
console.log(fibonacci (9));//-> 34
</script>

アロー関数(n=> n > 1 ? n * fb(n-1): 1)を変数(fb)に代入し、
その代入した関数を即時関数によって関数(fb)として返すように
代入(fibonacci )する

これは思いつかなかった

オブジェクトをDOMにして追加する。(かなり短い)

// append (document.body, {tag: 'h1', textContent: 'Test', child:[{tag:'label', textContent: 'version'}]);
// <h1>Test<label>version</label></h1>

let append = ((f=(a,b,c,{tag:T,child:C,...O}=b)=>Array.isArray(b)?b.reduce((a,b)=>append(a,b),a):(Object.assign(a.appendChild(c=document.createElement(T)),O),C&&append(c,C),a))=>f)();

原形

それにしても document.createDocumentFragment は長い。

function append (e, arg) {
  let fgm = document.createDocumentFragment ();
  
  if (Array.isArray (arg)) {
    arg.forEach (a=> append (fgm, a));
  }
  else if (Object === arg.constructor) {
    let
      {tag, child, ...attr} = arg,
      e = Object.assign (fgm.appendChild (document.createElement (tag)), attr);
    if (child) append (e, child);
  }
  e.appendChild (fgm);
  return e;
}

時間を遅らせてコールバック関数を起動する

時間を遅らせてコールバック関数を起動する

class DelayCBFunc {
  constructor (cbfunc, time = 500 /*ms*/) {
    this.cbfunc = cbfunc;
    this.time = time;
    this.tmid = null;
    this.disabled = false;
  }
  
  restart (...args) { 
    this.stop ();
    this.tmid = setTimeout (this.cbfunc.bind(this,...args), this.time);
  }
  
  stop () {
    if (this.tmid)
      this.tmid = clearTimeout (this.tmid);
  }
}

//--
function delayAlert (e) {
  alert (e.value + 'でした');
}

const DA = new DelayCBFunc (delayAlert, 1000);
const TG = document.querySelector ('input[type="serach"]');

function handle (event) {
  DA.restart (event.target);
}

TG.addEventListener ('input', handle, false);//1秒間に入力がないとアラートする

change イベントを遅らせて発火させる。(input イベントを監視する)

// input イベントを監視して、変更があったら change イベントを発火させる
// コールバックを指定すると change イベント直前に実行できる
class delayChangeEvent {
  constructor (target, delayTime, cbFunc) {

    this.target = target;
    this.delay  = delayTime; //遅延時間(ms)
    this.cbFunc = cbFunc; //その時に実行する関数
    this.disabled = false; //この値を true にすると実行されなくなる
    this.timeId   = null; //setTimeout で使用する
    this.value    = target.value;
    
    // input event を監視する
    target.addEventListener ('input', this, false);
  }
  
  
  //イベントハンドラ
  handleEvent (event) {
    if (this.disabled) return;

    switch (event.type) {
    case 'input' : //input event を監視する
      if (this.timerId)
        clearTimeout (this.timerId);//タイマーを解除

      this.timerId = setTimeout (this.execute.bind (this), this.delay);//タイマー設定
      break;
    }
  }
  

  //change イベントを発火させる
  execute () {
    if (this.disabled) return;

    this.timerId = null;
    if (this.value !== this.target.value) {
      let
        e = this.target,
        event = e.ownerDocument.createEvent ('HTMLEvents');
      event.initEvent ('change', true, true);

      //change イベントの前に コールバックを実行する
      if ('function' === typeof this.cbFunc)
        this.cbFunc (e);

      this.target.dispatchEvent (event);
    }
  }

  //
  static create (target, delayTime = 300, cbFunc = null) {
    if (! target)
      throw new Error ('要素がありません');
    return new this (target, delayTime, cbFunc);
  }
}

SELECTの連携を今更ながら書いてみた

寺尾で答えてみた
https://teratail.com/questions/310078
なぜか面白くない。
提示されたコードは見難い。なので最初から自分で好きなように書いた方が楽。
汚いコードを指摘してあげるより、私はこれが楽だ。
本当の意味でのショートコーディングは奥が深い。
オブジェクト指向の書き方も奥が深い。


アンカータグの name 属性は無くなっていたのか!知らなかった。
ちょっとだけ賢くなった。

<!DOCTYPE html> 
<html lang="ja">
<meta charset="UTF-8">
<title>連携SELECT</title>
<style>
li > ol {
  margin: 0; padding: 0;
}
</style>
<body>
<ol>
  <li>第一選択肢
    <p>
      <select name="cat1">
        <option value="" selected>選択してください</option>
        <option value="0">a</option>
        <option value="1">b</option>
      </select>
    </p>

  <li>第二選択肢
    <ol>
      <li>
        <select name="cat2">
          <option value="">選択してください</option>
          <option value="0">a-a</option>
          <option value="1">a-b</option>
        </select>
      <li>
        <select name="cat2">
          <option value="">選択してください</option>
          <option value="2">b-a</option>
          <option value="3">b-b</option>
        </select>
    </ol>

  <li>第三選択肢
    <ol>
      <li>
        <select name="cat3">
          <option value="">選択してください</option>
          <option value="1">a-a-a</option>
          <option value="2">a-a-b</option>
        </select>
      <li>
        <select name="cat3">
          <option value="">選択してください</option>
          <option value="3">a-b-a</option>
          <option value="4">a-b-b</option>
        </select>
      <li>
        <select name="cat3">
          <option value="">選択してください</option>
          <option value="5">b-a-a</option>
          <option value="6">b-a-b</option>
        </select>
      <li>
        <select name="cat3">
          <option value="">選択してください</option>
          <option value="7">b-b-a</option>
          <option value="8">b-b-b</option>
        </select>
    </ol>
  
  <li id="cat4">結果
    <ol>
      <li>まだ全て選択されていません</li>
      <li><a href="exp.co.jp#a-a-a">a-a-a</a>
      <li><a href="exp.co.jp#a-a-b">a-a-b</a>
      <li><a href="exp.co.jp#a-b-a">a-b-a</a>
      <li><a href="exp.co.jp#a-b-a">a-b-b</a>
      <li><a href="exp.co.jp#b-a-a">b-a-a</a>
      <li><a href="exp.co.jp#b-a-b">b-a-b</a>
      <li><a href="exp.co.jp#b-b-a">b-b-a</a>
      <li><a href="exp.co.jp#a-a-a">b-b-b</a>
    </ol>
</ol>
<hr>
<ol>
  <li>第一選択肢
    <p>
      <select name="chk0">
        <option value="" selected>選択してください</option>
        <option value="0">a</option>
        <option value="1">b</option>
      </select>
    </p>

  <li>第二選択肢
    <ol>
      <li>
        <select name="chk1">
          <option value="">選択してください</option>
          <option value="http://www.xxx.co.jp#AA">a-a</option>
          <option value="http://www.xxx.co.jp#AB">a-b</option>
        </select>
      <li>
        <select name="chk1">
          <option value="">選択してください</option>
          <option value="http://www.xxx.co.jp#BA">b-a</option>
          <option value="http://www.xxx.co.jp#BB">b-b</option>
        </select>
    </ol>
</ol>

<script>
class A {
  constructor (name) {
    this.name = name;
    this.reg = new RegExp ('^'+ name + '(\\d+)$');
    this.next (document.querySelector (`*[name^="${name}"]`));
  }

  next (e) {
    let r; 
    if (r = this.reg.exec (e.name))
      this.nextStage (this.name + (Number (r[1]) + 1), Number (e.value || "-1"));
  }

  nextStage (name, no) {
    let
      es = document.querySelectorAll (`*[name="${name}"], #${name}>ol>li`),
      n = no < 0 ? 0: no;
    
    es.forEach ((e, i)=> {
      if ('select-one' === e.type) {
        e.disabled = no != i;
        e.selectedIndex = 0;
      }
      let li = e.closest ('li');
      if (li)
        li.style.display = n == i ? 'block': 'none';
    });

    if (es[0])
      this.next (es[0]);
  }

  handleEvent ({target: e}) {
    this.next (e);
  }
}

class B {
  constructor (...select) {
    this.select = select;
  }

  handleEvent ({target: e}) {
    if (this.select.includes (e) && e.value)
      if (confirm (e.value + 'に移動します'))
        location.href = e.value;
  }
}
//_____

document.addEventListener ('change', new A ('cat'), false);
document.addEventListener ('change', new A ('chk'), false);
document.addEventListener ('change', new B (...document.querySelectorAll ('select[name="chk1"]')), false);
</script>