20190529のJavaScriptに関する記事は26件です。

GIF画像検索してブラウザでLGTM画像生成とアップロードをしてMarkdown記法でコピーできるツール作った話

今回の開発でわかったこと&この記事で言いたいこと

JavaScriptはすごい。
firebaseもすごい。
個人開発なら、なんでもかんでも自分で作ろうとしないこと。
世の中の既存のサービスにも目を向けること。
GIFバイナリは面白かった。
いやまじでfirebaseはすごい。

ツール開発経緯

この記事に触発された。

ちょっとそこのあなた! 自分だけのオリジナルLGTMをチームで使ってみませんか?

そうだよね!LGTM画像って動く方がいいよね!めっちゃわかる

でもLGTMって書かれていないGIF画像って素っ気ないよね!

じゃGIF画像検索できて、それをすぐにLGTM画像に生成してMarkdown形式でコピペしたい!!

何ができたのか

できたツールがこれ。

GIF検索してコピペできるLGTM画像生成ツールを作った

ChromeFirefoxでしか確認していません。。

大まかにどうやっているのか

  1. GIF画像をバイナリで取得する。
  2. バイナリを分析して一枚の画像ごとに分ける。
  3. LGTMの文字と一枚の画像をcanvasに描画していく。
  4. 全部くっつけてLGTM GIF画像が出来上がる。
  5. サーバーにLGTM画像をアップロードしURL取得して終わり。

サーバーでは画像を保存することしかしていません。

ブラウザのみでGIF検索、LGTM画像生成、アップロード、Markdown記法クリップボードにコピーをしています。

具体的にどうやってるのか

  1. GIPHY APISearch APIでGIF画像を検索する。
  2. クリックされたら幅200pxのGIF画像をarraybuffer形式でファイルを取得する。
  3. それをomggif.jsを使用して一枚ずつの画像にする。
  4. gif.js で一枚になった画像とLGTM文字をcanvasdrawする。
  5. GIF画像の枚数分、LGTM文字付きの画像をgif.jsaddFrameしたらrenderする。
  6. gif.jsrenderすると完成したGIF画像がblobに変換される。
  7. blobfirebase storageにそのままアップロードする。
  8. 最後にfirebase storagedownloadUrlを取得してクリップボードに保存する。

これを実装するまでの物語

目的:GIF画像検索してそれをLGTM画像にして簡単にMarkdown記法でコピーしてGithubに貼り付けたい!

よくRuby on Rails使っていたのでrmagickとかいう画像編集gemで実装してみる。
できたが、実装が悪いのかGIF画像生成に時間がかかった。
これではたくさん画像生成するとなると遅くなってしまう。。
うーむ。
もっと早く画像編集する方法を探る。
PythonOpenCVに出会う。
早い気がするが、結局画像生成に時間がかかる。
うーむ。
もっともっと早く画像編集したい!
Halideに出会う。
少し触って、「あれ、なにしようとしてたんだっけ??」ってなる。

--数年後--

ちょっとそこのあなた! 自分だけのオリジナルLGTMをチームで使ってみませんか?を見る。
そうだよな。LGTM画像ってLGTMの文字が入ってこそだよなぁ。
うーむ。

--数日後--

そっか、使ってもらう人に画像生成してもらえばサーバーの負荷減るなー。
今に至る。

ライセンス

https://github.com/deanm/omggif MIT License
https://github.com/jnordberg/gif.js MIT License

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

様々なプログラミング言語でクラス (あるいはクラスもどき) を書く

概要

  • 様々なプログラミング言語でクラス (あるいはクラスもどき) を書く

Java

MyCounter.java

public class MyCounter {

  public static MyCounter globalCounter = null;

  private String name = "No Name";
  private int count;

  public MyCounter(int initValue) {
    count = initValue;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public int addCount(int number) {
    count += number;
    return count;
  }

  public void print() {
    System.out.println(format());
  }

  private String format() {
    return name + ": " + count;
  }

  public static void createGlobalCounter(String name, int initValue) {
    globalCounter = new MyCounter(initValue);
    globalCounter.setName(name);
  }
}

MyMain.java

public class MyMain {

  public static void main(String[] args) {

    MyCounter counter = new MyCounter(0);
    counter.print();

    counter.setName("John Doe");
    String currentName = counter.getName();
    System.out.println("Current Name=" + currentName);

    int currentValue = counter.addCount(10);
    System.out.println("Current Value=" + currentValue);

    counter.print();

    MyCounter.createGlobalCounter("CafeBabe! Write once, run anywhere", 256);
    MyCounter.globalCounter.print();
  }
}

実行結果

No Name: 0
Current Name=John Doe
Current Value=10
John Doe: 10
CafeBabe! Write once, run anywhere: 256

JavaScript

my_counter.js

class MyCounter {

  constructor(initValue) {
    this._name = 'No Name';
    this.count = initValue;
  }

  get name() {
    return this._name;
  }

  set name(name) {
    this._name = name;
  }

  addCount(number) {
    this.count += number;
    return this.count;
  }

  print() {
    console.log(this._format());
  }

  _format() {
    return this._name + ': ' + this.count;
  }

  static createGlobalCounter(name, init_value) {
    MyCounter.global_counter = new MyCounter(init_value);
    MyCounter.global_counter.name = name;
  }
}

module.exports = MyCounter;

my_main.js

const MyCounter = require('./my_counter');

counter = new MyCounter(0);
counter.print();

counter.name = 'John Doe';
const currentName = counter.name;
console.log('Current Name=' + currentName);

const currentValue = counter.addCount(10);
console.log('Current Value=' + currentValue);

counter.print();

MyCounter.createGlobalCounter('JavaScript was born from Netscape Navigator 2.0', 256);
MyCounter.global_counter.print();

実行結果

$ node my_main.js
No Name: 0
Current Name=John Doe
Current Value=10
John Doe: 10
JavaScript was born from Netscape Navigator 2.0: 256

Python

my_counter.py

class MyCounter:

  global_counter = None

  def __init__(self, init_value):
    self.__name = "No Name"
    self.__count = init_value

  @property
  def name(self):
    return self.__name

  @name.setter
  def name(self, name):
    self.__name = name

  def add_count(self, number):
    self.__count += number
    return self.__count

  def print(self):
    print(self.__format())

  def __format(self):
    return "{0}: {1}".format(self.__name, self.__count)

  @classmethod
  def create_global_counter(cls, name, init_value):
    cls.global_counter = MyCounter(init_value)
    cls.global_counter.name = name

my_main.py

from my_counter import MyCounter

counter = MyCounter(0)
counter.print()

counter.name = "John Doe"
current_name = counter.name
print("Current Name={}".format(current_name))

current_value = counter.add_count(10)
print("Current Value={}".format(current_value))

counter.print()

MyCounter.create_global_counter("Batteries included. Beautiful is better than ugly.", 256)
MyCounter.global_counter.print()

実行結果

$ python my_main.py
No Name: 0
Current Name=John Doe
Current Value=10
John Doe: 10
Batteries included. Beautiful is better than ugly.: 256

Ruby

my_counter.rb

class MyCounter
  attr_accessor :name

  @@global_counter = nil

  def initialize(init_value)
    @name = 'No Name'
    @count = init_value
  end

  def add_count(number)
    @count += number
  end

  def print
    puts format
  end

  private

  def format
    "#{@name}: #{@count}"
  end

  def self.create_global_counter(name, init_value)
    @@global_counter = self.new(init_value)
    @@global_counter.name = name
  end

  def self.global_counter
    @@global_counter
  end
end

my_main.rb

require './my_counter'

counter = MyCounter.new(0)
counter.print

counter.name = 'John Doe'
current_name = counter.name
puts "Current Name=#{current_name}"

current_value = counter.add_count(10)
puts "Current Value=#{current_value}"

counter.print

MyCounter.create_global_counter('Everything is an object in Ruby', 256)
MyCounter.global_counter.print
$ ruby my_main.rb
No Name: 0
Current Name=John Doe
Current Value=10
John Doe: 10
Everything is an object in Ruby: 256
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

住所で見る転職ドラフト

ああ、やっぱり今回も駄目だったよ。あいつは話を聞かないからな。

結論

9割東京

東京以外は累計でもそれぞれ5社も存在しないかも(住所未記入企業がある)

知りたいこと

第19回転職ドラフトお疲れ様でした。1

ITエンジニアとしての価値を確認する手段の一つとして、
転職ドラフト」があります。

そこにドラフト参加している企業から年収付き指名をもらうことで自分の評価をお金として可視化できるのです。(指名数でも評価できるでしょう)
しかし、企業側から見ると、採用を前提とした大変労力がかかっている指名作業なので、経歴はもちろんとして、「希望勤務地」でもフィルターがかかっていると考えます。
すると場所によっては指名を検討する段階にも行かない可能性があります。

第12回転職ドラフトに企業側として参加しました。 – スタディスト開発ブログ – Medium


候補者絞り込み&指名プロセス(引用)

候補者絞り込み&指名プロセス

今後社内で分担して取り組むことも考えて自分がどうやって進めたのかを整理してみました。はじめからこういったプロセスを定義したわけではなく、私が結果として無意識的にやっていた作業を整理してみた内容です。
ステップ1 : 基本チェック

基本チェックとして登録ユーザー430人全員のプロフィールを確認し、スキルと勤務地を確認して絞り込みを行いました。

明らかに一致しない人を除いて、ここで430人から90人程度まで絞り込みました。

<判断基準>

弊社で使用している技術スタックの経験があるか
(iOS, Android, Windows, ruby, Vue.js, awsなど)
希望勤務地が合っているか、フルリモート前提の人ではないか


転職ドラフト運営チーム&企業サイドが、サービスの内幕を語り尽くすイベント「Inside 転職ドラフト

うちは人事を挟んでいます。絞り込みの条件は勤務地が東京であることと、あとはターゲットの年齢くらいでしょうか。

そこで、自分が希望する勤務地に対して転職ドラフトの利用は妥当かのいち判断材料として、参加企業はどこに住所があるのかを調査しました。

技術と指名や金額の関連性の話は他の方々がいろいろされているので別方面という感じで。

対象

直近で行われた
第19回参加企業一覧|転職ドラフト - ITエンジニアを競争入札
の企業を調べます。

生データ


第19回参加企業
[
  {
    "draftUrl": "https://job-draft.jp/companies/893",
    "name": "LINE Growth Technology株式会社",
    "url": "https://linecorp.com/ja/growth",
    "address": "東京都新宿区新宿四丁目1番6号 JR新宿ミライナタワー",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/449",
    "name": "株式会社ジャパンベンチャーリサーチ",
    "url": "https://www.uzabase.com/",
    "address": "東京都港区六本木7-7-7 TRI-SEVEN ROPPONGI 13F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/335",
    "name": "株式会社LIFULL",
    "url": "http://lifull.com/",
    "address": "東京都千代田区麹町1-4-4",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/128",
    "name": "株式会社フィードフォース",
    "url": "https://www.feedforce.jp/",
    "address": "東京都文京区湯島3-19-11 湯島ファーストビル5F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/130",
    "name": "株式会社ユーザベース",
    "url": "https://www.uzabase.com/",
    "address": "東京都港区六本木7-7-7  TRI-SEVEN ROPPONGI 13F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/833",
    "name": "株式会社Azit",
    "url": "https://azit.co.jp/",
    "address": "東京都港区東新橋2-12-1 PMO東新橋 6F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/470",
    "name": "株式会社FORCAS",
    "url": "https://www.forcas.com",
    "address": "東京都港区六本木7-7-7 TRI-SEVEN ROPPONGI 13F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/1019",
    "name": "クリエイティブサーベイ株式会社",
    "url": "https://jp.creativesurvey.com/company/team/",
    "address": "東京都港区南青山2-2-8 DFビルディング9階",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/214",
    "name": "メドピア株式会社",
    "url": "https://medpeer.co.jp/",
    "address": "東京都中央区銀座6-18-2 野村不動産銀座ビル11階",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/899",
    "name": "株式会社SCOUTER",
    "url": "https://corp.scouter.co.jp/",
    "address": "東京都渋谷区宇田川町2−1 渋谷ホームズ1305号室",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/208",
    "name": "株式会社ノハナ",
    "url": "https://nohana.co.jp",
    "address": "東京都渋谷区渋谷2-11-5 クロスオフィス渋谷メディオ6-C号室",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/725",
    "name": "Resily株式会社",
    "url": "https://resily.com",
    "address": "東京都渋谷区神宮前6-17-15 落合原宿ビル10階",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/383",
    "name": "ルームクリップ株式会社",
    "url": "https://corp.roomclip.jp/",
    "address": "東京都渋谷区千駄ヶ谷1-28-1 佳秀ビル2F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/134",
    "name": "株式会社WACUL",
    "url": "https://wacul.co.jp/",
    "address": "東京都千代田区神田小川町3-26-8 2F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/26",
    "name": "freee株式会社",
    "url": "https://www.freee.co.jp/",
    "address": "東京都品川区西五反田2-8-1",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/62",
    "name": "株式会社ニューズピックス",
    "url": "https://newspicks.com/",
    "address": "東京都港区六本木7-7-7 TRI-SEVEN ROPPONGI 13F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/4",
    "name": "株式会社リブセンス",
    "url": "https://www.livesense.co.jp/",
    "address": "東京都品川区上大崎2丁目25番2号 新目黒東急ビル5F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/857",
    "name": "株式会社ルーデル",
    "url": "http://rudel.jp/",
    "address": "東京都新宿区四谷1-22-5 WESTALL四谷ビル 1F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/530",
    "name": "株式会社MICIN",
    "url": "https://micin.jp/",
    "address": "東京都千代田区大手町2-6-2 日本ビル13階",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/150",
    "name": "Supership株式会社",
    "url": "https://supership.jp/",
    "address": "東京都港区南青山5-4-35 たつむら青山ビル",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/593",
    "name": "サイボウズスタートアップス株式会社",
    "url": "https://cstap.com",
    "address": "東京都品川区西五反田二丁目27番3号 A-PLACE 五反田 9階",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/154",
    "name": "コネヒト株式会社",
    "url": "http://connehito.com/",
    "address": "東京都港区南麻布3-20-1 Daiwa麻布テラス5階",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/1028",
    "name": "株式会社キッズライン",
    "url": "https://kidsline.me/corp/",
    "address": "東京都港区六本木5-2-3  マガジンハウス六本木ビル7F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/425",
    "name": "株式会社AppBrew",
    "url": "http://appbrew.io",
    "address": "東京都文京区本郷1丁目11−6 東接本郷ビル4階",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/881",
    "name": "株式会社VALU",
    "url": "https://valu.is/",
    "address": "東京都渋谷区神宮前三丁目42番2号VORT外苑前3 3階",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/836",
    "name": "Ubie株式会社",
    "url": "https://ubie.life",
    "address": "東京都中央区日本橋室町1-5-3福島ビル601号室",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/274",
    "name": "Fringe81株式会社",
    "url": "http://www.fringe81.com/",
    "address": "東京都港区六本木3-2-1 住友不動産六本木グランドタワー 43階",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/731",
    "name": "株式会社Showcase Gig",
    "url": "https://www.showcase-gig.com/",
    "address": "東京都港区南青山4-1-6 セブン南青山ビル4F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/24",
    "name": "Sansan株式会社",
    "url": "http://jp.corp-sansan.com/",
    "address": "東京都渋谷区神宮前5-52-2 青山オーバルビル13階",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/1031",
    "name": "株式会社VRize",
    "url": "https://vrize.io",
    "address": "東京都渋谷区神宮前6-28-5 宮崎ビルA棟3F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/1025",
    "name": "株式会社グリモア",
    "url": "https://grimoire.co/",
    "address": "東京都目黒区青葉台",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/1013",
    "name": "株式会社フィナンシェ",
    "url": "https://www.corp.financie.jp",
    "address": "東京都渋谷区26-1  セルリアンタワー15F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/1001",
    "name": "モノグサ株式会社",
    "url": "https://monoxer.com/",
    "address": "東京都千代田区麹町2-2-22 ACN半蔵門ビル4階",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/950",
    "name": "株式会社スペースキー",
    "url": "https://www.spacekey.co.jp/",
    "address": "〒150-0043 東京都渋谷区道玄坂2-10-7 新大宗ビル2号館4階",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/947",
    "name": "株式会社スイッチ・メディア・ラボ",
    "url": "https://www.switch-m.com/",
    "address": "東京都港区赤坂2-10-9 ラウンドクロス赤坂6F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/926",
    "name": "hachidori株式会社",
    "url": "https://hachidoriinc.com/",
    "address": "東京都千代田区神田錦町3-17 廣瀬ビル7F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/917",
    "name": "アライドアーキテクツ株式会社",
    "url": "https://www.aainc.co.jp/",
    "address": "東京都渋谷区恵比寿1-19-15 ウノサワ東急ビル 4階",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/890",
    "name": "LINE株式会社",
    "url": "https://linecorp.com/ja/",
    "address": "東京都新宿区新宿四丁目1番6号 JR新宿ミライナタワー23階",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/863",
    "name": "株式会社MyRefer",
    "url": "https://myrefer.co.jp/vision/",
    "address": "〒103-0016 東京都中央区日本橋小網町12-7 日本橋小網ビル4F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/851",
    "name": "MF KESSAI株式会社",
    "url": "https://corp.mfkessai.co.jp/",
    "address": "東京都千代田区大手町1−6−1 大手町ビル4階",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/839",
    "name": "株式会社グロービス",
    "url": "https://recruiting-tech.globis.co.jp/",
    "address": "東京都千代田区二番町5-1住友不動産麹町ビル",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/827",
    "name": "ビットバンク株式会社",
    "url": "https://bitcoinbank.co.jp/",
    "address": "東京都品川区西五反田7丁目20-9 KDX西五反田7F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/824",
    "name": "ユリシーズ株式会社",
    "url": "http://www.ulysses-ltd.com/",
    "address": "東京都千代田区内神田2-13-2 梶山ビル3F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/785",
    "name": "株式会社Globee",
    "url": "https://globeejapan.com",
    "address": "東京都港区東麻布一丁目7番3号 第二渡邊ビル7F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/713",
    "name": "株式会社エビリー",
    "url": "https://eviry.com/",
    "address": "東京都渋谷区渋谷1-3-9 ヒューリック渋谷一丁目ビルB1",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/659",
    "name": "株式会社ユビレジ",
    "url": "https://ubiregi.com/",
    "address": "東京都 渋谷区 千駄ヶ谷 3-59-4 クエストコート原宿",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/641",
    "name": "株式会社 xenodata lab.",
    "url": "https://www.xenodata-lab.com/",
    "address": "東京都渋谷区松濤1-29-1 渋谷クロスロードビル 5F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/629",
    "name": "atama plus 株式会社",
    "url": "https://www.atama.plus/",
    "address": "東京都中央区日本橋堀留町1-8-12 さくら堀留ビル7F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/587",
    "name": "株式会社Azoop",
    "url": "https://azoop.co.jp/",
    "address": "東京都世田谷区上馬2-25-4 フレックス三軒茶屋3F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/548",
    "name": "コインチェック株式会社",
    "url": "http://corporate.coincheck.com",
    "address": "東京都渋谷区渋谷3-28-13 渋谷新南口ビル3F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/545",
    "name": "株式会社パネイル",
    "url": "https://corp.panair.jp/",
    "address": "東京都千代田区丸の内 1-9-2 グラントウキョウ サウスタワー 17F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/398",
    "name": "OLTA株式会社",
    "url": "https://www.olta.co.jp/",
    "address": "東京都港区南青山1-15-41 QCcube南青山115ビル 3F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/324",
    "name": "ENECHANGE株式会社",
    "url": "http://enechange.co.jp/",
    "address": "東京都千代田区大手町2-6-2日本ビル3F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/248",
    "name": "アクセルマーク株式会社",
    "url": "https://www.axelmark.co.jp/",
    "address": "東京都中野区本町1-32-2 ハーモニータワー",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/204",
    "name": "株式会社Donuts",
    "url": "http://www.donuts.ne.jp/",
    "address": "東京都渋谷区代々木2-2-1 小田急サザンタワー8F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/174",
    "name": "株式会社Timers",
    "url": "http://timers-inc.com/",
    "address": "東京都渋谷区恵比寿南一丁目1番9号 岩徳ビル4階",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/126",
    "name": "株式会社マネーフォワード",
    "url": "http://corp.moneyforward.com/",
    "address": "東京都港区芝浦三丁目1番 21 号 msb Tamachi 田町ステーションタワーS 21 階",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/116",
    "name": "株式会社サイバーエージェント(メディア統括本部)",
    "url": "https://www.cyberagent.co.jp/",
    "address": "東京都渋谷区円山町19番1号 渋谷プライムプラザ12F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/98",
    "name": "スタディプラス株式会社",
    "url": "https://info.studyplus.jp",
    "address": "東京都千代田区神田駿河台2-5-12 NMF駿河台ビル4F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/44",
    "name": "株式会社ココナラ",
    "url": "http://coconala.co.jp/",
    "address": "東京都品川区西五反田8-1-5 五反田光和ビル9F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/40",
    "name": "株式会社Aiming",
    "url": "http://aiming-inc.com/ja/",
    "address": "〒151-0053 東京都渋谷区代々木2-1-1 新宿マインズタワー9F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/34",
    "name": "株式会社Speee",
    "url": "http://speee.jp/",
    "address": "東京都港区六本木4-1-4 黒崎ビル5階",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/22",
    "name": "BASE株式会社",
    "url": "https://binc.jp/",
    "address": "東京都港区六本木三丁目2番1号 住友不動産六本木グランドタワー 37F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/6",
    "name": "株式会社ディー・エヌ・エー",
    "url": "http://dena.com/",
    "address": "東京都渋谷区渋谷2-21-1 渋谷ヒカリエ",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/1034",
    "name": "株式会社Sun Asterisk",
    "url": "https://sun-asterisk.com/",
    "address": "東京都千代田区神田紺屋町15番地グランファースト神田紺屋町9階",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/1022",
    "name": "ココネ株式会社",
    "url": "https://www.cocone.co.jp/",
    "address": "東京都港区六本木3-2-1 住友不動産六本木グランドタワー42階",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/1004",
    "name": "アルファアーキテクト株式会社",
    "url": "https://alpha-architect.co.jp/",
    "address": "東京都渋谷区恵比寿南1-24-2 EBISU FORT1F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/998",
    "name": "スマートシッター株式会社",
    "url": "https://smartsitter.jp",
    "address": "東京都渋谷区広尾5-6-6 広尾プラザ5階",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/992",
    "name": "レッドフォックス株式会社",
    "url": "https://www.redfox.co.jp",
    "address": "東京都千代田区丸の内3丁目2番3号 丸の内二重橋ビル21階",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/986",
    "name": "株式会社ActEvolve",
    "url": "https://vark.co.jp/",
    "address": "東京都新宿区西新宿4-34-7住友不動産西新宿ビル5号館1階",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/983",
    "name": "株式会社コンベックス",
    "url": "https://comvex.co.jp/",
    "address": "東京都渋谷区渋谷2-15-1 渋谷クロスタワー15F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/980",
    "name": "株式会社TOBE",
    "url": "https://tobe.tokyo/",
    "address": "東京都港区高輪3-23-17",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/977",
    "name": "株式会社RevComm",
    "url": "https://www.revcomm.co.jp/",
    "address": "東京都渋谷区渋谷2-6-11",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/974",
    "name": "テテマーチ株式会社",
    "url": "https://tetemarche.co.jp/",
    "address": "東京都品川区西五反田3-12-14 西五反田プレイス2F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/965",
    "name": "株式会社Catallaxy",
    "url": "https://catallaxy.me",
    "address": "東京都港区六本木1-4-5 アークヒルズサウスタワー16F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/962",
    "name": "A1A株式会社",
    "url": "https://www.a1a.co.jp/",
    "address": "東京都千代田区鍛治町2-5-4 CREA神田ビル3F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/941",
    "name": "株式会社ディールコネクト",
    "url": "https://deal-connect.co.jp",
    "address": "東京都中央区銀座7-13-6",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/938",
    "name": "キャディ株式会社",
    "url": "https://corp.caddi.jp/",
    "address": "東京都墨田区東駒形 2-22-9",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/905",
    "name": "ユニファ株式会社",
    "url": "https://unifa-e.com/",
    "address": "東京都台東区東上野1-28-9 キクヤビル2F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/902",
    "name": "株式会社ウェリコ",
    "url": "https://welyco.com/",
    "address": "東京都千代田区丸の内1-6-6 日本生命丸の内ビル22F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/878",
    "name": "KLab株式会社",
    "url": "https://www.klab.com/jp/",
    "address": "東京都港区六本木6-10-1 六本木ヒルズ森タワー",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/866",
    "name": "株式会社ビットキー",
    "url": "https://bitkey.co.jp/",
    "address": "東京都中央区京橋3−1−1 東京スクエアガーデン14階",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/860",
    "name": "株式会社ジョリーグッド",
    "url": "https://www.jollygood.co.jp/",
    "address": "東京都中央区日本橋堀留町1-8-11 人形町スクエア 3F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/845",
    "name": "株式会社ZIZAI",
    "url": "https://zizai.co.jp/",
    "address": "東京都渋谷区渋谷2-7-5 EDGE渋谷2丁目ビル 3F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/788",
    "name": "株式会社オズビジョン",
    "url": "http://www.oz-vision.co.jp/",
    "address": "東京都港区新橋6-19-13 WeWork 4階",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/782",
    "name": "株式会社hokan",
    "url": "https://www.corp.hkn.jp/",
    "address": "東京都千代田区大手町1-6-1大手町ビル4階FINOLAB",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/758",
    "name": "株式会社HRBrain",
    "url": "https://www.hrbrain.jp/",
    "address": "〒106-0031 東京都港区西麻布3-2-12西麻布ソニックビル6階",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/749",
    "name": "株式会社クイック",
    "url": "https://919.jp/",
    "address": "東京都港区赤坂2-11-7 ATT新館",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/746",
    "name": "株式会社フィノバレー",
    "url": "https://finnovalley.jp/",
    "address": "東京都港区麻布台1-11-9 BPRプレイス神谷町9F/10F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/743",
    "name": "株式会社アイリッジ",
    "url": "https://iridge.jp/",
    "address": "東京都港区麻布台1-11-9 BPRプレイス神谷町9F/10F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/737",
    "name": "セーフィー株式会社",
    "url": "https://safie.link/",
    "address": "東京都品川区西五反田2-29-5 日幸五反田ビル6F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/734",
    "name": "株式会社フォトシンス",
    "url": "https://photosynth.co.jp/",
    "address": "東京都港区芝5-29-11 G-BASE田町15階",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/728",
    "name": "株式会社フォーデジット",
    "url": "https://www.4digit.jp/",
    "address": "東京都港区赤坂8-5-32 田中駒ビル2F/3F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/710",
    "name": "株式会社メルカリ",
    "url": "https://www.mercari.com/jp/",
    "address": "東京都港区六本木6-10-1六本木ヒルズ森タワー",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/707",
    "name": "株式会社マクロミル",
    "url": "https://www.macromill.com/",
    "address": "東京都港区港南2-16-1 品川イーストワンタワー 11F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/650",
    "name": "スマートキャンプ株式会社",
    "url": "https://smartcamp.asia/",
    "address": "東京都港区芝5-33-7 徳栄ビル本館7階",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/647",
    "name": "株式会社ZOZOテクノロジーズ",
    "url": "https://tech.zozo.com/",
    "address": "東京都渋谷区神宮前5丁目52-2 青山オーバル・ビル 3F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/596",
    "name": "株式会社ポケラボ",
    "url": "http://pokelabo.co.jp/",
    "address": "東京都港区六本木6-10-1 六本木ヒルズ森タワー",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/590",
    "name": "株式会社スマイループス",
    "url": "http://smiloops.com/",
    "address": "東京都港区三田1-2-17 MSビル 4F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/584",
    "name": "株式会社ビービット",
    "url": "http://www.bebit.co.jp/",
    "address": "東京都千代田区九段北4-2-1 市ヶ谷東急ビル7階",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/563",
    "name": "株式会社エブリー",
    "url": "https://corp.every.tv/",
    "address": "東京都港区 六本木3-2-1 住友不動産六本木グランドタワー38階",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/521",
    "name": "株式会社アラン・プロダクツ",
    "url": "http://alan-products.co.jp/",
    "address": "〒107-0061 東京都港区北青山3-3-11 ルネ青山ビル7F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/506",
    "name": "株式会社POL",
    "url": "https://pol.co.jp",
    "address": "〒103-0011 東京都中央区日本橋大伝馬町1-7 日本橋ノースプレイス3F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/497",
    "name": "株式会社ネストエッグ",
    "url": "https://finbee.jp/",
    "address": "東京都中央区日本橋人形町3-4-7 三勝ビル3階",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/494",
    "name": "D2Cグループ",
    "url": "http://www.d2c.co.jp/",
    "address": "東京都中央区銀座6-18-2 野村不動産銀座ビル",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/473",
    "name": "株式会社チュートリアル",
    "url": "https://tutorial.co.jp/",
    "address": "東京都千代田区麹町1-4-4 2F LIFULL HUB内",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/437",
    "name": "エイチームグループ",
    "url": "http://www.a-tm.co.jp/recruit",
    "address": "愛知県名古屋市中村区名駅三丁目28番12号 大名古屋ビルヂング32F",
    "prefectures": "愛知県"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/410",
    "name": "株式会社10ANTZ",
    "url": "https://10antz.co.jp/",
    "address": "東京都渋谷区渋谷3-12-18 渋谷南東急ビル4階",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/407",
    "name": "株式会社トクバイ",
    "url": "https://corp.tokubai.co.jp/",
    "address": "東京都渋谷区渋谷3-3-2 渋谷MKビル4F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/404",
    "name": "株式会社Gunosy",
    "url": "http://gunosy.co.jp/company/",
    "address": "東京都港区六本木6-10-1六本木ヒルズ森タワー",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/395",
    "name": "株式会社FOLIO",
    "url": "https://corp.folio-sec.com/",
    "address": "東京都千代田区一番町16-1 共同ビル一番町4階",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/371",
    "name": "株式会社アイスタイル",
    "url": "http://www.istyle.co.jp/",
    "address": "東京都港区赤坂一丁目 12 番 32 号 アーク森ビル 34 階",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/365",
    "name": "株式会社サイキンソー",
    "url": "https://cykinso.co.jp",
    "address": "東京都渋谷区渋谷2丁目6-6 Good Morning Building 301号室",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/330",
    "name": "株式会社ZEALS",
    "url": "http://zeals.co.jp",
    "address": "品川区西五反田1-25-1 KANOビル4F",
    "prefectures": ""
  },
  {
    "draftUrl": "https://job-draft.jp/companies/314",
    "name": "ラクスル株式会社",
    "url": "https://recruit.raksul.com/",
    "address": "東京都品川区上大崎2-24-9 アイケイビル1F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/256",
    "name": "株式会社LCL",
    "url": "https://www.lclco.com/",
    "address": "東京都中央区 晴海1−8−10 晴海アイランドトリトンスクエア オフィスタワーX棟40階",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/236",
    "name": "株式会社フロムスクラッチ",
    "url": "https://f-scratch.co.jp/",
    "address": "東京都新宿区西新宿7-20-1 住友不動産西新宿ビル26階)",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/232",
    "name": "株式会社ワークスアプリケーションズ",
    "url": "http://www.worksap.co.jp/",
    "address": "東京都港区赤坂1-12-32 アーク森ビル19階(総合受付21階)",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/218",
    "name": "株式会社Kyash",
    "url": "https://kyash.co",
    "address": "東京都港区南青山5-2-1 NBF ALLIANCE 201",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/212",
    "name": "株式会社ホワイトプラス",
    "url": "http://www.wh-plus.com/",
    "address": "東京都品川区西五反田7-20-9",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/188",
    "name": "株式会社ツクルバ",
    "url": "http://tsukuruba.com/",
    "address": "東京都目黒区上目黒1丁目1−5 第二育良ビル2F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/182",
    "name": "GMOアドパートナーズグループ",
    "url": "http://www.gmo-ap.jp/",
    "address": "東京都渋谷区道玄坂1丁目16番3号",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/138",
    "name": "株式会社プレイド",
    "url": "https://karte.io/",
    "address": "東京都中央区銀座6丁目10−1 GINZA SIX 10F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/84",
    "name": "ピクスタ株式会社",
    "url": "https://pixta.co.jp",
    "address": "東京都渋谷区渋谷二丁目12番19号 東建インターナショナルビル5階",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/82",
    "name": "弁護士ドットコム株式会社",
    "url": "https://corporate.bengo4.com/",
    "address": "東京都港区六本木4-1-4 黒崎ビル6階",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/52",
    "name": "ストリートアカデミー株式会社",
    "url": "www.street-academy.com",
    "address": "東京都渋谷区広尾1-10-5 テック広尾ビル4F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/38",
    "name": "株式会社アカツキ",
    "url": "http://aktsk.jp/",
    "address": "東京都品川区上大崎2丁目13-30 oak meguro 8F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/20",
    "name": "Retty株式会社",
    "url": "https://corp.retty.me/",
    "address": "東京都港区三田1丁目4番1号住友不動産麻布十番ビル3階",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/18",
    "name": "株式会社フリークアウト",
    "url": "https://www.fout.co.jp/freakout/",
    "address": "東京都港区六本木6-3-1 六本木ヒルズクロスポイント5F",
    "prefectures": "東京都"
  },
  {
    "draftUrl": "https://job-draft.jp/companies/10",
    "name": "株式会社ミクシィ",
    "url": "http://mixi.co.jp/",
    "address": "東京都渋谷区東1-2-20 住友不動産渋谷ファーストタワー7F)",
    "prefectures": "東京都"
  }
]


集計結果

jqを使って集計します。

cat output.txt | jq -r '[.[] | .prefectures] | group_by(.) | map({(.[0]): length}) | add '

{
  "": 1,
  "愛知県": 1,
  "東京都": 128
}

空の値は品川から始まる住所だったため、全130企業のうち129が東京でした。

コード

Puppeteerを使ってスクレイピングします。


コード
const puppeteer = require('puppeteer')
const url = require('url')
const path = require('path')

class Scrapper {
    init() {
        this.id = null
        this.page = null
        this.companys = []
        this.companyURLs = [] // 転職ドラフトの企業ページのアドレス
        if (this.browser) {
            this.browser.close()
        }
        this.browser = null
        this.eventNumber = null
    }

