ISO 8601 継続時間表記を正規表現で抜き出す

ISO 8601 日時の期間文字列から W, Y, M, D, h, m, s を抜き出す。

書式は、P[n]Y[n]M[n]DT[n]H[n]M[n]S
または、P[n]W

/^P(?=(?:\d+(?:\.\d+)?W|(?:\d+(?:\.\d+)?[YMD]){1,3}(?:T(?:\d+(?:\.\d+)?[HMS]){1,3})?|T(?:\d+(?:\.\d+)?[HMS]){1,3})$)(?:(.+)W|(?:(.+)Y)?(?:([\d\.]+)M)?(?:(.+)D)?)(?:T(?:(.+)H)?(?:(.+)M)?(?:(.+)S)?)?$/

Date オブジェクトと継続時間表記の文字列の加算

 function addDateDuration (date = new this, iso8601_duration_string, scale = 1) {
    const    //週の値は整数に制限[0-53], Y,M,D,h,m,s 共に少数を許容する
      REG_ISO8601_DURATION = /^P(?=(?:\d+(?:\.\d+)?W|(?:\d+(?:\.\d+)?[YMD]){1,3}(?:T(?:\d+(?:\.\d+)?[HMS]){1,3})?|T(?:\d+(?:\.\d+)?[HMS]){1,3})$)(?:(.+)W|(?:(.+)Y)?(?:([\d\.]+)M)?(?:(.+)D)?)(?:T(?:(.+)H)?(?:(.+)M)?(?:(.+)S)?)?$/,
      SECONDS = [ 86400, 3600, 60, 1 ],
      ONE_DAY = 365.2422 / 12,
      { floor: INT, ceil: CEIL } = Math;

    let parm = REG_ISO8601_DURATION.exec (iso8601_duration_string);
    if (parm) {
      let
        [, W, Y, M, D, h, m, s] = parm.map (n=> parseFloat (n) || 0),
        YM = Y * 12 + M,//Y,M の端数はDに加算して切り捨てる (時分秒に影響しない)
        ms = [D + W * 7 + INT ((YM % 1) * ONE_DAY), h, m, s].reduce ((a, b, i)=> a + b * SECONDS[i], 0) * 1000;
      date.setMonth (date.getMonth () + INT (YM * scale));
      date.setTime (date.getTime () + INT (ms * scale));
      return date;
    }
    else
      throw new Error ('Error!!: 解析できない文字列です。/:' + iso8601_duration_string);

  }

JavaScript エンターキーで移動する。その3

隠された要素は飛ばす。
[CTRL]+[Enter]では、 checked にする
テキストエリア内での処理を追加
IMEの未変換文字がある状態での Enter キーの対応
ルートの設定が個別にできること

<!DOCTYPE html>
<title></title>
<meta charset="utf-8">
<style>
</style>


<body>
<form id="F">
  <p>
    <input name="a" value="a">
    <input name="b" value="b" type="hidden">
    <input name="c" value="c">
    <span style="display:none;">
      <input name="d" value="d" id="d">

    </span>
  </p>
  <textarea name="j" cols="40" rows="10">fcvnruewihvnbre
vfreuoih\vreiojnreoi
vreiojnreoivreiojnreoi
  </textarea>
  <p>
    <input name="e" value="e">
    <input name="f" value="f" id="f">
  </p>
  <input type="reset">
  <p>
    <input type="radio" name="g" value="g0" checked>
    <input type="radio" name="g" value="g1">
    <input type="radio" name="g" value="g2">
  </p>

  <p>
    <input type="checkbox" name="h" value="h0" checked>
    <input type="checkbox" name="h" value="h1">
    <input type="checkbox" name="h" value="h2">
  </p>
  <p>
    <select name="i">
      <option value="i0">i0</option>
      <option value="i1">i1</option>
      <option value="i2">i2</option>
    </select>
  </p>
</form>


<script>

class ExEnter {

  constructor (root = document.documentElement, option = { }) {
    this.root = root;
    this.walker = document.createTreeWalker (root, NodeFilter.SHOW_ELEMENT, this.filter, true);
    this.option = Object.assign ({ }, this.constructor.defaultOption, option);

    //IME日本語入力中で未変換文字があり Enterが押されたことを感知するには
    //keypress イベントがが実行されないことを利用する
    this.imeFlag = null; //ime
  }


  //ルートの中の最初の要素を返す
  get firstElement () {
    this.walker.currentNode = this.root;
    return this.walker.firstChild ();
  }


  //ルートの最後の要素を返す
  get lastElement () {
    this.walker.currentNode = this.root;
    return this.walker.lastChild ();
  }


