123

Pen Settings

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

You're using npm packages, so we've auto-selected Babel for you here, which we require to process imports and make it all work. If you need to use a different JavaScript preprocessor, remove the packages in the npm tab.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Use npm Packages

We can make npm packages available for you to use in your JavaScript. We use webpack to prepare them and make them available to import. We'll also process your JavaScript with Babel.

⚠️ This feature can only be used by logged in users.

Code Indentation

     

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

HTML Settings

Here you can Sed posuere consectetur est at lobortis. Donec ullamcorper nulla non metus auctor fringilla. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec id elit non mi porta gravida at eget metus. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.

            
              #js-main.main
  #js-map.map

  #js-search.search
    form#js-search__form(
      method="GET"
    )
      input#js-query(
        type="text"
        name="query"
        placeholder="未入力で現在地から最寄り駅を検索します"
      )

      input#js-submit(
        type="submit"
        value="駅検索"
      )
      select#js-select(
        name="select"
      )

  .info
    h2.info__header 検索結果
    ul.info__content#js-root-info
    
      //- var dummy = [1, 2, 3, 4, 5, 6, 7, 8];
      //each val, ind in dummy
      //  li.info__item
      //    a.js-info__description(href="javascript:void(0)")
      //    
      //      - var item = {station: "駅名", Route: "路線名", dist: "距離", walk: "徒歩時間"};
      //      each propVal, propKey in item
      //      
      //        if 'station' === propKey
      //          h3(class="info__#{propKey}")= propVal + ' : ' + propKey + val
      //        else
      //          p(class="info__#{propKey}")= propVal + ' : ' + propKey + val

    a#js-btn-info.btn-info(href="javascript:void(0)")
            
          
!
            
              // ========================================
// VARIABLE
// ========================================
$main-width   : 100%;
$sub-width    : 280px;
$root-space   : 8px;
$main-bg-color: #eee;
$sub-bg-color : #eee;
$radius       : $root-space;
$root-color   : #666;

// ========================================
// LAYOUT
// ========================================
*,
*::before,
*::after {
  box-sizing: border-box;
}
html,
body,
.main,
.map,
.info {
  height: 100%;
}

html,
body {
  overflow: hidden;
  width: $main-width;
}

body {
  font: 14px/1 "Helvetica Neue",Helvetica,Arial,Verdana,Roboto,"游ゴシック","Yu Gothic","游ゴシック体",YuGothic,"ヒラギノ角ゴ Pro W3","Hiragino Kaku Gothic Pro","Meiryo UI","メイリオ",Meiryo,sans-serif
}

input[type="submit"],
input[type="button"] {
  border-radius: 0;
  -webkit-appearance: button;
  appearance: button;
  border: none;
  cursor: pointer;
  
  &::-webkit-search-decoration {
    display: none;
  }
  
  &::focus {
    outline-offset: -2px;
  }
}

/*
 * メインエリア
 */