    async getJSON(number) {
        this.init()
        this.eventNumber = number
        await this.initBrowser()
        return await this.scraping()
    }

    async initBrowser() {
        // 表示しながらよりheadlessのほうが異常に遅かったので不要なリクエストのabortを追加で改善

        if (process.env.NODE_ENV === 'production') {
            this.browser = await puppeteer.launch({
                args: ['--no-sandbox', '--headless', '--disable-gpu', '-—disable-dev-tools'],
                dumpio: true,
                devtools: false,
            })
        } else {
            this.browser = await puppeteer.launch({ headless: true, })
        }

        this.page = await this.browser.newPage()
        await this.page.setDefaultNavigationTimeout(60 * 5 * 1000)
        // 不要なリクエストは中断して読み込みを高速化する https://qiita.com/unhurried/items/56ea099c895fa437b56e
        await this.page.setRequestInterception(true)
        this.page.on('request', interceptedRequest => {
            const reqUrl = url.parse(interceptedRequest.url())
            const reqExt = path.extname(reqUrl.pathname)
            const reqHost = reqUrl.hostname

            if (RegExp('\.(css|png|jpe?g|gif)', 'i').test(reqExt)) {
                interceptedRequest.abort()
            } else if (reqHost.endsWith('job-draft.jp') || reqHost.endsWith('ajax.googleapis.com')) {
                // console.log(interceptedRequest.url())
                interceptedRequest.continue()
            } else {
                interceptedRequest.abort()
            }
        })
    }

    async scraping() {
        const URL = this.getJobDraftURL()
        await this.page.goto(URL)

        do {

            this.companyURLs = this.companyURLs.concat(await this.getCompanyURLs())

            await this.page.waitFor(1000) // 負荷調整のためのsleep
        } while (await this.gotoNextPage())

        await this.getCompanyinfo()

        return this.companys
    }

    getJobDraftURL() {
        return 'https://job-draft.jp/festivals/' + this.eventNumber + '/companies'
    }

    async gotoNextPage() {
        const selector = '.next > a'
        // evalは存在しないとErrorになるので$で事前にnullチェックする
        if (!await this.page.$(selector)) {
            return false
        }
        if (process.env.NODE_ENV === 'production') {
            return Promise.all([
                this.page.evaluate(selector => { document.querySelector(selector).click() }, selector),
                this.page.waitForNavigation(),
            ])
        } else {
            return Promise.all([
                this.page.click(selector),
                this.page.waitForNavigation(),
            ])
        }
    }

    async getCompanyURLs() {
        const selector = 'tbody > tr > td:nth-child(2) > a' // おおざっぱに二列目を a指定なのでおそらくリンクでない(退会済み?)企業は無視できるはず
        return await this.page.$$eval(selector, links => links.map(link => link.href))
    }

    async getCompanyinfo() {

        const nameSelector = 'h1.p-profile-canopy__name'
        const tableSelector = 'div.p-aside-row__body > table > tbody' // イマイチ
        const addressSlector = ' tr:nth-child(8) > td'
        const companyHomePageSelector = ' tr:nth-child(9) > td'

        for (const companyUrl of this.companyURLs) {
            await this.page.goto(companyUrl)
            const table = await this.page.$(tableSelector)
            const  info = {
                draftUrl  : companyUrl,
                name : await this.page.$eval(nameSelector, h1 => h1.textContent),
                url: await table.$eval(companyHomePageSelector, td => td.textContent),
                address : await table.$eval(addressSlector, td => td.textContent),
            }

            // 都道府県の取得 https://qiita.com/kajitack/items/d457fb4a811ddc53952a

            // https://job-draft.jp/companies/330 とか都道府県省略パターンも見つかったので、一度変数に入れてチェック
            // array?[num] 的な機能なかったかな?
            const prefectures = info.address.match(/.{2}[都道府県]|.{3}県/)
            info.prefectures = prefectures ? prefectures[0] : ''

            this.companys.push(info)
            await this.page.waitFor(1000) // 負荷調整のためのsleep
        }
    }
}

(async () => {
    const result = await (new Scrapper).getJSON(19)
    console.log(result)
    console.log(JSON.stringify(result))
})()


実行環境

  • Ubuntu 18
  • Node.js v11.14.0

ライブラリは

  • Puppeteer 1.17.0

のみ。

package.json
{
  "name": "job-draft",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "puppeteer": "^1.17.0"
  }
}

気をつけたいこと

そもそも関東重視

転職ドラフトの希望勤務地の候補は

  • 埼玉県
  • 千葉県
  • 東京都
  • 神奈川県
  • 愛知県
  • 京都府
  • 大阪府
  • 兵庫県
  • 福岡県
  • その他地域
  • リモート勤務

となっているので、関東以外で転職したい方はそもそもあまり利用しないかもしれません。

住所 ≠ 勤務地

地方オフィスでの勤務やリモートワークが出来る環境もあるので、本拠地でしか働けないというわけではありません。
指名にはそれぞれ勤務地の設定項があるので、本当の勤務地分布を知りたい場合は、それを集計するといいでしょう。

総指名数や重複排除などが面倒だったため
しかし勤務地は実際に指名しなければ観測できないこともあり今回は雰囲気重視で企業の住所にしました。
住所が同じなら本当に最低限、見てもらえる可能性がある企業、といえるのではないか。と考えてましたが、役立ちませんでした。

勤務地含むオファー情報を知りたい方はこちらを
転職ドラフトでどの時間帯に指名が多いのか調べてみた - Qiita

希望勤務地 ≠ 指名勤務地?

指名経験がぜんぜんないので分かりませんが、希望していない勤務地への指名があるかもしれません。
そういう指名が普通に行われているのならば、希望勤務地にかかわらず転職ドラフトで年収を知りたいという要望は達成できるんじゃないかなと思います。
ただ、前述のとおり希望勤務地が合わない方はフィルターする。という企業例はあるようです。

指名 ≠ 転職を前提とした指名?

転職ドラフトには「転職意欲」という設定項目があるので、
転職のためではなく価値を測る目的でそう宣言してあれば、
希望勤務地が合わない企業も査定して指名をしてくれるかもしれません。

東京・愛知以外の企業もあるよ

今回は結果的に2県しかでませんでしたが、別の都道府県の企業があることも1、2件は知っているので、0ではないことは念のため記しておきます。

参考


コードは自作の

から改変


個人備忘録として、転職ドラフトからQiitaへのアクセスはGoogleアナリティクスでみると8でした。
アウトプットどうこう以前の問題ですね。。。

おまけ

累計企業版

参加企業一覧|転職ドラフト - ITエンジニアを競争入札

ちらほらリンクになってない企業もありまして、おそらく除外できた全321件が対象です。

{
  "": 40,
  " 京都": 1,
  "兵庫県": 1,
  "北海道": 1,
  "大阪府": 1,
  "愛知県": 2,
  "東京都": 274,
  "神奈川県": 1
}

あっ…

に、任天堂さんとかは京都市だから引っかかってないだけですし…(最近参加してませんけど)
https://job-draft.jp/companies/389

では都道府県が無い住所(未記入ではない)の実態は40件のうちどうかというと、

(notがややこしい)selectで

cat all.txt | jq -r '.[] | select( (.["prefectures"] == "") and ((.["address"] == "") | not) ) '

こう。

{
  "draftUrl": "https://job-draft.jp/companies/884",
  "name": "株式会社justInCase",
  "url": "https://justincase.jp/",
  "address": "千代田区大手町1-6-1Finolab",
  "prefectures": ""
}
{
  "draftUrl": "https://job-draft.jp/companies/389",
  "name": "任天堂株式会社",
  "url": "https://www.nintendo.co.jp/corporate/index.html",
  "address": "京都市南区上鳥羽鉾立町11番地1",
  "prefectures": ""
}
{
  "draftUrl": "https://job-draft.jp/companies/178",
  "name": "株式会社リクルートテクノロジーズ",
  "url": "http://recruit-tech.co.jp/",
  "address": "千代田区丸の内1-9-2 グラントウキョウサウスタワー",
  "prefectures": ""
}
{
  "draftUrl": "https://job-draft.jp/companies/330",
  "name": "株式会社ZEALS",
  "url": "http://zeals.co.jp",
  "address": "品川区西五反田1-25-1 KANOビル4F",
  "prefectures": ""
}
{
  "draftUrl": "https://job-draft.jp/companies/392",
  "name": "株式会社エイチーム引越し侍",
  "url": "http://hikkoshi.a-tm.co.jp/",
  "address": "名古屋市中村区名駅三丁目28番12号大名古屋ビルヂング32F",
  "prefectures": ""
}

京都と愛知がそれぞれ増えました。
残りの35件はおそらく住所未記入か取得漏れですね。

ただし、愛知のエイチームさんはグループと引越し侍で別登録されており、住所は一緒ですね。
活動もグループのほうだけになっているようです。2

https://job-draft.jp/companies/392
https://job-draft.jp/companies/437

累計では緩和されると思っていたのですが、思っていた以上に東京一色でした。

特に福岡が近年盛り上がっている印象があり、勤務地候補にもあったのでちらほらあるのかと思っていましたが、本拠地を置いている企業は0件でしたね。ビックリ。オフィスが増えているのかしらん。

希望勤務地に東京を指定していない方のお話が気になりますね。

(余談で、企業ページのIDは1000越えしているものもあるので、企業の実態は1000社以上あり、何か数え間違いをしているのかもしれません)

生データ

長いので非整形