  //要素を移動する
  move (target, direction = false) {
    const
      walker = this.walker,
      isLoop = this.option.loop;

    this.walker.currentNode = target;
    let e = (direction)
    ? walker.previousNode () || (isLoop ? this.lastElement: null)
    : walker.nextNode ()     || (isLoop ? this.firstElement: null);
    e && e.focus ();
  }


  filter (node) {
    if (/^(INPUT|TEXTAREA|SELECT|BUTTON)$/.test (node.tagName))
      if (! node.disabled && ! node.readOnly) {
        if (! isHide (node)) 
          return NodeFilter.FILTER_ACCEPT;
      }
    return NodeFilter.FILTER_SKIP;
    //__
    function isHide (node) {
      for (let e = node, s; e; e = e.parentNode) {
        if (e === document.body) break;
        s = getComputedStyle (e, null);
        if ('none' === s.display
          || 'hidden' === s.visibility
          || !parseFloat (s.opacity || '') 
          || !parseInt (s.height || '', 10)
          || !parseInt (s.width || '', 10)
        ) return true;
      }
      return false;
    }
  }


  //_____

  //テキストエリアのキャレットの位置で分割(改行)する
  textareaSpliter (e) {
    let
      v = e.value,
      a = v.substring (0, e.selectionStart);

    e.value = a + '\n' + v.substr (e.selectionEnd);
    e.selectionStart = e.selectionEnd = a.length + 1;
  }

  
  //[CTRL]が押されていれば、適用させる
  apply (e, sw, prev) {
    let r = false; //要素に対して操作を行ったか
    switch (e.type) {
      case 'radio': case 'checkbox' :
        if (sw) {
          e.checked = !e.checked;
          r = this.option.stay;
        }
        break;
      case 'button' : case 'submit' : case 'reset' :
        if (sw) {
          let event = document.createEvent ('MouseEvents');
          event.initEvent("click", false, true);
          e.dispatchEvent (event);
          r = this.option.stay;
        }
        break;
      case 'textarea' :
        if ((sw ^ this.option.textarea_enter) && !prev) {
          this.textareaSpliter (e);
          r =true;
        }
        break;
    }
    return r;
  }


  //_____

  handleEvent (event) {
    this[event.type+ 'Handler'].call (this, event, event.target);
  }

  keyupHandler (event, e) {
    let { code, shiftKey, ctrlKey } = event;
    if ('Enter' !== code) return;
    if (! this.imeFlag) //IMEの動作で未変換中の文字があると判断してスルー
      return event.preventDefault ();
    this.imeFlag = null;
    if (! this.apply (e, ctrlKey, shiftKey))
      this.move (e, shiftKey);
  }

  keypressHandler (event, e) {
    if (/^(submit|reset|button|textarea)$/.test (e.type))
      event.preventDefault ();
    this.imeFlag = true;//未変換文字がある場合の対処として
  }

  //________

  static defaultOption = {
    stay: false, //true: radio,checkbox,submit,button を作用させた後に自動的に移動する
    textarea_enter: false, //true:テキストエリア内では改行する ,false:[Ctrl]+[Enter]で改行
    loop: true, //要素を巡回する
  };


  static create (root = document.documentElement, option = { }) {
    let obj = new this (root, option);
    root.addEventListener ('keyup', obj, false);
    root.addEventListener ('keypress', obj, false);
    return obj;
  }
}

ExEnter.create ();


</script>

JavaScriptでスライドを作る!しかもスクリプトは1行

基本的な考え方

  1. アニメーションはスタイルシートで行い。対象の要素群の先頭にだけ適用する。
  2. アニメーションが終了するとイベントが発生するのでそれを監視する。
  3. イベントの終了した要素を、その要素群の最後尾に移動する。

寺尾での回答

https://teratail.com/questions/321270

<style>
div {
  width: 800px;
  height: 200px;
  overflow: hidden;
  margin: 0 auto;
}

ul {
  white-space: nowrap;
  list-style: none;
  padding: 0;
  margin: 0;
}
ul li {
  display: inline-block;
  width: 400px;
  height: 200px;
  margin: 0;
  padding: 0;
}

li:first-of-type {
  animation-name: expansion;
  animation-duration: 2s;
  animation-timing-function:ease;
  animation-delay: 2s;
  animation-iteration-count: 1;
  animation-direction: normal;
  animation-fill-mode: backwards;
  animation-play-state: running;  
}

@keyframes expansion{
  0% { margin-left:-200px; }
  100% { margin-left:-600px; }
}

</style>