.main {
  position: relative;
  top: 0;
  left: 0;
  width: $main-width;
  background-color: $main-bg-color;
  transition-property: all; 
  transition-duration: .1s;
  transition-timing-function: linear;
  
  &.is-active {
    transform: translateX(#{$sub-width}); // Use GPU
  }
}

/*
 * マップエリア
 */
.map {
  width: $main-width;
}

/*
 * 検索フォームエリア
 */
.search {
  position: absolute;
  top: $root-space * 7;
  width: calc(#{$main-width} - #{$root-space * 2});
  margin: 0 $root-space;
  
  > form { 
    $height: $root-space * 5; 
   
    position: relative;
    display: table;
    width: 100%;
    border-radius: $radius;
    box-shadow: 0 1px 3px #aaa;
    
    > * {
      
      display: table-cell;
      height: $height;
      padding: 0 $root-space;
      border: none;
      
      &:nth-child(1) {
        width: 80%;
        border-radius: $radius 0 0 $radius;
      }
      
      &:nth-child(2) {
        width: 20%;
        border-radius: 0 $radius $radius 0;
        background-color: #f1ed73;
        letter-spacing: .25em;
      }
      
      &:nth-child(3) {
        position: absolute;
        bottom: -#{$height + $root-space};
        left: 0;
        display: none;
        width: 100%;
        border-radius: $radius;
        box-shadow: 0 0px 5px #aaa inset;
        &.is-active {
          display: block;
        }
      }
    }
    
  }
}

/*
 * サブエリア
 */
.info {
  $height-header: $root-space * 5;
  $shadow: 2px 1px 2px #999;
 
  position: absolute;
  top: 0;
  left: -#{$sub-width};
  width: $sub-width;
  background-color: $sub-bg-color;
  box-shadow: $shadow;
  
  &__header {
    position: absolute;
    z-index: 1;
    width: 100%;
    height: $height-header + $root-space;
    padding: 0 $root-space * 2;
    border-top: $root-space solid #f1ed73;
    background-color: #fff;
    box-shadow: $shadow;
    font-weight: bold;
    text-align: center;
    line-height: $height-header;
    letter-spacing: 0.3em;
  }
  
  .btn-info {
    $side: $root-space * 5; 
   
    position: absolute;
    top: calc(50% - #{$side});
    right: -#{$side};
    width: $side;
    height: $side * 2;
    border-radius: 0 $radius $radius 0;
    background-color: #fff;
    box-shadow: $shadow;
  }

  /*
   * 情報エリア
   */
  &__content {
    $padding: $root-space;
    overflow: auto;
    height: 100%;
    padding: #{$padding * 2 + $height-header} #{$padding / 2} 0 #{$padding / 2};
    pointer-events: none;
    -webkit-overflow-scrolling : touch;
    
    &.is-active {
      pointer-events: auto;
    }
  }
  
  /*
   * アイテム
   */
  &__item { 
    padding-bottom:$root-space * 2; 
    border-top: $root-space solid #f1ed73;
    border-bottom: 1px solid #d4d4d4;
    background: #fff;

    & + * {
      margin-top: $root-space * 2;  
    }

    a {
      display: block;
      padding: $root-space * 2 $root-space;
      color: $root-color;
      text-decoration: none;

      &:hover {
        opacity: .6;
      }
    }
  }

  &__station {
    margin-bottom: $root-space;
    padding-bottom: $root-space;
    border-bottom: 1px solid #ccc;
    font-weight: bold;
    line-height: 1.5;
  }
  &__dist,
  &__walk {
    display: inline-block;
    width: 49%;
  }
  
  &__more {
    $bd-color: #eae400;
    margin: 0 $root-space $root-space * 2;
    border-radius: $root-space;
    border: 3px solid $bd-color;
    box-shadow: 0 5px 0 $bd-color;
    text-align: center;
    font-weight: bold;
    letter-spacing: 0.3em;
    
    & + * {
      display: none;
      
      &.is-active {
        display: block;
      }
    }
  }
}

.adp,
.adp table {
  padding: 0 $root-space;
  
  .adp-placemark {
    .adp-marker {
      margin-top: -#{$root-space * 2};
      transform: rotate(-15deg);
      transform-origin: bottom; 
    }
    .adp-text {
      padding: $root-space $root-space / 2 $root-space / 2;
      vertical-align: middle;
      line-height: 1.4;
    }
  }
  
  .adp-summary {
    padding: $root-space;
  }
  
  .adp-step,
  .adp-substep {
    padding: $root-space $root-space $root-space * 2;
  }
}

            
          
!
            
              /**
 * @namespace
 */
let KOKOKARA = KOKOKARA || {}; // 名前空間 ブロックスコープ変数(ES6)
KOKOKARA.isDebug = true; // デバッグモード


{ // ブロックスコープ定義(ES6) ここから

  /* ========================================
    アプリケーション設定用
  ======================================== */
  /**
   * 初期中心座標
   * @const
   */
  const INITIAL_LATLNG          = { lat: 35.7100627, lng: 139.8107004 }; // 定数(ES6)
  /**
   * 初期ズーム率    
   * @const    
   */    
  const INITIAL_ZOOM            = 16; // 定数(ES6)
  /**    
   * 検索タイプ    
   * @const    
   */    
  const SEARCH_TYPES            = ['train_station', 'subway_station']  // 定数(ES6)
  /**    
   * 検索半径    
   * @const    
   */    
  const SEARCH_RADIUS           = 3000; // 定数(ES6)
  /**    
   * マーカーのデフォルトアニメーション    
   * @const    
   */    
  const MARKER_ANIMATION        = google.maps.Animation.DROP; // 定数(ES6)
  /**
   * マーカー間の表示遅延時間
   * @const
   */
  const MARKER_DISPLAY_DURATION = 35; // 定数(ES6)

  
  /* ========================================
    内部設定用(変更不可)
  ======================================== */
  /**
   * 共通のDOMアクセス指定子(id)のプレフィックス
   * @const
   */
  const JS_PREFIX_MULTI   = 'js-'; // 定数(ES6)
  /**
   * 共通のDOMアクセス指定子(id)のプレフィックス
   * @const
   */
  const JS_PREFIX         = '#' + JS_PREFIX_MULTI; // 定数(ES6)
  /**
   * コンポーネント共通の活性状態の指定子
   * @const
   */
  const ACTIVE_CLASS_NAME = 'is-active'; // 定数(ES6)
  /**
   * 検索原点割り出しクエリの指定子(name属性)
   * @const
   */
  const SEARCH_QUERY_NAME = 'query'; // 定数(ES6)
  /**
   * ジオコードのURL
   * @const
   */
  const GEOCODE_URL       = 'https://maps.googleapis.com/maps/api/geocode/json'; // 定数(ES6)

  
  /* ========================================
    イベントのインターフェイス
  ======================================== */
  KOKOKARA.ScriptEvent = class ScriptEvent { // クラス定義(ES6)
    /**
     * [コンストラクタの説明]
     * @constructor
     * @classdesc DOMに依らないイベントを提供するインターフェイスクラス
     */
    constructor() {
      /* --------------------
        プロパティ
      -------------------- */
      /**
       * 発火済みのイベント
       * @prop
       */
      this.didEvents = [];
      /**
       * 登録済みのイベントリスナー
       * @prop
       */
      this.registeredHandlers = {};
      /**
       * デバッグモードのヘッダースタイル
       * @prop
       */
      this.styleHeader = [
        'font-weight:bold;',
        'color:#0041d4;'
      ].join('');
    }
    
    /**
     * 初動処理
     */
    init() {}
    
    /**
     * イベントリスナー登録
     * @param {string} eventName イベント名
     * @param {function} handler イベントリスナー
     * @param {number} order 優先度
     */
    add(eventName, handler, order = 10) { // デフォルト引数(ES6)
      if('string'   !== typeof eventName || 
         'function' !== typeof handler ||
         'number'   !== typeof order)
      {
        console.error('どれかの引数の型がまちがってるよ: ', eventName, handler);
        return;
      }
      
      if(!KOKOKARA.scriptEvent.registeredHandlers[eventName]) {
        KOKOKARA.scriptEvent.registeredHandlers[eventName] = [];
      }
      
      let handle = { // ブロックスコープ変数(ES6)
        handler: handler,
        order  : order
      }

      KOKOKARA.scriptEvent.registeredHandlers[eventName].push(handle);
    }
    
    /**
     * イベント実行
     * @param {string} eventName イベント名
     * @param {mix} args 各実行イベント枚の引数
     */
    do(eventName, ...args) { // 残余引数(ES6)
      if('string' !== typeof eventName) {
        console.error('イベント名は文字列入力だよ: ', eventName);
        return; 
      }
      
      // 発火したイベントを管理
      KOKOKARA.scriptEvent.didEvents.push(eventName);
      
      let registeredHandler = KOKOKARA.scriptEvent.registeredHandlers[eventName];
      
      // 登録イベントなし 
      if (!registeredHandler) { return; }
      
      // 優先度でソートする 
      registeredHandler.sort((a, b) => { return a.order > b.order ? 1 : -1; }); // アロー関数(ES6)
      
      // 優先順で実行開始
      registeredHandler.forEach((handler, ind, registers) => { // アロー関数(ES6)
        // デバッグモード
        this.debugContent(eventName, handler, args);   
        // 実行
        handler.handler(...args);// 展開演算子(ES6)
      });
    }
    
    /**
     * イベントリスナー削除
     * @param {string} eventName イベント名
     * @param {function} handler イベントリスナー
     * @param {boolean} isDeleteEvent イベントもさくじょするかどうか
     */
    remove(eventName, deleteHandler, isDeleteEvent = false) { // デフォルト引数(ES6)
      if('string' !== typeof eventName) {
        console.error('イベント名は文字列入力だよ: ', eventName);
        return; 
      }
      if('function' !== typeof deleteHandler) {
        console.error('ハンドラ名は関数入力だよ: ', deleteHandler);
        return; 
      }
      if('boolean' !== typeof isDeleteEvent) {
        console.error('イベント名は文字列入力だよ: ', isDeleteEvent);
        return; 
      }
      
      let registeredHandler = KOKOKARA.scriptEvent.registeredHandlers[eventName];
      registeredHandler.forEach((handler, ind, registers) => { // アロー関数(ES6)
        if(deleteHandler === handler.handler) {
          registeredHandler.splice(ind, 1);
        }
      });
      
      // イベント自体を削除
      if (isDeleteEvent) {
        this.removeEvent(eventName);
      }
    }
    
    /*
     * イベント削除
     * @param {string} eventName イベント名
     */
    removeEvent(eventName) {
      if('string' !== typeof eventName) {
        console.error('イベント名は文字列入力だよ: ', eventName);
        return; 
      }
     
      delete KOKOKARA.scriptEvent.registeredHandlers[eventName];
    }
    
    /**
     * デバッグコンテンツを表示する
     * @param {string} eventName イベント名
     * @param {function} handler イベントリスナー
     * @param {mix} args 各実行イベント枚の引数
     */
    debugContent(eventName, handler, args) {
      if (KOKOKARA.isDebug) {
        let handleName = handler.handler.name ? handler.handler.name : '無名関数';
        console.info(`%cスクリプトイベント名: ${eventName} ----------`, KOKOKARA.scriptEvent.styleHeader); // テンプレートリテラル(ES6)
        console.log('ハンドル名: ', handleName);
        console.log('引数: ', ...args); // 残余引数(ES6)
        console.log('優先度: ', handler.order);
        console.count(`通算実行回数: ${eventName}`); // テンプレートリテラル(ES6)
      }
    }
  }
  
  
  /* ========================================
    メイン
  ======================================== */
  KOKOKARA.Main = class Main { // クラス定義(ES6)
    /**
     * [コンストラクタの説明]
     * @constructor
     * @classdesc 全体を管理するクラス
     */
    constructor() {
      /* --------------------
        プロパティ
      -------------------- */
       /**
        * 検索位置原点オブジェクト
        * @prop
        */
      this.originPoint = [];
       /**
        * 最寄り駅オブジェクト
        * @prop
        */
      this.station = [];
      /* --------------------
        DOM
      -------------------- */
      this.el = {
       /**
        * アプリケーションのルート要素
        * @prop
        */
        main: document.querySelector(`${JS_PREFIX}main`) // テンプレートリテラル(ES6)
      };
    }
    
    /**
     * 初動処理
     */
    init() {
      // アプリケーションに必要なクラスを初期化する
      KOKOKARA.scriptEvent = new KOKOKARA.ScriptEvent();
      KOKOKARA.scriptEvent.isDebug = true;
      
      KOKOKARA.map = new KOKOKARA.Map(/* options */);
      KOKOKARA.map.init();
      
      KOKOKARA.search = new KOKOKARA.Search();
      KOKOKARA.search.init();
      
      KOKOKARA.info = new KOKOKARA.Info();
      KOKOKARA.info.init();
      
      /* --------------------
        イベント
      -------------------- */
      // 地点が生成されたとき
      KOKOKARA.scriptEvent.add('point_create_init_after', this.add.bind(this));
      // 検索位置原点確定
      KOKOKARA.scriptEvent.add('search_originPos_after', this.reset.bind(this), 1);
    }
    
    /**
     * 地点を追加する
     * @param {string} cstrName 地点の種類
     * @param {object} instance 地点のインスタンスオブジェクト
     */
    add(cstrName, instance) {
      this[cstrName].push(instance);
    }     
    
    /**
     * 地点をリセットする
     */
    reset() {
      let targets = ['originPoint', 'station'];
      
      targets.forEach((points) => {
        this[points].forEach(function(point) {
          point.delete();
        });
        this[points].length = 0;
      });
    }
  }

  
  /* ========================================
    地図
  ======================================== */
  KOKOKARA.Map = class Map { // クラス定義(ES6)
    /**
     * [コンストラクタの説明]
     * @constructor
     * @classdesc 地図を管理するクラス
     * @param {object} options 地図作成のオプション
     */
    constructor(options = {}) { // デフォルト引数(ES6)
      let defaults = {
        lat      : INITIAL_LATLNG.lat,
        lng      : INITIAL_LATLNG.lng,
        zoom     : INITIAL_ZOOM,
        mapTypeId: google.maps.MapTypeId.ROADMAP
      };
      /* --------------------
        プロパティ
      -------------------- */
      /**
       * 地図作成のオプション
       * @type {object}
       */
      this.options = mixin(defaults, options);
      /**
       * Goole Map
       * @type {object}
       */
      this.map;
      /* --------------------
        DOM
      -------------------- */
      this.el = {
        /**
         * マップ要素
         * @type {object}
         */
        map : document.querySelector(JS_PREFIX + 'map')
      };
    }

    /**
     * 初動処理
     */
    init() {
      let latLng = new google.maps.LatLng(this.options.lat, this.options.lng); // 局所変数(ES6)
      let options = { // 局所変数(ES6)
        zoom     : this.options.zoom,
        center   : latLng,
        mapTypeId: this.options.mapTypeId
      };
      // 地図生成
      this.map = new google.maps.Map(this.el.map, options);
     
      /* --------------------
        イベント
      -------------------- */
      // 検索位置原点確定
      KOKOKARA.scriptEvent.add('search_originPos_after', this.updateCenter.bind(this), 10);
    }

    /**
     * 中心位置を更新する
     * @param {object} pos 位置オブジェクト
     */
    updateCenter(pos) {
      this.map.panTo(pos);
    }
  }
 

  /* ========================================
    ポイント
  ======================================== */
  KOKOKARA.Point = class Point { // クラス定義(ES6)
    /**
     * [コンストラクタの説明]
     * @constructor
     * @classdesc 地点の親クラス
     */
    constructor(options) {
      /**
       * 設定オプションデフォルト値
       * @private
       */
      let defaults = {
        map      : KOKOKARA.map.map,
        animation: MARKER_ANIMATION
      };
      
      /* --------------------
        プロパティ
      -------------------- */
      /**
       * 設定オプション
       * @prop
       */
      this.options = mixin(defaults, options);
      //this.options = Object.assign({}, defaults, options);
      /**
       * マーカー
       * @prop
       */
      this.marker = {};
    }
                
    /**
     * 初動処理
     */
    init() {
      this.createMarker(this.options);
    }
    
    /**
     * マーカーを作成
     */
    createMarker(options) {
      this.marker = new google.maps.Marker(options);
    }  
    
    /**
     * マーカーを削除
     */
    deleteMarker() {
      this.marker.setMap(null);
    }  
    
    /**
     * 駅情報を削除
     */
    delete() {
      this.deleteMarker();
    }  
    
    /**
     * インスタンス生成
     * @static
     * @param {object} options 設定オプション
     * @param {object} place プレイスオブジェクト
     */
    static create(options, place) {// 静的メソッド(ES6)
      let cstrName = uclcfirst(this.prototype.constructor.name, 'lc');
      let instance = new this(options, place);   
      instance.init();
      KOKOKARA.scriptEvent.do('point_create_init_after', cstrName, instance);
    }
  }
  
  
  /* ========================================
    検索位置原点
  ======================================== */
  KOKOKARA.OriginPoint = class OriginPoint extends KOKOKARA.Point { // クラス継承(ES6)
    /**
     * [コンストラクタの説明]
     * @constructor
     * @classdesc 検索原点を管理するクラス
     */
    constructor(options) {
      super(options); // スーパークラスへアクセス(ES6)
    }
 
    /**
     * 初動処理
     */
    init() {
      super.init(); // スーパークラスへアクセス(ES6)
    }
    
    /**
     * インスタンス生成時のオプションを取得する
     + @param {object} pos 位置オブジェクト
     * @param {object} 設定オプション
     */
    static getOptions(pos) { // 静的メソッド(ES6)
      return {
        position : pos,
        animation: google.maps.Animation.BOUNCE,
        icon: {
          path       : google.maps.SymbolPath.CIRCLE,
          scale      : 5,
          strokeColor: '#393'
        }
      };
    }
  }
  
  
  /* ========================================
    駅
  ======================================== */
  KOKOKARA.Station = class Station extends KOKOKARA.Point { // クラス継承(ES6)
    /**
     * [コンストラクタの説明]
     * @constructor
     * @classdesc 検索結果の最寄り駅を管理するクラス
     */
    constructor(options, place) {
      super(options); // スーパークラスへアクセス(ES6)
      /* --------------------
        プロパティ
      -------------------- */
      /**
       * プレイスオブジェクト
       * @prop
       */
      this.place = place;
      /**
       * ルート案内オブジェクト
       * @prop
       */
      this.direction = null;
      /**
       * 駅詳細
       * @prop
       */
      this.details = null;
      /**
       * google.maps.DirectionsServiceクラス
       * @prop
       */
      this.directionsService = null;
      /**
       * google.maps.DirectionsRendererクラス
       * @prop
       */
      this.directionsDisplay = null;
      this.el = {
        /**
         * 情報エリアへの表示内容
         * @prop
         */
        listInfo: null 
      }
      /**
       * イベント(MapsEventListener) 
       * @prop
       */
      this.ev = {};
    }

    /**
     * 初動処理
     */
    init() {
      super.init(); // スーパークラスへアクセス(ES6)
      
      /* --------------------
        イベント
      -------------------- */
      // マーカークリック時
      this.ev.click = google.maps.event.addListener(this.marker, 'click', () => { // アロー関数(ES6)
        this.getDirections(this);
      });
    }
    
    /**
     * 駅のルート案内を取得する
     * @param {object} station 駅データ
     */
    getDirections(station) {
      (new Promise((resolve, reject) => { // アロー関数(ES6) Promise(ES6)
        console.info('getDirections then 1');
        // 既取得時
        if (station.direction) {
          console.info('getDirections then 1.5 既取得時');
          resolve(station.direction);
        // 未取得時
        } else {
          console.info('getDirections then 1.5 未取得時');
          this.getDirectionsService(resolve, reject, station);
        }
      }))
        .then(response => { // アロー関数(ES6)
          console.info('getDirections then 2');
          // ルート案内ライン
          this.renderLine(response);
          // 駅情報
          this.setDetails(response);
          // 発火
          KOKOKARA.scriptEvent.do('station_marker_click_succeess_ended', this);
        })
        .catch(err => { // アロー関数(ES6)
          console.log('getDirections catch', err);
          alert('エラー: ' + err.message);
        });
    }
    
    /**
     * 駅のルート案内を取得する
     * @param {object} resolve Promiseオブジェクト
     * @param {object} reject Promiseオブジェクト
     * @param {object} station 駅データ
     */
    getDirectionsService(resolve, reject, station) {
      if (!this.directionsService) {
        this.directionsService = new google.maps.DirectionsService(); }
      
      let origin = {
        lat: KOKOKARA.main.originPoint[0].options.position.lat,
        lng: KOKOKARA.main.originPoint[0].options.position.lng
      };
      let destination = {
        lat: station.options.position.lat,
        lng: station.options.position.lng
      };
      // 取得
      this.directionsService.route({
          origin     : origin,
          destination: destination,
          travelMode : google.maps.TravelMode.WALKING
        }, (response, status) => { // アロー関数(ES6)
          // 成功時
          if (status === google.maps.DirectionsStatus.OK) {
            station.direction = response;
            resolve(response);
          // 失敗時 
          } else {
            let error = new Error(status);
            reject(error);
          }
        }
      );
    }
    
    /**
     * ルートを表示する
     * @param {object} resoponse ルート案内オブジェクト
     * @return {object} Promiseオブジェクト
     */
    renderLine(response) {
      return new Promise((resolve, reject) => { // アロー関数(ES6) Promise(ECMA2015)
        let map = KOKOKARA.map.map;
        if (!this.directionsDisplay) {
          this.directionsDisplay = new google.maps.DirectionsRenderer({
            map: map,
            draggable: true
          }); }

        this.directionsDisplay.setMap(map);
        this.directionsDisplay.setDirections(response);
        
        // 行きかたが変更されたとき
        this.directionsDisplay.addListener('directions_changed', () => { // アロー関数(ES6)
          let result   = this.directionsDisplay.getDirections();
          let route    = result.routes[0];
          let data     = {}; 
          
          if (this.el.listInfo) {
            let bounds = JSON.stringify(route.bounds);
            this.el.listInfo.setAttribute('data-place-bounds', bounds);
          }
          
          for (let i = 0, l = route.legs.length; i < l; i++) {
            data = {
              originPoint: route.legs[i].start_address,
              station    : route.legs[i].end_address,
              distance   : route.legs[i].distance.text,
              duration   : route.legs[i].duration.text
            }
          }
          
          KOKOKARA.scriptEvent.do('station_renderLine_directions_changed', data, this);
        });
      })
    }
   
    /**
     * 駅情報をセットする
     * @param {object} resoponse ルート案内オブジェクト
     */
    setDetails(response) {
      if (!this.details) {
        this.details = response.routes[0].legs[0];
      }
    }
    
    /**
     * 駅インスタンスを削除する
     */
    delete() {
      super.delete(); // スーパークラスへアクセス(ES6)
      this.ev.click.remove();
      // ルート案内
      if (this.directionsDisplay) { this.directionsDisplay.setMap(null); }
    }
    
    /**
     * インスタンス生成時のオプションを取得する
     + @param {object} station 駅オブジェクト
     * @param {object} 設定オプション
     */
    static getOptions(station) { // 静的メソッド(ES6)
      return {
        position: {
          lat: station.geometry.location.lat(),
          lng: station.geometry.location.lng()
        },
        icon: {
          path       : google.maps.SymbolPath.CIRCLE,
          scale      : 5,
          strokeColor: '#f30'
        }
      };
    }
  }
  
  
  /* ========================================
    検索
  ======================================== */
  KOKOKARA.Search = class Search { // クラス定義(ES6)
    /**
     * [コンストラクタの説明]
     * @constructor
     * @classdesc 検索機能を管理するクラス
     */
    constructor() {
      /* --------------------
        プロパティ
      -------------------- */
      /**
       * Promiseオブジェクトアクセス用
       * @prop
       */
      this.promise = { /* resolve, reject */ };
      
      /* --------------------
        DOM
      -------------------- */
      this.el = {
        /**
         * 検索フォーム要素
         * @type {object}
         */
        searchForm: document.querySelector(`${JS_PREFIX}search__form`), // テンプレートリテラル(ES6)
        /**
         * 検索テキストボックス要素
         * @type {object}
         */
        query: document.querySelector(`${JS_PREFIX}query`), // テンプレートリテラル(ES6)
        /**
         * 検索ボタン要素
         * @type {object}
         */
        submit: document.querySelector(`${JS_PREFIX}submit`), // テンプレートリテラル(ES6)
        /**
         * 住所選択セレクトボックス要素
         * @type {object}
         */
        select: document.querySelector(`${JS_PREFIX}select`) // テンプレートリテラル(ES6)
      }
    }
    
    /**
     * 初動処理
     */
    init() {
      /* --------------------
        イベント
      -------------------- */
      // 検索
      this.el.searchForm.addEventListener('submit', e => { // アロー関数(ES6)
        e.preventDefault();
        this.searchFlow(e.target[SEARCH_QUERY_NAME].value);
      }, false);
      
      // 検索位置原点候補選択
      this.el.select.addEventListener('change', e => { // アロー関数(ES6)
        e.preventDefault();
        this.promise.resolve(JSON.parse(e.target.value));
        this.elLock(); // 検索フォームをロックする
      }, false);
      
      // 検索位置原点確定
      KOKOKARA.scriptEvent.add('search_originPos_after', function create(pos) {
        KOKOKARA.OriginPoint.create(KOKOKARA.OriginPoint.getOptions(pos)); // インスタンス生成
      }, 9);
  
      // 距離の近い順にソート済みの駅をループ時
      KOKOKARA.scriptEvent.add('search_sortedStation_loop', function create(station) {
        KOKOKARA.Station.create(KOKOKARA.Station.getOptions(station), station); // インスタンス生成
      });
    }

    /**
     * フォーム操作をロックする
     * @param {boolean} mode
     */
    elLock(mode = false) {
      if ('boolean' !== typeof mode) { return; }
      // フォームロック
      if (true === mode) {
        this.el.query.disabled  = true;
        this.el.submit.disabled = true;
        this.el.select.classList.add(ACTIVE_CLASS_NAME);
      // フォーム解除
      } else if (false === mode) {
        this.el.query.disabled  = false;
        this.el.submit.disabled = false;
        this.el.select.classList.remove(ACTIVE_CLASS_NAME);
        while (this.el.select.firstChild) {
          this.el.select.removeChild(this.el.select.firstChild);
        }
      }
    }
    
    /**
     * 検索フロー
     * @param {string} query 検索する原点の文字列
     */
    searchFlow(query) {
      // 検索原点取得
      this.getOrignPosBranch(query)// Promise(ECMA2015)

        // 地図表示を更新
        .then(pos => { // アロー関数(ES6)
          console.log('searchFlow then 2', pos);
          // 検索原点取得後
          KOKOKARA.scriptEvent.do('search_originPos_after', pos);
          return pos;
        })

        // 最寄り駅取得
        .then(pos => { // アロー関数(ES6)
          console.log('searchFlow then 3', pos);
          return this.getNearlyStations(pos);
        })

        // 最寄り駅情報追加
        .then(stations => { // アロー関数(ES6)
          console.log('searchFlow then 4', stations);
        })

        // エラー処理 
        .catch(err => { // アロー関数(ES6)
          console.log('searchFlow catch', err);
          alert('エラー: ' + err.message);
        });
    }
  
    /**
     * 検索原点フロー
     * @param {string} query 検索する原点の文字列
     */
    getOrignPosBranch(query) {
      return new Promise((resolve, reject) => { // アロー関数(ES6) Promise(ECMA2015)
        console.log('searchFlow then 1');

        this.setPromise(resolve, reject);

        // 検索地点なし
        if ('' === query) {
          this.getPosFromCurrentPos();

        // 検索地点あり
        } else {
          this.getPosFromPlace(query);
        }
      });
    }    
    
    /**
     * 現在地からの位置取得
     */
    getPosFromCurrentPos() {
      if (!navigator.geolocation) {
        alert('このブラウザは現在地の取得に対応してないよ。');
      }

      let options = {
        enableHighAccuracy: true,
        timeout           : 5000,
        maximumAge        : 0
      };

      navigator.geolocation.getCurrentPosition(
        this.getCurrentPosSuccess,
        this.getCurrentPosError,
        options
      );
    }

    /**
     * 現在地からの位置取得成功時
     * @param {object} position 位置情報
     */
    getCurrentPosSuccess(position) {
      let coords = position.coords;
      let pos = {
        lat: coords.latitude,
        lng: coords.longitude
      };
      KOKOKARA.search.promise.resolve(pos);
    }

    /**
     * 現在地からの位置取得失敗時
     * @param {object} err エラー情報
     */
    getCurrentPosError(err) {
      KOKOKARA.search.promise.reject(err);
    }
    
    /**
     * 任意の場所からの位置取得
     * @param {string} query 検索する原点の文字列
     */
    getPosFromPlace(query) {
      let q   = encodeURIComponent(query);
      let url = `${GEOCODE_URL}?address=${q}`; // テンプレートリテラル(ES6)

      this.sendXhr(
        url,
        this.getPosFromPlaceSuccess.bind(this),
        this.getPosFromPlaceError
      );
    }
    
    /**
     * 任意の場所からの位置取得成功時
     * @param {string} responseText 位置情報のテキスト
     */
    getPosFromPlaceSuccess(responseText) {
      let response = JSON.parse(responseText);
      let results  = response.results;
      let length   = results.length;
      let pos      = {};
      
      if (1 === length) {
        pos.lat = results[0].geometry.location.lat;
        pos.lng = results[0].geometry.location.lng;
        KOKOKARA.search.promise.resolve(pos);

      // 複数の候補地
      } else if (1 < length) {
        this.selectPosFromPlaces(results);

      // 該当場所なし
      } else {
        let err = new Error(response.status);
        KOKOKARA.search.promise.reject(err);
      }
    }
 
    /**
     * 任意の場所からの位置取得時、検索結果が複数の場合
     * @param {array} positions 複数の候補地
     */
    selectPosFromPlaces(positions) {
      let frg          = document.createDocumentFragment();
      let option       = document.createElement('option');
      option.innerHTML = '候補地が複数あります';
      option.value     = JSON.stringify({});
      frg.appendChild(option);
      
      positions.forEach(pos => { // アロー関数(ES6)
        let option       = document.createElement('option');
        option.innerHTML = pos.formatted_address;
        option.value     = JSON.stringify(pos.geometry.location);
        frg.appendChild(option);
      });
      
      this.el.select.appendChild(frg);
      this.elLock(true);
    }
    
    /**
     * 任意の場所からの位置取得失敗時
     * @param {object} err エラーオブジェクト
     */
    getPosFromPlaceError(err) {
      KOKOKARA.search.promise.reject(err);
    }
    
    /**
     * 最寄り駅検索
     * @param {object} pos 緯度経度
     */
    getNearlyStations(pos) {    
      return new Promise((resolve, reject) => { // アロー関数(ES6) Promise(ECMA2015)
        KOKOKARA.search.setPromise(resolve, reject);
        
        let service = new google.maps.places.PlacesService(KOKOKARA.map.map);

        // 取得
        service.radarSearch({
          location: pos,
          radius  : SEARCH_RADIUS,
          types   : SEARCH_TYPES
        }, this.resultStations.bind(this));
      });
    }
   
    /**
     * 最寄り駅検索結果を処理
     * @param {object} stations 最寄り駅検索結果
     * @param {object} status ステータス コード
     * @param {object} pagination ページネーション
     */
    resultStations(stations, status, pagination) {
      // 成功時
      if (status === google.maps.places.PlacesServiceStatus.OK) {
        // 駅データを距離でソートする
        let sortedStations = this.sortStationsByDistance(
                               KOKOKARA.main.originPoint[0].options.position,
                               stations
                             );
        // 最寄り駅生成
        sortedStations.forEach((station, ind) => { // アロー関数(ES6)
          setTimeout(() => { // アロー関数(ES6)
            KOKOKARA.scriptEvent.do('search_sortedStation_loop', station);
          }, ind * MARKER_DISPLAY_DURATION);
        });

        KOKOKARA.search.promise.resolve(sortedStations);

      // 失敗時
      } else {
        let err = new Error(status);
        KOKOKARA.search.promise.reject(err);
      }
    }
    
    /**
     * 駅データを距離からソートする
     * (駅データに原点からの距離データを付与)
     * @param {object} originPoint 緯度経度を含む位置原点オブジェクト
     * @param {array} stations ソートする駅データ 
     * @return {array} ソート済み駅データ
     */
    sortStationsByDistance(originPoint, stations) {
      let sortedStations = [];

      if (!Array.isArray(stations) || !stations.length) { return sortedStations; }

      stations.forEach((station, ind) => { // アロー関数(ES6)
        let point1 = originPoint;
        let point2 = {
          lat: station.geometry.location.lat(),
          lng: station.geometry.location.lng()
        }
        station.distance = getDistance(point1, point2);
        sortedStations.push(station);
      });
      
      sortedStations.sort((a, b) => { // アロー関数(ES6)
        return a.distance > b.distance ? 1 : -1;  
      });
      
      return sortedStations;
    }
        
    /**
     * AJAX通信する
     * @param {string} url リクエストurl
     * @param {function} success 成功時処理
     * @param {function} error 失敗時処理
     * @param {boolean} async 非同期かどうか
     */
    sendXhr(url, success, error, async = true) { // デフォルト引数(ES6)
      let xhr = new XMLHttpRequest();

      // 成功
      xhr.addEventListener('load', () => { // アロー関数(ES6)
        if (xhr.status === 200) {
          success(xhr.responseText);
        } else {
          // Status: 401, 404, 500
        }
      });

      // 失敗
      xhr.addEventListener('error', error);

      // 通信開始
      xhr.open('GET', url, async);
      xhr.send(null);
    }
    
    /**
     * プロミスオブジェクトを管理
     * @param {function} resolve
     * @param {function} reject
     */
    setPromise(resolve = null, reject = null) {
      this.promise = {
        resolve: resolve,
        reject : reject
      };
    }
  }
  
  
  /* ========================================
    情報エリア
  ======================================== */
  KOKOKARA.Info = class Info { // クラス定義(ES6)
    /**
     * リストアイテムクラス名
     */
    static INFOPREFIX = 'info__';// 静的プロパティ(ES6)
    /**
     * [コンストラクタの説明]
     * @constructor
     * @classdesc 情報エリアを管理するクラス
     */
    constructor() {
      /* --------------------
        DOM
      -------------------- */
      this.el = {
        /**
         * 情報エリア動的生成ルート要素
         * @prop
         */
        root: document.querySelector(`${JS_PREFIX}root-info`), // テンプレートリテラル(ES6)
        /**
         * 情報エリア開閉ボタン要素
         * @prop
         */
        btn : document.querySelector(`${JS_PREFIX}btn-info`) // テンプレートリテラル(ES6)
      };
    }
    
    /**
     * 初動処理
     */
    init() {
      /* --------------------
        イベント
      -------------------- */
      this.el.btn.addEventListener('click', () => { // アロー関数(ES6)
        // スライド
        KOKOKARA.main.el.main.classList.toggle(ACTIVE_CLASS_NAME);
        // GPU
        if ('transform' === KOKOKARA.main.el.main.style.willChange) {
          KOKOKARA.main.el.main.style.willChange = 'auto';
        } else {
          KOKOKARA.main.el.main.style.willChange = 'transform';
        }
        // pointer-events 
        this.el.root.classList.toggle(ACTIVE_CLASS_NAME);
      }, false);

      KOKOKARA.scriptEvent.add('search_originPos_after',                this.reset.bind(this));
      KOKOKARA.scriptEvent.add('station_marker_click_succeess_ended',   this.updateContent.bind(this));
      KOKOKARA.scriptEvent.add('station_renderLine_directions_changed', this.updateDescription);  
    }
   
    /**
     * 情報エリアアイテムを更新する
     * @param {object} station 駅インスタンス
     */
    updateContent(station) {
      let node = this.createListItem(station); // ブロックスコープ変数(ES6)
      this.renderContent(node);
    }
    
    /**
     * 情報エリアアイテムを表示する
     * @param {object} station 駅インスタンス
     */
    renderContent(node) {
      this.el.root.appendChild(node);
    }
    
    /**
     * 情報エリアアイテムを生成する
     * @param {object} station 駅インスタンス
     * @return {object} 生成したノード
     */
    createListItem(station) {
      let list; // ブロックスコープ変数(ES6)
      
      // アイテム取得
      if (station.el.listInfo) {
        console.warn('情報エリアリストアイテム作成済み', station.el.listInfo);
        list = station.el.listInfo;
        
      // アイテム生成
      } else {
        console.warn('情報エリアリストアイテム作成', station.el.listInfo);
        list = document.createElement('li');
        list.classList.add(`${KOKOKARA.Info.INFOPREFIX}item`); // テンプレートリテラル(ES6)
        list.setAttribute('data-place-id',     station.place.place_id); // 未使用
        list.setAttribute('data-place-bounds', JSON.stringify(station.direction.routes[0].bounds));
        list.innerHTML      = this.getListItemTemplate(station);
        station.el.listInfo = list;
        station.directionsDisplay.setPanel(list); // 行きかた表示
        
        // 行きかた表示イベント
        let moreEl = list.querySelector(`.${JS_PREFIX_MULTI}${KOKOKARA.Info.INFOPREFIX}more`); // テンプレートリテラル(ES6)
        moreEl.addEventListener('click', function(e) {
          e.preventDefault();
          e.stopPropagation();
          e.target.nextSibling.classList.toggle(ACTIVE_CLASS_NAME);
        }, false);
        
        // ルートを画面にフィットさせるイベント
        list.addEventListener('click', function(e) {
          e.preventDefault();
          
          let el = e.target; // ブロックスコープ変数(ES6)
          let target; // ブロックスコープ変数(ES6)
          
          do {
            if (list === el) {
              target     = el;
              let map    = KOKOKARA.map.map; // ブロックスコープ変数(ES6)
              let bounds = JSON.parse(target.getAttribute('data-place-bounds')); // ブロックスコープ変数(ES6)
              map.fitBounds(bounds);
              break;
            }
            el = el.parentElement;
          } while(el);
        }, false);
      }
      
      return list;
    }
  
    /**
     * 情報エリア一覧テンプレート
     * @param {object} station 駅インスタンス
     * @return {string} データをあてたHTML文字列
     */
    getListItemTemplate(station) {
      let template = `
        <a class="${JS_PREFIX_MULTI}${KOKOKARA.Info.INFOPREFIX}description" href="javascript:void(0)">
           <h3 class="${KOKOKARA.Info.INFOPREFIX}station">移動元(A) : <br>
             <span class="${JS_PREFIX_MULTI}${KOKOKARA.Info.INFOPREFIX}originPoint">${station.details.start_address}</span></h3>
           <h3 class="${KOKOKARA.Info.INFOPREFIX}station">移動先(B) : <br>
              <span class="${JS_PREFIX_MULTI}${KOKOKARA.Info.INFOPREFIX}station">${station.details.end_address}</span></h3>
           <p class="${KOKOKARA.Info.INFOPREFIX}dist">距離 : 
             <span class="${JS_PREFIX_MULTI}${KOKOKARA.Info.INFOPREFIX}distance">${station.details.distance.text}</span></p>
           <p class="${KOKOKARA.Info.INFOPREFIX}walk">徒歩時間 : 
             <span class="${JS_PREFIX_MULTI}${KOKOKARA.Info.INFOPREFIX}duration">${station.details.duration.text}</span></p>
        </a>
        <a class="${KOKOKARA.Info.INFOPREFIX}more ${JS_PREFIX_MULTI}${KOKOKARA.Info.INFOPREFIX}more" href="javascript:void(0)">行きかた</a>`; // テンプレートリテラル(ES6)
      return template;
    }
    
    /**
     * 概要情報を更新する
     * @param {object} data 概要データ
     * @param {object} station 駅データ
     */
    updateDescription(data, station) {
      let listEl = station.el.listInfo; // ブロックスコープ変数(ES6)
      
      Object.keys(data).forEach((key) => { // アロー関数(ES6)
        let targetEl = listEl.querySelector(`.${JS_PREFIX_MULTI}${KOKOKARA.Info.INFOPREFIX}${key}`); // テンプレートリテラル(ES6)
        targetEl.textContent = data[key]; 
      });
    }
    
    /**
     * 情報エリアのリストをリセットする
     */
    reset() {
      while (this.el.root.firstChild) { this.el.root.removeChild(this.el.root.firstChild); }
    }
  }

 
  /* ========================================
    実行
  ======================================== */
  // 実行開始
  document.addEventListener('DOMContentLoaded', () => { // アロー関数(ES6)
    KOKOKARA.main = new KOKOKARA.Main();
    KOKOKARA.main.init();
  }, false);  
 
  
  /* ========================================
    その他
  ======================================== */
  /**
   * 1文字目を大文字か小文字に変換する
   * @param {string} str 変換する文字列
   * @param {string} uclc変換ケース
   * @return {string} 変換した文字列
   */
  function uclcfirst(str, uclc = 'uc') { // デフォルト引数(ES6)
    let _case = {
      uc: 'toUpperCase',
      lc: 'toLowerCase'
    }
    return str.charAt(0)[_case[uclc]]() + str.slice(1);
  }
  
  /**
   * オブジェクトリテラル同士をマージする(非破壊的)
   * ネスト非対応
   * @param {Array.<object>} arguments マージするオブジェクトリテラル配列
   * @return {Array.<object>} マージしたオブジェクトリテラル配列
   */
  function mixin( /* arguments */ ) {
    let ret = {}, i = 0, l = arguments.length; // ブロックスコープ変数(ES6)
    for (; i < l; i++) {
      for (let prop in arguments[i]) {
        if (arguments[i].hasOwnProperty(prop)) {
          ret[prop] = arguments[i][prop];
        }
      }
    }
    return ret; 
  }
  
  /**
   * 二点間の緯経度から直線距離を算出する
   * @param {object} point1 緯経度
   * @param {object} point2 緯経度
   * @param {string} unit 単位
   * @return {number} 算出した直線距離
   */
  function getDistance(point1, point2, unit = 'k') { // デフォルト引数(ES6)
		const r = 6378.137; // 定数(ES6) 赤道半径[km]
    const u = { // 定数(ES6)
      m: 1000,
      k: 1
    }
    
		let lat1 = point1.lat * Math.PI / 180; // ブロックスコープ変数(ES6)
		let lng1 = point1.lng * Math.PI / 180; // ブロックスコープ変数(ES6)
		let lat2 = point2.lat * Math.PI / 180; // ブロックスコープ変数(ES6)
		let lng2 = point2.lng * Math.PI / 180; // ブロックスコープ変数(ES6)

		// 2点間の距離[km]
		let distance = r * Math.acos(Math.sin(lat1) * Math.sin(lat2) + Math.cos(lat1) * Math.cos(lat2) * Math.cos(lng2 - lng1)); // ブロックスコープ変数(ES6)

    return distance * u[unit];
  }
  
} // ブロックスコープ定義(ES6) ここまで

            
          
!
999px
🕑 One or more of the npm packages you are using needs to be built. You're the first person to ever need it! We're building it right now and your preview will start updating again when it's ready.

Console