第19回参加企業
[{"draftUrl":"https://job-draft.jp/companies/983","name":"株式会社コンベックス","url":"https://comvex.co.jp/","address":"東京都渋谷区渋谷2-15-1 渋谷クロスタワー15F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/884","name":"株式会社justInCase","url":"https://justincase.jp/","address":"千代田区大手町1-6-1Finolab","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/686","name":"GMOグローバルサイン株式会社","url":"https://jp.globalsign.com/cominfo/","address":"東京都渋谷区桜丘町26番1号 セルリアンタワー10階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/362","name":"株式会社日本医療データセンター","url":"https://www.jmdc.co.jp/","address":"東京都港区芝大門2-5-5 住友芝大門ビル12階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/168","name":"クックパッド株式会社","url":"http://cookpad.com/","address":"","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/389","name":"任天堂株式会社","url":"https://www.nintendo.co.jp/corporate/index.html","address":"京都市南区上鳥羽鉾立町11番地1","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/692","name":"株式会社マクアケ","url":"http://www.makuake.co.jp/","address":"東京都渋谷区道玄坂1-12-1 渋谷マークシティ13階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/377","name":"スマートニュース株式会社","url":"http://about.smartnews.com/ja/","address":"〒150-0001 東京都渋谷区神宮前6-25-16 いちご神宮前ビル 3F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/196","name":"株式会社bitFlyer","url":"https://bitflyer.jp/","address":"","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/680","name":"株式会社ALIS","url":"https://alismedia.jp/ja/","address":"東京都渋谷区神南1丁目20−7 川原ビル 6階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/497","name":"株式会社ネストエッグ","url":"https://finbee.jp/","address":"東京都中央区日本橋人形町3-4-7 三勝ビル3階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/48","name":"サイボウズ株式会社","url":"http://cybozu.co.jp/","address":"東京都中央区日本橋2-7-1 東京日本橋タワー 27階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/893","name":"LINE Growth Technology株式会社","url":"https://linecorp.com/ja/growth","address":"東京都新宿区新宿四丁目1番6号 JR新宿ミライナタワー","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/256","name":"株式会社LCL","url":"https://www.lclco.com/","address":"東京都中央区 晴海1−8−10 晴海アイランドトリトンスクエア オフィスタワーX棟40階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/710","name":"株式会社メルカリ","url":"https://www.mercari.com/jp/","address":"東京都港区六本木6-10-1六本木ヒルズ森タワー","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/84","name":"ピクスタ株式会社","url":"https://pixta.co.jp","address":"東京都渋谷区渋谷二丁目12番19号 東建インターナショナルビル5階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/641","name":"株式会社 xenodata lab.","url":"https://www.xenodata-lab.com/","address":"東京都渋谷区松濤1-29-1 渋谷クロスロードビル 5F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/302","name":"株式会社GIFMAGAZINE","url":"https://gifmagazine.co.jp/","address":"東京都杉並区天沼3-30-40フヨウハウス203号","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/716","name":"株式会社キーエンス","url":"https://www.keyence.co.jp","address":"大阪府大阪市東淀川区東中島1-3-14","prefectures":"大阪府"},{"draftUrl":"https://job-draft.jp/companies/560","name":"ノイン株式会社","url":"https://noin.tv","address":"東京都新宿区新宿1-9-1 第2タケビル4階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/12","name":"ヤフー株式会社","url":"http://www.yahoo.co.jp/","address":"東京都港区赤坂9-7-1 ミッドタウン・タワー)","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/74","name":"株式会社サイバーエージェント(アドテクスタジオ)","url":"https://www.cyberagent.co.jp/","address":"","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/104","name":"株式会社Kaizen Platform","url":"https://kaizenplatform.com/ja/","address":"〒108-0072 東京都港区白金1-27-6 白金高輪ステーションビル 10階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/250","name":"株式会社オウチーノ","url":"http://www.o-uccino.jp/","address":"東京都港区西新橋3-23-5 御成門郵船ビルディング","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/202","name":"株式会社gumi","url":"https://gu3.co.jp/","address":"","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/395","name":"株式会社FOLIO","url":"https://corp.folio-sec.com/","address":"東京都千代田区一番町16-1 共同ビル一番町4階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/890","name":"LINE株式会社","url":"https://linecorp.com/ja/","address":"東京都新宿区新宿四丁目1番6号 JR新宿ミライナタワー23階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/38","name":"株式会社アカツキ","url":"http://aktsk.jp/","address":"東京都品川区上大崎2丁目13-30 oak meguro 8F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/76","name":"DMM.com Group","url":"http://recruit.dmm.com/","address":"東京都港区六本木三丁目2番1号 住友不動産六本木グランドタワー24階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/986","name":"株式会社ActEvolve","url":"https://vark.co.jp/","address":"東京都新宿区西新宿4-34-7住友不動産西新宿ビル5号館1階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/698","name":"株式会社フリークアウト・ホールディングス","url":"","address":"","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/647","name":"株式会社ZOZOテクノロジーズ","url":"https://tech.zozo.com/","address":"東京都渋谷区神宮前5丁目52-2 青山オーバル・ビル 3F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/635","name":"株式会社スタディスト","url":"https://studist.jp","address":"東京都千代田区神田神保町3-2-3 Daiwa神保町3丁目ビル 3階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/449","name":"株式会社ジャパンベンチャーリサーチ","url":"https://www.uzabase.com/","address":"東京都港区六本木7-7-7 TRI-SEVEN ROPPONGI 13F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/440","name":"株式会社ハシラス","url":"https://hashilus.com/","address":"東京都千代田区有楽町1-7-1","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/316","name":"株式会社ジーニアス","url":"https://www.ge-nius.com","address":"東京都千代田区神田佐久間町3丁目23番地","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/286","name":"SideCI株式会社","url":"https://sideci.com/ja","address":"東京都渋谷区猿楽町2-13 メッツ代官山 5F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/272","name":"株式会社マナボ","url":"https://manabo.com/index.html","address":"東京都港区六本木7-18-18 住友不動産六本木通ビル","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/254","name":"株式会社お金のデザイン","url":"https://www.money-design.com/","address":"","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/206","name":"株式会社エアークローゼット","url":"http://corp.air-closet.com/","address":"","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/14","name":"株式会社イグニス","url":"http://1923.co.jp/","address":"","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/112","name":"SORABITO株式会社","url":"https://www.sorabito.com","address":"東京都中央区日本橋茅場町1丁目9番2号 稲村ビル8階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/8","name":"株式会社サイバーエージェント(ゲーム事業)","url":"https://www.cyberagent.co.jp","address":"東京都渋谷区円山町28番1号","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/54","name":"株式会社トレタ","url":"https://corp.toreta.in/","address":"東京都品川区西五反田7-22-17 TOCビル8F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/22","name":"BASE株式会社","url":"https://binc.jp/","address":"東京都港区六本木三丁目2番1号 住友不動産六本木グランドタワー 37F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/314","name":"ラクスル株式会社","url":"https://recruit.raksul.com/","address":"東京都品川区上大崎2-24-9 アイケイビル1F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/6","name":"株式会社ディー・エヌ・エー","url":"http://dena.com/","address":"東京都渋谷区渋谷2-21-1 渋谷ヒカリエ","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/728","name":"株式会社フォーデジット","url":"https://www.4digit.jp/","address":"東京都港区赤坂8-5-32 田中駒ビル2F/3F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/542","name":"株式会社AbemaTV","url":"https://abema.tv/","address":"東京都渋谷区円山町19-1 渋谷プライムプラザ12階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/482","name":"株式会社スマートドライブ","url":"https://smartdrive.co.jp/","address":"東京都港区新橋6-19-13 WeWork新橋3階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/62","name":"株式会社ニューズピックス","url":"https://newspicks.com/","address":"東京都港区六本木7-7-7 TRI-SEVEN ROPPONGI 13F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/26","name":"freee株式会社","url":"https://www.freee.co.jp/","address":"東京都品川区西五反田2-8-1","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/572","name":"株式会社MERY","url":"https://mery.co.jp/","address":"東京都千代田区神田神保町三丁目3番 神保町SFⅢ","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/178","name":"株式会社リクルートテクノロジーズ","url":"http://recruit-tech.co.jp/","address":"千代田区丸の内1-9-2 グラントウキョウサウスタワー","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/130","name":"株式会社ユーザベース","url":"https://www.uzabase.com/","address":"東京都港区六本木7-7-7  TRI-SEVEN ROPPONGI 13F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/419","name":"株式会社カオナビ","url":"https://corp.kaonavi.jp/","address":"東京都港区元赤坂1丁目2番7号 AKASAKA K-TOWER 5階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/344","name":"株式会社リンクバル","url":"http://linkbal.co.jp/","address":"","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/36","name":"株式会社リクルートライフスタイル","url":"http://engineer.recruit-lifestyle.co.jp/recruiting/","address":"東京都千代田区丸の内1-9-2 グラントウキョウサウスタワー","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/467","name":"株式会社タレンティオ","url":"https://www.talentio.co.jp/","address":"東京都港区六本木 6-3-1 六本木ヒルズクロスポイント 5F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/96","name":"株式会社CyberZ","url":"https://cyber-z.co.jp/","address":"","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/208","name":"株式会社ノハナ","url":"https://nohana.co.jp","address":"東京都渋谷区渋谷2-11-5 クロスオフィス渋谷メディオ6-C号室","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/116","name":"株式会社サイバーエージェント(メディア統括本部)","url":"https://www.cyberagent.co.jp/","address":"東京都渋谷区円山町19番1号 渋谷プライムプラザ12F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/875","name":"株式会社ビジプル","url":"https://www.bizpl.co.jp/","address":"東京都渋谷区恵比寿4-20-3","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/638","name":"株式会社ミラティブ","url":"https://www.mirrativ.co.jp/","address":"東京都渋谷区桜丘町31-14 岡三桜丘ビルSLACK SHIBUYA 402","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/545","name":"株式会社パネイル","url":"https://corp.panair.jp/","address":"東京都千代田区丸の内 1-9-2 グラントウキョウ サウスタワー 17F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/719","name":"BizteX株式会社","url":"http://www.biztex.co.jp/","address":"東京都港区北青山3-3-13 共和五番館2F-C","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/977","name":"株式会社RevComm","url":"https://www.revcomm.co.jp/","address":"東京都渋谷区渋谷2-6-11","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/701","name":"株式会社ZUU","url":"https://zuu.co.jp/","address":"〒153-0042 東京都目黒区青葉台3-6-28 住友不動産青葉台タワー9F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/536","name":"PAY株式会社","url":"https://pay.co.jp","address":"東京都港区六本木三丁目2番1号 住友不動産六本木グランドタワー 37F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/148","name":"株式会社Socket","url":"http://socket.co.jp","address":"東京都港区南青山5-4-35 たつむら青山ビル3F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/18","name":"株式会社フリークアウト","url":"https://www.fout.co.jp/freakout/","address":"東京都港区六本木6-3-1 六本木ヒルズクロスポイント5F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/338","name":"dely株式会社","url":"https://www.dely.jp/","address":"東京都品川区西五反田7-17-3五反田第二長岡ビル2階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/20","name":"Retty株式会社","url":"https://corp.retty.me/","address":"東京都港区三田1丁目4番1号住友不動産麻布十番ビル3階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/902","name":"株式会社ウェリコ","url":"https://welyco.com/","address":"東京都千代田区丸の内1-6-6 日本生命丸の内ビル22F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/34","name":"株式会社Speee","url":"http://speee.jp/","address":"東京都港区六本木4-1-4 黒崎ビル5階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/304","name":"Repro株式会社","url":"https://repro.io","address":"東京都新宿区西新宿6-15-1 ラ・トゥール新宿 504号室","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/16","name":"クルーズ株式会社","url":"http://crooz.co.jp/","address":"東京都港区六本木6-10-1 六本木ヒルズ森タワー38階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/404","name":"株式会社Gunosy","url":"http://gunosy.co.jp/company/","address":"東京都港区六本木6-10-1六本木ヒルズ森タワー","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/371","name":"株式会社アイスタイル","url":"http://www.istyle.co.jp/","address":"東京都港区赤坂一丁目 12 番 32 号 アーク森ビル 34 階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/126","name":"株式会社マネーフォワード","url":"http://corp.moneyforward.com/","address":"東京都港区芝浦三丁目1番 21 号 msb Tamachi 田町ステーションタワーS 21 階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/749","name":"株式会社クイック","url":"https://919.jp/","address":"東京都港区赤坂2-11-7 ATT新館","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/122","name":"ウンログ株式会社","url":"https://unlog.me","address":"","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/86","name":"ChatWork株式会社","url":"http://corp.chatwork.com/ja/","address":"東京都台東区松が谷 4-24-11-301","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/142","name":"株式会社Viibar","url":"http://viibar.com/","address":"東京都品川区上大崎2-13-17 目黒東急ビル5階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/386","name":"株式会社アプトポッド","url":"https://www.aptpod.co.jp/index.html","address":"東京都新宿区四谷4-3 四谷トーセイビル5F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/470","name":"株式会社FORCAS","url":"https://www.forcas.com","address":"東京都港区六本木7-7-7 TRI-SEVEN ROPPONGI 13F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/194","name":"株式会社アクセルスペース","url":"https://www.axelspace.com/","address":"","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/359","name":"ヘルスデータ・プラットフォーム株式会社","url":"https://healthdataplatform.co.jp","address":"東京都港区芝大門2-5−5 住友芝大門ビル11F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/320","name":"株式会社Nagisa","url":"https://nagisa-inc.jp/","address":"東京都目黒区青葉台4-7-7 青葉台ヒルズ10F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/70","name":"akippa株式会社","url":"https://www.akippa.com ","address":"東京都渋谷区渋谷2-1-11 郁文堂青山通りビル5F)","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/614","name":"株式会社LOB","url":"https://www.lob-inc.com/","address":"東京都世田谷区玉川1-14-1 楽天クリムゾンハウス","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/932","name":"パルスボッツ株式会社","url":"https://palsbots.net/","address":"東京都目黒区駒場2-8-10","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/4","name":"株式会社リブセンス","url":"https://www.livesense.co.jp/","address":"東京都品川区上大崎2丁目25番2号 新目黒東急ビル5F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/662","name":"株式会社リクルート","url":"https://www.recruit.co.jp/company/data/","address":"東京都千代田区丸の内1-9-2","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/306","name":"playground株式会社","url":"https://playground.live","address":"東京都渋谷区東2-27-10 TBCビル 6F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/136","name":"Qrio株式会社","url":"https://qrio.me/","address":"東京都渋谷区恵比寿西2-3-4 東新産業ビル3F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/10","name":"株式会社ミクシィ","url":"http://mixi.co.jp/","address":"東京都渋谷区東1-2-20 住友不動産渋谷ファーストタワー7F)","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/174","name":"株式会社Timers","url":"http://timers-inc.com/","address":"東京都渋谷区恵比寿南一丁目1番9号 岩徳ビル4階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/184","name":"株式会社メタップス","url":"http://www.metaps.com/","address":"東京都新宿区 西新宿6丁目8-1 住友不動産新宿オークタワー30 階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/128","name":"株式会社フィードフォース","url":"https://www.feedforce.jp/","address":"東京都文京区湯島3-19-11 湯島ファーストビル5F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/64","name":"株式会社GameWith","url":"https://gamewith.co.jp/\t\t","address":"","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/24","name":"Sansan株式会社","url":"http://jp.corp-sansan.com/","address":"東京都渋谷区神宮前5-52-2 青山オーバルビル13階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/764","name":"株式会社ティアフォー","url":"https://www.tier4.jp/","address":"愛知県名古屋市中村区名駅1-1-3 名古屋大学オープンイノベーション拠点内","prefectures":"愛知県"},{"draftUrl":"https://job-draft.jp/companies/335","name":"株式会社LIFULL","url":"http://lifull.com/","address":"東京都千代田区麹町1-4-4","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/563","name":"株式会社エブリー","url":"https://corp.every.tv/","address":"東京都港区 六本木3-2-1 住友不動産六本木グランドタワー38階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/725","name":"Resily株式会社","url":"https://resily.com","address":"東京都渋谷区神宮前6-17-15 落合原宿ビル10階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/66","name":"株式会社クラウドワークス","url":"https://crowdworks.co.jp/","address":"東京都渋谷区恵比寿4-20-3","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/1019","name":"クリエイティブサーベイ株式会社","url":"https://jp.creativesurvey.com/company/team/","address":"東京都港区南青山2-2-8 DFビルディング9階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/929","name":"株式会社テクロコ ","url":"https://www.techloco.co.jp/","address":"東京都千代田区神田駿河台三丁目4番地 龍名館本店ビル4階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/500","name":"株式会社エス・エム・エス","url":"https://www.bm-sms.co.jp/","address":"東京都港区芝公園2-11-1","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/326","name":"ライフイズテック株式会社","url":"https://lifeistech.co.jp/","address":"東京都港区南麻布2-12-3 南麻布ビル1F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/274","name":"Fringe81株式会社","url":"http://www.fringe81.com/","address":"東京都港区六本木3-2-1 住友不動産六本木グランドタワー 43階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/176","name":"GMOリサーチ株式会社","url":"https://gmo-research.jp/","address":"東京都渋谷区桜丘町26番1号 セルリアンタワー","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/98","name":"スタディプラス株式会社","url":"https://info.studyplus.jp","address":"東京都千代田区神田駿河台2-5-12 NMF駿河台ビル4F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/218","name":"株式会社Kyash","url":"https://kyash.co","address":"東京都港区南青山5-2-1 NBF ALLIANCE 201","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/150","name":"Supership株式会社","url":"https://supership.jp/","address":"東京都港区南青山5-4-35 たつむら青山ビル","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/138","name":"株式会社プレイド","url":"https://karte.io/","address":"東京都中央区銀座6丁目10−1 GINZA SIX 10F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/244","name":"vivit株式会社","url":"https://vivit.co.jp/","address":"東京都目黒区下目黒1-1-11 目黒東洋ビル4階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/584","name":"株式会社ビービット","url":"http://www.bebit.co.jp/","address":"東京都千代田区九段北4-2-1 市ヶ谷東急ビル7階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/455","name":"笑屋株式会社","url":"https://syoya.com/","address":"東京都千代田区神田神保町2-12-3 安富ビル","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/548","name":"コインチェック株式会社","url":"http://corporate.coincheck.com","address":"東京都渋谷区渋谷3-28-13 渋谷新南口ビル3F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/416","name":"株式会社SmartHR","url":"https://smarthr.co.jp/","address":"東京都渋谷区千駄ヶ谷3-13-7 原宿OMビル2F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/368","name":"アクトインディ株式会社","url":"https://iko-yo.net/","address":"東京都品川区西五反田1-27-2 ヒューリック五反田ビル8階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/845","name":"株式会社ZIZAI","url":"https://zizai.co.jp/","address":"東京都渋谷区渋谷2-7-5 EDGE渋谷2丁目ビル 3F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/264","name":"株式会社ヤプリ","url":"http://yappli.co.jp","address":"東京都港区赤坂2-14-27 国際新赤坂ビル 東館19階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/232","name":"株式会社ワークスアプリケーションズ","url":"http://www.worksap.co.jp/","address":"東京都港区赤坂1-12-32 アーク森ビル19階(総合受付21階)","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/110","name":"株式会社ラクーン","url":"http://www.raccoon.ne.jp/","address":"","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/623","name":"株式会社エウレカ","url":"https://eure.jp/","address":"東京都港区三田1-4-1 住友不動産麻布十番ビル4階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/124","name":"株式会社ビザスク","url":"http://visasq.co.jp/","address":"東京都目黒区青葉台4-7-7 住友不動産青葉台ヒルズ9F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/914","name":"UUUM株式会社","url":"https://www.uuum.co.jp","address":"東京都港区六本木 6-10-1 六本木ヒルズ森タワー 37階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/854","name":"株式会社リクポ","url":"https://requpo.jp","address":"東京都渋谷区神宮前5ー17ー9","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/120","name":"株式会社Lang-8","url":"https://lang-8.jp","address":"","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/60","name":"株式会社Finc Technologies","url":"https://company.finc.com/","address":"東京都千代田区有楽町1丁目12-1 新有楽町ビル5F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/398","name":"OLTA株式会社","url":"https://www.olta.co.jp/","address":"東京都港区南青山1-15-41 QCcube南青山115ビル 3F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/296","name":"株式会社VOYAGE GROUP","url":"https://voyagegroup.com","address":"東京都渋谷区神泉町8-16 渋谷ファーストプレイス8F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/383","name":"ルームクリップ株式会社","url":"https://corp.roomclip.jp/","address":"東京都渋谷区千駄ヶ谷1-28-1 佳秀ビル2F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/226","name":"株式会社ニコリー","url":"https://corp.nicoly.jp/","address":"東京都品川区西五反田1-21-8 KSS五反田ビル6F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/653","name":"株式会社COUNTERWORKS","url":"http://www.counterworks.jp/","address":"東京都目黒区下目黒2-19-6 F&Tビル2F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/242","name":"Wovn Technologies 株式会社","url":"https://wovn.io/","address":"東京都港区三田4-1-27 FBR三田ビル8階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/40","name":"株式会社Aiming","url":"http://aiming-inc.com/ja/","address":"〒151-0053 東京都渋谷区代々木2-1-1 新宿マインズタワー9F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/140","name":"イタンジ株式会社","url":"http://itandi.co.jp/","address":"東京都港区六本木3-2-1 住友不動産六本木グランドタワー 40 階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/106","name":"株式会社Loco Partners","url":"http://loco-partners.com/","address":"東京都港区東新橋2-14-1 コモディオ汐留4F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/863","name":"株式会社MyRefer","url":"https://myrefer.co.jp/vision/","address":"〒103-0016 東京都中央区日本橋小網町12-7 日本橋小網ビル4F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/530","name":"株式会社MICIN","url":"https://micin.jp/","address":"東京都千代田区大手町2-6-2 日本ビル13階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/280","name":"株式会社ネットネイティブ","url":"https://net-native.net/","address":"東京都品川区上大崎3-14-12井雅ビル5F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/162","name":"株式会社QualiArts","url":"https://qualiarts.jp/","address":"東京都渋谷区南平台町16-28 グラスシティ渋谷2階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/46","name":"株式会社コロプラ","url":"http://colopl.co.jp/","address":"東京都渋谷区恵比寿4−20−3 恵比寿ガーデンプレイスタワー11階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/154","name":"コネヒト株式会社","url":"http://connehito.com/","address":"東京都港区南麻布3-20-1 Daiwa麻布テラス5階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/158","name":"株式会社レコチョク","url":"http://recochoku.jp/corporate/index.html","address":"東京都渋谷区渋谷2-16-1","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/458","name":"株式会社ゲームエイト","url":"https://game8.co.jp","address":"東京都渋谷区東1-32-12 渋谷プロパティータワー7階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/134","name":"株式会社WACUL","url":"https://wacul.co.jp/","address":"東京都千代田区神田小川町3-26-8 2F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/593","name":"サイボウズスタートアップス株式会社","url":"https://cstap.com","address":"東京都品川区西五反田二丁目27番3号 A-PLACE 五反田 9階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/617","name":"株式会社ドワンゴ","url":"http://dwango.co.jp/","address":"東京都中央区銀座四丁目12番15号 歌舞伎座タワー","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/836","name":"Ubie株式会社","url":"https://ubie.life","address":"東京都中央区日本橋室町1-5-3福島ビル601号室","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/78","name":"株式会社サイバード","url":"http://www.cybird.co.jp/\t\t\t","address":"東京都渋谷区猿楽町10-1 TEL:03-6746-3129","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/926","name":"hachidori株式会社","url":"https://hachidoriinc.com/","address":"東京都千代田区神田錦町3-17 廣瀬ビル7F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/365","name":"株式会社サイキンソー","url":"https://cykinso.co.jp","address":"東京都渋谷区渋谷2丁目6-6 Good Morning Building 301号室","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/324","name":"ENECHANGE株式会社","url":"http://enechange.co.jp/","address":"東京都千代田区大手町2-6-2日本ビル3F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/713","name":"株式会社エビリー","url":"https://eviry.com/","address":"東京都渋谷区渋谷1-3-9 ヒューリック渋谷一丁目ビルB1","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/425","name":"株式会社AppBrew","url":"http://appbrew.io","address":"東京都文京区本郷1丁目11−6 東接本郷ビル4階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/599","name":"株式会社LIG","url":"https://liginc.co.jp/","address":"東京都台東区小島2-20-11 LIGビル","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/190","name":"楽天株式会社","url":"http://corp.rakuten.co.jp/","address":"東京都世田谷区玉川一丁目14番1号 楽天クリムゾンハウス(本社)","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/56","name":"株式会社ドリコム","url":"http://www.drecom.co.jp/","address":"東京都目黒区下目黒1-8-1 アルコタワー17階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/322","name":"株式会社オハコ","url":"https://ohako-inc.jp/5th/","address":"東京都渋谷区本町3-12-1 住友不動産西新宿ビル6号館8階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/182","name":"GMOアドパートナーズグループ","url":"http://www.gmo-ap.jp/","address":"東京都渋谷区道玄坂1丁目16番3号","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/887","name":"株式会社すむたす","url":"https://sumutasu.co.jp/","address":"東京都目黒区青葉台3-18-3 THE WORKS 403","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/872","name":"スタークス株式会社","url":"https://starx.co.jp/","address":"東京都品川区西五反田1-21-8 KSS五反田ビル5F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/782","name":"株式会社hokan","url":"https://www.corp.hkn.jp/","address":"東京都千代田区大手町1-6-1大手町ビル4階FINOLAB","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/632","name":"株式会社360Channel","url":"https://www.360ch.tv/","address":"東京都渋谷区恵比寿4-20-3 恵比寿ガーデンプレイス11F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/509","name":"株式会社Oneteam","url":"https://one-team.com/ja/","address":"東京都中央区銀座3-11-5 第2中山ビル5階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/328","name":"株式会社iCARE","url":"https://www.icare.jpn.com/","address":"東京都渋谷区円山町10-18マイキャッスル渋谷JP203","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/212","name":"株式会社ホワイトプラス","url":"http://www.wh-plus.com/","address":"東京都品川区西五反田7-20-9","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/90","name":"株式会社イトクロ","url":"http://www.itokuro.jp/","address":"東京都港区赤坂2-9-11 オリックス赤坂2丁目ビル6F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/82","name":"弁護士ドットコム株式会社","url":"https://corporate.bengo4.com/","address":"東京都港区六本木4-1-4 黒崎ビル6階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/833","name":"株式会社Azit","url":"https://azit.co.jp/","address":"東京都港区東新橋2-12-1 PMO東新橋 6F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/88","name":"ビルコム株式会社","url":"http://www.bil.jp/","address":"東京都港区六本木6-2-31","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/180","name":"株式会社IDOM(旧社名:株式会社ガリバーインターナショナル)","url":"http://221616.com/idom/","address":"東京都千代田区丸の内2-7-3 東京ビル25階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/44","name":"株式会社ココナラ","url":"http://coconala.co.jp/","address":"東京都品川区西五反田8-1-5 五反田光和ビル9F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/192","name":"appArray株式会社","url":"http://apparray.biz/","address":"東京都中央区日本橋茅場町2-8-7 茅場町サウスビルディング 4F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/731","name":"株式会社Showcase Gig","url":"https://www.showcase-gig.com/","address":"東京都港区南青山4-1-6 セブン南青山ビル4F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/210","name":"株式会社ぐるなび","url":"http://www.gnavi.co.jp/company/","address":"東京都千代田区有楽町1-2-2 東宝日比谷ビル6F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/30","name":"株式会社ペロリ","url":"http://peroli.jp/","address":"東京都渋谷区渋谷2-12-4 ネクストサイト渋谷ビル11F12F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/851","name":"MF KESSAI株式会社","url":"https://corp.mfkessai.co.jp/","address":"東京都千代田区大手町1−6−1 大手町ビル4階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/216","name":"株式会社gloops","url":"http://gloops.com/","address":"東京都港区六本木一丁目4番5号","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/204","name":"株式会社Donuts","url":"http://www.donuts.ne.jp/","address":"東京都渋谷区代々木2-2-1 小田急サザンタワー8F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/353","name":"ダイヤモンドメディア株式会社","url":"https://www.diamondmedia.co.jp/","address":"東京都港区南青山4-9-1 シンプル青山ビル1F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/752","name":"株式会社CAMPFIRE","url":"https://campfire.co.jp/","address":"〒150-0002 東京都渋谷区渋谷2丁目22-3 渋谷東口ビル 5F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/248","name":"アクセルマーク株式会社","url":"https://www.axelmark.co.jp/","address":"東京都中野区本町1-32-2 ハーモニータワー","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/839","name":"株式会社グロービス","url":"https://recruiting-tech.globis.co.jp/","address":"東京都千代田区二番町5-1住友不動産麹町ビル","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/659","name":"株式会社ユビレジ","url":"https://ubiregi.com/","address":"東京都 渋谷区 千駄ヶ谷 3-59-4 クエストコート原宿","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/443","name":"株式会社f4samurai","url":"http://www.f4samurai.jp/","address":"東京都千代田区外神田4-14-1 秋葉原UDX 14階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/144","name":"ソネット・メディア・ネットワークス株式会社","url":"http://www.so-netmedia.jp/","address":"","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/965","name":"株式会社Catallaxy","url":"https://catallaxy.me","address":"東京都港区六本木1-4-5 アークヒルズサウスタワー16F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/938","name":"キャディ株式会社","url":"https://corp.caddi.jp/","address":"東京都墨田区東駒形 2-22-9","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/650","name":"スマートキャンプ株式会社","url":"https://smartcamp.asia/","address":"東京都港区芝5-33-7 徳栄ビル本館7階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/312","name":"ファンプレックス株式会社","url":"https://funplex.co.jp/","address":"東京都港区三田一丁目4番1号 住友不動産麻布十番ビル5階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/330","name":"株式会社ZEALS","url":"http://zeals.co.jp","address":"品川区西五反田1-25-1 KANOビル4F","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/695","name":"株式会社デザインワン・ジャパン","url":"https://www.designone.jp/","address":"東京都新宿区西新宿7-5-25西新宿プライムスクエア8F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/974","name":"テテマーチ株式会社","url":"https://tetemarche.co.jp/","address":"東京都品川区西五反田3-12-14 西五反田プレイス2F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/737","name":"セーフィー株式会社","url":"https://safie.link/","address":"東京都品川区西五反田2-29-5 日幸五反田ビル6F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/626","name":"株式会社テクロス","url":"https://www.techcross.co.jp/","address":"〒600-8413 京都市下京区大政所町685  京都四条烏丸ビル2F","prefectures":" 京都"},{"draftUrl":"https://job-draft.jp/companies/566","name":"株式会社未来ボックス","url":"http://miraibox.jp/","address":"神奈川県横浜市中区海岸通4-17東信ビル","prefectures":"神奈川県"},{"draftUrl":"https://job-draft.jp/companies/220","name":"株式会社エンターモーション","url":"http://www.entermotion.jp","address":"東京都目黒区目黒1-24-12 オリックス目黒ビル 9F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/899","name":"株式会社SCOUTER","url":"https://corp.scouter.co.jp/","address":"東京都渋谷区宇田川町2−1 渋谷ホームズ1305号室","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/228","name":"株式会社マンションマーケット","url":"https://corp.mansion-market.com/","address":"東京都中央区銀座7丁目13-12サクセス銀座7ビル9F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/494","name":"D2Cグループ","url":"http://www.d2c.co.jp/","address":"東京都中央区銀座6-18-2 野村不動産銀座ビル","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/428","name":"株式会社PR TIMES","url":"http://prtimes.co.jp/","address":"東京都港区南青山2-27-25 ヒューリック南青山ビル3F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/42","name":"コイニー株式会社","url":"http://coiney.com/","address":"東京都渋谷区恵比寿一丁目20番18号 三富ビル新館6階)","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/629","name":"atama plus 株式会社","url":"https://www.atama.plus/","address":"東京都中央区日本橋堀留町1-8-12 さくら堀留ビル7F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/539","name":"株式会社Macbee Planet","url":"http://macbee-planet.com/","address":"東京都渋谷区渋谷3-11-11 IVYイーストビル5F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/848","name":"株式会社侍","url":"https://corp.sejuku.net/","address":"東京都目黒区大橋2丁目3番5号 Ohashi235 5F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/58","name":"株式会社ニジボックス","url":"http://nijibox.jp/","address":"東京都中央区勝どき1-13-1 イヌイビル・カチドキ4F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/608","name":"株式会社ファームノート","url":"https://farmnote.jp/","address":"北海道帯広市公園東町1-3-14","prefectures":"北海道"},{"draftUrl":"https://job-draft.jp/companies/590","name":"株式会社スマイループス","url":"http://smiloops.com/","address":"東京都港区三田1-2-17 MSビル 4F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/52","name":"ストリートアカデミー株式会社","url":"www.street-academy.com","address":"東京都渋谷区広尾1-10-5 テック広尾ビル4F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/100","name":"株式会社アイ・エム・ジェイ","url":"https://www.imjp.co.jp/","address":"東京都目黒区青葉台3-6-28","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/842","name":"株式会社フォアキャスト・コミュニケーションズ","url":"https://www.4cast.co.jp","address":"東京都港区東新橋一丁目1番21号 今朝ビル3階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/146","name":"株式会社コードタクト","url":"http://codetakt.com/","address":"東京都渋谷区代々木2-20-19新宿東洋ビル501号室","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/410","name":"株式会社10ANTZ","url":"https://10antz.co.jp/","address":"東京都渋谷区渋谷3-12-18 渋谷南東急ビル4階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/941","name":"株式会社ディールコネクト","url":"https://deal-connect.co.jp","address":"東京都中央区銀座7-13-6","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/596","name":"株式会社ポケラボ","url":"http://pokelabo.co.jp/","address":"東京都港区六本木6-10-1 六本木ヒルズ森タワー","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/506","name":"株式会社POL","url":"https://pol.co.jp","address":"〒103-0011 東京都中央区日本橋大伝馬町1-7 日本橋ノースプレイス3F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/294","name":"株式会社Amazia","url":"http://www.amazia.co.jp","address":"","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/268","name":"株式会社エムティーアイ","url":"http://www.mti.co.jp/","address":"〒163-1435 東京都新宿区西新宿3-20-2 東京オペラシティタワー35F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/266","name":"株式会社Liquid","url":"https://liquidinc.asia","address":"東京都千代田区大手町1-6-1","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/172","name":"株式会社ギフティ","url":"https://giftee.co.jp/","address":"東京都品川区上大崎4-5-37 山京目黒ビル205号室","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/32","name":"ナイル(旧:ヴォラーレ) 株式会社","url":"http://nyle.co.jp/","address":"東京都品川区東五反田1-24-2 東五反田1丁目ビル7F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/214","name":"メドピア株式会社","url":"https://medpeer.co.jp/","address":"東京都中央区銀座6-18-2 野村不動産銀座ビル11階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/917","name":"アライドアーキテクツ株式会社","url":"https://www.aainc.co.jp/","address":"東京都渋谷区恵比寿1-19-15 ウノサワ東急ビル 4階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/160","name":"株式会社Cygames","url":"https://www.cygames.co.jp/","address":"","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/860","name":"株式会社ジョリーグッド","url":"https://www.jollygood.co.jp/","address":"東京都中央区日本橋堀留町1-8-11 人形町スクエア 3F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/857","name":"株式会社ルーデル","url":"http://rudel.jp/","address":"東京都新宿区四谷1-22-5 WESTALL四谷ビル 1F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/707","name":"株式会社マクロミル","url":"https://www.macromill.com/","address":"東京都港区港南2-16-1 品川イーストワンタワー 11F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/188","name":"株式会社ツクルバ","url":"http://tsukuruba.com/","address":"東京都目黒区上目黒1丁目1−5 第二育良ビル2F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/437","name":"エイチームグループ","url":"http://www.a-tm.co.jp/recruit","address":"愛知県名古屋市中村区名駅三丁目28番12号 大名古屋ビルヂング32F","prefectures":"愛知県"},{"draftUrl":"https://job-draft.jp/companies/222","name":"株式会社クレジットエンジン","url":"https://www.creditengine.jp","address":"","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/527","name":"株式会社オクト","url":"http://88oct.co.jp/","address":"東京都千代田区神田紺屋町15 グランファースト神田紺屋町 6F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/224","name":"かっこ株式会社","url":"http://cacco.co.jp/","address":"東京都港区元赤坂1-5-31 新井ビル4F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/166","name":"バリュース株式会社","url":"http://valus.co.jp/","address":"東京都渋谷区恵比寿1-24-15 シエルブルー恵比寿EAST 7F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/962","name":"A1A株式会社","url":"https://www.a1a.co.jp/","address":"東京都千代田区鍛治町2-5-4 CREA神田ビル3F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/521","name":"株式会社アラン・プロダクツ","url":"http://alan-products.co.jp/","address":"〒107-0061 東京都港区北青山3-3-11 ルネ青山ビル7F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/236","name":"株式会社フロムスクラッチ","url":"https://f-scratch.co.jp/","address":"東京都新宿区西新宿7-20-1 住友不動産西新宿ビル26階)","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/743","name":"株式会社アイリッジ","url":"https://iridge.jp/","address":"東京都港区麻布台1-11-9 BPRプレイス神谷町9F/10F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/557","name":"株式会社マツリカ","url":"https://mazrica.com/","address":"東京都品川区東五反田5-28-9 五反田第三花谷ビル9F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/350","name":"カープライス株式会社","url":"https://www.carprice.co.jp/","address":"東京都杉並区上高井戸1-22-14","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/156","name":"バリューコマース株式会社","url":"https://www.valuecommerce.ne.jp/","address":"","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/881","name":"株式会社VALU","url":"https://valu.is/","address":"東京都渋谷区神宮前三丁目42番2号VORT外苑前3 3階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/947","name":"株式会社スイッチ・メディア・ラボ","url":"https://www.switch-m.com/","address":"東京都港区赤坂2-10-9 ラウンドクロス赤坂6F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/866","name":"株式会社ビットキー","url":"https://bitkey.co.jp/","address":"東京都中央区京橋3−1−1 東京スクエアガーデン14階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/824","name":"ユリシーズ株式会社","url":"http://www.ulysses-ltd.com/","address":"東京都千代田区内神田2-13-2 梶山ビル3F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/722","name":"Creww株式会社","url":"https://creww.in/","address":"東京都目黒区青葉台1-18-14 3F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/491","name":"株式会社チーム・ファクトリー","url":"https://www.teamfactory.co.jp/","address":"東京都港区 芝浦3-13-1 矢島ビル3階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/270","name":"ソルフレア株式会社","url":"https://solflare.co.jp/","address":"東京都千代田区神田神保町2-15 第一冨士ビル 8F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/644","name":"HiCustomer株式会社","url":"https://hicustomer.jp/","address":"東京都品川区東五反田4-10-9シャトレー五反田2階D号室","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/944","name":"テックタッチ株式会社","url":"https://techtouch.co.jp/","address":"東京都千代田霞ヶ関3-2-5 霞が関5階 31VENTURES霞が関","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/905","name":"ユニファ株式会社","url":"https://unifa-e.com/","address":"東京都台東区東上野1-28-9 キクヤビル2F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/164","name":"株式会社スタートアウツ","url":"http://startouts.co.jp","address":"","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/920","name":"rakumo株式会社","url":"https://corporate.rakumo.com/","address":"東京都千代田区麹町3-2","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/374","name":"株式会社コネクトム","url":"http://www.connectom.co.jp/index.html","address":"東京都千代田区四番町 6 番 東急番町ビル","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/332","name":"株式会社FOWD","url":"https://fowd.co.jp/","address":"東京都目黒区中目黒1-71-1 KN","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/827","name":"ビットバンク株式会社","url":"https://bitcoinbank.co.jp/","address":"東京都品川区西五反田7丁目20-9 KDX西五反田7F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/524","name":"株式会社ワンキャリア","url":"https://onecareer.co.jp","address":"東京都渋谷区道玄坂2-10-7 新大宗ビル1号館10階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/485","name":"株式会社ジークレスト","url":"https://www.gcrest.com","address":"東京都渋谷区南平台町16-28 グラスシティ渋谷","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/788","name":"株式会社オズビジョン","url":"http://www.oz-vision.co.jp/","address":"東京都港区新橋6-19-13 WeWork 4階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/1028","name":"株式会社キッズライン","url":"https://kidsline.me/corp/","address":"東京都港区六本木5-2-3  マガジンハウス六本木ビル7F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/1022","name":"ココネ株式会社","url":"https://www.cocone.co.jp/","address":"東京都港区六本木3-2-1 住友不動産六本木グランドタワー42階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/950","name":"株式会社スペースキー","url":"https://www.spacekey.co.jp/","address":"〒150-0043 東京都渋谷区道玄坂2-10-7 新大宗ビル2号館4階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/746","name":"株式会社フィノバレー","url":"https://finnovalley.jp/","address":"東京都港区麻布台1-11-9 BPRプレイス神谷町9F/10F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/758","name":"株式会社HRBrain","url":"https://www.hrbrain.jp/","address":"〒106-0031 東京都港区西麻布3-2-12西麻布ソニックビル6階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/401","name":"株式会社マーケティングアプリケーションズ","url":"https://mkt-apps.com/","address":"東京都新宿区新宿1-28-11 KOSUGIビル7F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/288","name":"株式会社マイベスト","url":"https://my-best.com/company","address":"東京都中央区","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/785","name":"株式会社Globee","url":"https://globeejapan.com","address":"東京都港区東麻布一丁目7番3号 第二渡邊ビル7F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/587","name":"株式会社Azoop","url":"https://azoop.co.jp/","address":"東京都世田谷区上馬2-25-4 フレックス三軒茶屋3F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/515","name":"株式会社Pathee","url":"https://corp.pathee.com/","address":"東京都品川区東五反田1-4-1 ハニー五反田第2ビル4F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/1031","name":"株式会社VRize","url":"https://vrize.io","address":"東京都渋谷区神宮前6-28-5 宮崎ビルA棟3F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/1025","name":"株式会社グリモア","url":"https://grimoire.co/","address":"東京都目黒区青葉台","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/1013","name":"株式会社フィナンシェ","url":"https://www.corp.financie.jp","address":"東京都渋谷区26-1  セルリアンタワー15F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/1004","name":"アルファアーキテクト株式会社","url":"https://alpha-architect.co.jp/","address":"東京都渋谷区恵比寿南1-24-2 EBISU FORT1F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/1001","name":"モノグサ株式会社","url":"https://monoxer.com/","address":"東京都千代田区麹町2-2-22 ACN半蔵門ビル4階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/971","name":"カクテルメイク株式会社","url":"https://cocktail-make.com/","address":"東京都渋谷区富ヶ谷2-21-10 セントラル富ヶ谷7F、福岡県福岡市博多区博多駅東 1-12-6 花村ビル7F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/911","name":"株式会社チャオ","url":"https://www.ciaoinc.jp/","address":"東京都港区浜松町1-18-16 住友浜松町ビル8階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/878","name":"KLab株式会社","url":"https://www.klab.com/jp/","address":"東京都港区六本木6-10-1 六本木ヒルズ森タワー","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/830","name":"株式会社クリプタクト","url":"https://www.cryptact.com/","address":"〒102-0093 東京都千代田区平河町2丁目5−3 Nagatacho GRID 5F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/755","name":"株式会社リクルートマーケティングパートナーズ","url":"http://www.recruit-mp.co.jp/","address":"東京都品川区上大崎2丁目13-30 oak meguro","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/734","name":"株式会社フォトシンス","url":"https://photosynth.co.jp/","address":"東京都港区芝5-29-11 G-BASE田町15階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/689","name":"株式会社オープンエイト","url":"https://open8.com/","address":"東京都渋谷区神宮前6-25-14 神宮前メディアスクエアビル6階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/464","name":"株式会社ジーニー","url":"https://geniee.co.jp","address":"東京都新宿区西新宿7-20-1 住友不動産西新宿ビル25F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/356","name":"株式会社ロジックロジック","url":"https://www.logiclogic.jp/index.html","address":"東京都渋谷区恵比寿南1-15-1 A-PLACE恵比寿南4階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/347","name":"株式会社Gogasha","url":"https://gogasha.co.jp/","address":"東京都新宿区新宿5-11-30 新宿第五葉山ビル3F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/318","name":"エコーズ株式会社","url":"https://www.echoes.co.jp","address":"","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/310","name":"株式会社メディアドゥ","url":"https://www.mediado.jp/about/about_company/","address":"","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/278","name":"ワイジェイFX株式会社","url":"https://www.yjfx.jp/","address":"","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/276","name":"株式会社みんなのウェディング","url":"http://www.mwed.co.jp/","address":"","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/246","name":"デジタルバンク株式会社","url":"http://corp-digitalbank.com/","address":"","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/198","name":"株式会社インフラトップ","url":"http://infratop.jp/","address":"","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/186","name":"株式会社ジモティー","url":"http://jmty.co.jp/","address":"","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/152","name":"株式会社SHIFT","url":"http://www.shiftinc.jp/","address":"","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/132","name":"株式会社スポットライト","url":"https://spotlig.ht/","address":"","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/114","name":"ソフトブレーン株式会社","url":"http://www.softbrain.co.jp/","address":"","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/108","name":"HEROZ株式会社","url":"http://heroz.co.jp/","address":"","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/72","name":"株式会社Schoo(スクー)","url":"https://schoo.jp/","address":"","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/1034","name":"株式会社Sun Asterisk","url":"https://sun-asterisk.com/","address":"東京都千代田区神田紺屋町15番地グランファースト神田紺屋町9階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/998","name":"スマートシッター株式会社","url":"https://smartsitter.jp","address":"東京都渋谷区広尾5-6-6 広尾プラザ5階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/992","name":"レッドフォックス株式会社","url":"https://www.redfox.co.jp","address":"東京都千代田区丸の内3丁目2番3号 丸の内二重橋ビル21階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/980","name":"株式会社TOBE","url":"https://tobe.tokyo/","address":"東京都港区高輪3-23-17","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/968","name":"AlpacaJapan株式会社","url":"http://www.alpaca.ai/ja/","address":"東京都千代田区内神田1-12-5 Nest-Lab北大手町 2階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/953","name":"株式会社laboro.AI","url":"https://laboro.ai/","address":"東京都中央区銀座8丁目11-1 GINZA GS BLD.2 3F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/935","name":"ディライトワークス株式会社","url":"https://www.delightworks.co.jp/","address":"東京都目黒区青葉台三丁目6番28号","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/869","name":"株式会社MonotaRO","url":"https://www.monotaro.com","address":"兵庫県尼崎市竹谷町2-183 リベル3F","prefectures":"兵庫県"},{"draftUrl":"https://job-draft.jp/companies/767","name":"ガンホー・オンライン・エンターテイメント株式会社","url":"https://www.gungho.co.jp/","address":"東京都千代田区丸の内1丁目11−1 パシフィックセンチュリープレイス丸の内21階","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/704","name":"UiPath株式会社","url":"","address":"","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/671","name":"株式会社ティンク","url":"https://tink.co.jp","address":"東京都港区六本木5-16-5 インペリアル六本木1号館 400号室","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/605","name":"Cansell株式会社","url":"https://cansell.jp","address":"東京都渋谷区恵比寿1-15-19 シルク恵比寿403","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/518","name":"株式会社マーケットプレイス","url":"http://www.marketplace.co.jp/","address":"東京都渋⾕区恵⽐寿⻄2-8-10 ORIX恵⽐寿⻄ビル6F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/473","name":"株式会社チュートリアル","url":"https://tutorial.co.jp/","address":"東京都千代田区麹町1-4-4 2F LIFULL HUB内","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/461","name":"株式会社ストライド","url":"","address":"東京都渋谷区桜丘町29-33","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/446","name":"バイクリメンツ株式会社","url":"https://lemuria-trade.com/about/","address":"東京都港区西麻布3-1-25 金谷ホテルマンション601","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/407","name":"株式会社トクバイ","url":"https://corp.tokubai.co.jp/","address":"東京都渋谷区渋谷3-3-2 渋谷MKビル4F","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/392","name":"株式会社エイチーム引越し侍","url":"http://hikkoshi.a-tm.co.jp/","address":"名古屋市中村区名駅三丁目28番12号大名古屋ビルヂング32F","prefectures":""},{"draftUrl":"https://job-draft.jp/companies/200","name":"JapanTaxi株式会社","url":"https://japantaxi.co.jp/","address":"東京都千代田区紀尾井町3-12","prefectures":"東京都"},{"draftUrl":"https://job-draft.jp/companies/170","name":"株式会社リッチメディア","url":"","address":"","prefectures":""}]



  1. 22時終わりだと思っていたらもう一時間ありましたが眠いしあまり動かないでしょうから 

  2. とは言うもののエイチームさんは東京・大阪・福岡にオフィスを持ってらっしゃるので、住所が当てにならない良い例ですね。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GoogleSpreadSheetをDBみたいにするAPIを作成しました。