<body>
<div>
  <ul>
    <li><img src="https://placehold.jp/400x200.png" alt="">
    <li><img src="http://placehold.jp/24/cc9999/993333/400x200.png" alt="">
    <li><img src="https://placehold.jp/400x200.png" alt="">
    <li><img src="http://placehold.jp/24/cc9999/993333/400x200.png" alt="">
    <li>WHAT's NEW! ^^; WHAT's NEW! ^^; WHAT's NEW! ^^;
  </ul>
</div>

<script>
document.querySelector ('ul')
  .addEventListener ('animationend',
    ({target: e})=> e.parentNode.appendChild (e), false);
</script>

SELECT[multiple]:selected を取得する方法を発明した?

そもそも SELECT:selected なんてものはない!
そこで change イベントで select[multiple] をすべて監視し、selected になったもに
"data-selected" を付与しておけば、いつでも querySelectorAll で取得できる。
もちろん、クエリーにもそれを付与しなければならないが。

let select =
  document.querySelectorAll (
    'select:not([multiple]), select[multiple] option[data-selected]'
 );

考え方は、select[multiple]を除外してその子要素の option 要素を集める
option 要素の親要素をたどれば select 要素に行き着く。
そこで name を取得すればOK!


Memo:
select.selectedOptions があることを知った。

FORM要素(とは限らない)から、有効な値を取り出すには・・・

これで、 radio, checkbox, select[multiple] から有効な値を取得することができるようになった。

<!DOCTYPE html>
<meta charset="UTF-8" />
<title>Document</title>
<style>
</style>
<body>
<form>
  <p><input name="A" value="a">
  <p><input name="B" value="b" type="hidden">
  <p><output name="C" value="cccc">c</output>
  <p><textarea name="D">d</textarea>
  <p>
    <input name="E" value="e0" type="radio">E0 /
    <input name="E" value="e1" type="radio" checked>E1 /
    <input name="E" value="e2" type="radio">E2
  <p>
    <input name="F" value="f0" type="checkbox" checked>F0 /
    <input name="F" value="f1" type="checkbox">F1 /
    <input name="F" value="f2" type="checkbox" checked>F2
  <p>
    <select name="G">
      <option value="g0">G0</option>
      <option value="g1" selected>G1</option>
      <option value="g2">G2</option>
    </select>
  </p>
  <p>
    <select name="H" multiple id="H">
      <option value="h0" selected>H0</option>
      <option value="h1">H1</option>
      <option value="h2" selected>H2</option>
    </select>
  </p>

  <input type="button" value="Test" onclick="test()">
</form>
<div>
  <textarea id="T" cols="60" rows="30"></textarea>
</div>

<script>

class SurveillanceSELECT {
  constructor (root = document, key) {
    this.root = root;
    this.key = 'data-' + key;
  }

  switch (select) {
    for (let op of select.options) {
     op.selected 
     ? op.setAttribute (this.key, '')
     : op.removeAttribute (this.key);
    }
  }

  setting () {
    Array.from (
      this.root.querySelectorAll ('select[multiple]'),
      sel => this.switch (sel)
    );
  }

  handleEvent (event) {
    let e = event.target;
    if ('select-multiple' === e.type)
      this.switch (e);
  }

  static create (root = document) {
    const key = 'selected';
    let obj = new this (root, key);
    obj.setting ();
    root.addEventListener ('change', obj, false);
    return obj;
  }
}

SurveillanceSELECT.create ();
//_________________________________


function test () {
  const q0 = '*[name]:not([type="checkbox"]):not([type="radio"]):not(select[multiple]),*[name]:checked,select[name][multiple] option[data-selected]';
  let es = document.querySelectorAll (q0);
  T.value = [...es].map(e=>[(e.nodeName == 'OPTION' ? e.parentNode: e).name, e.value].join (" : ")).join (" \n");
}

</script>

location.hash から値を取り出してオブジェクト型で返す

location.hash から値を取り出してオブジェクト型で返す

Ajaxで使うかもしれない

let obj = hash2obj ("http://hoge.com#label?a=123&b=");

