<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <link rel="stylesheet" href="./lib/style.css">
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/waypoints/4.0.1/noframework.waypoints.min.js"></script>
    <script type="text/javascript" src="./lib/simple-dom-transmitter.js"></script>
    <script type="text/javascript">
      class NavbarEventBindToButton extends SimpleDOMTransmitter {
        process () {
          new Waypoint({
            element: this.src,
            handler: (direction) => {
              if (direction == 'down') {
                this.target.dispatchEvent(new Event('nav-has-gone'))
              } else {
                this.target.dispatchEvent(new Event('nav-is-back'))
              }
            }
          })
        }
      }

      class HideBackButton extends SimpleDOMTransmitter {
        hide () {
          this.target.style.display = 'none'
        }
        process () {
          this.hide()
        }
        handler () {
          this.hide()
        }
      }
      
      class ShowBackButton extends SimpleDOMTransmitter {
        handler (event) {
          this.target.style.display = 'block'
        }
      }

      class BacktoTop extends SimpleDOMTransmitter {
        handler (event) {
          this.target.scrollTo(0, 0)
        }
      }
    </script>
    <title>ナビゲーションが隠れたら下にボタンが出るデモ(ES6版)</title>
  </head>
  <body>
    <section id="header">
      <div class="nav">
        <ul>
          <li>ナビ</li>
          <li>ゲー</li>
          <li>ション</li>
          <li>とか</li>
        </ul>
      </div>
      <section class="eyecatch">
        <h1>ナビゲーションが隠れたら下に戻るボタンが出るデモ(ES6版)</h1>
        <picture>
          <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAADUlEQVQImWP4//8/AwAI/AL+hc2rNAAAAABJRU5ErkJggg==" width="80%" height="300" alt="アイキャッチ画像">
        </picture>
      </section>
    </section>
    <article>
      <p>本文的ななにか</p>
    </article>
    <section id="footer">
      <p>copyrightとか</p>
      <div class="button-container">
        <button id="back-top-top"> ^ </button>
      </div>
    </section>
    <script type="text/javascript">
      const backBtn = document.querySelector('#back-top-top')
      const navBar = document.querySelector('.nav')

      NavbarEventBindToButton.run({ src: navBar, target: backBtn })
      HideBackButton.run({ src: backBtn })
      HideBackButton.listen({ on: 'nav-is-back', src: backBtn })
      ShowBackButton.listen({ on: 'nav-has-gone', src: backBtn })
      BacktoTop.listen({ on: 'click', src: backBtn, target: window })
    </script>
  </body>
</html>
#header .nav {
  display: block;
  box-shadow: 0 10px 10px  #cccccc;
}
#header .nav ul {
  display: flex;
}
#header .nav li {
  display: inline;
  padding: 0 0.2rem;
}
#header picture {
  display: block;
  text-align: center;
}
#header picture img {
  width: 80%;
  border: 1px solid;
  border-radius: 2px;
}
article {
  height: 50rem;
  background: #f0f0f0;
}
#footer .button-container {
  position: fixed;
  bottom: 0.1em;
  right: 0.2em;
  text-align: right;
  border: 1px solid #aaaaaa;
  border-radius: 4px;
  padding: 0;
}
#footer .button-container button {
  background: #cccccc;
  font-size: 150%;
  margin: 0;
}
class ElementUndefined extends Error {
  get name () { return 'ElementUndefined' }
}
class NotImplemented extends Error {
  get name () { return 'NotImplemented' }
}

/**
 * 一つあるいは複数の要素の関係する単純な flip や reaction の類のコードにパターンを与えるクラス
 *
 * 使用例
 *
 * 1. ある要素のあるイベントに応じて他の要素に変更を加える
 *     * 同じく、自身に変更を加える
 * 2. DOMを読み込んだら特定の要素の特定の値を読み取って他の要素に変更を加える
 *     * 同じく、自身に変更を加える
 *
 * このクラスを継承してロジックを書く。内容は以下の通り。
 *  a) init を override する
 *  b) 目的に応じて handler() / process()  を定義
 *  c) event に対応する場合は static listen() を利用して初期化
 *     event がない場合は static run() を利用して即時に実行
 *
 * このクラスはロジックを担うものであり、継承したクラスも export する
 * ことが前提であり、entry point で即時実行してはならない。即時実行す
 * るとテストが難しくなってしまう。
 */
class SimpleDOMTransmitter {
  /**
   * @param {object} args
   * @param {object} args.src
   * @param {object} args.target
   */
  constructor ({ src, target = undefined }) {
    this._src = src
    this._target = target || src

    if (!this._src) throw new ElementUndefined(`src element is ${this._src}`)
  }

  /**
   * @return {object}
   */
  get src () {
    return this._src
  }

  /**
   * @return {object}
   */
  get target () {
    return this._target
  }

  /**
   * @param {Function} callback
   * @return {object}
   */
  static extend (callback) {
    return callback.call(this)
  }

  /**
   * @param {object} child
   * @return {object}
   */
  static bless (child) {
    child.prototype = Object.create(this.prototype)
    child.prototype.constructor = child
    child.init = this.init
    child.listen = this.listen
    child.run = this.run

    return child
  }

  /**
   * @param {object} args
   * @param {object} args.src
   * @param {object} args.target
   * @return {object}
   */
  static init ({ src, target = undefined }) {
    return new this({ src, target })
  }

  /**
   * @param {object} args
   * @param {string} args.on
   * @param {object} args.src
   * @param {object} args.target
   * @return {object}
   */
  static listen ({ on, src, target = undefined }) {
    const transmitter = this.init({ src, target })
    transmitter.on(on, transmitter.handler.bind(transmitter))

    return transmitter
  }

  /**
   * @param {object} args
   * @param {object} args.src
   * @param {object} args.target
   * @return {object}
   */
  static run ({ src, target = undefined }) {
    const transmitter = this.init({ src, target })
    transmitter.process(transmitter.src)

    return transmitter
  }

  /**
   * @param {object} src
   */
  process (src) {
    throw new NotImplemented()
  }

  /**
   * @param {object} event
   */
  handler (event) {
    throw new NotImplemented()
  }

  /**
   * @param {string} event
   * @param {Function} func
   * @return {void}
   */
  on (event, func) {
    if (this.src.forEach instanceof Function) {
      this.src.forEach((e) => {
        e.addEventListener(event, func)
      })
    } else {
      this.src.addEventListener(event, func)
    }
  }
}