GoogleSpreadSheetをDBみたいにしてみた。

背景

アプリのモックを作成するときに、サーバーサイドの開発環境が用意できなかったため、GASの勉強も兼ねて、GoogleSpreadSheetで代用してみました。

GoogleSpreadSheet内を検索

コメントで参照したときの補完機能も充実してたので書いてみました。

gssTableSearch.gs
/**
 * 全件検索した結果を取得します。
 * @param {string} sheetName シートの名称
 * @return {obj} 取得したデータ
 */ 
function getData(sheetName) {
  var sheet = SpreadsheetApp.getActive().getSheetByName(sheetName);
  var data = sheet.getDataRange().getValues(); // シートの中のデータを行単位で配列として入る
  var key = data.splice(0, 1)[0];  // 一番上の行をキー値として配列に格納する

  var returnData = data.map(function(row) {
    var obj = {};

    row.map(function(value, index) {

      // キー名を設定して、配列に格納する(連想配列にする)
      obj[String(key[index])] = String(value);

    });

    return obj;
  });

  return returnData;
}

GoogleSpreadSheetを更新する

追加、更新、削除をやってみます。
主キー重複のチェックとかは、面倒だったので作ってません。

gssTableUpdate.gs
/**
 * シート内の表形式に合わせた、一行を作成します。(2行以上のJSONの場合、2行目以降は無視します)
 * (最後にアンダーバーつけると外部参照できなくできなくなるみたい。便利!)
 * @param {string} sheetName シートの名称
 * @param {string} postJson JSON形式の文字列 
 */ 
function createRow_(sheetName, postJson) {
  var keys = sheetName.getDataRange().getValues()[0];
  var row = [];

  keys.map(function(key) {
    row.push(postJson[key]);
  });

  return row;
}

/**
 * データを追加します。
 * @param {string} sheetName シートの名称
 * @param {string} postJson JSON形式の文字列 
 */ 
function insert(sheetName, postJson) {
  var row = createRow_(sheetName, postJson);
  sheetName.appendRow(row);
}

/**
 * データを更新します。
 * A列を主キーとして扱います。
 * @param {string} sheetName シートの名称
 * @param {string} postJson JSON形式の文字列
 */ 
function update(sheetName, postJson) {
  var row = createRow_(sheetName, postJson);
  var data = sheetName.getDataRange().getValues();

  for (var i = 0; i < data.length; i++) {
    if (data[i][0] === row[0]) {
      sheetName.getRange(i + 1, 1, 1, data[i].length).setValues([row]);
    }
  }
}

/**
 * データを削除します。
 * A列を主キーとして扱います。
 * @param {string} sheetName シートの名称
 * @param {string} postJson JSON形式の文字列 
 */ 
function remove(sheetName, postJson) {
  var row = createRow_(sheetName, postJson);
  var data = sheetName.getDataRange().getValues();

  for (var i = 0; i < data.length; i++) {
    if (data[i][0] === row[0]) {
      sheetName.deleteRow(i + 1);
    }
  }
}

API作成

GETリクエストと、POSTリクエストの処理を作ります。
POST時の"action"クエリの値によって、更新モードを分岐させます。

本当はPUT、DELETEメソッド使いたかったんだけど、公式ドキュメントのHTTPメソッドのところにdoGet()、doPost()の仕様しか見つからなかったので妥協。

main.gs
function doGet() {
  var data = gssTableSearch.getData("シート1")
  return ContentService.createTextOutput(JSON.stringify(data, null, 2))
  .setMimeType(ContentService.MimeType.JSON);
}

function doPost(e) {
  var sheetName = SpreadsheetApp.getActive().getSheetByName('シート1');
  var postJson = JSON.parse(e.postData.contents);

  switch(e.parameter.action) {
    case "insert":
      gssTableUpdate.insert(sheetName, postJson);

      break;

    case "update":
      gssTableUpdate.update(sheetName, postJson);  

      break;

    case "delete":
      gssTableUpdate.remove(sheetName, postJson);

      break;

    default:
      console.log("動作モードが指定されていません。");
      break;
  }
}


WebAPIとして公開する。

  1. 上部のタブから、「公開」を選択
  2. Webアプリケーションとして導入
  3. プロジェクトバージョンを最新にして、「公開」をクリック

すごくわかりやすく書いてくれている人がいるため、参考にしました。
今から10分ではじめる Google Apps Script(GAS) で Web API公開

あとがき

GASは初めて触りましたが、API代わりにすることもできるのは衝撃でした。
JavaScriptと同じなので、個人的に書きやすいのもいいですね。
あとExcelのVBA信者から逃げることができるのも高評価

GitHubに上げました。GAS_WebAPI

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.jsで環境変数(AppDataの場所、PATH、その他)を取得したい

結論

process.envを使います。

node.js - how to get the OS platforms user data folder - Stack Overflow

実践

ターミナルでnodeを叩くと始まるInteractive Rubyみたいなアレで実行してみましょう。

{ ALLUSERPROFILE : "C:\\ProgramData",
  APPDATA : "C:\Users\jotaro\AppData\Roaming",
  (省略
}

こんなObjectが返ってきました。これはもう要するに、process.env.APPDATAと書けば、"C:\Users\jotaro\AppData\Roaming"が返ってくるというわけですね。あとはprocess.env.PATHからPATHの編集も…は…できないようです。

制限

Process - Node.js Documentation によれば、

It is possible to modify this object, but such modifications will not be reflected outside the Node.js process. In other words, the following example would not work:

$ node -e 'process.env.foo = "bar"' && echo $foo

「値を変更することはできるけど、Node.jsのプロセス内だけでしか変わらないよ」(超適当訳)とのこと。PATHを通したり環境変数をいじることは不可能なようです。うっかりPATH破壊でもしたらと思うと、納得がいきます。
もしかしたら環境変数の変更方法というのもあるのかもしれませんが、適当にググった結果ないと判断しました。

応用

スタートメニュー

応用して、Windowsのユーザーのスタートメニューにアクセスしてみます。

test.js
console.log(`${process.env.APPDATA}\\Microsoft\\Windows\\Start Menu\\Programs\\`);

スタートメニューにショートカットを作るのも楽ちんですね。おしまい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[JavaScript] 伸び縮みするテキストボックスを実装する (Vanilla JS)

やりたいこと

値の長さに応じて幅が変わる input 要素を実装したい。

実装

/*
 * fromElement から toElement にスタイルをコピーする。
 */
const copyComputedStyle = (fromElement, toElement, except = []) => {
  const computedStyle = getComputedStyle(fromElement);
  Array.from(computedStyle).forEach((key) => {
    if (!except.includes(key)) {
      const value = computedStyle.getPropertyValue(key);
      const priority = computedStyle.getPropertyPriority(key);
      toElement.style.setProperty(key, value, priority);
    }
  });
};

/*
 * 文字列を HTML エスケープする。
 */
const escapeHTML = (text) => {
  if (typeof text !== 'string') {
    return text;
  }

  return text.replace(/[&'`"<> ]/g, (match) => {
    return {
      '&': '&amp;',
      "'": '&#x27;',
      '`': '&#x60;',
      '"': '&quot;',
      '<': '&lt;',
      '>': '&gt;',
      ' ': '&nbsp;'
    }[match]
  });
};

/*
 * 仮の span 要素の innerHTML に input 要素の値を設定して width を計測する。
 */
const getTextWidth = (input) => {
  // span 要素はなるべく input 要素と同じスタイルにする。
  const span = document.createElement('span');
  copyComputedStyle(input, span, ['display', 'width']);
  span.style.maxWidth = '100vw';

 span.innerHTML = escapeHTML(input.value);
  document.body.appendChild(span);
  const width = span.getBoundingClientRect().width;
  document.body.removeChild(span);

  return width;
};

const adjustWidth = (input) => {
  input.style.width = `${getTextWidth(input)}px`;
};

const input = document.querySelector('input');
adjustWidth(input);
input.addEventListener('keyup', adjustWidth.bind(null, input));

デモ

デモ用画面の幅に余裕を持たせるため 0.5x で試すことをおすすめします。

See the Pen 伸び縮みするテキストボックス by QUANON (@quanon) on CodePen.

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Vue.js] 複数の引数を持つfilterを使ってみたメモ

Filter.js

src直下にpluginsディレクトリを切り、その中にfilter処理だけを書くFilter.jsを置く

export default {
  install(vue) {
    vue.filter('decimalPointShaping', value => {
      if (!Number(value)) {
        return value
      }
      return Math.round(value)
    })

  // 複数の引数を持つfilterはこれ↓
    vue.filter('percentageCalculation', (value, sum) => {
        console.log('percentageCalculation',value,sum)
      return (value / sum) * 100
    })
  },
}

使い方