obj = {
    hash : '#label',
    id : 'label',
    value: { a: 123, b: undefined },
    query : '?a=123&b='
}
      function hash2obj (arg = window.location.hash) {
        const
          reg0 = /^(#([^\?\;\=]*)?)(\?.*)?/, //hash, id, paramater に切り分ける 
          reg1 = /\??([^\=\&\;\?]+)(?:=?([^\&\;$]*)?(?:\&|\;|$))/g; // name = value 
      
        if (arg = reg0.exec (arg)) {
          let [, hash, id, query] = arg, value = { };
          for (let r; r = reg1.exec (query); value[r[1]] = r[2]) ;
          return { id, hash, query, value };
        }
        else return null;
      }

オブジェクトからHTML要素を構成する

//obj = { tag:  '????', attr: { key: value, ... }, text: 'sample text', cildlen: [obj0, obj1, ... ]}

let arg = [
  {
    tag: 'ol', attr: { id: 'hoge', className: 'abc def', childlen: [
     //以下は同じ表示
      { tag: 'li', text: 'ABCDEFG' },
      { tag: 'li:ABCDEFG' },
      { tag: 'li', childlen: ['ABCDEFG']
    ]
  },
  {  tag: 'ol#hoge .abc .def' } //上の ol 要素と同じ意味
];
document.body.appendChild (makeElement (arg));

code:

function makeElement (arg, target = document.createDocumentFragment ()) {
  //tag: str -> tag #id .class : text に切り分ける
  const
    reg0 = /^\s*([a-z]+)\s*(?:#([a-z][0-9a-z]*))?\s*(\.[^\s\.]+(?:\s+\.[^\s\.]+)*)?\s*(?::?\s*(.*?)\s*)$/i,
    reg1 = /^\s+|\s{2,}|\s+$/g,//前後の空白、中間の2文字以上の空白を削除
    reg2 = /\./g;

  switch (Object.prototype.toString.call (arg).slice(8, -1)) {

  //引数の型で処理
  case 'Object' :
    let
      { tag = null, attr = { }, childlen = null, text = null } = arg,
      [ tagName, id, className, text1 ] = reg0.exec (tag),
      attr1 = { }, e;

    if (null == tagName)
      throw new Error ('tagName がありません');

    e = target.appendChild (document.createElement (tagName));//要素作成

    if (id) attr1.id = id;
    if (className) attr1.className = className.replace (reg1,'').replace (reg2,' ');

    Object.assign (e, attr, attr1);//属性上書き
    (text1 != null)&& ary2Element (text1, e);//文字列
    (text != null)&& ary2Element (text, e);//文字列
    (childlen)&& ary2Element (childlen, e); //子要素の追加
    break;
  
  case 'Array' :
    let fgm = document.createDocumentFragment ();
    arg.forEach (a=> ary2Element (a, fgm));
    target.appendChild (fgm);
    break;

  case 'undefined' : case 'Null' :
    arg = '';//break しない
  default :
    target.appendChild (document.createTextNode (String (arg)));
    break;
  }
  
  return target;
}

配列からテーブルを構成する

  //配列をテーブルに変換する(テキストの先頭を判別して th/td, colspan, rowspan, class を指定できる)
  function ary2TBody (ary, tbody = document.createElement ('tbody'), fg_del = false) {
    const
      //#[4,2].abc .def AAAA => <th colspan="4" rowspan="2" class="abc def">AAAA</th>
      reg0 = /^(#)?(?:\[(\d+)?(?:\,(\d+)?)?\])?\s*(\.[^\s\.]+(?:\s+\.[^\s\.]+)*)?\s*(.*?)$/,
      reg1 = /^\s+|\s{2,}|\s+$/g,//前後の空白、中間の2文字以上の空白を削除
      reg2 = /\./g;

    if (fg_del)
      [...tbody.childNodes].forEach (e=> e.remove ());//子要素の削除

    for (let row of ary) {
      let tr = tbody.appendChild (document.createElement ('tr')); //tbody.insertRow ();//fgmを許容するため
      for (let cell of row) {
        let
          str = null == cell ? '': String (cell);// null == undefined => true
        let  [, hash, colSpan = 1, rowSpan = 1, className = '', textContent ] = reg0.exec (str);
        let  td = tr.appendChild (document.createElement (hash ? 'th': 'td'));

        className = className.replace (reg1,'').replace (reg2,' ');//2つ以上のクラスに対応するため
        Object.assign (td, { colSpan, rowSpan, className, textContent});
      }
    }
    return tbody;
  }

配列からテーブルを作成する(単純高速版)

  //配列をテーブルに変換する(高速・フラグメントを tbody に指定できない)
  static ary2QTBody (ary = [ ], tbody = document.createElement ('tbody')) {
    for (let row of ary) {
      let tr = tbody.insertRow ();
      for (let cell of row) {
        tr.insertCell ().textContent = cell;
      }
    }
    return tbody;
    //return ary.reduce((a,b)=>(b.reduce((c,d)=>(c.insertCell().textContent=d,c),a.insertRow()),a),tbody);
  }