<p>
{{ 【value】 | percentageCalculation(【sum】) | decimalPointShaping }}%
</p>

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

javascript

:use strict
"use strict"という文字列をファイルまたは関数の先頭に書くことで、そのスコープにあるコードはstrict modeで実行

変数
var 変更できる
const 変更できない
エラー
構文エラーと実行エラーの2種類


SyntaxError: missing ) after argument list[詳細] index.js:1:13 
^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^      ^^^^^^^^ ^^^^
エラーの種類                |                        |  行番号:列番号
                  エラー内容の説明                 ファイル名

実行エラー

ReferenceError

データ型
データ型を大きく分けると、プリミティブ型とオブジェクト


プリミティブ型(基本型): 真偽値や数値などの基本的な値の型
オブジェクト型: プリミティブ型以外

確かに配列もオブジェクト型だったな

console.log(typeof true);// => "boolean"
console.log(typeof 42); // => "number"
console.log(typeof "JavaScript"); // => "string"
console.log(typeof Symbol("シンボル"));// => "symbol"
console.log(typeof undefined); // => "undefined"
console.log(typeof null); // => "object"
console.log(typeof ["配列"]); // => "object"
console.log(typeof { "key": "value" }); // => "object"
console.log(typeof function() {}); // => "function"
実行

undefined
undefinedはリテラルではない
オブジェクトリテラル
 オブジェクトリテラルは{}(中括弧)を書くことで、新しいオブジェクトを作成


{}がオブジェクト

https://jsprimer.net/basic/data-type/
演算子
べき乗演算子

console.log(2 ** 4); // => 16


NaNは"Not-a-Number"


三項演算子
const valueA = true ? "A" : "B";
console.log(valueA); // => "A"


OR演算子(||)

const x = true;
const y = false;
// xがtrueなのでyは評価されない
console.log(x || y); // => true
// yはfalseなのでxを評価した結果を返す
console.log(y || x); // => true
関数
functionがキーワード

// 関数宣言
function 関数名(仮引数1, 仮引数2) {
    // 関数を呼び出された時の処理
    // ...
    return 関数の返り値;
}
// 関数呼び出し
const 関数の結果 = 関数名(引数1, 引数2);
console.log(関数の結果); // => 関数の返り値


例)

function multiple(num) {
    return num * 2;
}

console.log(multiple(10)); 

デフォルト引数


function echo(x = "デフォルト値") {
    return x;
}

console.log(echo(1)); // => 1
console.log(echo()); // => "デフォルト値"


可変長引数

引数を固定した数ではなく任意の個数の引数を受け取れること

const max = Math.max(1, 5, 10, 20);
console.log(max); // => 20


...args

※argsの命名はなんでも良い

function fn(...args) {
    // argsは引数の値が順番に入った配列
    console.log(args[0]); // => "a" 
    console.log(args[1]); // => "b" 
    console.log(args[2]); // => "c" 
}
fn("a", "b", "c");

分割代入

const { id } = user;

関数式

※factorialに引数を持たせたいときは()をつけていいのか

const factorial = function innerFact(n) {
    if (n === 0) {
        return 1;
    }
    // innerFactを再帰的に呼び出している
    return n * innerFact(n - 1);
};
console.log(factorial(3)); // => 6


アロー関数

const 関数名 = () => {
    // 関数を呼び出した時の処理
    // ...
    return 関数の返す値;
};


// 仮引数の数と定義
const fnA = () => { /* 仮引数がないとき */ };
const fnB = (x) => { /* 仮引数が1つのみのとき */ };
const fnC = x => { /* 仮引数が1つのみのときは()を省略可能 */ };
const fnD = (x, y) => { /* 仮引数が複数の時 */ };
// 値の返し方
// 次の2つの定義は同じ意味となる
const mulA = x => { return x * x; }; // ブロックの中でreturn
const mulB = x => x * x;            // 1行のみの場合はreturnとブロックを省略できる


メソッド

const object = {
    method1: function() {
        // `function`キーワードでのメソッド
    },
    method2: () => {
        // Arrow Functionでのメソッド
    }
};

const object = {};
object.method = function() {
};


省略記法


const object = {
    method() {
        return "this is method";
    }
};
console.log(object.method()); // => "this is method"
ブロック文

{
    文;
    文;
}

条件分岐
const version = "ES6";
if (version === "ES5") {
    console.log("ECMAScript 5");
} else if (version === "ES6") {
    console.log("ECMAScript 2015");
} else if (version === "ES7") {
    console.log("ECMAScript 2016");
}

ループ処理
while

let x = 0;
console.log(`ループ開始前のxの値: ${x}`);
while (x < 10) {
    console.log(x);
    x += 1;
}
console.log(`ループ終了後のxの値: ${x}`);


for

let total = 0; // totalの初期値は0
// for文の実行フロー
// iを0で初期化
// iが10未満(条件式を満たす)ならfor文の処理を実行
// iに1を足し、再び条件式の判定へ
for (let i = 0; i < 10; i++) {
    total += i + 1; // 1から10の値をtotalに加算している
}
console.log(total); // => 55


forEach

const array = [1, 2, 3];
array.forEach(currentValue => {
    console.log(currentValue);
});
// 1

続きはオブジェクトから

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JS】時間を遅らせる必殺技,setTimeout

こんなときに

  • 数秒、遅らせて処理を行いたい。

コード

setTimeout(() => {
    //処理
}, 2000);

第一引数に処理。
第二引数に時間。単位はミリ秒(1000=1秒

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

IonicとFirebaseを使って3日で作る写真共有アプリ - 画面処理の作成(Typescript)

「IonicとFirebaseを使って3日で作る写真共有アプリ」シリーズの「画面処理の作成(Typescript)」編です。
まとめ記事はコチラ↓
https://qiita.com/kazuki502/items/585aef0d79ed1235bec0

前回までで見た目の部分は出来てきたので、今回は画面内の処理を作っていこうと思います。サーバーとの通信の部分は後程やるので、今回はアップロードする写真をプレビュー表示する部分を作っていきます。

写真をアップロードする前にプレビュー表示させる

前回の画面を作った段階ではこのような画面になっていると思います。

image.png

…もっとカッコよくしたいですよね。

画面のデザインを整える

こういう時は、他のサイトを参考にして画面をデザインしていきましょう!とりあえず出てきたのはこんな感じの画面ですね。

image.png

破線の枠がある感じです。ではまずこれを作っていきましょう。ここでは"label"タグをいじっていきます。

modal-upload.component.html
<label class="photo" for="photo-upload">画像を選択</label>
<input type="file" name="photo" id="photo-upload">

<ion-button>送信</ion-button>

余計な文言を消して、"label"にcssクラスを追加しました。

そしてscssファイルを以下のように編集します(コピペでOKです)。

modal-upload.component.scss
.photo {
    width: 90%;
    height: 200px;
    border: 1px #000 dashed;
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    top: 30px;
}

そうすると、先ほどのモーダル画面がこんな風になります。

image.png

それっぽくなってきました。もうひと頑張りしたい方は、枠の中に写真のロゴなんか入れてみるとさらにそれっぽくなりますよ!Ionicにはロゴも用意されているので、目的にあるロゴを選びましょう。

Ionic logo
https://ionicons.com/

今回は写真なので"photo"と検索してみてください。
image.png

アイコンをクリックして下に出てきた"WEB COMPONENT CODE"をクリックしてコピーして自分のソースにペーストしましょう。それから少し調整を加えるとこのようになります。

image.png

modal-upload.component.html
<label class="photo" for="photo-upload">
  <ion-icon name="image"></ion-icon><br>
  画像を選択
</label>
<input type="file" name="photo" id="photo-upload">

<ion-button>送信</ion-button>
modal-upload.component.scss
.photo {
    width: 90%;
    height: 200px;
    border: 1px #000 dashed;
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    top: 30px;
    text-align: center;
    vertical-align: middle;

    & > ion-icon {
        font-size: 4em;
    }
}

それっぽくなりましたね。

"ファイルを選択ボタン"を消す

ファイルを選択ボタンはなんかいらない気がしますよね。枠線の中のエリアをクリックするとファイル選択画面が開くようにしたいですよね。はい、やりましょう。とても簡単です。ボタンの表示設定で非表示に設定すればいいんです。

modal-upload.component.scss
.photo {
    width: 90%;
    height: 200px;
    border: 1px #000 dashed;
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    top: 30px;
    text-align: center;
    vertical-align: middle;

    & > ion-icon {
        font-size: 4em;
    }
}

// 送信ボタンスタイル
.send {
    position: absolute;
    bottom: 20px;
    left: 50%;
    transform: translateX(-50%);
    width: 90%;
}

// 画像選択input
#photo-upload {
    display: none; // ファイルをアップロードボタンを非表示に設定
}

そうすると、このような画面になります。(送信ボタンなど一部スタイルをして調整しています。

image.png

選択した写真を表示させる。

このままだと、写真を選択しても画面には何も反映されません。選択した写真を画面に反映させるにはどうすればいいでしょうか。それには、"FileReader"を使えばうまくいきます。まずは完成イメージと、完成ソースコードをご覧ください。説明はその後にします。

image.png

modal-upload.component.html
<label class="photo" for="photo-upload">
  <div [ngClass]="{'inactive': isSelected}">
    <ion-icon name="image"></ion-icon><br>
    画像を選択
  </div>
  <ion-img [ngClass]="{'inactive': !isSelected}" [src]="imageSrc"></ion-img>
</label>
<input type="file" name="photo" id="photo-upload" accept="image/*" (change)="previewPhoto($event)">

<ion-button class="send">送信</ion-button>
modal-upload.component.scss
.photo {
    width: 90%;
    height: 200px;
    border: 1px #000 dashed;
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    top: 30px;
    text-align: center;
    vertical-align: middle;

    ion-icon {
        font-size: 4em;
    }
}

.inactive {
    display: none;
}

// 送信ボタンスタイル
.send {
    position: absolute;
    bottom: 20px;
    left: 50%;
    transform: translateX(-50%);
    width: 90%;
}

// 画像選択input
#photo-upload {
    display: none;
}
modal-upload.component.ts
import { Component, OnInit, Input } from '@angular/core'; // Inputを追加
import { NavParams } from '@ionic/angular'; // 追加

const RESULT = 'result';

@Component({
  selector: 'app-modal-upload',
  templateUrl: './modal-upload.component.html',
  styleUrls: ['./modal-upload.component.scss'],
})
export class ModalUploadComponent implements OnInit {

  // "value" passed in componentProps
  public imageSrc: any;
  public isSelected: boolean;
  @Input() value: number;
  private reader = new FileReader();

  constructor(
    navParams: NavParams // 追加
  ) { }

  ngOnInit() {}

  public previewPhoto(event) {
    const file = event.target.files[0];
    this.reader.onload = ((e) => {
      this.imageSrc = e.target[RESULT];
      this.isSelected = true;
    });
    this.reader.readAsDataURL(file);
  }
}

このコードをコピペすればとりあえず動くと思います。それでは、それぞれで何をしているのか順を追って説明していきましょう。

FileReaderを設定する

まずは"modal-upload.component.ts"をご覧ください。今回はFileReaderをつかうので、内部オブジェクトとして宣言しています。

  private reader = new FileReader();

そして、写真を選択したときの処理を定義します。

  public previewPhoto(event) {
    const file = event.target.files[0]; // パラメータからファイルを抽出
    this.reader.onload = ((e) => {
      this.imageSrc = e.target[RESULT]; // 予め用意しておいた変数に画像データを格納する
      this.isSelected = true; // 写真選択フラグを切り替える
    });
    this.reader.readAsDataURL(file);
  }

これで、選択した写真がthis.imageSrcに格納されます。FileReaderに関してはこれだけです。

previewを設定する

選択した写真をアプリに保持出来るようになったので、写真をどのように表示させるのかを説明していきます。先ほど"previewPhoto"という関数を定義しましたが、定義しただけでは関数を実行させることはできません。画面の要素と関連付ける必要があります。今回関連付けるべき要素は、"input"タグですね。画面要素と処理の紐づけはこのようにして書くことが出来ます。

modal-upload.component.html
<input type="file" name="photo" id="photo-upload" accept="image/*" (change)="previewPhoto($event)">

(change)="previewPhoto($event)"が重要な部分です。"(change)"はangularのイベントバインディングと呼ばれるもので、"change"というのは、要素に変更があったときなので写真が選択されたときですね。Angularのバインディングについて詳しく説明しているサイトが合ったので、もっと知りたい方はこちらをご覧ください。

これで、「写真が選択された」というイベントと、「選択された写真をアプリの中に保持する」という処理がつながりました。画像を表示するためにはこのように書けばよいです。

  <ion-img [ngClass]="{'inactive': !isSelected}" [src]="imageSrc"></ion-img>

ion-imgタグのsrc要素に画像情報が保持されている変数を割り当てるだけです。"src"ではなく、"[src]"と括弧が付いているのも、Angularのバインディングの一種です。[ngClass]="{'inactive': !isSelected}"は、条件によってCSSクラスを切り替えるための記述です。写真を設定したときに、初期表示で出ていた文字と写真アイコンを消して、画像を表示するためのものです。これが無い場合は、初期表示・写真選択時にこんな表示の仕方になります。

image.png

イメージと違うというか、すこぶるダサいですよね笑。今回は、CSSクラスで表示・非表示を切り替えましたが、"*ngIf=..."というのを使っても同じことが出来ます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript 再入門 基本構文編

前置き

今月で JavaScript 歴 1 年になりました。
Node.JS、バニラ js、React を使ってきました。
(JavaScript 以外にも、C#、Ruby、Swift とか少しずつ色々してます。)
その結果、「ソースを読めば何をしたいか理解できるようになってきた」

...そう思ってた時期が自分にもありました。
OSS のソースを読む機会があり、
その時、prototypeがいきなり現れ、、、ナニソレオイシイノ状態に、、、

ということで改めて JavaScript を基礎から勉強しよう、と思い本記事を書くに至りました。

ECMAScript

説明

ECMAScript は JavaScript の標準仕様書であり、この ECMAScript をもとに JavaScript が作られています。
ECMAScript は毎年バージョンアップされ、その都度、JavaScript の言語仕様が変わっています。
ES6 だの ES7 だの聞くかと思いますが、基本的には
ECMAScript2015(2015年バージョン) と ES6
ECMAScript2016(2016年バージョン) と ES7
はそれぞれ同意語のようです。

この ECMAScript ですが、ES6(つまり ECMAScript2015)で大きく変わりました。
const, let, 分割代入, class などなど。
今まで、JavaScript には無かった機能が一気に投入され、書き方が大きく変わりました。

本記事では両方と比較しながらまとめていきます。

変数の宣言 var, let, const

ソース スコープ 特徴
(無) word = "HelloWorld" 常にグローバルスコープ 同名変数があった場合は上書き
use strictを使用した際はエラーが発生する
var var word = "HelloWorld" グローバルスコープ / 関数スコープ 重複する変数が一つのスコープ内にあった場合、上書きされる
let
【ES6】
let word = "HelloWorld" グローバルスコープ / 関数スコープ / ブロックスコープ 重複する変数が一つのスコープ内にあった場合、エラーが発生する
const
【ES6】
const word = "HelloWorld" グローバルスコープ / 関数スコープ / ブロックスコープ letの特徴に加え、定数である
プリミティブ型は書き換え不可
参照型の内部の値は書き換え可

スコープ

JavaScript の変数のスコープは以下の 3 つです

種類 説明
グローバルスコープ スクリプト全体から参照できる
ローカルスコープ 関数の中からのみ参照できる
ブロックスコープ ブロックの中からのみ参照できる

ES6 で登場した、let,constを使用することでブロックスコープを定義することができます。
ブロックスコープを使用しないということは下記のようなコードがかけます。

if (true) {
  var hello = "Hello World"
  console.log(hello) // Hello World
}
console.log(hello) // Hello World

予期せぬ動作を回避するために
ES6 で書ける環境ではvarではなく、ブロックスコープを持つletconstを使用することが推奨されています

余談

※Swift 等のletvarとは全く意味が違うので注意!
var はバグの温床になるため、ES6 以上が使える場合は積極的に let,const を使うのが一般的なようです。

私はたまに swift でletで宣言したけど、書き換えできなかった
とかなるので。。。

JavaScript のデータ型

JavaScript は動的型付け言語のため、Java や C#のように変数宣言時にデータ型が決まりませんが、型は存在します。

一覧

種類 データ型 意味
プリミティブ型 number 数値を表す型
プリミティブ型 string 文字列を表す型
プリミティブ型 boolean 真偽値(true / false)を表す
プリミティブ型 symbol シンボル?を表す (次回?まとめます。。。)
プリミティブ型 null オブジェクトが存在しないことを表す型
プリミティブ型 undefined 値が未定義であることを指す型
例)未代入の変数、戻り値が未定義の関数の戻り値
参照型 Object 上記以外

補足 プリミティブ型

プリミティブ型はメソッドを持たないデータ型のことです。
ですが、JavaScript では以下のようにかけます

const hello = "javascript";
const uppperHello = hello.toUpperCase();
console.log(uppperHello); // JAVASCRIPT

これは、JavaScript が string 型(プリミティブ型)から string のラッパーオブジェクトである String オブジェクトに自動変換してくれているため、実行できます。

(new String(hello)).toUpperCase()

【ES6】文字列へ変数を埋め込む \${expression}

let name = "matsuda"
console.log(`彼は${name}です。`) // 彼はmatsudaです。
console.log(`彼は${20 + 4}歳です。`) // 彼は24歳です。

バッククォート「``」と「\$」を使用することで文字列リテラルの中で変数や式を使用することができます。

【ES6】分割代入 { user_id, user_name, ... } = { user_id:"0001", user_name: matsuda, user_old: 24 }

分割代入とは、配列 / オブジェクト から値を取り出し個別の変数に代入し直す JavaScript の構文のことです。

オブジェクト

const userInfo = {
  name: "matsuda-naoya",
  old: "24",
  birthplace: {
    country: "Japan",
    prefecture: "ehime"
  }
};

// --- ES5まで ---
// const name = userInfo.name;
// const prefecture = userInfo.birthplace.prefecture;
// const likeProgrammingLanguage = "JavaScript";
// const another = { old : userInfo.old}

// --- ES6から ---
const {
  name: userName, // 1 変数userNameにuserInfo.nameを代入
  birthplace: { prefecture }, // 2 変数prefectureにuserInfo.birthplace.prefectureを代入
  likeProgrammingLanguage = "JavaScript", // 3 変数likeProgrammingLanguageに"JavaScript"を代入
  ...another // 4 変数anotherに{old: "24"}オブジェクトを代入
} = userInfo;

  console.log(userName); // matsuda-naoya
  console.log(prefecture); // ehime
  console.log(likeProgrammingLanguage); // JavaScript
  console.log(another) // { old: "24"}
};
  1. 変数名: 別名と記載することで分割代入時に変数名を変更できる
  2. 変数名: { 入れ子の変数名 }と記載することで入れ子のオブジェクトも個別に展開することができる
  3. 変数名 = リテラルとすることで、分割対象のプロパティが存在しなかった場合、指定したリテラルで初期化する
  4. ...変数名とすることで分割代入によって展開されなかった残りの列挙可能なプロパティを指定した変数に代入する

配列

const snsList = ["line", "twitter", "youtube", "facebook", "Instagram"];
// ES5まで
// const LINECorporation = snsList[0]
// const twitter = snsList[1]
// const faceBookInc = [snsList[3], snsList[4]]

// ES6から
const [
  LINECorporation,     // 1. 変数LINECorporationにsnsList[0]を代入
  twitter,             // 1. 変数twitterにsnsList[1]を代入
  ,                   // 2. snsList[3]の値は無視する
  ...faceBookInc       // 3. snsListの残りの値をfaceBookIncに代入
] = snsList;
console.log(LINECorporation); // line
console.log(twitter); // twitter
console.log(faceBookInc); // ["facebook", "Instagram"]
  1. [変数名, 変数名] = 配列と記載することで配列の Index 順に、宣言した変数に代入される
  2. , ,と記載することでそのインデックスに応じた値は代入されない(無視される)
  3. ...変数名とすることで分割代入によって展開されなかった残りの値が指定した変数に代入される

false になる値(falsy)

以下は Boolean 型に変換された際に false になる値です。

  • "", ``, '' (空文字)
  • 0 (数値型のゼロ)
  • NaN (Not a Number)
  • null
  • undefined

上記以外はtrueになります

JavaScript は動的型付けで、if(hogehoge)のように記載した場合は強制的に型変換が行われるため、以下のようなコードがかけます。

if("") {
  // 実行されない
}
if(0) {
  // 実行されない
}
let emptyName;
if (emptyName) {
  // 実行されない
}

const name = "matsuda";
if (name) {
  console.log(name) // matsuda
}

論理演算子 ( && || ) とショートサーキット評価(短絡演算子)

JavaScript には以下の 3 つの論理演算子が存在します。

演算子 説明
&& (AND) value1 && value2 value1 が false とみなせる場合、value1 を返し、
true とみなせる場合は value2 の値を返す
すなわち、左右どちらも true の場合 true を返し、
左が false の場合 false を返す
|| (OR) vlue1 || vaue2 value1 が true とみなせる場合、value2 を返し、
false とみなせる場合は value1 の値を返す
すなわち、左右どちらも true の場合 true を返し、
左が false の場合、右の true を返す
! (NOT) !value 式が true とみなせる場合は false を返し、false とみなせる場合は true を返す

ショートサーキット評価

論理演算子&&||は左の式から右の式順で評価が行われ、左側の式を評価した際に最終評価が確定した場合は、右側の式は評価されません。
さらに、&&||は Boolean 型以外も返却できるため、以下のような書き方ができます。

  let emptyName;
  emptyName = emptyName || "初期値"  // 1
  console.log(emptyName) // 初期値

  let tempObj = {
    notEmpty: "空じゃないよ"
  };
  temp = tempObj && tempObj.notEmpty // 2
  console.log(temp) // 空じゃないよ
  1. emptyName はundefinedfalseとみなすことができ、||の性質から、右辺の初期値emptyNameに代入される
  2. tempObj は falsy な値でないため、trueとみなすことができ、&&の性質から、右側の値がtempに代入される ※ 2 の書き方は直感的に理解しにくいため、三項演算子を使用した書き方や、デフォルト引数【ES6】で書く場合が多いとか... (この辺は関数編で詳細にまとめます...)
// 三項演算子
temp = tempObj ? tempObj.notEmpty : ""


// 【ES6】デフォルト引数
function main(firstName, familyName = "Matsuda") {
  return firstName + familyName;
}
console.log(main("Naoya")); // NaoyaMatsuda

オブジェクトの null チェック

!演算子を使用することで、以下の書き方ができます。

let notEmptyObj = {
  name: "matsuda"
}
if (notEmptyObj) {
  // notEmptyObjに値がある場合(falsyな値でない)場合処理が実行
}

let emptyObj = undefined
if (!emptyObj) {
  // emptyObjがundefined(falsyな値の)場合処理が実行
}

まとめ

JavaScript の基本構文についてまとめました。
冒頭のprototypeはどこじゃいってなりそうですが、スミマセン。
いざ再学習し始めると想像以上に知らなかったことや詳細を理解していないことがたくさんあり、予想以上に時間がかかってしまいました。

関数やオブジェクトについては次回まとめることにします。

参考

改訂新版 JavaScript 本格入門~モダンスタイルによる基礎から現場での応用まで
JavaScript の「&&」「||」について盛大に勘違いをしていた件
[WIP]JavaScript の入門書
MDN web docs

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pipe関数の力

Javascriptで役に立つ関数型プログラミングというシリーズを始めます。

今回は、関数型プログラミングの中心的な概念、関数合成について書こうと思います。
関数合成とは〜という学術的な話をするとキリがないので、例を見ていきましょう:

const addFive = num => num + 5;
const double = num => num * 2;
const addFiveAndDouble = num => double(addFive(num));

addFiveAndDoubleaddFivedoubleを合成した関数になります。タスクによっては、二つ以上の関数を合成することが多いと思います。しかしそうなると、コードが以下のように読みづらくなります:

const result = countLikes(pickLongest(excludeCommentsByAuthor(getTodayComments(post))));

関数のネーミング関係なく、カッコが多くて長い表現になってしまいます。

ここに、pipe関数が登場します!

Pipeで関数合成

Javascriptでは、pipeを以下のように定義できます。

// わかりやすくするためにあえてfunctionを使います
function pipe(...fns) {
  return function(arg) {
    return fns.reduce((val, fn) => fn(val), arg);
  }
}

上記の長すぎる表現はpipeを使えばもっと簡潔に書けます:

const result = pipe(
  getTodayComments,
  excludeCommentsByAuthor,
  pickLongest,
  countLikes
)(post);

行う処理も、上が最初で下が最後、人間にとって自然な流れになっています。もちろん左から右に書いても構わないです。Array.prototype.reduceの仕組みがわかる前提で書いてますが、自分でreduceを定義してみると、pipeもわかりやすくなると思います:

function reduce(list, fn, startVal) {
  let acc, startIdx;
  if (arguments.length == 3) {
    acc = startVal;
    startIdx = 0;
  } else {
    acc = list[0];
    startIdx = 1;
  } 
  for (let i = startIdx; i < list.length; i++) {
    acc = fn(acc, list[i]);
  }
  return acc;
}

もちろん、pipeをワンラインで定義したければ、できます:

const pipe = (...fns) => arg => fns.reduce((x, f) => f(x), arg);

pipeの定義を見ればわかりますが、関数の配列fnsはクロージャによって、アクセス可能なので、pipeに関数の配列だけ与えると、その配列を覚えた合成関数が返ってきます。最初に挙げた例に戻ると、たとえば投稿のコメントを処理する合成関数を以下のように作り、再利用できます:

const processPostComments = pipe(
  getTodayComments,
  excludeCommentsByAuthor,
  pickLongest,
  countLikes
);
// ...
// コードのどこかで
fetch(postUrl)
.then(response => response.json())
.then(processPostComments);

おまけに

ちなみに、関数型言語と言われる言語には、合成のために演算子があって、非常に便利です。
Haskellではこういう書き方ができます:

processPostComments = countLikes . pickLongest . excludeCommentsByAuthor . getTodayComments

Javascriptには合成演算子はないですが、将来導入される可能性があります。もしそうなれば以下のような書き方ができるようになります:

let result = post
  |> getTodayComments
  |> excludeCommentsByAuthor
  |> pickLongest
  |> countLikes;

以上、pipeの話でした。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue.js】v-modelの基本的な使い方と、修飾子の使い方

本記事について

v-modelの挙動と、v-modelでのinputタグの値の取得のTipsを記載します。

目次

・v-modelとは
・v-modelを使ってみる
・v-modelで使う修飾子

v-modelとは

v-modelとはそもそも何なのか。
公式ドキュメントによると

form の input 要素 や textarea 要素、 select 要素に双方向 (two-way) データバインディングを作成するには、v-model ディレクティブを使用することができます。それは、自動的に入力要素のタイプに基づいて要素を更新するための正しい方法を選択します。

とのことです。うーん、ちょっとわかりにくい。。

とりあえず、コードを書いて理解してみましょう。

v-modelを使ってみる

とても基本的な使い方ですが、まずはHTMLに下記のように書きます。

index.html
<!-- Vue.jsが読み込まれている前提 -->
<div id="app">
  <input type="text" v-model="sample_v_model" placeholder="sample_v_model"><br>
  <p>ここにsample_v_modelの値をリアルタイムに表示: {{ sample_v_model }}</p>
</div>

<script>
new Vue({
  el: "#app",
  data: {
    sample_v_model: ''
  }
})
</script>

inputタグにv-model="sample_v_model"という記述があります。
そして、scriptタグの中身にsample_v_model: ''という記述があります。

new Vueとして、Vueインスタンスを作っているわけですが、それをマウントする先が#appで囲まれた箇所になるので、HTML側のv-model="sample_v_model"とjs側のsample_v_model: ''が紐づいているのですね。

公式ページに書いてあった、「双方向 (two-way) データバインディングを作成するには、v-model ディレクティブを使用することができます」という部分がこれに当たるのだと理解できました。

jsの方ではsample_v_modelというデータを持っていて、ページが読みこまれた表示された時にはその中身は空っぽ、そして、inputタグも空っぽという状態です。なので、ページが表示された時には

・inputタグの中身は空っぽ
・js上で定義されたデータの中身も空っぽ

ということになります。

↓こんな感じですね。

スクリーンショット 2019-05-29 16.12.03.png

これで、v-modelの基本的な使い方を理解することができました。

v-modelで使う修飾子

今回はinputタグの中身を計算する上で数値にする必要があり、そのデータをHTML上にも反映しようとしたところ、v-forの部分でそれがうまくいかず、「100」と入力すると。「1」「0」「0」と入力されたと見なされてしまったので、その解決のためにnumber修飾子を使いました。

その実際のコードがこちらです

<div class="">
  <label for="number_of_years">運用する年数</label>
  <input type="number" v-model.number="number_of_years" placeholder="運用する年数を入力してください"></div>

(略)

<tr v-for="number_of_year in number_of_years">
  <th>{{ number_of_year }}年後</th>
  <td>{{ Math.round(amountOfShare * Math.pow(rateOfIncrease, number_of_year)) }}</td>
</tr>

これで、無事に数値としてnumber_of_yearsをv-forのところで使う数値の部分に入れることができました。

今回はnumberを使いましたが、他にも修飾子があったので下記にまとめておきます。

修飾子 内容
lazy 入力ボックスに入力されたタイミングではなく、入力し終わったタイミングでデータを変更する。
number 入力した値の型変換を自動で数値にする。
trim 入力した値から自動で空白を削除する

それぞれ使うタイミングが出てきたら具体的な例も追記できればと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Nuxtを使ってPromise, async/awaitのコードを書く

動機

なお、サンプルコードは上のリンクのコードを用いる(下に転載)が、少し機能を変えて、
1. inputタグ内の文字列を,区切りでリスト化する
2. hogeという文字列から始まっていればその文字列を、それ以外はfugaという文字列を結果に格納
3. 結果を1つずつ表示する

という風にしている。

fetchResources.js
function dummyFetch(path) {
    return new Promise(function(resolve, reject){
        setTimeout(function(){
            if (path.startsWith("/resource")) {
                resolve({ body: `Response body of ${path}` });
            } else {
                reject(new Error("NOT FOUND"));
            }
        }, 1000 * Math.random());
    });
}

// 複数のリソースを取得しレスポンスボディの配列を返す
async function fetchResources(resources) {
    // リソースをまとめて取得する
    const promises = resources.map(function(resource){
        return dummyFetch(resource);
    });
    // すべてのリソースが取得できるまで待つ
    // Promise.allは [ResponseA, ResponseB] のように結果が配列となる
    const responses = await Promise.all(promises);
    // 取得した結果からレスポンスのボディだけを取り出す
    return responses.map(function(response){
        return response.body;
    });
}
const resources = ["/resource/A", "/resource/B"];
// リソースを取得して出力する
fetchResources(resources).then(function(results){
    console.log(results); // => ["Response body of /resource/A", "Response body of /resource/B"]
});

1ファイル内で完結させる実装

まずはvuexのStore機能を使わず、dummy.vueファイル内のみで完結させる

pages/dummy.vue
<template>
  <div>
    <!-- 送信用 -->
    <div>
      <input v-model="resources" type="text">
      <button @click="submit">
        送信
      </button>
    </div>
    <!-- 表示用 -->
    <div>
      <ul>
        <li v-for="(result, index) in results" :key="index">
          {{ result }}
        </li>
      </ul>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      resources: '',
      results: []
    }
  },
  methods: {
    async submit() {
      const resources = this.resources.split(',')
      const promises = resources.map((resource) => {
        return this.dummyFetch(resource)
      })
      const responses = await Promise.all(promises)
      return responses.map((response) => {
        this.results.push(response.body)
      })
    },
    dummyFetch(resource) {
      return new Promise((resolve) => {
        setTimeout(() => {
          if (resource.startsWith('hoge')) {
            resolve({ body: resource })
          } else {
            resolve({ body: 'fuga' })
          }
        }, 1000 * Math.random())
      })
    }
  }
}
</script>

挙動はこんな感じ
わかりにくいですが、上の文字列が自分で打った文字列(入力)、下のリストが結果です。
スクリーンショット 2019-05-29 15.09.51.png

Store機能を使って実装する

NuxtにおけるStoreは、2つの書き方があります、

・モジュールモード: store ディレクトリ内のすべての *.js ファイルが 名前空間付きモジュール に変換されます(index はルートモジュールとして存在します)
・クラシックモード(廃止予定): store/index.js がストアインスタンスを返します

(上のリンクより)

クラシックモードで書かれているコードが多いと思いますが、廃止予定とあったので今回はモジュールモードを使ってStore機能を使用します。
また、vueファイル内の<template>は全く同じなので割愛します

dummy.vue
<script>
import { mapActions, mapState } from 'vuex'

export default {
  data() {
    return {
      resources: ''
    }
  },
  computed: {
    ...mapState('dummy', ['results'])
  },
  methods: {
    // `this.fetch()` を `this.$store.dispatch('dummy/fetch')` にマッピング
    ...mapActions('dummy', ['fetch']),
    submit() {
      this.fetch(this.resources)
    }
  }
}
</script>
store/dummy.js
export const state = () => ({
  results: []
})

export const mutations = {
  fetchResources(state, results) {
    // state.results.push(response.body)
    state.results = results
  }
}

function dummyFetch(resource) {
  return new Promise((resolve) => {
    setTimeout(() => {
      if (resource.startsWith('hoge')) {
        resolve({ body: resource })
      } else {
        resolve({ body: 'fuga' })
      }
    }, 1000 * Math.random())
  })
}

async function fetchResources(resources) {
  const promises = resources.split(',').map((resource) => {
    return dummyFetch(resource)
  })
  const responses = await Promise.all(promises)
  return responses.map((response) => {
    return response.body
  })
}

export const actions = {
  fetch({ commit }, resources) {
    fetchResources(resources).then((results) => {
      commit('fetchResources', results)
    })
  }
}

store内のresultsに対して、action, mutationを用いて結果を格納し、それを描画しています。

最後に

dummyFetch, fetchResources関数はどこに置けばよいのかがわからなかった。
一旦は呼び出しができるようにファイル内にかきましたが、pluginsディレクトリ内にutils.jsなどをおいて書いていけばいいのか、そのほかに方法があるのか...
わかる方、教えていただけますと幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

文系学生のプログラミング学習3日目ー成長ー

配列が分かってきた

ドットインストールで「王様ゲーム」というものを作成したのですが、そこで配列を使用しました。
命令をいくつか格納して、それをランダムで取り出すという作業をしたのですが、「配列」と聞くとどうしても数字というイメージが強くなってしまい、「なんで命令に配列を使うんだろう?」と思っていました。

配列は何かをランダムに取り出したいときなどに、[]に命令を格納し、Math.floor(Math.random*length)を使ってランダムに取り出すことができるんですね。
クイズゲームの問題などは配列を使ってランダムに取り出しているのですかね?
JavaScriptではいろいろなことが出来るので、はやく何かを作ることができる段階になりたいです。

アウトプットの難しさ

ブログの投稿が3日に1回とかになっていますが、プログラミング自体は毎日やっています。毎日投稿出来ていないのはその日その日の気づきをアウトプットすることがとても難しいからです。「今日書くほどの気づきないな」と思う日が良くあります。
これでは効率が悪いと思うので、些細なことでも自分の成長につなげたいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

チェックボックス、ドロップダウンの操作方法

Javascriptでチェックボックス、ドロップダウンの基本的な扱い方。
(個人的にjQueryを使う事が多かったので、Vanillaでも使えるようとメモします。)

チェックボックス

チェックボックスの情報の取得・変更

取得する場合は<input type="checkbox" />に対してcheckedプロパティーを使います。
checkedプロパティーは真偽値(Boolean型)で返します。

<label><input type="checkbox" id="checkboxA" value="Blue"></label>
<label><input type="checkbox" id="checkboxB" value="Yellow" checked></label>
const cb_A      = document.querySelector('#checkboxA');
const checked_A = cb_A.checked;

const cb_B      = document.querySelector('#checkboxB');
const checked_B = cb_B.checked;

console.log('checked_Aの値:', checked_A); // 結果 false
console.log('checked_Bの値:', checked_B); // 結果 true

変更する場合はcheckedプロパティーに真偽値を代入します。

const cb_A = document.querySelector('#checkboxA');
cb_A.checked = true;  // チェックされた状態になる

チェックボックスの変更を検知

値の変更を検知するにはchangeイベントで監視します。

<label><input type="checkbox" id="checkboxA">Check</label>
const tgtCheckbox = document.getElementById('checkboxA');
tgtCheckbox.addEventListener('change', function(event) {
  const value = event.target.checked;
  console.log(value);
});

ドロップダウンメニューの情報の取得・変更

取得する場合は、対象となるselect要素を参照し、value値を調べます。

<select id="dropdown-menu">
  <option value="eagle">イーグル</option>
  <option value="shark">シャーク</option>
  <option value="panther" selected>パンサー</option>
</select>
/**
 * select要素の参照
 */
const tgtDropdownMenu = document.querySelector('#dropdown-menu');

/**
 * 値を取得
 */
const value = tgtDropdownMenu.value;

console.log(value);  // 結果 panther

変更する場合はoptionで定義されている値を代入します。

const tgtDropdownMenu = document.querySelector('#dropdown-menu');
tgtDropdownMenu.value = 'eagle';

ドロップダウンメニューの変更を検知

値の変更を検知するにはchangeイベントで監視します。

<select id="dropdown-menu">
  <option value="eagle">イーグル</option>
  <option value="shark">シャーク</option>
  <option value="panther" selected>パンサー</option>
</select>
const tgtDropdownMenu = document.querySelector('#dropdown-menu');
tgtDropdownMenu.addEventListener('change', function(event) {
  const value = tgtDropdownMenu.value;
  console.log(value);
});
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptのfilterをdiscord.jsで使ってみたメモ

こんにちは、ほぼ毎日discord.jsでbot作ってる人です。(?)

前置き

今回は、discord.jsでJavaScriptのfilterを使ってみたのを記事にしてます
(説明が下手なのは許してください)
(まだ無知なことがいっぱい)
discord.js: https://discord.js.org/
Collection: https://discord.js.org/#/docs/main/stable/class/Collection

使い方

テキストチャンネルだけの数

const textChannels = message.guild.channels.filter(t => t.type == "text").size
message.channel.send(textChannels);

これでテキストチャンネルのみの数が表示されます。
説明すると
message.guild.channels.filter
(メッセージが送信されたギルドのチャンネルをフィルターする)
[以下略].filter.(t => t.type == 'text')
(tmessage.guild.channelsの省略版です(多分))
(t.type == 'text'はチャンネルのタイプです。)
'text'に入るのは、https://discord.js.org/#/docs/main/stable/class/Channel?scrollTo=type をご覧下さい。

追記
.sizeは○○の数です。

説明はこれで終わりです

終わりに

他にもfindとかあるのでいつかだそうかなと思ってます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ImmerjsでRedux周りをスッキリさせたい

前提

- react,reduxでフロント開発中
- 「Immerjs便利」とすすめられる
- 「そもそもイミュータブルってなんだ?」

使うライブラリ:GitHub - immerjs/immer: Create the next immutable state by mutating the current one

そもそもイミュータブルとは

  • 「イミュータブル(immutable)」
    • 不変、変わらない
  • プログラミングにおいて「イミュータブル」とは?
    • オブジェクトの状態が変更されないこと
    • redux等はイミュータブルを実現してる

簡単なサンプルコード:codepen

const array = {
  test1: '国語',
  test2: '理科',
  test3: '算数',
}

console.log({array});

// ミュータブル
// arrayの値が変わってしまう
array['test1'] = '英語';
console.log({array});

// イミュータブル
// arrayの値は変えず、新しいオブジェクトを生成する
const array2 = {...array, test1:'英語'};
console.log({array2});

Immerjs導入

  • 導入にあたって
    • 上述のイミュータブルのいいところは既にReduxがやってくれてる
    • 期待するのは、記述の簡素化
      • スプレッド演算子があんまり好きじゃない
        • newState={...state, todo1: 'マヨネーズ買う'}
        • 一回くらいならいいけど、reducerは...stateばっかになる。コード見づらい
      • オブジェクトの階層が深くなると分かりづらくなる

サンプルコード

GitHub - immerjs/immer:より引用です。

↓ Reduxデフォの書き方

// Redux reducer
// Shortened, based on: https://github.com/reactjs/redux/blob/master/examples/shopping-cart/src/reducers/products.js
const byId = (state, action) => {
    switch (action.type) {
        case RECEIVE_PRODUCTS:
            return {
                ...state,
                ...action.products.reduce((obj, product) => {
                    obj[product.id] = product
                    return obj
                }, {})
            }
        default:
            return state
    }
}

↓ immerjs使った場合

import produce from "immer"

const byId = (state, action) =>
    produce(state, draft => {
        switch (action.type) {
            case RECEIVE_PRODUCTS:
                action.products.forEach(product => {
                    draft[product.id] = product
                })
        }
    })

導入した感想

  • reducerがスッキリした
    • スプレッド演算子なくって万歳
  • draftという単語に慣れない
    • 別の命名も思いつかない
    • 慣れの問題だと思うので使い続ける
  • 次はimmerjs/use-immerも使ってみる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【日記】技術書どおりにやって上手くいかなかったwinstonを中途半端に上手くいかせた

最近、こちらの本を読んでWebアプリ開発を勉強しています。
JavaScriptでのWeb開発 ~ Node.js + Express + MongoDB + ReactでWebアプリを開発しよう ~ その2(iOS対応版)
途中、winstonというロギングアプリを導入する方法が解説されていたところで詰まりました。

本書では次のようにwinstonの設定ファイルを定義するよう書いてありました。

lib/logger.js
var winston = require('winston')

function Logger(){
  return winston.add(winston.transports.File, (
    filename: "log/warning.log",
    maxsize: 1048576,
    level: "warn"
  });
}

module.exports = new Logger();

この通りにファイルを作成してアプリを起動しようとすると、次のようなエラーが発生。

$ node app.js
/Users/anaakikutsushita/Desktop/code/learn-nodejs/hoge/node_modules/winston-transport/legacy.js:18
    throw new Error('Invalid transport, must be an object with a log method.');
    ^

Error: Invalid transport, must be an object with a log method.
    at new LegacyTransportStream (/Users/anaakikutsushita/Desktop/code/learn-nodejs/hoge/node_modules/winston-transport/legacy.js:18:11)
    at DerivedLogger.add (/Users/anaakikutsushita/Desktop/code/learn-nodejs/hoge/node_modules/winston/lib/winston/logger.js:345:11)
    at Object.winston.<computed> [as add] (/Users/anaakikutsushita/Desktop/code/learn-nodejs/hoge/node_modules/winston/lib/winston.js:110:68)
    at new Logger (/Users/anaakikutsushita/Desktop/code/learn-nodejs/hoge/lib/logger.js:4:20)
    at Object.<anonymous> (/Users/anaakikutsushita/Desktop/code/learn-nodejs/hoge/lib/logger.js:12:18)
    at Module._compile (internal/modules/cjs/loader.js:759:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:770:10)
    at Module.load (internal/modules/cjs/loader.js:628:32)
    at Function.Module._load (internal/modules/cjs/loader.js:555:12)
    at Module.require (internal/modules/cjs/loader.js:666:19)
    at require (internal/modules/cjs/helpers.js:16:16)
    at Object.<anonymous> (/Users/anaakikutsushita/Desktop/code/learn-nodejs/hoge/app.js:12:11)
    at Module._compile (internal/modules/cjs/loader.js:759:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:770:10)
    at Module.load (internal/modules/cjs/loader.js:628:32)
    at Function.Module._load (internal/modules/cjs/loader.js:555:12)

ググったところ、次のような解決法を見つけました。
Invalid transport, must be an object with a log method winston mongodb logging

これに従って、前述のfunction Logger()を次のように修正。

lib/logger.js
function Logger(){
  return winston.add(new winston.transports.File({
    filename: "log/error.log",
    maxsize: 1048576,
    level: "error"
  }));
}

こうすることで正常にアプリが起動し、ログファイルも生成されるようになりました。

しかし問題はここからで、技術書にあるような詳細なログが出力されませんでした。
出力されたログはと言うと、次のようなちょっとしたもの。

error.log
{"level":"error"}

この一行で全文です。
明らかに正常な動作とは思えず、しかしどこがどう間違っているのかもよくわからず、現時点ではひとまずこの問題は諦めるということで切り上げました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Vue.js]Vue.jsでChart.jsのoptionsの渡し方についてのメモ

はじめに

vue.jsでchart.jsを使うとき、opitonsの書き方をよく忘れてしまうのでメモ。
構成としては、グラフそのものを表示させる「TheRerdarGraph.vue」と、
その設定をしてある「TheRerdarGraphSetting.vue」を用いる。

memo

TheRardarGraph.vue

<template>
  <div class="small">
    <the-big5-graph-setting :chart-data="datacollection" :options="options" />  /*** ←★opitonsをバインドさせる ***/
  </div>
</template>

<script>
import TheBig5GraphSetting from '@/components/object/project/TheBig5GraphSetting'

export default {
  name: 'TheBig5Graph',
  components: {
    TheBig5GraphSetting,
  },
  props: {
    analysisResult: {
      type: Object,
      required: true,
    },
  },
  data() {
    // @todo this.analysisResult.big5_** の値をグラフ用の datacollection に変換する
    // console.log('big5', this.analysisResult)
    return {
      datacollection: null,
      big5_neurotic_ism: this.analysisResult.big5_neurotic_ism,
      big5_extra_version: this.analysisResult.big5_extra_version,
      big5_openness: this.analysisResult.big5_openness,
      big5_agreeableness: this.analysisResult.big5_agreeableness,
      big5_conscientiousness: this.analysisResult.big5_conscientiousness,
/************************* ★optionsの書き方★ *************************/
      options: {
        scale: {
          ticks: { //http://www.chartjs.org/docs/#scales-radial-linear-scale
            stepSize: 10, // 目盛の間隔
            max: 100, //最大値
          }
        },
        legend: {
          display: false,
        },
      },
    }
/************************* ★optionsの書き方★ *************************/
  },
  mounted() {
    this.fillData()
  },
  methods: {
    fillData() {
      this.datacollection = {
        labels: ['感情性', '外向性', '開放的', '協調性', '誠実性'],
        datasets: [
          {
            label: '1',
            backgroundColor: 'rgba(244, 40, 88,0.3)',
            data: [
              this.big5_neurotic_ism,
              this.big5_extra_version,
              this.big5_openness,
              this.big5_agreeableness,
              this.big5_conscientiousness,
            ],
          },
        ],
      }
    },
    getRandomInt() {
      return Math.floor(Math.random() * (50 - 5 + 1)) + 5
    },
  },
}
</script>

<style>
.small {
  max-width: 300px;
}
</style>

TheRardarGraphSetting.vue

<script>
import { Radar, mixins } from 'vue-chartjs'
const { reactiveProp } = mixins

export default {
  name: 'TheBig5GraphSetting',
  extends: Radar,
  mixins: [reactiveProp],
  props: ['options', 'chartData'],
  mounted() {
    // this.chartData is created in the mixin.
    // If you want to pass options please create a local options object
    this.renderChart(this.chartData, this.options)
  },
}
</script>

<style></style>

最後に

dataにopitonsの内容をオブジェクトとして格納し、
それを

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptで電話番号をハイフン区切りに

//JavaScriptで電話番号をハイフン区切りに
function format_phone_number(input) {
  var groups = [];

  groups[5] = {
            '01564' : 1,
            '01558' : 1,
            '01586' : 1,
            '01587' : 1,
            '01634' : 1,
            '01632' : 1,
            '01547' : 1,
            '05769' : 1,
            '04992' : 1,
            '04994' : 1,
            '01456' : 1,
            '01457' : 1,
            '01466' : 1,
            '01635' : 1,
            '09496' : 1,
            '08477' : 1,
            '08512' : 1,
            '08396' : 1,
            '08388' : 1,
            '08387' : 1,
            '08514' : 1,
            '07468' : 1,
            '01655' : 1,
            '01648' : 1,
            '01656' : 1,
            '01658' : 1,
            '05979' : 1,
            '04996' : 1,
            '01654' : 1,
            '01372' : 1,
            '01374' : 1,
            '09969' : 1,
            '09802' : 1,
            '09912' : 1,
            '09913' : 1,
            '01398' : 1,
            '01377' : 1,
            '01267' : 1,
            '04998' : 1,
            '01397' : 1,
            '01392' : 1,
  };
  groups[4] = {
            '0768' : 2,
            '0770' : 2,
            '0772' : 2,
            '0774' : 2,
            '0773' : 2,
            '0767' : 2,
            '0771' : 2,
            '0765' : 2,
            '0748' : 2,
            '0747' : 2,
            '0746' : 2,
            '0826' : 2,
            '0749' : 2,
            '0776' : 2,
            '0763' : 2,
            '0761' : 2,
            '0766' : 2,
            '0778' : 2,
            '0824' : 2,
            '0797' : 2,
            '0796' : 2,
            '0555' : 2,
            '0823' : 2,
            '0798' : 2,
            '0554' : 2,
            '0820' : 2,
            '0795' : 2,
            '0556' : 2,
            '0791' : 2,
            '0790' : 2,
            '0779' : 2,
            '0558' : 2,
            '0745' : 2,
            '0794' : 2,
            '0557' : 2,
            '0799' : 2,
            '0738' : 2,
            '0567' : 2,
            '0568' : 2,
            '0585' : 2,
            '0586' : 2,
            '0566' : 2,
            '0564' : 2,
            '0565' : 2,
            '0587' : 2,
            '0584' : 2,
            '0581' : 2,
            '0572' : 2,
            '0574' : 2,
            '0573' : 2,
            '0575' : 2,
            '0576' : 2,
            '0578' : 2,
            '0577' : 2,
            '0569' : 2,
            '0594' : 2,
            '0827' : 2,
            '0736' : 2,
            '0735' : 2,
            '0725' : 2,
            '0737' : 2,
            '0739' : 2,
            '0743' : 2,
            '0742' : 2,
            '0740' : 2,
            '0721' : 2,
            '0599' : 2,
            '0561' : 2,
            '0562' : 2,
            '0563' : 2,
            '0595' : 2,
            '0596' : 2,
            '0598' : 2,
            '0597' : 2,
            '0744' : 2,
            '0852' : 2,
            '0956' : 2,
            '0955' : 2,
            '0954' : 2,
            '0952' : 2,
            '0957' : 2,
            '0959' : 2,
            '0966' : 2,
            '0965' : 2,
            '0964' : 2,
            '0950' : 2,
            '0949' : 2,
            '0942' : 2,
            '0940' : 2,
            '0930' : 2,
            '0943' : 2,
            '0944' : 2,
            '0948' : 2,
            '0947' : 2,
            '0946' : 2,
            '0967' : 2,
            '0968' : 2,
            '0987' : 2,
            '0986' : 2,
            '0985' : 2,
            '0984' : 2,
            '0993' : 2,
            '0994' : 2,
            '0997' : 2,
            '0996' : 2,
            '0995' : 2,
            '0983' : 2,
            '0982' : 2,
            '0973' : 2,
            '0972' : 2,
            '0969' : 2,
            '0974' : 2,
            '0977' : 2,
            '0980' : 2,
            '0979' : 2,
            '0978' : 2,
            '0920' : 2,
            '0898' : 2,
            '0855' : 2,
            '0854' : 2,
            '0853' : 2,
            '0553' : 2,
            '0856' : 2,
            '0857' : 2,
            '0863' : 2,
            '0859' : 2,
            '0858' : 2,
            '0848' : 2,
            '0847' : 2,
            '0835' : 2,
            '0834' : 2,
            '0833' : 2,
            '0836' : 2,
            '0837' : 2,
            '0846' : 2,
            '0845' : 2,
            '0838' : 2,
            '0865' : 2,
            '0866' : 2,
            '0892' : 2,
            '0889' : 2,
            '0887' : 2,
            '0893' : 2,
            '0894' : 2,
            '0897' : 2,
            '0896' : 2,
            '0895' : 2,
            '0885' : 2,
            '0884' : 2,
            '0869' : 2,
            '0868' : 2,
            '0867' : 2,
            '0875' : 2,
            '0877' : 2,
            '0883' : 2,
            '0880' : 2,
            '0879' : 2,
            '0829' : 2,
            '0550' : 2,
            '0228' : 2,
            '0226' : 2,
            '0225' : 2,
            '0224' : 2,
            '0229' : 2,
            '0233' : 2,
            '0237' : 2,
            '0235' : 2,
            '0234' : 2,
            '0223' : 2,
            '0220' : 2,
            '0192' : 2,
            '0191' : 2,
            '0187' : 2,
            '0193' : 2,
            '0194' : 2,
            '0198' : 2,
            '0197' : 2,
            '0195' : 2,
            '0238' : 2,
            '0240' : 2,
            '0260' : 2,
            '0259' : 2,
            '0258' : 2,
            '0257' : 2,
            '0261' : 2,
            '0263' : 2,
            '0266' : 2,
            '0265' : 2,
            '0264' : 2,
            '0256' : 2,
            '0255' : 2,
            '0243' : 2,
            '0242' : 2,
            '0241' : 2,
            '0244' : 2,
            '0246' : 2,
            '0254' : 2,
            '0248' : 2,
            '0247' : 2,
            '0186' : 2,
            '0185' : 2,
            '0144' : 2,
            '0143' : 2,
            '0142' : 2,
            '0139' : 2,
            '0145' : 2,
            '0146' : 2,
            '0154' : 2,
            '0153' : 2,
            '0152' : 2,
            '0138' : 2,
            '0137' : 2,
            '0125' : 2,
            '0124' : 2,
            '0123' : 2,
            '0126' : 2,
            '0133' : 2,
            '0136' : 2,
            '0135' : 2,
            '0134' : 2,
            '0155' : 2,
            '0156' : 2,
            '0176' : 2,
            '0175' : 2,
            '0174' : 2,
            '0178' : 2,
            '0179' : 2,
            '0184' : 2,
            '0183' : 2,
            '0182' : 2,
            '0173' : 2,
            '0172' : 2,
            '0162' : 2,
            '0158' : 2,
            '0157' : 2,
            '0163' : 2,
            '0164' : 2,
            '0167' : 2,
            '0166' : 2,
            '0165' : 2,
            '0267' : 2,
            '0250' : 2,
            '0533' : 2,
            '0422' : 2,
            '0532' : 2,
            '0531' : 2,
            '0436' : 2,
            '0428' : 2,
            '0536' : 2,
            '0299' : 2,
            '0294' : 2,
            '0293' : 2,
            '0475' : 2,
            '0295' : 2,
            '0297' : 2,
            '0296' : 2,
            '0495' : 2,
            '0438' : 2,
            '0466' : 2,
            '0465' : 2,
            '0467' : 2,
            '0478' : 2,
            '0476' : 2,
            '0470' : 2,
            '0463' : 2,
            '0479' : 2,
            '0493' : 2,
            '0494' : 2,
            '0439' : 2,
            '0268' : 2,
            '0480' : 2,
            '0460' : 2,
            '0538' : 2,
            '0537' : 2,
            '0539' : 2,
            '0279' : 2,
            '0548' : 2,
            '0280' : 2,
            '0282' : 2,
            '0278' : 2,
            '0277' : 2,
            '0269' : 2,
            '0270' : 2,
            '0274' : 2,
            '0276' : 2,
            '0283' : 2,
            '0551' : 2,
            '0289' : 2,
            '0287' : 2,
            '0547' : 2,
            '0288' : 2,
            '0544' : 2,
            '0545' : 2,
            '0284' : 2,
            '0291' : 2,
            '0285' : 2,
            '0120' : 3,
            '0570' : 3,
            '0800' : 3,
            '0990' : 3,
  };
  groups[3] = {
            '020' : 3,
            '070' : 3,
            '080' : 3,
            '090' : 3,
            '099' : 3,
            '054' : 3,
            '058' : 3,
            '098' : 3,
            '095' : 3,
            '097' : 3,
            '052' : 3,
            '053' : 3,
            '011' : 3,
            '096' : 3,
            '049' : 3,
            '015' : 3,
            '048' : 3,
            '072' : 3,
            '084' : 3,
            '028' : 3,
            '024' : 3,
            '076' : 3,
            '023' : 3,
            '047' : 3,
            '029' : 3,
            '075' : 3,
            '025' : 3,
            '055' : 3,
            '026' : 3,
            '079' : 3,
            '082' : 3,
            '027' : 3,
            '078' : 3,
            '077' : 3,
            '083' : 3,
            '022' : 3,
            '086' : 3,
            '089' : 3,
            '045' : 3,
            '044' : 3,
            '092' : 3,
            '046' : 3,
            '017' : 3,
            '093' : 3,
            '059' : 3,
            '073' : 3,
            '019' : 3,
            '087' : 3,
            '042' : 3,
            '018' : 3,
            '043' : 3,
            '088' : 3,
            '050' : 4,
  };
  groups[2] = {
            '04' : 4,
            '03' : 4,
            '06' : 4,
  };
  groups[6] = {
            'aa' : 4,
            'bb' : 4,
  };

  var number = input.replace('/[^\d]++/', '');

  var tel = '';

  groups.forEach(function (data, i){
    var area = number.slice(0,i);

    if(data[area]){
      var formatted = '';
      var test1 = number.slice(0,i);
      var test2 = number.slice(i,i+data[area]);
      var test3 = number.slice(i+data[area]);

      tel = test1 + '-' + test2 + '-' + test3;
    }
  });

  return tel;

}

参考:
https://qiita.com/mpyw/items/431c0c8cb70084a74be5

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Next.js ApplicationにPassport+Auth0で認証機能を追加する

Next.jsのサンプルアプリケーションにPassport+Auth0を組み込んで認証機能を追加してみました。この記事はこちらの原文を元に作成しています。完成版のソースコードはここで公開しています。

環境

  • OS : macOS Mojave 10.14.5
  • Node.js : 10.15.3
  • npm : 6.4.1

手順

準備〜基本プロジェクトの作成

NPM Project Directoryを作成して初期化します。

$ mkdir nextjs-passport
$ cd nextjs-passport
$ npm init -y

必要なパッケージをインストールします。

$ npm i body-parser bootstrap dotenv \
  dotenv-webpack express isomorphic-fetch \
  next react react-bootstrap \
  react-dom styled-components

.babelrcを作成してコンパイルパラメータを設定します。

.babelrc
// ./.babelrc

{
  "presets": ["next/babel"],
  "plugins": [["styled-components", { "ssr": true }]]
}

.envを作成して環境変数を設定します。このタイミングではPORTだけで問題ありません。

.env
# ./.env

PORT=3000

next.config.jsを作成して.envファイルのパスを指定します。

next.config.js
// ./next.config.js

require("dotenv").config();

const path = require("path");
const Dotenv = require("dotenv-webpack");

module.exports = {
  webpack: config => {
    config.plugins = config.plugins || [];

    config.plugins = [
      ...config.plugins,

      // Read the .env file
      new Dotenv({
        path: path.join(__dirname, ".env"),
        systemvars: true
      })
    ];

    return config;
  }
};

ソースコードを保管するディレクトリを作成します。

$ mkdir -p src/components
$ mkdir src/pages
$ mkdir src/state

src/pages配下にindex.jsを作成します。

index.js
// ./src/pages/index.js
import styled from "styled-components";

const Rocket = styled.div`
  text-align: center;
  img {
    width: 630px;
  }
`;

function Index() {
  return (
    <Rocket>
      <img src="https://media.giphy.com/media/QbumCX9HFFDQA/giphy.gif" />
    </Rocket>
  );
}

export default Index;

src/pages配下に_document.jsを作成します。

_document.js
// ./src/pages/_document.js

import Document, { Head, Html, Main, NextScript } from "next/document";
import { ServerStyleSheet } from "styled-components";

export default class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const sheet = new ServerStyleSheet();
    const originalRenderPage = ctx.renderPage;

    try {
      ctx.renderPage = () =>
        originalRenderPage({
          enhanceApp: App => props => sheet.collectStyles(<App {...props} />)
        });

      const initialProps = await Document.getInitialProps(ctx);
      return {
        ...initialProps,
        styles: (
          <>
            {initialProps.styles}
            {sheet.getStyleElement()}
          </>
        )
      };
    } finally {
      sheet.seal();
    }
  }

  render() {
    return (
      <Html>
        <Head>
          <link
            rel="stylesheet"
            href="https://bootswatch.com/4/darkly/bootstrap.min.css"
          />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

package.jsonのscriptsプロパティを下記のように修正します。

package.json
// ./package.json

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "next ./src"
  },

npm run devを実行してアプリケーションを起動しChromeでhttp://localhost:3000にアクセスします。画面が正常に表示されれば成功です。

$ npm run dev



Applicationの作成

src配下にthoughts-api.jsを作成してAPI Endpointを定義します。

thoughts-api.js
// ./src/thoughts-api.js

const bodyParser = require("body-parser");
const express = require("express");

const router = express.Router();

router.use(bodyParser.json());

const thoughts = [
  { _id: 123, message: "I love pepperoni pizza!", author: "unknown" },
  { _id: 456, message: "I'm watching Netflix.", author: "unknown" }
];

router.get("/api/thoughts", (req, res) => {
  const orderedThoughts = thoughts.sort((t1, t2) => t2._id - t1._id);
  res.send(orderedThoughts);
});

router.post("/api/thoughts", (req, res) => {
  const { message } = req.body;
  const newThought = {
    _id: new Date().getTime(),
    message,
    author: "unknown"
  };
  thoughts.push(newThought);
  res.send({ message: "Thanks!" });
});

src配下にserver.jsを作成してhttpリクエストをlistenします。

server.js
// ./src/server.js

require("dotenv").config();
const express = require("express");
const http = require("http");
const next = require("next");
const thoughtsAPI = require("./thoughts-api");

const dev = process.env.NODE_ENV !== "production";
const app = next({
  dev,
  dir: "./src"
});
const handle = app.getRequestHandler();

app.prepare().then(() => {
  const server = express();

  server.use(thoughtsAPI);

  // handling everything else with Next.js
  server.get("*", handle);

  http.createServer(server).listen(process.env.PORT, () => {
    console.log(`listening on port ${process.env.PORT}`);
  });
});

package.jsonのscriptsプロパティを下記のように修正します。

package.json
// ./package.json

"scripts": {
  "dev": "node ./src/server.js",
  "build": "next build ./src",
  "start": "NODE_ENV=production node ./src/server.js"
},

npm run devを実行してアプリケーションを起動しChromeでhttp://localhost:3000/api/thoughtsにアクセスします。画面が正常に表示されれば成功です。

$ npm run dev

src/components配下にThought.js, Thoughts.jsを作成してAPIを呼び出します。

Thought.js
// ./src/components/Thought.js

import Card from "react-bootstrap/Card"

export default function Thought({ thought }) {
  const cardStyle = { marginTop: "15px" };
  return (
    <Card bg="secondary" text="white" style={cardStyle}>
      <Card.Body>
        <Card.Title>{thought.message}</Card.Title>
        <Card.Text>by {thought.author}</Card.Text>
      </Card.Body>
    </Card>
  );
}
Thoughts.js
// ./src/components/Thoughts.js

import Col from "react-bootstrap/Col";
import Row from "react-bootstrap/Row";
import Thought from "./Thought";

export default function Thoughts(props) {
  return (
    <Row>
      <Col xs={12}>
        <h2>Latest Thoughts</h2>
      </Col>
      {props.thoughts &&
        props.thoughts.map(thought => (
          <Col key={thought._id} xs={12} sm={6} md={4} lg={3}>
            <Thought thought={thought} />
          </Col>
        ))}
      {!props.thoughts && <Col xs={12}>Loading...</Col>}
    </Row>
  );
}

src/pages/index.jsを修正してAPI経由で取得するデータを表示します。

index.js
// ./src/pages/index.js

import Container from "react-bootstrap/Container";
import fetch from "isomorphic-fetch";
import Thoughts from "../components/Thoughts";

function Index(props) {
  return (
    <Container>
      <Thoughts thoughts={props.thoughts} />
    </Container>
  );
}

Index.getInitialProps = async ({ req }) => {
  const baseURL = req ? `${req.protocol}://${req.get("Host")}` : "";
  const res = await fetch(`${baseURL}/api/thoughts`);
  return {
    thoughts: await res.json()
  };
};

export default Index;

npm run devを実行してアプリケーションを起動しChromeでhttp://localhost:3000にアクセスします。画面が正常に表示されれば成功です。

src/components配下にNavbar.jsを、src/pages配下に_app.jsを作成してナビゲーションバーを追加します。

Navbar.js
// ./src/components/Navbar.js

import Link from "next/link";
import Container from "react-bootstrap/Container";
import Navbar from "react-bootstrap/Navbar";
import Nav from "react-bootstrap/Nav";

export default function AppNavbar() {
  const navbarStyle = { marginBottom: "25px" };
  return (
    <Navbar bg="light" expand="lg" style={navbarStyle}>
      <Container>
        <Navbar.Brand>
          <Link href="/">
            <a>Thoughts!</a>
          </Link>
        </Navbar.Brand>
        <Navbar.Toggle aria-controls="basic-navbar-nav" />
        <Navbar.Collapse id="basic-navbar-nav">
          <Nav className="mr-auto">
            <Link href="/share-thought">
              <a className="nav-link">New Thought</a>
            </Link>
          </Nav>
        </Navbar.Collapse>
      </Container>
    </Navbar>
  );
}
_app.js
// ./src/pages/_app.js

import React from "react";
import App, { Container as NextContainer } from "next/app";
import Head from "next/head";
import Container from "react-bootstrap/Container";
import Jumbotron from "react-bootstrap/Jumbotron";
import Navbar from "../components/Navbar";

class MyApp extends App {
  render() {
    const { Component, pageProps } = this.props;

    return (
      <NextContainer>
        <Head>
          <title>Thoughts!</title>
        </Head>
        <Navbar />
        <Container>
          <Jumbotron>
            <Component {...pageProps} />
          </Jumbotron>
        </Container>
      </NextContainer>
    );
  }
}

export default MyApp;

src/pages配下にshare-thought.jsを作成して新しいルートを定義します。

share-thought.js
// ./src/pages/share-thought.js

import Form from "react-bootstrap/Form";
import Router from "next/router";
import Button from "react-bootstrap/Button";
import Container from "react-bootstrap/Container";
const { useState } = require("react");

export default function ShareThought() {
  const [message, setMessage] = useState("");

  async function submit(event) {
    event.preventDefault();
    await fetch("/api/thoughts", {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        message
      })
    });
    Router.push("/");
  }

  return (
    <Container>
      <Form onSubmit={submit}>
        <Form.Group>
          <Form.Label>What is in your mind?</Form.Label>
          <Form.Control
            type="text"
            placeholder="Say something"
            onChange={e => setMessage(e.target.value)}
            value={message}
          />
        </Form.Group>
        <Button variant="primary" type="submit">
          Share
        </Button>
      </Form>
    </Container>
  );
}

npm run devを実行してアプリケーションを起動しChromeでhttp://localhost:3000/api/thoughtsにアクセスします。画面が正常に表示されれば成功です。


認証機能の組み込み

Auth0にログインしてテナントを作成します。この記事では詳細な手順は割愛しています。こちらをご参照お願いします。

作成したApplicationをAuth0に登録します。左のペインからApplicationsをクリックして+CREATE APPLICATIONを押します。


Nameに任意の名前を入力、Choose an application typeでRegular Web Applicationsを選択してCREATEを押します。

Login/Logout後にリダイレクトさせるURLを指定するため、Settingsタブを選択してAllowed Callback URLs, Allowed Logout URLsに下記の値を入力してSAVE CHANGESを押します。

  • Allowed Callback URLs : http://localhost:3000/callback
  • Allowed Logout URLs : http://localhost:3000

必要なパッケージをインストールします。

$ npm install passport passport-auth0 express-session uid-safe

src配下のserver.jsを修正します。

server.js
// ./src/server.js

require("dotenv").config();
const express = require("express");
const http = require("http");
const next = require("next");
const session = require("express-session");
// 1 - importing dependencies
const passport = require("passport");
const Auth0Strategy = require("passport-auth0");
const uid = require('uid-safe');
const authRoutes = require("./auth-routes");
const thoughtsAPI = require("./thoughts-api");

const dev = process.env.NODE_ENV !== "production";
const app = next({
  dev,
  dir: "./src"
});
const handle = app.getRequestHandler();

app.prepare().then(() => {
  const server = express();

  // 2 - add session management to Express
  const sessionConfig = {
    secret: uid.sync(18),
    cookie: {
      maxAge: 86400 * 1000 // 24 hours in milliseconds
    },
    resave: false,
    saveUninitialized: true
  };
  server.use(session(sessionConfig));

  // 3 - configuring Auth0Strategy
  const auth0Strategy = new Auth0Strategy(
    {
      domain: process.env.AUTH0_DOMAIN,
      clientID: process.env.AUTH0_CLIENT_ID,
      clientSecret: process.env.AUTH0_CLIENT_SECRET,
      callbackURL: process.env.AUTH0_CALLBACK_URL
    },
    function(accessToken, refreshToken, extraParams, profile, done) {
      return done(null, profile);
    }
  );

  // 4 - configuring Passport
  passport.use(auth0Strategy);
  passport.serializeUser((user, done) => done(null, user));
  passport.deserializeUser((user, done) => done(null, user));

  // 5 - adding Passport and authentication routes
  server.use(passport.initialize());
  server.use(passport.session());
  server.use(authRoutes);

  server.use(thoughtsAPI);

  // 6 - you are restricting access to some routes
  const restrictAccess = (req, res, next) => {
    if (!req.isAuthenticated()) return res.redirect("/login");
    next();
  };

  server.use("/profile", restrictAccess);
  server.use("/share-thought", restrictAccess);

  // handling everything else with Next.js
  server.get("*", handle);

  http.createServer(server).listen(process.env.PORT, () => {
    console.log(`listening on port ${process.env.PORT}`);
  });
});

src配下にauth-routes.jsを作成して認証ルートを定義します。

auth-routes.js
// ./src/auth-routes.js

const express = require("express");
const passport = require("passport");

const router = express.Router();

router.get("/login", passport.authenticate("auth0", {
  scope: "openid email profile"
}), (req, res) => res.redirect("/"));

router.get("/callback", (req, res, next) => {
  passport.authenticate("auth0",  (err, user) => {
    if (err) return next(err);
    if (!user) return res.redirect("/login");
    req.logIn(user, (err) => {
      if (err) return next(err);
      res.redirect("/");
    });
  })(req, res, next);
});

router.get("/logout", (req, res) => {
  req.logout();

  const {AUTH0_DOMAIN, AUTH0_CLIENT_ID, BASE_URL} = process.env;
  res.redirect(`https://${AUTH0_DOMAIN}/logout?client_id=${AUTH0_CLIENT_ID}&returnTo=${BASE_URL}`);
});

module.exports = router;

.envに必要な環境変数を追加します。

.env
PORT=3000
AUTH0_DOMAIN=mokomoko.auth0.com
AUTH0_CLIENT_ID=o4u8CRD1roZEN2t96AZZnfrO3NeegeN7
AUTH0_CLIENT_SECRET=...
AUTH0_CALLBACK_URL=http://localhost:3000/callback
BASE_URL=http://localhost:3000

AUTH0_DOMAIN, AUTH0_CLIENR_ID, AUTH0_CLIENT_SECRETはApplications->Settingsから確認できます。

src配下のthoughts-api.jsを修正します。

thoughts-api.js
const bodyParser = require("body-parser");
const express = require("express");

const router = express.Router();

router.use(bodyParser.json());

const thoughts = [
  { _id: 123, message: "I love pepperoni pizza!", author: "unknown" },
  { _id: 456, message: "I'm watching Netflix.", author: "unknown" }
];

router.get("/api/thoughts", (req, res) => {
  const orderedThoughts = thoughts.sort((t1, t2) => t2._id - t1._id);
  res.send(orderedThoughts);
});

function ensureAuthenticated(req, res, next) {
  if (req.isAuthenticated()) return next();
  res.send(401);
}

router.post("/api/thoughts", ensureAuthenticated, (req, res) => {
  const { message } = req.body;
  const newThougth = {
    _id: new Date().getTime(),
    message,
    author: req.user.displayName
  };
  thoughts.push(newThougth);
  res.send({ message: "Thanks!" });
});

module.exports = router;

src/components配下のNavbar.jsを修正します。

Navbar.js
// ./src/components/Navbar.js

import Link from "next/link";
import Container from "react-bootstrap/Container";
import Navbar from "react-bootstrap/Navbar";
import Nav from "react-bootstrap/Nav";

export default function AppNavbar({ user }) {
  const navbarStyle = { marginBottom: "25px" };
  return (
    <Navbar bg="light" expand="lg" style={navbarStyle}>
      <Container>
        <Navbar.Brand>
          <Link href="/">
            <a>Thoughts!</a>
          </Link>
        </Navbar.Brand>
        <Navbar.Toggle aria-controls="basic-navbar-nav" />
        <Navbar.Collapse id="basic-navbar-nav">
          <Nav className="mr-auto">
            {user && (
              <>
                <Link href="/share-thought">
                  <a className="nav-link">New Thought</a>
                </Link>
                <Link href="/profile">
                  <a className="nav-link">Profile</a>
                </Link>
                <Link href="/logout">
                  <a className="nav-link">Log Out</a>
                </Link>
              </>
            )}
            {!user && (
              <Link href="/login">
                <a className="nav-link">Log In</a>
              </Link>
            )}
          </Nav>
        </Navbar.Collapse>
      </Container>
    </Navbar>
  );
}

src/pages配下にprofile.jsを作成してユーザプロファイル画面を表示します。

profile.js
// ./src/pages/profile.js

import styled from "styled-components";

const Picture = styled.img`
  border-radius: 50%;
  border: 3px solid white;
  width: 100px;
`;

function Profile({ user }) {
  return (
    <div>
      <h2>
        <Picture src={user.picture} alt={user.displayName} /> Hello, {user.displayName}
      </h2>
      <p>This is what we know about you:</p>
      <ul>
        { Object.keys(user).map(key => (
          <li key={key}>{key}: {user[key].toString()}</li>
        ))}
      </ul>
    </div>
  );
}

export default Profile;

src/pages配下の_app.jsを修正します。

_app.js
// ./src/pages/_app.js

import React from "react";
import App, { Container as NextContainer } from "next/app";
import Head from "next/head";
import Container from "react-bootstrap/Container";
import Jumbotron from "react-bootstrap/Jumbotron";
import Navbar from "../components/Navbar";

class MyApp extends App {
  static async getInitialProps({ Component, ctx }) {
    let pageProps = {};
    if (Component.getInitialProps) {
      pageProps = await Component.getInitialProps(ctx);
    }
    if (ctx.req && ctx.req.session.passport) {
      pageProps.user = ctx.req.session.passport.user;
    }
    return { pageProps };
  }

  constructor(props) {
    super(props);
    this.state = {
      user: props.pageProps.user
    };
  }

  render() {
    const { Component, pageProps } = this.props;

    const props = {
      ...pageProps,
      user: this.state.user,
    };

    return (
      <NextContainer>
        <Head>
          <title>Thoughts!</title>
        </Head>
        <Navbar user={this.state.user} />
        <Container>
          <Jumbotron>
            <Component {...props} />
          </Jumbotron>
        </Container>
      </NextContainer>
    );
  }
}

export default MyApp;

npm run devを実行してアプリケーションを起動しChromeでhttp://localhost:3000にアクセスします。Auth0の組み込みLogin画面が表示されて、ログイン後Profileタブをクリックしてユーザプロファイル正常に表示されてれば成功です。



おわりです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptのInfinityについて

Infinity

グローバルプロパティ Infinity は無限大を表す数値です。
仕様

> 1 / 0
Infinity
> -1 / 0
-Infinity

以下の定数でも表すことができます。

> Number.POSITIVE_INFINITY
Infinity
> Number.NEGATIVE_INFINITY
-Infinity

比較

> Infinity === Infinity
true
> -Infinity === -Infinity
true
> Infinity === -Infinity
false

加算・減算

Infinityに対しての加算・減算の結果はInfinityになります。

> Infinity + 1
Infinity
> Infinity - 1
Infinity
> Infinity++
Infinity
> Infinity--
Infinity
> Infinity + Infinity
Infinity
> -Infinity -Infinity
-Infinity
> Infinity - Infinity // 不定形でNaNになります。
NaN

ちなみにNumberで扱える値の範囲を越えるとInfinityになるようです。
精度の問題で丸めが発生するので、Number.MAX_VALUEよりも大きく見えても、Number.MAX_VALUEが返るケースもあります。

> Number.MAX_VALUE
1.7976931348623157e+308
> 1.7976931348623158e+308
1.7976931348623157e+308
> 1.7976931348623159e+308
Infinity

そのため、数値が大きすぎる比較は意図せずtrueになることもあるようです。

> 2e+308 === 3e+308
true

乗算

> Infinity * 0 // 不定形でNaNになります。
NaN
> Infinity * 1
Infinity
> Infinity * -1
-Infinity
> Infinity * Infinity
Infinity
> -Infinity * Infinity
-Infinity

除算

> Infinity / Infinity // 不定形でNaNになります。
NaN
> Infinity / 0
Infinity
> Infinity / -1
-Infinity

剰余演算

> Infinity % Infinity // 不定形でNaNになります。
NaN
> 3 % Infinity // 被除数が有限の時は被除数と同じ結果になります。
3

累乗

> Infinity**0 // 0乗は1になります。
1
> Infinity**1
Infinity
> Infinity**Infinity
Infinity
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go言語でJavaScriptを使ってオリジナルなコマンドシェルを作る(EXCELの読み込みオブジェクトの追加)

概要

前回投稿した「Go言語でJavaScriptを使ってオリジナルなコマンドシェルを作る」にEXCELを読み込む関数を追加する。これを実装することでGo側でJavaScriptのJSON, Arrayの作り方、そしてJavaScript側のCallback関数の呼び出しを実装する

環境

  • 前回投稿のソースに追加するだけなので、全ソースコードは掲載しません
  • EXCELのパッケージ
    • github.com/tealeg/xlsx
  • テストEXCELデータ
    • ブック名 Book1.xlsx
    • シート名 Sheet1
A B C
1 111 aaa
2 112 bbb
3 113 ccc
4 114 ddd

実装

excel(ブック名, シート名)の関数を定義する

initialSetting()
    rt.Set("excel", js.excel)

import "github.com/tealeg/xlsx"

type excelSheet struct {
    js    *jsRuntime
    sheet *xlsx.Sheet
}

func (js *jsRuntime) excel(file, sheet string) *excelSheet {
    rt := js.runtime
    xfile, err := xlsx.OpenFile(file)
    if err != nil {
        rt.Interrupt(rt.NewGoError(err))
        return nil
    }
    xsheet := xfile.Sheet[sheet]
    if xsheet == nil {
        rt.Interrupt("Error: not found excel sheet: " + sheet)
        return nil
    }
    return &excelSheet{js: js, sheet: xsheet}
}

オブジェクトは出来ましたがメソッドがないので、これから次の3メソッドを作っていきます。すべてJavaScriptのCallBack関数を呼び出す仕様です。

  • ForEachSlice
    • Go言語のSliceを返すがJavaScriptのArrayではないのでArrayのメソッドで使えないものがあります。
  • ForEachArray
    • JavaScriptのArrayオブジェクトを返す
  • ForEachJSON
    • JavaScriptのJSONオブジェクトを返す

ForEachSliceの実装

convData関数はEXCELのデータタイプを判断してタイプに合った型に変換します。ただし、今回はxlsx.CellTypeNumericとそれ以外だけにしました。

func (s *excelSheet) convData(cell *xlsx.Cell) goja.Value {
    rt := s.js.runtime
    typ := cell.Type()
    if typ == xlsx.CellTypeNumeric {
        val, err := cell.Int()
        if err == nil {
            return rt.ToValue(val)
        }
        return rt.ToValue(cell.String())
    }
    return rt.ToValue(cell.String())
}

ForEachSliceのソース。引数はJavaScriptのCallBack関数です。CallBack関数はfunction(行番号,行のSlice)です。行番号は1から始めます。

func (s *excelSheet) ForEachSlice(callBack goja.Callable) {
    rt := s.js.runtime
    if s.sheet == nil {
        rt.Interrupt("sheet is closed")
        return
    }
    for rix, row := range s.sheet.Rows {
        cells := make([]interface{}, len(row.Cells))
        for cix, cell := range row.Cells {
            cells[cix] = s.convData(cell)
        }
        _, err := callBack(goja.Undefined(), rt.ToValue(rix+1), rt.ToValue(cells))
        if err != nil {
            rt.Interrupt(err)
            return
        }
    }
    s.sheet = nil
}

callBackの第一引数をundefinedとしていますが、第一引数はJavaScript側でthisとして使われますので、JavaScript側でthisが必要ならForEachSliceの引数にthisが受け取れるように追加してください。当然呼び出し側もthisにしたいオブジェクトをセットします。

func (s *excelSheet) ForEachSlice(this goja.Value, callBack goja.Callable) {
       callBack(this, rt.ToValue(rix+1), rt.ToValue(cells))
}
go run main.go
> excel("Book1.xlsx", "Sheet1").ForEachSlice(function(ix, row) {
...>    print(ix, row)
...> })
1 [1,111,"aaa"]
2 [2,112,"bbb"]
3 [3,113,"ccc"]
4 [4,114,"ddd"]
undefined
>

JavaScriptのArrayではないのでpushなどは使えません。forEachは使えます。

go run main.go
> excel("Book1.xlsx", "Sheet1").ForEachSlice(function(ix, row) {
...>   row.push(ix)
...>   print(ix,row)
...> })
TypeError: Cannot extend Go slice at push (native) at console:1:43(9)
>

ForEachArrayの実装

newArray関数はArrayオブジェクトの生成と要素を追加するpushメソッドを取得します

func (s *excelSheet) newArray() (goja.Value, goja.Callable) {
    rt := s.js.runtime
    arr, err := rt.RunString("new Array()")
    if err != nil {
        rt.Interrupt(err)
        return nil, nil
    }
    arrObj, ok := arr.(*goja.Object)
    if !ok {
        rt.Interrupt("Array not defined")
        return nil, nil
    }

    push := arrObj.Get("push")
    pushFunc, ok := goja.AssertFunction(push)
    if !ok {
        rt.Interrupt("Array.push not defined")
        return nil, nil
    }
    return arr, pushFunc
}

ForEachArrayのソース。引数はJavaScriptのCallBack関数です。CallBack関数はfunction(行番号,行のArray)です。行番号は1から始めます。

func (s *excelSheet) ForEachArray(callBack goja.Callable) {
    rt := s.js.runtime
    if s.sheet == nil {
        rt.Interrupt("sheet is closed")
        return
    }
    for rix, row := range s.sheet.Rows {
        arr, pushFunc := s.newArray()
        if arr == nil {
            return
        }
        for _, cell := range row.Cells {
            pushFunc(arr, s.convData(cell))
        }
        _, err := callBack(goja.Undefined(), rt.ToValue(rix+1), arr)
        if err != nil {
            rt.Interrupt(err)
            return
        }
    }
    s.sheet = nil
}
go run main.go
> excel("Book1.xlsx", "Sheet1").ForEachArray(function(ix, row) {
...>   print(ix,row)
...> })
1 [1,111,"aaa"]
2 [2,112,"bbb"]
3 [3,113,"ccc"]
4 [4,114,"ddd"]
undefined
>

上記の結果はForEachSliceと変わらなく見えます。今度はpushを使ってみます。

go run main.go
> excel("Book1.xlsx", "Sheet1").ForEachArray(function(ix, row) {
...>   row.push(ix)
...>   print(ix,row)
...> })
1 [1,111,"aaa",1]
2 [2,112,"bbb",2]
3 [3,113,"ccc",3]
4 [4,114,"ddd",4]
undefined
>

ちゃんとpushできています。

ForEachJSONの実装

ForEachJSONのソース。引数はJavaScriptのCallBack関数です。CallBack関数はfunction(行番号,行のJSON)です。行番号は1から始めます。下記のJSONのフィールド名はA1,B1,C1のようにEXCELのセルの形式にしました。ただしA~Zまでです。

const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

func (s *excelSheet) ForEachJSON(callBack goja.Callable) {
    rt := s.js.runtime
    if s.sheet == nil {
        rt.Interrupt("sheet is closed")
        return
    }
    for rix, row := range s.sheet.Rows {
        json := rt.NewObject()
        for cix, cell := range row.Cells {
            name := fmt.Sprintf("%s%d", alphabet[cix:cix+1], rix+1)
            json.Set(name, s.convData(cell))
        }
        _, err := callBack(goja.Undefined(), rt.ToValue(rix+1), json)
        if err != nil {
            rt.Interrupt(err)
            return
        }
    }
    s.sheet = nil
}
go run main.go
> excel("Book1.xlsx", "Sheet1").ForEachJSON(function(ix, row) {
...>   print(ix,JSON.stringify(row))
...> })
1 {"A1":1,"B1":111,"C1":"aaa"}
2 {"A2":2,"B2":112,"C2":"bbb"}
3 {"A3":3,"B3":113,"C3":"ccc"}
4 {"A4":4,"B4":114,"C4":"ddd"}
undefined
>

サブドキュメントを作るには

   json := rt.NewObject()
   json.Set("a", rt.ToValue(1))
   subdoc := rt.NewObject()
   subdoc.Set("field1", rt.ToValue("abcde"))
   json.Set("subdoc", subdoc)

とすると下記のようなJSONができます。

{"a":1,"subdoc":{"field1":"abcde"}}

参照

まとめ

3つのメソッドを用意しましたが、一つで良ければexcel(ブック名,シート名,callback関数)だけで良いかもしれません。

これでJSONとcallbackができたのでほとんどの事が出来ます。
データ量が少なければ2次元配列またはJSONの配列で全データを返すもありだと思います。この時はcallback関数は不要でexcel()の戻り値として処理します。

いままでエラーはInterruptを使っていますが、JavaScript実行中に呼び出されたGo言語でpanicを使っても異常終了せずに、JavaScriptでthrowと同様のふるまいをします。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

vue-cli無しでvueの単一コンポーネントファイルを使用する。scssも。

経緯

よくvue関連の情報で「単一ファイルコンポーネントを使用するにはvue-cliが必要」と目にする気がします。
情報例:
https://jp.vuejs.org/v2/guide/single-file-components.html 
 (「これはあなたがまだ使ったことのない、いくつかの追加ツールの使い方を学ぶことを意味します。」「Vue CLI 3 を確認することをお勧めします。」)

単一ファイルコンポーネントはとても便利なのですが、vue-cliは学習敷居が高いので以下のような困った場面があります。
・手早く単一ファイルコンポーネントを学習したい。
・既存のwebシステムにvueを適応させたい。単一ファイルコンポーネントありで。
・開発メンバーにvueを普及させたい。

「node.jsとかnpmとかwebpackとかvue-cliとか使用せずに単純にhtmlからvueファイルを使用したい」「できれば、スタイルシートもscssで記述したい」と思っていたところ、http-vue-loaderというものがあるそうです。
@horikeso様の記事で知りました。

サンプル1

これで単一ファイルコンポーネントを使用できました。

index-1.html
<!DOCTYPE html>
<html>

<head>
</head>

<body>
    <div id="app">
        <vc-main-1></vc-main-1>
    </div>

    <!-- promise (IEに必要) -->
    <script src="https://unpkg.com/es6-promise"></script>

    <!-- vue -->
    <script src="https://unpkg.com/vue"></script>

    <!-- http-vue-loader -->
    <script src="https://unpkg.com/http-vue-loader"></script>

    <script type="text/javascript">
        //メインコンポーネント
        var vue = new Vue({
            el: '#app',
            components: {
                'vc-main-1': httpVueLoader('vc-main-1.vue') //他階層にあるなら、('js/components/vc-main-1.vue')の様な指定も可能
            }
        });
    </script>
</body>

</html>
vc-main-1.vue
<template>
  <div class="vc-main-1">
    <h1>Hello</h1>
    <p>{{message}}</p>
  </div>
</template>

<script>
module.exports = {
  data: function() {
    return {
      message: 'こんにちは'
    }
  }
}
</script>

<style>
.vc-main-1 h1{
    font-size: 3.5rem;
}
.vc-main-1 p{
    font-weight: bold;
}
</style>

サンプル1の注意点

「module.exports = {」の箇所が「export default {」だとダメな様子です。

サンプル2 scssを使用する

こんな風にするとscssも使用できるみたいです。

index-2.html
<!DOCTYPE html>
<html>

<head>
</head>

<body>
    <div id="app">
        <vc-main-2></vc-main-2>
    </div>

    <!-- promise (IEに必要) -->
    <script src="https://unpkg.com/es6-promise"></script>

    <!-- sass -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/sass.js/0.10.13/sass.sync.min.js"></script>

    <!-- vue -->
    <script src="https://unpkg.com/vue"></script>

    <!-- http-vue-loader -->
    <script src="https://unpkg.com/http-vue-loader"></script>

    <script type="text/javascript">
        //set sass to http-vue-loader
        httpVueLoader.langProcessor.scss = function(scssText) {
            return new Promise(function(resolve, reject) {
                Sass.compile(scssText, function(result) {
                    if (result.status === 0)
                        resolve(result.text)
                    else
                        reject(result)
                });


            });
        }

        //メインコンポーネント
        var vue = new Vue({
            el: '#app',
            components: {
                'vc-main-2': httpVueLoader('vc-main-2.vue')
            }
        });
    </script>
</body>

</html>
vc-main-2.vue
<template>
  <div class="vc-main-2">
    <h1>Hello</h1>
    <p>{{message}}</p>
  </div>
</template>

<script>
module.exports = {
  data: function() {
    return {
      message: 'こんにちは'
    }
  }
}
</script>

<style lang="scss">
.vc-main-2 {
  h1{
    font-size: 3.5rem;
  }
  p{
    font-weight: bold;
  }
}
</style>

サンプル2の注意点

IEだとできませんでした。。。
なにか方法があるかもしれませんが。

以上です。
vue-cliが使用できない場合にとても便利かも。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Catapultのトラザクションを送信しよう!

今回はCatapultのトランザクションを送信します。
これができればもうCatapultで何だってできる(はずです)。

注)今回説明するのはXEMBook-sdkを利用したトランザクションの作成方法です。
NEM財団が推奨するnem-libraryを使用したい場合はnem developer centerを参考にしてください。
https://nemtech.github.io/ja/guides/transaction/sending-a-transfer-transaction.html

必要な作業は以下の通りです。

  • 必要なデータを揃える
  • データをシリアライズ化する
  • シリアライズされたデータを署名する
  • Catapultネットワークにアナウンスする

ではまず、データ構成です。JSONオブジェクト形式で定義していきます。


const jsonTx = {
    "version": 36867,
    "type": 16724,
    "fee": [0, 0],
    "deadline": deadline(),
    "recipient": address,
    "message": {
        "type": 0,
        "payload": "Hello XEMBook-sdk! "
    },
    "mosaics": [
        {
        "id": [853116887, 2007078553],
        "amount": [0, 0]
        }
    ]
}

NEM1のころと大きく変わっている箇所があります。
fee,amountが [0,0]という感じで配列になっていますね。

これはJavaScriptなどで小数点を含んだ桁幅の大きい数字を扱う(四則演算する)と誤差が生じてしまうので分解して指定することになります。大きな数字でなければ[1234,0]といった感じで0番目(左側)の方に指定してやります。
deadlineも配列指定になるので、uint64.fromUintというfunctionを使って以下のように出力させます。
NEMは独自のタイムスタンプを持っており西暦には依存しません。西暦で言うと2016-04-01 00:00(UTC 1459468800000ミリ秒)がCatapultの始まりと定義されているようです。

function deadline(deadlineParam) {
    const NetworkTime = (new Date()).getTime() - 1459468800000;
    const deadlineValue = deadlineParam || 60 * 60 * 1000;
    return uint64.fromUint(deadlineValue + NetworkTime);
}

次に作成したデータをシリアライズします。
通常のWebサービスだとjsonのまま気軽になげられるのですが、ブロックチェーンではクライアント側で署名をする必要があるので、作成したデータの内容が一意になるようにシリアライズする必要があります。個人的にはここが一番ハードルが高いかなと思っています。

const serializedTx = serialize(jsonTx);

XEMBook-sdkライブラリを使うと1行でかけますが、以下のような処理を内部で行なっています。今回もNodeJS依存のflatbuffersを利用しているのでbrowserifyでバンドルしたものをrequireしておきます。

function serialize(transfer){

    let xembook = require("/main.js");
    var builder = xembook.getFlatbuffers();

    // メッセージ部
    const bytePayload = convert.hexToUint8(convert.utf8ToHex(transfer.message.payload));
    const payload = createVector(builder, 1,bytePayload,1,'addInt8');

    builder.startObject(2);
    builder.addFieldInt8(0, transfer.message.type, 0);
    builder.addFieldOffset(1, payload, 0);
    const message = builder.endObject();

    // モザイク部
    const mosaics = [];
    transfer.mosaics.forEach(mosaic => {
        const id     = createVector(builder, 4,mosaic.id    ,4,'addInt32');
        const amount = createVector(builder, 4,mosaic.amount,4,'addInt32');
        builder.startObject(2);
        builder.addFieldOffset(0, id, 0);
        builder.addFieldOffset(1, amount, 0);
        mosaics.push(builder.endObject());
    });

    const feeVector       = createVector(builder, 4,transfer.fee      ,4,'addInt32');
    const mosaicsVector   = createVector(builder, 4,mosaics           ,4,'addOffset');
    const signerVector    = createVector(builder, 4,Array(...Array(32)).map(Number.prototype.valueOf, 0),4,'addInt8');
    const deadlineVector  = createVector(builder, 4,deadline()         ,4,'addInt32');
    const recipientVector = createVector(builder, 1,stringToAddress(transfer.recipient),1,'addInt8');
    const signatureVector = createVector(builder, 1,Array(...Array(64)).map(Number.prototype.valueOf, 0),1,'addInt8');

    builder.startObject(12);
    builder.addFieldInt32(0, 149 + (16 * transfer.mosaics.length) + bytePayload.length, 0);
    builder.addFieldOffset(1, signatureVector, 0);
    builder.addFieldOffset(2, signerVector, 0);
    builder.addFieldInt16( 3, transfer.version, 0);
    builder.addFieldInt16( 4, transfer.type, 0);
    builder.addFieldOffset(5, feeVector, 0);
    builder.addFieldOffset(6, deadlineVector, 0);
    builder.addFieldOffset(7, recipientVector, 0);
    builder.addFieldInt16( 8, bytePayload.length + 1, 0);
    builder.addFieldInt8(  9, transfer.mosaics.length, 0);
    builder.addFieldOffset(10, message, 0);
    builder.addFieldOffset(11, mosaicsVector, 0);
    const codedTransfer = builder.endObject();
    builder.finish(codedTransfer);
    bytes = builder.asUint8Array();

    let i = 0;
    let resultBytes = [];
    let buffer = Array.from(bytes);
    resultBytes = resultBytes.concat(findParam( buffer[0], 4 + (i * 2), buffer, 4));i++;
    resultBytes = resultBytes.concat(findVector(buffer[0], 4 + (i * 2), buffer, 1));i++;
    resultBytes = resultBytes.concat(findVector(buffer[0], 4 + (i * 2), buffer, 1));i++;
    resultBytes = resultBytes.concat(findParam( buffer[0], 4 + (i * 2), buffer, 2));i++;
    resultBytes = resultBytes.concat(findParam( buffer[0], 4 + (i * 2), buffer, 2));i++;
    resultBytes = resultBytes.concat(findVector(buffer[0], 4 + (i * 2), buffer, 4));i++;
    resultBytes = resultBytes.concat(findVector(buffer[0], 4 + (i * 2), buffer, 4));i++;
    resultBytes = resultBytes.concat(findVector(buffer[0], 4 + (i * 2), buffer, 1));i++;
    resultBytes = resultBytes.concat(findParam( buffer[0], 4 + (i * 2), buffer, 2));i++;
    resultBytes = resultBytes.concat(findParam( buffer[0], 4 + (i * 2), buffer, 1));i++;
    resultBytes = resultBytes.concat(tableSerialize(     buffer, 4 + (i * 2)));i++;
    resultBytes = resultBytes.concat(tableArraySerialize(buffer, 4 + (i * 2)));i++;

    return resultBytes;
}

これでシリアライズできました。大きく分けて固定長部とmosaic部とmessage部の3部分で構成されます。bufferに詰め込んでいって、バイト配列に並べて行きます。以下のようなものが出来上がります。

C000000046D0B4EB4677D2FCEA0D5242E9E8368ED49293B13F6C61A3DEADE0746835B69F9F3F9B6927A42D0C4A063357FE9DA21B75A43064D6CC99904C153832BF820607FF6E61F2A0440FB09CA7A530C0C64A275ADA3A13F60D1EC916D7F1543D7F0574039054410000000000000000FB1B5A101700000090758EB47C28D6143BAA3DE6A8D9C319B503A1BFD8E789E9E20C00020048656C6C6F204E656D322144B262C46CEABB8500000000000000001C29E1B7B29912940000000000000000

適当に改行をいれていくとこんな感じです。

C0000000 //サイズ
46D0B4EB4677D2FCEA0D5242E9E8368ED49293B13F6C61A3DEADE0746835B69F9F3F9B6927A42D0C4A063357FE9DA21B75A43064D6CC99904 C153832BF820607 //署名
FF6E61F2A0440FB09CA7A530C0C64A275ADA3A13F60D1EC916D7F1543D7F0574 //署名者公開鍵
0390 //version
5441 //type
0000000000000000 //手数料
FB1B5A1017000000 //有効期限
90758EB47C28D6143BAA3DE6A8D9C319B503A1BFD8E789E9E2 //宛先アドレス(decoded)
0C00 //メッセージ長
0200 //モザイク長
48656C6C6F204E656D3221 //メッセージ
44B262C46CEABB850000000000000000 //モザイク1
1C29E1B7B29912940000000000000000 //モザイク2

次に署名を行います。

const signedTx = signTransaction(keyPair,serializedTx);

内部での処理は以下の通りです。

function signTransaction(keyPair,byteBuffer) {

    const signingBytes = byteBuffer.slice(4 + 64 + 32);
    const keyPairEncoded = KeyPair.createKeyPairFromPrivateKeyString(convert.uint8ToHex(keyPair.privateKey));
    const signature = Array.from(catapult.crypto.sign( new Uint8Array(signingBytes),keyPair.publicKey, keyPair.privateKey,catapult.hash.createHasher()));
    const signedTransactionBuffer = byteBuffer
        .splice(0, 4)
        .concat(signature)
        .concat(Array.from(keyPairEncoded.publicKey))
        .concat(byteBuffer.splice(64 + 32, byteBuffer.length));
    const payload = convert.uint8ToHex(signedTransactionBuffer);

    return {
        payload,
        hash: createTransactionHash(payload)
    };
}

function createTransactionHash(transactionPayload) {
    const byteBuffer = Array.from(convert.hexToUint8(transactionPayload));
    const signingBytes = byteBuffer
        .slice(4, 36)
        .concat(byteBuffer.slice(4 + 64, 4 + 64 + 32))
        .concat(byteBuffer.splice(4 + 64 + 32, byteBuffer.length));

    const hash = new Uint8Array(32);
    sha3Hasher.func(hash, signingBytes, 32);
    return convert.uint8ToHex(hash);
}

これで署名完了です。
最後にCatapultネットワークにアナウンスします。

putNemInfo("/transaction/",signedTx.payload)
.then(function(res){

    intervalCheckUnconfirmed = setInterval(function(){
        getNemInfo("/transaction/" + signedTx.hash + "/status")
        .then(function(res2){
            parseTransactionStatus(res2);
        })
    }
    ,3000
    );
});

これは従来のXEMBook-sdkがそのまま使えます。
先ほど署名で取得したsignedTxは署名本体のpayloadと確認用のhash値の2つのパラメータを持ちます。
payloadのみをAPIに指定してHTTPのPUTで投げます。処理が返ってきたらGETで状態を確認します。
subscribeするとややこしくなるので、今回はポーリングを使用しています。

ここでセキュリティ的な注意点。有名なWebでのハッキング手法として送信先アドレスを署名直前にすり替えられるというものがあるので、署名後にシリアライズされた文字列からアドレスを抽出し送信直前の最終確認とするとよいでしょう。(署名後にアドレスを改ざんすることはできません)

これで /transaction/{hash}/state の返事が "Success"になれば通知完了です。

最後に動くもの、置いておきます。
http://xembook.net/xembook-sdk/snapshot/20190526/examples/220_transfer.html

実際に刻んだトランザクションを確認できるTransaction Viewerも準備しています。
説明は次回に。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む