20200701のJavaScriptに関する記事は30件です。

ブックマークレットで今見ているページのTwitter投稿を補助する

ブックマークレットで今見ているページのTwitter投稿を補助する

ニュースやブログなど、何らかのサイトを見ていて
:bird:Twitterで投稿したいけど、シェアボタンが無くて不便!
ボタンはあるけど文章がついていないなどの経験はありませんか?
特に古い記事、ブログ以外のサイトなど。

そんな時にこのブックマークレットがあれば、
タイトルまたはH1タグまたは囲んだテキスト+
URL付きの投稿画面を表示出来ます。

下記のコードをコピペして、ブラウザのブックマークの
URLに貼り付ける事で利用できます。
※PC版Chromeで動作確認しています。2020.07.01現在

選択しているテキストを文章とする版

javascript:tt=window.getSelection().toString();window.open('https://twitter.com/intent/tweet?url='+encodeURI(location.href)+'&text='+encodeURI(tt),'_blank')

ページタイトル版

javascript:tt=document.title;window.open('https://twitter.com/intent/tweet?url='+encodeURI(location.href)+'&text='+encodeURI(tt),'_blank')

H1があればH1のテキストを、なければページタイトルを使う版

javascript:tt="";if(document.getElementsByTagName('h1')[0]==undefined){tt=document.title;}else{tt=document.getElementsByTagName('h1')[0].textContent;}window.open('https://twitter.com/intent/tweet?url='+encodeURI(location.href)+'&text='+encodeURI(tt),'_blank')

コード解説(H1版)

すごく単純なものです。h1タグが存在するかを確認し、
存在無ければタイトルを、存在すればh1内のテキストを使います。
tt="";if(document.getElementsByTagName('h1')[0]==undefined){tt=document.title;}else{tt=document.getElementsByTagName('h1')[0].textContent;}

location.hrefでURLを取得します。
日本語に対応するためURLもテキストもエンコードします。
window.open('https://twitter.com/intent/tweet?url='+encodeURI(location.href)+'&text='+encodeURI(tt),'_blank')

利用イメージと利用方法

1.適当なブックマークを作ります。名前も適度に付けます。
2.そのURLを上記のコードにします。Javascript:~を貼り付ける
3.投稿したいサイトを開いている時にこのブックマークを押す

画像はGoogleが対象サイトだった場合の例です。

tw.gif

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

今見ているページのTwitter投稿を補助するブックマークレット【選択したテキスト+URL】

ニュースやブログなど、何らかのサイトを見ていて
:bird:Twitterで投稿したいけど、シェアボタンが無くて不便!
ボタンはあるけど文章がついていないなどの経験はありませんか?
特に古い記事、ブログ以外のサイトなど。

そんな時にこのブックマークレットがあれば、ワンボタンで
タイトルまたはH1タグまたは【選択したテキスト】+
URL付きの投稿画面を表示出来ます。

下記のコードをコピペして、ブラウザのブックマークの
URLに貼り付ける事で利用できます。
※PC版Chromeで動作確認しています。2020.07.01現在

選択しているテキストを文章とする版

javascript:tt=window.getSelection().toString();window.open('https://twitter.com/intent/tweet?url='+encodeURI(location.href)+'&text='+encodeURI(tt),'_blank')

ページタイトル版

javascript:tt=document.title;window.open('https://twitter.com/intent/tweet?url='+encodeURI(location.href)+'&text='+encodeURI(tt),'_blank')

H1があればH1のテキストを、なければページタイトルを使う版

javascript:tt="";if(document.getElementsByTagName('h1')[0]==undefined){tt=document.title;}else{tt=document.getElementsByTagName('h1')[0].textContent;}window.open('https://twitter.com/intent/tweet?url='+encodeURI(location.href)+'&text='+encodeURI(tt),'_blank')

コード解説(H1版)

すごく単純なものです。h1タグが存在するかを確認し、
存在無ければタイトルを、存在すればh1内のテキストを使います。
tt="";if(document.getElementsByTagName('h1')[0]==undefined){tt=document.title;}else{tt=document.getElementsByTagName('h1')[0].textContent;}

location.hrefでURLを取得します。
日本語に対応するためURLもテキストもエンコードします。
window.open('https://twitter.com/intent/tweet?url='+encodeURI(location.href)+'&text='+encodeURI(tt),'_blank')

利用イメージと利用方法

1.適当なブックマークを作ります。名前も適度に付けます。
2.そのURLを上記のコードにします。Javascript:~を貼り付ける
3.投稿したいサイトを開いている時にこのブックマークを押す

画像はGoogleが対象サイトだった場合の例です。

tw.gif

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

firebase functionsから別のfunctionsをキックする

やりたいこと

  • functionsから別のfunctionsをキックしたい

先に代替案

firebase-queue というものがあるので、条件に合えばこれを使っても良さそう

  • firebase realtime databaseを利用している
  • 4年くらい更新されていないので怖い

ユースケース例

この記事では下記のような挙動を例にします

  • チャットアプリ
  • ユーザーAがメッセージを投稿する
  • 同じチャットルームに属するユーザーB, C, Dにpush通知が届く

具体的には下記のような処理フローになります

  • ユーザーAがメッセージを投稿するとfirestoreにドキュメントがinsertされる
  • ドキュメント作成のイベントを受けて、functionsAがキックされる
  • functionsAの中で
    • 同じルームのメンバー一覧を取得
    • メンバーそれぞれについて、push通知を発行するfunctionBをキックする

functionA

実装のイメージです

// ポイント1: functionsの中だけど、クライアント用のSDKを利用している
import firebaseClient from "firebase";

firebaseClient.initializeApp(client_sdk_config);
const functionsClient = firebaseClient
  .app()
  .functions("asia-northeast1");

// ポイント2: timeoutを指定することで、functionsBをキックするけど結果を待たない、ということができる
const functionB = functionsClient.httpsCallable(
  "functionB",
  { timeout: 1000 } // msec
);

const pushTargetUsers = ["user-B", "user-C", "user-D"];
const pushSendTasks = pushTargetUsers.map(async (userId) => {
  try {
    await functionB(userId);
  } catch (error) {
    if (e.code === "deadline-exceeded") {
      // functionBの完了は待たないので、timeoutエラーは無視する
      return;
    }

    throw e;
  }
});

await Promise.all(pushSendTasks);

ポイント

  • client用のSDKを使う
    • functions内部では普通はadmin SDKしか利用しない
    • しかしadmin SDKにはfunctionsを呼び出す機能がない
    • なのでclient用のSDKを利用することでfunctionsを呼び出す
  • functionsを呼び出すときにtimeout値を必要に応じてセットする

    • timeoutをセットしない場合
      • functionBが完了するまで待つことになる
      • この場合、functionBの返り値を受け取ることができる
      • 返り値を受け取りたい場合、わざわざ別functionに分離するメリットはあまりないので、このパターンはあまり利用機会がなさそう
    • timeoutをセットする場合
      • timeout以内にfunctionBが完了しなかった場合、 deadline-exceeded のエラーがスローされる
      • そのエラーは想定内なので握りつぶす
  • functionsのタイムアウトは2種類ある

↑xのタイムアウトを60秒、yのタイムアウトを10秒にセットしてfunctionを呼び出す、みたいなことができる

その場合、下記のような挙動になる

  • 10秒以内に処理が完了した場合
    • functionは正常にレスポンスを返す
  • 10秒以上かかった場合
    • 呼び出し側ではyのタイムアウトエラーがスローされる
    • しかし、キックされたfunction自体は10秒を超えても動いており、60秒以内に処理が完了すれば正常終了となる

感想

AWS LambdaではSDKが同期呼び出し、非同期呼び出しなどをサポートしており、もっとシンプルに同じことがやれます

firebase functionsでも同じことがやりたいと思って試行錯誤してこの結論に辿り着いたのですが、SDKでサポートしてくれるとありがたいですね...

また、そもそもfunctionからfunctionを直接呼び出すというのはあまり良い手段ではなく、AWSではstep functionsを使うべき場面だと思います

firebaseでも同様に別の手段を模索するべきなのかなとちょっと思いました

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

firestoreのデータをvue-chartでグラフ化する時にハマった

firestoreからデータを引っ張り出しvue-chartでレンダリングしようとした時に躓き時間をだいぶ割いてしまったので同じように悩んでる人が居れば(多分いない)助けになりたいと思い共有

データの流れ

API(openweathermap)→ firestore → vue-chart

絵画してみる

chart.vue
  mounted() {
    this.renderChart(this.chartData, this.options);
  },

グラフは表示されない
92623f11a023e392b4188d019dbcff63.png
タブの大きさ等変更すると表示される
808f10d38bc132c34a312d9cb31f30da.png

???????????状態

データはしかっりと入っている事を確認できる

affefbd363a48674a6fe3b5d1b49c327.png

色々やってみる①

watchで監視

chart.vue
  watch: {
    data: {
      handler() {
        this.renderChart(this.chartData, this.options);
      },
      deep: true,
      immediate: false
    }
  }

mounted()では表示されずwatchで表示される

色々やってみる②

試しに数字べた書きしてみる

chart.vue
  data() {
    return {
      loaded: false,
      Today: null,
      data: {
        labels: [1,2,3],
        datasets: [
          {
            label: "osaka",
            data: [200,300,400,],
          },

a23f5f27e013c93634edab2ee254cb44.png
当然べた書だとグラフ化に成功

原因

データが完全に移動する前にレンダリングしようとしているのが原因でした。
タブの大きさ変更などで表示されていたのは、その時にはデータが移動完了していたからですね

chart.vue
  <allchart v-if="loaded" :data="data" :options="options" />

chart.vue
        snapshot.forEach(doc => {
          this.data.labels.push(doc.data().Timestamp2);
          this.loaded = true;
        }

これで解決できます

おまけ

公式をしっかり読みましょう

API呼び出しが非同期だということです。 この時、データが到着する前にあなたはチャートを表示しようとしてしまいます。
これを防ぐには、単純な v-ifが最善の解決策です。

Qiita読み漁る前に公式をしっかり読みましょう

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

JavaScriptのループ処理等で日付が変わったら処理するのはDate型のgetDay()を使うと楽

前提

arrayにはこういう構造でデータが入っているものとする

const messages = [
  {
    "date": new Date("2020/06/29")
  },
   {
    "date": new Date("2020/06/30")
  },
]

コード

dateのgetDay()関数で曜日を示すindexが取れ、それが日付が変わることを示すのでそれを使った判定が楽だと思います。(日を取って比較してもいいがこちらのほうが手間がかからない)

formatにはdayjsを使っていますが気にしないでください

  let day

  messages.forEach((message) => {
    // 次の日になったら日付を出力する
    if (message.date.getDay() !== day) {
      console.log(`\n${dayjs(message.date).format("YYYY/MM/DD")}`)
    }

    day = message.date.getDay()
  });
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptで日付が変わったら処理する

改訂

以下に書いてある記事は思いつきで書いたものです、当初は毎日入っているデータに対して曜日を取れば処理できると思っていましたが、色々考えた結果普通にgetDateを使ったほうが同じ手間で簡単でした

前提

arrayにはこういう構造でデータが入っているものとする

const messages = [
  {
    "date": new Date("2020/06/29")
  },
   {
    "date": new Date("2020/06/30")
  },
]

曜日比較コード

dateのgetDay()関数で曜日を示すindexが取れ、それが日付が変わることを示すのでそれを使った判定が楽だと思います。(日を取って比較してもいいがこちらのほうが手間がかからない)

formatにはdayjsを使っていますが気にしないでください

  let day

  messages.forEach((message) => {
    // 次の日になったら日付を出力する
    if (message.date.getDay() !== day) {
      console.log(`\n${dayjs(message.date).format("YYYY/MM/DD")}`)
    }

    day = message.date.getDay()
  });

備考

  • 曜日取得で判定しているので一週間分急にデータがなく過ぎたらバグる

改良版日付比較コード

dateのgetDay()関数で曜日を示すindexが取れ、それが日付が変わることを示すのでそれを使った判定が楽だと思います。(日を取って比較してもいいがこちらのほうが手間がかからない)

formatにはdayjsを使っていますが気にしないでください

  let date

  messages.forEach((message) => {
    // 次の日になったら日付を出力する
    if (message.date.getDate() !== date) {
      console.log(`\n${dayjs(message.date).format("YYYY/MM/DD")}`)
    }

    date = message.date.getDate()
  });

備考

  • 日付の数字しか取れないので、一ヶ月急に過ぎたらバグるが多分現実にあまりないと思うので気にしなくていい
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptの基礎文法

スクリーンショット 2020-07-01 22.54.25.png
コンソールにて Heloo world と出力されます

下記の解説をします。

let btn = document.querySelector("button");

ノードオブジェクトのbuttonをbtnに代入しています。

function printHello() {

console.log("Hello world");
printHello関数を定義しています。
2番目の記述で、コンソールにHello worldと出力しています

btn.addEventListener("click", printHello);

buttonのノードオブジェクトに対して、clickイベントとprintHello関数を紐付ける仕組みであるイベントリスナを追加する。

DOM

DOMとはDocument Object Model(ドキュメントオブジェクトモデル)の略です。HTMLを解析し、データを作成する仕組みです。HTMLで書かれたファイルは結局ただの文字情報なので、そのまま表示しても意味がありません。HTMLを解析し、CSSやJavascriptによる装飾を行ってから画面に映すという工程が必要です。

ノード

HTML1つ1つのタグが、DOMツリーの中ではノードと呼ばれます。

document.getElementById("id名");

documentは、開いているWebページのDOMツリーが入っているオブジェクトです。documentに対していくつかのメソッドを利用することで、DOMツリーに含まれる要素を抽出して取得することができます。.getElementById("id名")はDOMツリーから特定の要素を取得するためのメソッドの1つです。引数に渡したidを持つ要素を取得します。

document.querySelector("セレクタ名");

セレクタ名とは、CSSでスタイルを適用するために指定している要素です。セレクタ名を指定してDOMを取得する場合、querySelectorメソッドを使用します。HTML上から、引数で指定したセレクタに合致するもののうち一番最初に見つかった要素1つを取得します。

addEventListener

(ノードオブジェクト).addEventListener("イベント名", 関数);
上記のような記述により、この記述の読み込み以降で「ノードオブジェクト」に「イベント」が起きた時、「関数」を実行するようになります。

addEventListener

「ノードオブジェクト」に「イベント」が起きた時、「関数」を実行します。

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

[Javascript]おばあとおじいの1週間の献立を無理やり再帰にする準備をする。

背景

「どうせ学んでいるのならアウトプットせい!」とお叱り愛の叱咤激励をもらいました。
ごもっともなので毎日投稿を習慣化してみる。

自己紹介(=情報の信頼度)

2020年4月から学習をはじめた駆け出しエンジニア
HTML、CSS、Javascript、React、Vue、Laravelを学習
AWSもamplifyとかS3、EC2あたりをちょこっと。ふくおか。

今日のおはなし

〜おじいおばあの家〜

おばあ「今週のごはんは何にしようか。」

おじい「精のつくものとらねばねえ。」

おじい「しかし、ついつい昨日のごはんも忘れてしまうとよ。困ったもんちゃ!」

おばあ「では、私が「1週間の献立」をつくってみようかね」

おばあ「最近、ぷろぐらみんぐ教室で学んだの。」

お題

[朝、昼、夜]の献立表をつくる。

献立表=[
[?、?、?][?、?、?][?、?、?]
[?、?、?][?、?、?][?、?、?]
[?、?、?][?、?、?][?、?、?]
[?、?、?][?、?、?][?、?、?]
[?、?、?][?、?、?][?、?、?]
[?、?、?][?、?、?][?、?、?]
[?、?、?][?、?、?][?、?、?]
[?、?、?][?、?、?][?、?、?]
[?、?、?][?、?、?][?、?、?]
]

※おじいさんとおばあさんは、カレー、寿司、ラーメンしかたべません。

再帰を学ぼう!

再帰とは

関数が自分自身を呼ぶこと。
たとえば、繰り返し処理(forループ)を、
Screen Shot 2020-07-01 at 21.44.29.png
こう書き換えられる。
Screen Shot 2020-07-01 at 21.45.47.png

この場合、
n==3であれば、x*pow(2,3) //-> 2*2
n==2であれば、x*pow(2,3) //-> 2*2*2
n==1になるとき、x*pow(2,3)//-> 8

となっている。

再帰を考えるときのコツ

再帰を終了する条件:ベースケース
再帰を継続する条件:リカーシブケース
と呼ぶらしい。

再帰関数自体にベースケース、リカーシブケースに関係する値を引数としていれておくこと。

実際に再帰で考えてみよう!

おばあ「まず、全体の流れを理解しよう。」

おばあ「すべての献立パターンがつくれて、関数を実行すると「献立表」が返ってくる。」

おばあ「食べ物は、食糧庫からとってこよう。」

おばあ「朝、昼、夜、の3食決まったら再帰は終了だね。完食はしないからね」

おばあ「じゃあ、まだ朝、昼までしか決まっていないときは、再帰を継続する必要があるね。」

Screen Shot 2020-07-01 at 22.55.24.png
おばあ「あ、再帰したときに、減らす引数がない!」
Screen Shot 2020-07-01 at 22.55.03.png

おばあ「これでよしっと。」

おばあ「繰り返し処理になっている部分は、、と。」

おばあ「各食事に対してpantryからすべての食事を取り出す。」

おばあ「まず、[?、?、?]をつくるには、」

おばあ「1回目の食事を[?]いれて、2回目の食事にする。」

おばあ「2回目の食事を[?]いれて、3回目の食事にする。」

おばあ「3回目の食事を[?]いれて、3食分の献立をresultにいれる。」

おばあ「このあたりが繰り返しの処理っぽいな。」

おばあ「次に、[[?、?、?]をつくる。]

おばあ「途中まで[?、?]をつくってるから、その上に対して?を入れたいけど、どうしようか」

おばあ「作成途中の献立[?、?]を受けとれるようにしよう。」

おばあ「作成途中の献立を受け取れるように引数を変更して、」

おばあ「それらに対してpantryから次の食事を加えよう。」
Screen Shot 2020-07-01 at 22.53.25.png

おばあ「これで、自分自身を呼ぶことで、配列を作るrecuse関数ができあがった」

おばあ「あ。recuse関数の発火をしてない。えい。」
Screen Shot 2020-07-01 at 22.53.58.png

その後

おばあ「meal()[1]」

おじい「もぐもぐ」

おばあ「meal()[4]」

おじい「もぐもぐ」

補足

pushではなく、concatで記述している理由は、配列のままにしたいからです。

pushだと要素数を返す
=>数字になる。
=>昼のごはんが選べなくなる
=cannnot read proptyとなるため避けています。

recuse関数外にresultを定義しているのはクロージャーを使いたいためです。

後日記事にします。

解釈間違い等、何かあればぜひコメントください。

誰か再帰の使いみちを教えてください・・・。

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

Error ExecJS::RuntimeUnavailable: 発生時の対処法

発生現象

AWSのEC2でWebサーバ、アプリケーションサーバの設定時に、環境変数の設定をする際の$ rake secretを実行した際に下記Errorが発生。

terminal
ExecJS::RuntimeUnavailable: Could not find a JavaScript runtime. See https://github.com/rails/execjs for a list of available runtimes.
/var/www/chat-space/config/application.rb:7:in `<top (required)>'
/var/www/chat-space/Rakefile:4:in `require_relative'
/var/www/chat-space/Rakefile:4:in `<top (required)>'
(See full trace by running task with --trace)

→Javascriptがうまく走っていないので、Node.jsをinstallする。

install確認

local環境にて

terminal
$ node --version
v12.16.1

AWSの本番環境にもinstallする

terminal
sudo yum install nodejs --enablerepo=epel  ←実行
読み込んだプラグイン:priorities, update-motd, upgrade-helper
amzn-main                                                          | 2.1 kB  00:00:00     
amzn-updates                                                       | 3.8 kB  00:00:00     
epel/x86_64/metalink                                               | 5.3 kB  00:00:00     
epel                                                               | 4.7 kB  00:00:00     
nodesource                                                         | 2.5 kB  00:00:00     
(1/3): epel/x86_64/group_gz                                        |  74 kB  00:00:00     
(2/3): epel/x86_64/updateinfo                                      | 789 kB  00:00:00     
(3/3): epel/x86_64/primary_db                                      | 6.1 MB  00:00:00     
1073 packages excluded due to repository priority protections
依存性の解決をしています
--> トランザクションの確認を実行しています。
---> パッケージ nodejs.x86_64 2:6.17.1-1nodesource を インストール
--> 依存性の処理をしています: python >= 2.6 のパッケージ: 2:nodejs-6.17.1-1nodesource.x86_64
--> トランザクションの確認を実行しています。
---> パッケージ python26.x86_64 0:2.6.9-2.89.amzn1 を インストール
--> 依存性の処理をしています: libpython2.6.so.1.0()(64bit) のパッケージ: python26-2.6.9-2.89.amzn1.x86_64
--> トランザクションの確認を実行しています。
---> パッケージ python26-libs.x86_64 0:2.6.9-2.89.amzn1 を インストール
--> 依存性解決を終了しました。

依存性を解決しました

==========================================================================================
 Package              アーキテクチャー
                                    バージョン                    リポジトリー       容量
==========================================================================================
インストール中:
 nodejs               x86_64        2:6.17.1-1nodesource          nodesource         13 M
依存性関連でのインストールをします:
 python26             x86_64        2.6.9-2.89.amzn1              amzn-main         5.8 M
 python26-libs        x86_64        2.6.9-2.89.amzn1              amzn-main         697 k

トランザクションの要約
==========================================================================================
インストール  1 パッケージ (+2 個の依存関係のパッケージ)

総ダウンロード容量: 20 M
インストール容量: 59 M
Is this ok [y/d/N]: y
Downloading packages:
警告: /var/cache/yum/x86_64/latest/nodesource/packages/nodejs-6.17.1-1nodesource.x86_64.rpm: ヘッダー V4 RSA/SHA512 Signature、鍵 ID 34fa74dd: NOKEY
nodejs-6.17.1-1nodesource.x86_64.rpm の公開鍵がインストールされていません
(1/3): nodejs-6.17.1-1nodesource.x86_64.rpm                        |  13 MB  00:00:00     
(2/3): python26-libs-2.6.9-2.89.amzn1.x86_64.rpm                   | 697 kB  00:00:00     
(3/3): python26-2.6.9-2.89.amzn1.x86_64.rpm                        | 5.8 MB  00:00:01     
------------------------------------------------------------------------------------------
合計                                                       16 MB/s |  20 MB  00:00:01     
file:///etc/pki/rpm-gpg/NODESOURCE-GPG-SIGNING-KEY-EL から鍵を取得中です。
Importing GPG key 0x34FA74DD:
 Userid     : "NodeSource <gpg-rpm@nodesource.com>"
 Fingerprint: 2e55 207a 95d9 944b 0cc9 3261 5ddb e8d4 34fa 74dd
 Package    : nodesource-release-el7-1.noarch (installed)
 From       : /etc/pki/rpm-gpg/NODESOURCE-GPG-SIGNING-KEY-EL
上記の処理を行います。よろしいでしょうか? [y/N]y
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  インストール中          : python26-libs-2.6.9-2.89.amzn1.x86_64                     1/3 
  インストール中          : python26-2.6.9-2.89.amzn1.x86_64                          2/3 
  インストール中          : 2:nodejs-6.17.1-1nodesource.x86_64                        3/3 
  検証中                  : 2:nodejs-6.17.1-1nodesource.x86_64                        1/3 
  検証中                  : python26-2.6.9-2.89.amzn1.x86_64                          2/3 
  検証中                  : python26-libs-2.6.9-2.89.amzn1.x86_64                     3/3 

インストール:
  nodejs.x86_64 2:6.17.1-1nodesource                                                      

依存性関連をインストールしました:
  python26.x86_64 0:2.6.9-2.89.amzn1        python26-libs.x86_64 0:2.6.9-2.89.amzn1       

完了しました!

以上で本番環境でもjavascriptが走るようになりました。

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

Denoでファイルダウンロードをする

最近Denoでファイルダウンロードをする必要があったので、実装しました。
てきとうに書いたのであまり良くない書き方をしているかもなので、その時はコメントでご指摘ください。

結論

import ky from "https://deno.land/x/ky/index.js";

const url = "https://raw.githubusercontent.com/denolib/high-res-deno-logo/master/deno_hr.png";
const path = "deno.png";

ky.get(url)
    .arrayBuffer()
    .then(buffer => {
        Deno.writeFileSync(path, new Uint8Array(buffer));
    });

console.log("Completed download!");

kyでファイルを取得し、ArrayBufferからUint8Arrayにしたあと保存しています。

やってみる

try.gif

無事にダウンロードできました。

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

ncestryによる多階層構造データを表示、投稿!! ~Ajax~

はじめに

ancestryで作成したカテゴリーデータを用いて、選択肢を動的に変化させる機能を実装しました。

学習メモとして投稿します。
まだ、理解が浅いところもありますが参考になればと思います!

完成形

https://gyazo.com/8a5adc080698873d544b8665855c0901

以下が完成コードです!

routes
resources :products, except: [:index]  do 
    get 'new/children_category', to: 'products#children_category'
    get 'new/grandchildren_category', to: 'products#grandchildren_category'
  end
puroducts_controller
before_action :set_categories, only: [:edit, :update]


〜省略〜

  def children_category
    @children_category = Category.where(ancestry: params[:parent_category_id])
    render json:  @children_category
  end

  def grandchildren_category
    @grandchildren_category = Category.where(ancestry: "#{params[:parent_category_id]}/#{params[:children_category_id]}")
    render json: @grandchildren_category 
  end

puroducts/new_html_haml
.input-field__contents
  .input-field__contents-data
    %p.subline
      商品の詳細
    .input-field__contents-image__headline
      .headlabel
        = f.label :category_id, "カテゴリー"
        %span.necessary
           必須
           .sell__about__right__wrap-box.parent
             %select.select-box1#parent
               %option{value: 0} ---
               -  @parents.each do |parent|
                 %option{value: "#{parent.id}"} #{parent.name}

           .child
             %select.select-box2#child
           .grand_child
             .select-box3
               = f.collection_select(:category_id, [], :id, :name, {prompt: "---"}, {id: "grand_child"})

category_js
$(function(){
  let buildPrompt = `<option value>---</option>`
  let buildHtmlOption = function(parent) {
    let option = `<option value ="${parent.id}">${parent.name}</option>`
    return option
  }
  $('#parent').change(function() {
    let parent_id = $(this).val();
    $.ajax({
      type: 'GET',
      url: 'products/new/children_category',
      data: {parent_category_id: parent_id},
      dataType: 'json'
    })
    .done(function(parent) {
      $('.child').css('display', 'block');
        $('#child').empty();
        $('.grand_child').css('display', 'none');
        $('#child').append(buildPrompt);

      parent.forEach(function(child) {
        var html_option = buildHtmlOption(child);
        $('#child').append(html_option);
      });
    })
    .fail(function() {
      alert('エラー')
    });
  });
  $(this).on("change", "#child", function() {
    let parent_id = $("#parent").val();
    let child_id = $("#child").val();
    $.ajax({
        type: 'GET',
        url: 'products/new/grandchildren_category',
        data: {
          parent_category_id: parent_id,
          children_category_id: child_id
        },
        dataType: 'json'
    })
    .done(function(parent) {
      $('.grand_child').css('display', 'block');
      $('#grand_child').empty();
      $('#grand_child').append(buildPrompt);

       parent.forEach(function(child) {
        var html_option = buildHtmlOption(child);
         console.log(buildHtmlOption(html_option));
        $('#grand_child').append(html_option);
      });
    })
  });
})

考え方

・親カテゴリーを選択しイベントを発火させたら、カテゴリーをappend(追加)する
・子カテゴリーを選択しイベントを発火させたら、カテゴリーをappend(追加)する
・ajaxを使用を子カテゴリー及び孫カテゴリーが表示されるための通り道を作成する
・最終的には孫カテゴリーの値が保存される様にする

ざっくりとこんな感じです。

では、一つ一つ見ていきましょう!

ルーティング

プログラムの処理の流れとして、最終的にviewに子カテゴリーと親カテゴリーを表示させます。
それは実際に、コントローラーとjsで処理を行いますのでリクエストが会った際のコントローラーへの通り道を作成します。

routes
resources :products, except: [:index]  do 
   #children_categoryアクションに行くためのパス
    get 'new/children_category', to: 'products#children_category'
   #grandchildren_categoryアクションに行くためのパス
    get 'new/grandchildren_category', to: 'products#grandchildren_category'
  end

コントローラー

前提として、ajax処理行うのでjsでajax処理が行われたあとはコントローラーに行きます。
その際、コントローラーではカテゴリーの値を探してjsに返してあげる必要があります。
したがって、以下の様に書きます。

puroducts_controller
before_action :set_categories, only: [:edit, :update]


〜省略〜

  def children_category
    #.whereを使ってancestryから値を探して、インスタンス変数に代入する
    @children_category = Category.where(ancestry: params[:parent_category_id])
   #ancestryから探した値をjsに返してあげる
    render json:  @children_category
  end

  def grandchildren_category
  #.whereを使ってancestryから値を探して、インスタンス変数に代入する
    @grandchildren_category = Category.where(ancestry: "#{params[:parent_category_id]}/#{params[:children_category_id]}")
  #ancestryから探した値をjsに返してあげる
    render json: @grandchildren_category 
  end

JSの処理

jsでは、カテゴリーが選択されるたびにイベントが発火する様にします。
具体的に、
カテゴリーが選択されたら、イベントが発火し要素のカテゴリー表示させる
カテゴリーが選択されたら、イベントが発火し要素のカテゴリー表示させる

処理としては、イベントが発火したらajaxでコントローラーから値を取得しforEachで全てを表させる流れになります。

category_js
//①=====HTMLで表示させるviewを定義===========================
$(function(){
  let buildPrompt = `<option value>---</option>`
  let buildHtmlOption = function(parent) {
    let option = `<option value ="${parent.id}">${parent.name}</option>`
    return option
  }
//=================================================



//②=====親カテゴリーが選択され子カテゴリーを呼び出す処理============
  $('#parent').change(function() {
    let parent_id = $(this).val();
    //ajaxでコントローラーに送る
    $.ajax({
      type: 'GET',
      url: 'products/new/children_category',
      data: {parent_category_id: parent_id},
      dataType: 'json'
    })
  //以下はコントローラーからのレスポンス後の処理
    .done(function(parent) {
      $('.child').css('display', 'block');
        $('#child').empty();
        $('.grand_child').css('display', 'none');
        $('#child').append(buildPrompt);

  //コントローラーから取得した値をforEachで全て取得し、.appendでHTML要素に追加する
      parent.forEach(function(child) {
        var html_option = buildHtmlOption(child);
        $('#child').append(html_option);
      });
    })
    .fail(function() {
      alert('エラー')
    });
  });
//=============================================


//②=====子カテゴリーが選択され孫カテゴリーを呼び出す処理============
  $(this).on("change", "#child", function() {
    let parent_id = $("#parent").val();
    let child_id = $("#child").val();
  //ajaxでコントローラーに送る
    $.ajax({
        type: 'GET',
        url: 'products/new/grandchildren_category',
        data: {
          parent_category_id: parent_id,
          children_category_id: child_id
        },
        dataType: 'json'
    })
  //以下はコントローラーからのレスポンス後の処理
    .done(function(parent) {
      $('.grand_child').css('display', 'block');
      $('#grand_child').empty();
      $('#grand_child').append(buildPrompt);
  //コントローラーから取得した値をforEachで全て取得し、.appendでHTML要素に追加する
       parent.forEach(function(child) {
        var html_option = buildHtmlOption(child);
         console.log(buildHtmlOption(html_option));
        $('#grand_child').append(html_option);
      });
    })
  });
//=============================================
})

最後には、HTML

HTMLで注意する点は、jsのid属性とHTMLでのid属性に齟齬かないかぐらいです。

ただし、最後の孫カテゴリーの値を保存するためには少し工夫が必要です。

puroducts/new_html_haml
.input-field__contents
  .input-field__contents-data
    %p.subline
      商品の詳細
    .input-field__contents-image__headline
      .headlabel
        = f.label :category_id, "カテゴリー"
        %span.necessary
           必須
           .sell__about__right__wrap-box.parent
             %select.select-box1#parent
               %option{value: 0} ---
                # 親カテゴリーの値を全て表示させる
               -  @parents.each do |parent|
                 %option{value: "#{parent.id}"} #{parent.name}

           .child
        # #childのところにjsで定義したviewが挿入される
             %select.select-box2#child
           .grand_child
             .select-box3
          # id:grand_childのところにjsで定義したviewが挿入される
                # また、選択孫カテゴリーの値が保存正しく保存されるために以下の様に書きます。
               = f.collection_select(:category_id, [], :id, :name, {prompt: "---"}, {id: "grand_child"})

補足で、以下の記述については以下のサイトを参考にしましたのでご確認ください

 f.collection_select(:category_id, [], :id, :name, {prompt: "---"}, {id: "grand_child"})

 #参考記述
 #collection_select(オブジェクト名, メソッド名, 要素の配列, value属性の項目, テキストの項目 [, オプション or HTML属性 or イベント属性])

参考記事:
https://railsdoc.com/page/collection_select

終わりに

処理としては、そこまで複雑ではないため1つ1つ確認しながら行ったら上手く行きました!

もし、エラーや上手く値が取得できていない場合は、binding.pryや、console.log();debuggerで確認してみてください!

ありがとうございました!

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

CSSアニメーションとJSで『崩壊するサイト』を簡単に作る

今回はサイトに崩壊するイースターエッグ(隠し要素)みたいな物を、基本的なコードのみで実装してみたので方法をまとめます。
なので初学者の方でも比較的分かりやすいかと思います。

インフラエンジニア「もっと手軽に崩壊できる。」
CSS職人「こんな崩壊にしてみました。」
やらかしエンジニア「崩壊しました。」
な話がある場合はぜひ教えてください!(自分は最近やらかしました。)
 

? 作ったもの

collapse.gif
(↑サンプルページのGif動画)

■ サンプルサイトURL(PCのみ)
https://aocattleya.github.io/Collapse-Site

■ GitHub
https://github.com/aocattleya/Collapse-Site

サイト内のキャラクターアイコンをクリックすると、サイトが崩壊します。

今回は解説がしやすいのでjQueryで解説します。
アイデア重視で実際やってることは簡単なので、Vue.jsなどでも簡単に応用出来ます。
 

簡単な仕組み解説

クリックした場合にフェードアウトするアニメーションのクラスを追加する。

もっと簡単にいうと、クリックでクラスを追加する。

使用している物 説明
HTML, CSS ---
javaScript jQuery, Vue.js, その他
クラスを追加出来れば何でもOK
Animate.css CSSアニメーションを簡単に
実装出来るライブラリ

 

Animate.css

もちろん自分でアニメーションを作っても良いですが、難しいアニメーションなんて作れない!という方に、フェードインや跳ねたりのアニメーションをクラスを付けるだけで簡単に実装出来るライブラリがあります。

・Animate.css(公式サイト)
https://daneden.github.io/animate.css

簡単に使い方の紹介です。

1、ライブラリを読み込む
ダウンロードして読み込みも出来ますが、CDN(ヘッダーにURL置くだけ)でも使用出来ます。

index.html
<head>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.0.0/animate.min.css">
</head>

CDNのバージョンによってクラス名が違ったりするので公式のREADMEにて
npmなど他の方法でインストールしたい場合なども書かれています。
→ animate.css(GitHub)

2、クラスを付ける

アニメーションを使用したいクラスに、animate__animatedアニメーションのクラスを付与

index.html
<div class="animate__animated animate__fadeInUp">フェードインするよ</div>

これでページを読み込んだ時に、すでに反映されています。簡単!

animete.gif
どんなアニメーションのクラスがあるかは、公式サイトで実際の動きを確認できます。
スピードを変えたりなどのオプションもあります。

公式サイト
 

? クリックで落下

hinge.gif

Animate.cssで簡単にアニメーションが実装出来る事が分かったところで、
次に、要素をクリックしてアニメーションのクラスを追加し、フェードアウトさせます。

何でもOKですが、今回はjQueryでクラス追加してみます。

index.html
<div class="collapse">
  <div class="button">ボタン</div>
</div>
style.css
.collapse {
  width: 200px;
  height: 200px;
  background-color: lightblue;
}

buttonクラスをクリックすると、
collapseクラスにanimate__animated animate__hingeの2つのクラスを追加する。

hingeは、animete.cssの中のポロッと落下するアニメーションの1つです。

main.js
// クリックでクラス追加
$('.button').click(function() {
  $(".collapse").addClass("animate__animated animate__hinge");
})


サンプル全部見たい人用(クリックで開く)
index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <link rel="stylesheet" href="style.css" />
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.0.0/animate.min.css" />
    <title>クリックで崩壊</title>
  </head>
  <body>
    <div class="collapse">
      <div class="button">クリック</div>
    </div>
    <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
    <script src="main.js"></script>
  </body>
</html>
style.css
.collapse {
    width: 200px; height: 200px;
    background-color: lightblue; }
main.js
$('.button').click(function() {
    $(".collapse").addClass("animate__animated animate__hinge");
})

基本はこれだけです。
あとはアニメーションを要素に追加するタイミングを変えて全体が崩壊していくように見せます。
 

? 連続で落下し崩壊

rem0h-tyoak-compressor.gif

ボタンクリック後に2つ目を一定時間後に実行させます。
一定時間後に特定の処理を行うにはsetTimeout()を使います。

index.html
<div class="contents">
  <div class="collapse1">
    <div class="button">クリック</div>
  </div>
  <div class="collapse2"></div>
</div>
main.js
$(".button").click(function () {
  // 1つ目に追加
  $(".collapse1").addClass("animate__animated animate__hinge");
  // 2つ目に追加(1秒後)
  setTimeout(function () {
    $(".collapse2").addClass("animate__animated animate__hinge");
  }, 1000);
});

 
今回はシンプルにこんな形で、あとはアニメーションをさせたい要素に好きなタイミングで付けていくと崩壊するサイトが完成です!
collapse.gif
 

? まとめ

大きい要素をほど重くなるので、やりすぎは注意してください。

やっていること自体はとても簡単な物ですが、アイデア次第でとても面白い事が出来ますね!エイプリールフールネタなどに使ってみたりも面白いと思います。

他にも少し応用して、5回クリック後に崩壊させてみたり

main.js
let count = 0;
$(".button").click(function () {
  count += 1;
  // クリックでcountが5回になったら
  if (count === 5) {
    $(".collapse1").addClass("animate__animated animate__hinge");
    setTimeout(function () {
      $(".collapse2").addClass("animate__animated animate__hinge");
    }, 1000);
  }
});

 
画像を変えたり、別のアニメーションを追加するなど色々カスタマイズして、
自分ポートフォリオなどに実装しても面白いかもしれませんね!

(キャラアイコンをクリックし過ぎると怒って崩壊するイースターエッグ)

 

リンク

記事のLGTM:thumbsup:など貰えたらとても励みになります。

:octocat: GitHub
https://github.com/aocattleya
? Twitter
https://twitter.com/aocattleya
? Qiita
https://qiita.com/aocattleya

・ 前回の記事
【GitHub】README.mdをカッコ可愛くデザインしてアプリの魅力を120%にする

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

CSSアニメーションとJSで崩壊するサイトを簡単に作る

今回はサイトに崩壊するイースターエッグ(隠し要素)みたいな物を、基本的なコードのみで実装してみたので方法をまとめます。
なので初学者の方でも比較的分かりやすいかと思います。

インフラエンジニア「もっと手軽に崩壊できる。」
CSS職人「こんな崩壊にしてみました。」
やらかしエンジニア「崩壊しました。」
な話がある場合はぜひ教えてください!(自分は最近やらかしました。)
 

? 作ったもの

collapse.gif
(↑サンプルページのGif動画)

■ サンプルサイトURL(PCのみ)
https://aocattleya.github.io/Collapse-Site

■ GitHub
https://github.com/aocattleya/Collapse-Site

サイト内のキャラクターアイコンをクリックすると、サイトが崩壊します。

今回は解説がしやすいのでjQueryで解説します。
アイデア重視で実際やってることは簡単なので、Vue.jsなどでも簡単に応用出来ます。
 

簡単な仕組み解説

クリックした場合にフェードアウトするアニメーションのクラスを追加する。

もっと簡単にいうと、クリックでクラスを追加する。

使用している物 説明
HTML, CSS ---
javaScript jQuery, Vue.js, その他
クラスを追加出来れば何でもOK
Animate.css CSSアニメーションを簡単に
実装出来るライブラリ

 

Animate.css

もちろん自分でアニメーションを作っても良いですが、難しいアニメーションなんて作れない!という方に、フェードインや跳ねたりのアニメーションをクラスを付けるだけで簡単に実装出来るライブラリがあります。

・Animate.css(公式サイト)
https://daneden.github.io/animate.css

簡単に使い方の紹介です。

1、ライブラリを読み込む
ダウンロードして読み込みも出来ますが、CDN(ヘッダーにURL置くだけ)でも使用出来ます。

index.html
<head>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.0.0/animate.min.css">
</head>

CDNのバージョンによってクラス名が違ったりするので公式のREADMEにて
npmなど他の方法でインストールしたい場合なども書かれています。
→ animate.css(GitHub)

2、クラスを付ける

アニメーションを使用したいクラスに、animate__animatedアニメーションのクラスを付与

index.html
<div class="animate__animated animate__fadeInUp">フェードインするよ</div>

これでページを読み込んだ時に、すでに反映されています。簡単!

animete.gif
どんなアニメーションのクラスがあるかは、公式サイトで実際の動きを確認できます。
スピードを変えたりなどのオプションもあります。

公式サイト
 

? クリックで落下

hinge.gif

Animate.cssで簡単にアニメーションが実装出来る事が分かったところで、
次に、要素をクリックしてアニメーションのクラスを追加し、フェードアウトさせます。

何でもOKですが、今回はjQueryでクラス追加してみます。

index.html
<div class="collapse">
  <div class="button">ボタン</div>
</div>
style.css
.collapse {
  width: 200px;
  height: 200px;
  background-color: lightblue;
}

buttonクラスをクリックすると、
collapseクラスにanimate__animated animate__hingeの2つのクラスを追加する。

hingeは、animete.cssの中のポロッと落下するアニメーションの1つです。

main.js
// クリックでクラス追加
$('.button').click(function() {
  $(".collapse").addClass("animate__animated animate__hinge");
})


サンプル全部見たい人用(クリックで開く)
index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <link rel="stylesheet" href="style.css" />
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.0.0/animate.min.css" />
    <title>クリックで崩壊</title>
  </head>
  <body>
    <div class="collapse">
      <div class="button">クリック</div>
    </div>
    <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
    <script src="main.js"></script>
  </body>
</html>
style.css
.collapse {
    width: 200px; height: 200px;
    background-color: lightblue; }
main.js
$('.button').click(function() {
    $(".collapse").addClass("animate__animated animate__hinge");
})

基本はこれだけです。
あとはアニメーションを要素に追加するタイミングを変えて全体が崩壊していくように見せます。
 

? 連続で落下し崩壊

rem0h-tyoak-compressor.gif

ボタンクリック後に2つ目を一定時間後に実行させます。
一定時間後に特定の処理を行うにはsetTimeout()を使います。

index.html
<div class="contents">
  <div class="collapse1">
    <div class="button">クリック</div>
  </div>
  <div class="collapse2"></div>
</div>
main.js
$(".button").click(function () {
  // 1つ目に追加
  $(".collapse1").addClass("animate__animated animate__hinge");
  // 2つ目に追加(1秒後)
  setTimeout(function () {
    $(".collapse2").addClass("animate__animated animate__hinge");
  }, 1000);
});

 
今回はシンプルにこんな形で、あとはアニメーションをさせたい要素に好きなタイミングで付けていくと崩壊するサイトが完成です!
collapse.gif
 

? まとめ

大きい要素をほど重くなるので、やりすぎは注意してください。

やっていること自体はとても簡単な物ですが、アイデア次第でとても面白い事が出来ますね!エイプリールフールネタなどに使ってみたりも面白いと思います。

他にも少し応用して、5回クリック後に崩壊させてみたり

main.js
let count = 0;
$(".button").click(function () {
  count += 1;
  // クリックでcountが5回になったら
  if (count === 5) {
    $(".collapse1").addClass("animate__animated animate__hinge");
    setTimeout(function () {
      $(".collapse2").addClass("animate__animated animate__hinge");
    }, 1000);
  }
});

 
画像を変えたり、別のアニメーションを追加するなど色々カスタマイズして、
自分ポートフォリオなどに実装しても面白いかもしれませんね!

(キャラアイコンをクリックし過ぎると怒って崩壊するイースターエッグ)

 

リンク

記事のLGTM:thumbsup:など貰えたらとても励みになります。

:octocat: GitHub
https://github.com/aocattleya
? Twitter
https://twitter.com/aocattleya
? Qiita
https://qiita.com/aocattleya

・ 前回の記事
【GitHub】README.mdをカッコ可愛くデザインしてアプリの魅力を120%にする

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

Electronをセキュアな設定の状態で、jsのrequierを行う

Electronの基本おさらい

Electronは、メインプロセスと、そこから読みだされwebページ画面として動くレンダラープロセスの2つの領域が存在します。
そのうち、表示を行うレンダラープロセスはセキュリティを厳しくすることが推奨されています。

Electronのセキュリティ設定

Electronのドキュメント見ると推奨のセキュリティ設定が上げられています。
https://www.electronjs.org/docs/tutorial/security#reporting-security-issues

しかし、デフォルトとのセキュリティ要件だとレンダラー領域でrequierもできずnodeのライブラリを読めません。
もちろん、require('electron')もできないのでレンダラープロセスとメインプロセスとの通信もできません。

requireを渡す

preloadを経由することでrequireを渡すことができます

electronの仕様で、preload内でcontextBridgeを実行することでレンダリングプロセスのjsに紐づきます。
"windows.xx"のxxの部分に紐付けることができます。

requireを使う例です。

メインプロセス
//レンダラー読み出し部分
const mainWindow = new BrowserWindow({
  width: 800,
  height: 600,
  webPreferences: {
    preload: path.join(__dirname, 'preload.js'),//<--ファイルを指定。ここでは同一階層にあるpreload.js
    contextIsolation: true,//<--requireを渡すために必要な設定
  }
})

preload.jsの例

preload.js
const { contextBridge, ipcRenderer} = require("electron")
const fs = require('fs')

contextBridge.exposeInMainWorld(
  "requires", {// <-- ここでつけた名前でひもづく。ここでは"window.requires"
    json5 : require("json5"),//npmで取得したライブラリを渡す時の例。レンダラーにそのまま渡す

    ipcRenderer : ipcRenderer,//ipcRendererも渡せるのでやり取りできる

    getSetting :  () => {// fsも読み込める。レンダリングプロセスにそのまま渡さず、functionにしてできることを制限したほうがセキュリアそう。。。
      const setting_path = 'c:/appSetting.json5';
      return fs.existsSync(setting_path) ? fs.readFileSync(setting_path, 'utf8') : '{}'
    }
  }
);

レンダリングプロセス側の読み出し

レンダリングプロセス
const json5 = window.requires.json5;//requireしたライブラリを読み込み

let app_setting_text = window.requires.getSetting();//preload内のファンクション実行

let app_setting = json5.parse( app_setting_text );// 普通にrequireしたように使える
console.log( app_setting );

まとめ

一工夫いるがElectronのセキュリティ設定を緩めずに、requiresを使用できました。

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

【javascript】 テンプレートリテラル とは 一言で。

【ゴール】

テンプレートリテラルの理解

【テンプレートリテラルとは、、、】

定数や変数を楽して出力できる

不使用の場合
*「+」を2回も。。面倒だな。。。

hoge.js
const name = "太郎";

console.log("私の名前は" + name +"です"); //私の名前は太郎ですが出力

使用した場合

hoge.js
const name = "太郎";

console.log(`私の名前は${name}です`); //私の名前は太郎ですが出力

【具体的な使い方】

①console.log()の括弧内を「``」(バッククォーテーション)で囲む
 ※Macの方は「command + @」です

②変数や、定数で指定したものを${}で囲む

以上。意外と使います。

【合わせて読みたい】

■ 【Javascript】 メソッド まとめ 基礎基本コード メモ
https://qiita.com/tanaka-yu3/items/2438798d159fa402b1d5

■ 【Javascript】JS 変数 定数 違い  一言でまとめました
https://qiita.com/tanaka-yu3/items/51b8b0630a1e4e2d52c8

■ 【JavaScript】 js 繰り返し文 for / while/ case
https://qiita.com/tanaka-yu3/items/942d9d4838ebe14be1c2

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

JavaScriptのデータ型を変換するの一番簡単な方法

JavaScriptのデータ型を変換するの一番簡単な方法がこの記事で説明します。

また、本記事で紹介するコードスニペットはV8エンジン(V8 8.1.307.32)を搭載するGoogle Chromeのバージョン81.0.4044.138 (64-bit)で検証されていました。

JavaScript は弱い型付けあるいは動的型付けの言語です。javascriptのどのデータも、他のデータ型のコンテキストに意味を持っていますのでデータ型変換するが簡単です。

文字列に変換する

+ 演算子と空文字列を使って文字列に変換することができます。
例えば:
let val = 1 + "";
console.log(val); // 結果: "1"
console.log(typeof val); // データ型: "string"

数値に変換する:

データか変数の前+ 演算子を使ってそのデータとデータ型が数値になります。
例えば:
let val = "15.3";
val = +val;
console.log(val); // 結果: 15.3
console.log(typeof val); //データ型: "number"

浮動小数点数から整数に変換する:

2つ以上方法があります。しかし、ビット演算子の範囲は32ビットまでです。
1つ目は、データか変数の後に演算子とを追加してそのデータが浮動小数点数から整数になります。
例えば:
let pval = 23.9 | 0;
let nval = -23.9 | 0;
console.log( pval ); // 結果: 23
console.log( nval) ; // 結果: -23

2つ目は、データか変数の前に〜〜演算子を追加してそのデータが浮動小数点数から整数になります。
例えば:
console.log( 〜〜23.9 ); // 結果: 23
console.log( 〜〜-23.9 ); // 結果: -23

ブール型に変換する:

データか変数の前に!!演算子を追加してそのデータとデータ型がブーリアンになります。
例えば:
let isFalse = !!0;
console.log(isFalse); // 結果: false
console.log(typeof isFalse); // データ型: "boolean"

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

IndexedDB + Vue CLI, Chart.js で、測定値の表示 ファイルのインポート可

概要

前と同様、IndexedDB + Vue CLIで
Dexie.js ライブラリを使用した構成となり。
測定値の登録、chart.jsグラフ表示

・ブラウザ内 IndexedDBデータの エクスポート、インポート機能で
jsonファイル経由で可能で、
別PCや、外出先PCのブラウザにインポートできます。

構成

Chrome 83
Vue CLI
dexie : 3.0.1
vue: 2.6.11
vue-router
chart.js
SinglePageApplication / SPA
Progressive Web Apps / PWA


参考

https://vue-chartjs.org/ja/

https://nori-life.com/vue-cli-chart-js/

npm 追加

Vue CLIで、chart.jsだけでは、描画できず。
vue-chartjs ライブラリも。追加しました。

npm install vue-chartjs chart.js --save

package.json

https://github.com/kuc-arc-f/vue_spa3b_4mdats/blob/master/package.json


chart.js sample

・canvas が、動作しないようで
 chart部分を、コンポートネントにする例で。描画できそうでした
親の呼出し側

chart_sample.vue
https://github.com/kuc-arc-f/vue_spa3b_4mdats/blob/master/src/components/IndexMdats/chart_sample.vue

import ChartView from './ChartView'

//
export default {
  name: 'LineSample',
  data () {
    return {
    }
  },
  components: {
    ChartView
  }
}

・子の chart
ChartView.vue
https://github.com/kuc-arc-f/vue_spa3b_4mdats/blob/master/src/components/IndexMdats/ChartView.vue

import { Line } from 'vue-chartjs'

export default {
  extends: Line,
  mounted () {
    this.renderChart({
      labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
      datasets: [
        {
          label: 'Data-1',
          fill: false,
          backgroundColor: '#FF6384',
          borderColor: '#FF6384',
          data: [40, 39, 10, 40, 39, 80, 40]
        }
      ]
    },
    { responsive: true, maintainAspectRatio: false }
    )
  }
}

・上記の、サンプル グラフの画像
ss-chat-sample-0701a.png

画面

・グラフ
ss-mdat-chart-0701a.png

・リスト
 上部分に、エクスポート、インポートの
 ボタンを配置
ss-mdat-index-0701b.png


Vue components

・create
https://github.com/kuc-arc-f/vue_spa3b_4mdats/blob/master/src/components/IndexMdats/new.vue

・index
https://github.com/kuc-arc-f/vue_spa3b_4mdats/blob/master/src/components/IndexMdats/Index.vue

・chart
https://github.com/kuc-arc-f/vue_spa3b_4mdats/blob/master/src/components/IndexMdats/chart.vue


参考のページ

https://knaka0209.hatenablog.com/entry/indexed_db_4mdats

IndexedDB + Dexie.js で CRUDの作成。ファイルインポート可、Vue CLI版
https://qiita.com/knakaqi/items/765a1fb37a53a26278e9


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

指定したHTML要素にスクロール

JavaScript で指定した HTML 要素にスクロールさせます。自動的にフォーカスを移動させることなどを想定しています。

よくある処理だと思いますが、そのものズバリのサンプルが見当たらなかったので、実装を残しておきます。

See the Pen ensureVisible by 七誌 (@7shi) on CodePen.

このサンプルでは [Start] をクリックすると、0.5 秒ごとに選択位置を移動して、それに追随してスクロールします。

実装

.NET Framework の ListView には EnsureVisible というメソッドがあり、指定した項目が画面外にある場合はスクロールして表示することができます。

それを真似て HTML でも ensureVisible を実装しました。マージンと HTMLElement を指定します。HTMLElement は複数指定できます。

function ensureVisible(margin, ...elems) {
  let rs = elems.map((elem) => elem.getBoundingClientRect());
  let tp = Math.min(...rs.map((r) => r.top   )) - margin;
  let bt = Math.max(...rs.map((r) => r.bottom)) + margin;
  if (tp < 0) {
    let top = pageYOffset + tp;
    scroll({ top: top, behavior: "smooth" });
  }
  if (bt > innerHeight) {
    let top = pageYOffset + tp - (innerHeight - (bt - tp));
    scroll({ top: top, behavior: "smooth" });
  }
}

位置の指定

getBoundingClientRect は画面上の位置(相対座標)を取得します。スクロールすると変化します。

scroll はドキュメント上の位置(絶対座標)を指定します。そのため表示の開始位置 pageYOffset を足すことで補正します。今回の実装ではスムーズスクロールを利用しています。

    scroll({ top: top, behavior: "smooth" });

スクロール量を指定する scrollBy もありますが、スムーズスクロールをサポートしていないため利用しませんでした。

サンプル

サンプルでは 0.5 秒のウェイトを Promise で処理しています。

function wait(timeout) {
  return new Promise((resolve, reject) => setTimeout(resolve, timeout));
}

start.onclick = async () => {
  for (let td of Array.from(table.getElementsByTagName("td"))) {
    ensureVisible(20, td);
    td.classList.add("selected");
    await wait(500);
    td.classList.remove("selected");
  }
  ensureVisible(20, moveToTop);
};

詳細は以下の記事を参照してください。

経緯

音声合成の読み上げ位置を追うために実装しました。

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

rubyの変数をjavascriptで使う。

色々方法はあるみたいですが、今回実行した方法。

結論

index.html.erb



@test = { 'hoge' => 'huga', 'hogehoge' => 'hugahuga' }

<div id="hoge" data-hoge-id="<%= @test.to_json %>"></div>

<script>
  var test = $('#hoge').data('hoge-id');
</script>

たったこれだけでした。

他にも"gon"というgemを使っても実装できるとの事。

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

【chart.js】X軸の間隔の調整

ラズパイで室温と湿度ラズパイのCPU温度をchart.jsで書いてみたけど、X軸の間隔が微妙すぎる。

スクリーンショット 2020-07-01 12.44.57.png

理想は1時間単位のX軸にしたい。

取得時の時刻をタイムスタンプにする

取得時の日時をタイムスタンプとすることでX軸でのメモリ幅のオプションstepSizeに任意な数字を入れて区切ることができるのではないかと思った。

...中略...
          ticks: { // 目盛り
            // fontColor: "red", // 目盛りの色
            // fontSize: 14 // フォントサイズ
            begintZero: true,
            minRotation: 0, // ┐表示角度水平
            maxRotation: 0, // ┘
            maxTicksLimit: 24,
            stepSize: 20,
          }
...中略...

しかし、Y軸のstepSizeは機能するのにX軸のstepSizeが機能しない。


Y軸の区切りを10単位に変更。X軸区切りは1500000000
スクリーンショット 2020-07-01 13.43.47.png
Y軸の区切りが20単位に変更。X軸区切りは1500000000
スクリーンショット 2020-07-01 13.44.04.png
このようにX軸の区切りはなぜか変わらない。
chart.jsの使い方から始めなければいけないのか。。

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

公式サポートされたM5Stack 用obnizOSをインストールしてみよう!

2020/7/1にリリースされたobnizOS 3.3.0より、公式でM5StackやM5Stickを扱えるようになりました。

いままでだってうごいてたじゃんか!っていうのはありますが、今回のアップデートでちゃんと起動時にディスプレイから設定ができるようになったりして、いままで不便だったところが解決して obniz Board と同じように扱えるようになりました!!

公式サポートになってできることを確認してみましょう。

  • 起動直後のディスプレイ対応
  • スイッチのサポート
  • 設定画面のサポート
  • Wi-Fiの設定画面のサポート

だいぶ便利になったのがわかるでしょうか?

さてそんな便利になったobnizOSをインストールしてみましょう。

インストール済みのデバイスを購入した場合は、次の項目の「obnizOSのセットアップ」は不要になります。

obnizOSのセットアップ

obniz-cliを使い、M5StackやM5Stickにセットアップをしていきます。

まず、obniz-cliをインストールしていきます。

詳しい使い方は次のサイトをご覧ください。

https://docs.obniz.io/ja/guides/obnizos-for-esp32/quick-start/

https://github.com/obniz/obniz-cli

obniz-cliのインストール

前もって次の環境を整えてください。

  • python 2.7以降 or python 3.4以降
  • esptoolのインストール pip install esptool でインストールできます
  • Node.js 12以降

obniz-cliをインストールします。次のコマンドにてインストールできます。

npm i obniz-cli -g

obniz-cliでobnizOSをインストールする

obniz-cliでM5StackやM5StickにobnizOSをインストールしていきます。

まずは、USBでPCとM5Stackを接続します。

どのポートに接続しているか確認しましょう。obniz-cli os:ports と入力すると書き込めそうなポートを教えてくれます。何もでない場合は必要なドライバーがインストールされていないかもしれません。

M5StackやM5Stickであれば、下記のリンクの「CP2104 Driver」からダウンロードできます。

$ obniz-cli os:ports
===Found Serial Ports===
COM11

こんな感じで確認できます。この例であればCOM11がそのポートになります。

複数あるときは抜き差したときの差分を確認して判断してください。

あと、サインインする必要があります!

$ obniz-cli signin

上記のコマンドを実行すると、ブラウザが表示されます。

2020-06-30_12h19_10.png

ログインしてくださいと出るのでログインして許可してあげてください。

ログインが完了すると、次のようにログインできたことが確認できます!

$ obniz-cli signin
Local Server Created xxxx
Authenticating...
Logged in as "xxxxx@xxx.com"

接続できていることを確認したうえで、obnizOSをインストールしていきます。

設定項目がありますので、確認してください。

これから書き込むものによって-h以降で適切なものを選んでください。

  • M5Stack : m5stack_basic
  • M5StickC : m5stickc
  • DevKitC : esp32w

あとは先ほど確認した、ポートを入力してください。-p以降でポートを入力します。

$ obniz-cli os:flash-create -h m5stickc -p COM11

実行するとこんな感じでログが出ます!

$ obniz-cli os:flash-create -h m5stickc -p COM11
3.2.1 is the latest for m5stickc. going to use it.
Downloading https://s3-ap-northeast-1.amazonaws.com/obniz-os/obnizos__m5stickc__3.2.1.bin
Downloading https://s3-ap-northeast-1.amazonaws.com/obniz-os/obnizos__m5stickc__3.2.1__bootloader.bin
Downloading https://s3-ap-northeast-1.amazonaws.com/obniz-os/obnizos__m5stickc__3.2.1__partition.bin

***
flashing obnizOS
 serialport: COM11
 baud: 1500000

 hardware: m5stickc
 version: 3.2.1
***

esptool.py v2.8
Serial port COM11
Connecting....
Chip is ESP32D0WDQ6 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
Crystal is 40MHz
MAC: 3c:71:bf:9a:1f:6c
Uploading stub...
Running stub...
Stub running...
Changing baud rate to 1500000
Changed.
Configuring flash size...
Auto-detected Flash size: 4MB
Compressed 10928 bytes to 7680...
Wrote 10928 bytes (7680 compressed) at 0x00001000 in 0.1 seconds (effective 1390.8 kbit/s)...
Hash of data verified.
Compressed 757712 bytes to 497701...
Wrote 757712 bytes (497701 compressed) at 0x00010000 in 6.7 seconds (effective 907.9 kbit/s)...
Hash of data verified.
Compressed 3072 bytes to 137...
Wrote 3072 bytes (137 compressed) at 0x00008000 in 0.0 seconds (effective 3508.7 kbit/s)...
Hash of data verified.

Leaving...
Hard resetting via RTS pin...

***
created one device on obniz Cloud.
obnizID: xxxx-xxxxx
region: jp
description:

obniz-cli going to flash Devicekey to connected device.
***

インストールできたら、ディスプレイに情報が表示されるのを確認できるでしょう!

obnizOSの設定

インストールが完了したらobnizOSの設定をしていきます。

インストール済みのものを購入した場合は、この項目からセットアップをしていきましょう!

一旦こちらのクイックスタートをごらんください!!

https://docs.obniz.io/ja/guides/m5stack/quick-start/

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

公式サポートされた M5Stack + M5StickC 用obnizOSをインストールしてみよう!

2020/7/1にリリースされたobnizOS 3.3.0より、公式でM5StackやM5Stickを扱えるようになりました。

いままでだってうごいてたじゃんか!っていうのはありますが、今回のアップデートでちゃんと起動時にディスプレイから設定ができるようになったりして、いままで不便だったところが解決して obniz Board と同じように扱えるようになりました!!

公式サポートになってできることを確認してみましょう。

  • 起動直後のディスプレイ対応
  • スイッチのサポート
  • 設定画面のサポート
  • Wi-Fiの設定画面のサポート

だいぶ便利になったのがわかるでしょうか?

さてそんな便利になったobnizOSをインストールしてみましょう。

インストール済みのデバイスを購入した場合は、次の項目の「obnizOSのセットアップ」は不要になります。

obnizOSのセットアップ

obniz-cliを使い、M5StackやM5Stickにセットアップをしていきます。

まず、obniz-cliをインストールしていきます。

詳しい使い方は次のサイトをご覧ください。

https://docs.obniz.io/ja/guides/obnizos-for-esp32/quick-start/

https://github.com/obniz/obniz-cli

obniz-cliのインストール

前もって次の環境を整えてください。

  • python 2.7以降 or python 3.4以降
  • esptoolのインストール pip install esptool でインストールできます
  • Node.js 12以降

obniz-cliをインストールします。次のコマンドにてインストールできます。

npm i obniz-cli -g

obniz-cliでobnizOSをインストールする

obniz-cliでM5StackやM5StickにobnizOSをインストールしていきます。

まずは、USBでPCとM5Stackを接続します。

どのポートに接続しているか確認しましょう。obniz-cli os:ports と入力すると書き込めそうなポートを教えてくれます。何もでない場合は必要なドライバーがインストールされていないかもしれません。

M5StackやM5Stickであれば、下記のリンクの「CP2104 Driver」からダウンロードできます。

$ obniz-cli os:ports
===Found Serial Ports===
COM11

こんな感じで確認できます。この例であればCOM11がそのポートになります。

複数あるときは抜き差したときの差分を確認して判断してください。

あと、サインインする必要があります!

$ obniz-cli signin

上記のコマンドを実行すると、ブラウザが表示されます。

2020-06-30_12h19_10.png

ログインしてくださいと出るのでログインして許可してあげてください。

ログインが完了すると、次のようにログインできたことが確認できます!

$ obniz-cli signin
Local Server Created xxxx
Authenticating...
Logged in as "xxxxx@xxx.com"

接続できていることを確認したうえで、obnizOSをインストールしていきます。

設定項目がありますので、確認してください。

これから書き込むものによって-h以降で適切なものを選んでください。

  • M5Stack : m5stack_basic
  • M5StickC : m5stickc
  • DevKitC : esp32w

あとは先ほど確認した、ポートを入力してください。-p以降でポートを入力します。

$ obniz-cli os:flash-create -h m5stickc -p COM11

実行するとこんな感じでログが出ます!

$ obniz-cli os:flash-create -h m5stickc -p COM11
3.2.1 is the latest for m5stickc. going to use it.
Downloading https://s3-ap-northeast-1.amazonaws.com/obniz-os/obnizos__m5stickc__3.2.1.bin
Downloading https://s3-ap-northeast-1.amazonaws.com/obniz-os/obnizos__m5stickc__3.2.1__bootloader.bin
Downloading https://s3-ap-northeast-1.amazonaws.com/obniz-os/obnizos__m5stickc__3.2.1__partition.bin

***
flashing obnizOS
 serialport: COM11
 baud: 1500000

 hardware: m5stickc
 version: 3.2.1
***

esptool.py v2.8
Serial port COM11
Connecting....
Chip is ESP32D0WDQ6 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
Crystal is 40MHz
MAC: 3c:71:bf:9a:1f:6c
Uploading stub...
Running stub...
Stub running...
Changing baud rate to 1500000
Changed.
Configuring flash size...
Auto-detected Flash size: 4MB
Compressed 10928 bytes to 7680...
Wrote 10928 bytes (7680 compressed) at 0x00001000 in 0.1 seconds (effective 1390.8 kbit/s)...
Hash of data verified.
Compressed 757712 bytes to 497701...
Wrote 757712 bytes (497701 compressed) at 0x00010000 in 6.7 seconds (effective 907.9 kbit/s)...
Hash of data verified.
Compressed 3072 bytes to 137...
Wrote 3072 bytes (137 compressed) at 0x00008000 in 0.0 seconds (effective 3508.7 kbit/s)...
Hash of data verified.

Leaving...
Hard resetting via RTS pin...

***
created one device on obniz Cloud.
obnizID: xxxx-xxxxx
region: jp
description:

obniz-cli going to flash Devicekey to connected device.
***

インストールできたら、ディスプレイに情報が表示されるのを確認できるでしょう!

obnizOSの設定

インストールが完了したらobnizOSの設定をしていきます。

インストール済みのものを購入した場合は、この項目からセットアップをしていきましょう!

一旦こちらのクイックスタートをごらんください!!

https://docs.obniz.io/ja/guides/m5stack/quick-start/

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

[Rails]画像の複数投稿を実装してみた

はじめに

某プログラミングスクールでフリマアプリを作成しました。画像の複数投稿・編集を担当し、その備忘録として記事を投稿します。今回は、特に苦労した画像の複数投稿について記述していきます。また、プログラミング初学者であるため、拙い箇所や間違ってる解釈があるかもしれません。ご了承の程、よろしくお願い致します。

また、下記の記事を参考にさせて戴きました。
https://qiita.com/gakinchoy7/items/ac1d8e64e33c3ddd377b
https://qiita.com/shinnosuke960801/items/66f2a511803d7dac53a3
https://qiita.com/mylevel/items/bae2204f8a40ff1d2d37

仕様

1、10枚まで投稿ができるようにしました。
2、5枚画像を選択した後、2段目に移動するようにしました。
3、1枚ずつプレビューされるようにしました。
4、削除ボタンを押すと、プレビューが消えるようにしました。
(下記URLで挙動の確認ができます)
https://i.gyazo.com/baa9fdb1aaa4c2ffc4a1dc2225043486.mp4
https://i.gyazo.com/6a1b8af554c9c34792c8870cfd796580.mp4
https://i.gyazo.com/c73679d9c9bc3437b1abec53e63ce78c.mp4

モデルのアソシエーション

user.rb
has_many :products
image.rb
belongs_to :product
product.rb
belongs_to :user
has_many :images, dependent: :destroy

画像付きで出品できるように

まず最初に、実装に必要なGemfileを編集します。

gemfile
gem 'carrierwave'
gem 'mini_magick'
gem 'jquery-rails'

インストールが終わったら、モデルに追記します。

image.rb
mount_uploader :image, ImageUploader
product.rb
accepts_nested_attributes_for :images, allow_destroy: true

productが保存される時に、imageが紐づいて保存されるようになります。

次に、コントローラを編集します。

products_controller.rb
def new
  @product = Product.new
  @product.images.new
end

def create
    @product = Product.create(product_params)
    if @product.save
      redirect_to root_path
    # image以外のデータも送信する想定で下記のように記述しています。
    else
      unless @product.images.present?
        @product.images.new
        render 'new'
      else
        render 'new'
      end
    end
  end

private
def product_params
  params.require(:product).permit(images_attributes: [:image, :_destroy, :id]).merge(user_id: current_user.id)
end

form内で異なるモデルを編集する際に、fields_forというメソッドを使用します(今回だと、productを編集してimageを保存)。その際に、newアクションの@product.images.newという記述がないと、viewにフォームが表示されません。また、createアクションですが、画像が送信されたかそうでないかで条件分岐させています。単にrender 'new'だと画像を送信せずにページに戻ってきた際にフォームが表示されません。また、画像を送信しているにも関わらず@product.images.newの記述があると、フォームが2個表示されてしまいます。

続いて、viewを作成していきます。

new.html.haml
.content-bg-gray
  .shadowed-rounded-rectangle
    = form_with(model: @product, local: true, class: "product-new-form") do |f|
      %section.mainbox-product-header
        %h2.mainbox-header__text
          商品の情報を入力
        .product__block__form
          %span.label_title.profile-form__label
            出品画像
            %span.require 必須
            %p.upload_limit 最大10枚までアップロードできます
          .product-new__field__uploader
            = f.fields_for :images do |image|
              %ul#previews
                %li.input
                  %label.upload-label
                    .upload-label__text
                      %p クリックしてファイルをアップロード
                    .input-area
                      = image.file_field :image, class: "hidden image_upload"
_new.scss
.content-bg-gray{
  background-color: #EFEFEF;
  padding-top: 80px;
  padding-bottom: 100px;
  .shadowed-rounded-rectangle{
    width: 700px;
    box-shadow: 1px 1px 10px 1px rgba(0,0,0,0.1);
    margin: 100px auto 0;
    background-color: #ffffff;
    border-radius: 60px;
    padding: 30px;
    .product-new-form{
      width: 434px;
      margin: auto;
      .product__block__form{
        .product-new__field__uploader{
          margin-bottom: 50px;
          width: 100%;
          ul{
            .input{
              display: flex;
              flex-wrap: wrap;
              width: 100%;
              .upload-label{
                width: 100%;
                height: 121px;
                background-color: rgb(245, 245, 245);
                .upload-label__text{
                  text-align: center;
                  position: absolute;
                  top: 50%;
                  z-index: 0;
                  width: 100%;
                  transform: translate(0, -50%);
                }
                .input-area{
                  display: none;
                }
              }
            }
          }
        }
      }
    }
  }
}
.mainbox-product-header{
  margin: 30px 0 8px;
  width: 434px;
  &__text{
    margin-bottom: 20px;
  }
}
.require{
  display: inline;
  width: 32px;
  height: 15px;
  padding: 2.5px 5.5px;
  background-color: #68C7CC;
  color: #ffffff;
  border-radius: 7px;
}

BEM規則に基づいて命名できていないので、分かりづらいかもしれませんがviewの完成です。
出品フォーム.png

次に、画像を載せるごとに入力欄を変化させるようにします。
ます、jQueryを使えるよに下記ファイルを編集します。

application.js
//= require turbolinks
//= require jquery
//= require jquery_ujs
//= require_tree .

new_image.jsファイルを作成し、編集していきます。

new_image.js
$(function () {
  # プレビュー機能
  # 'change'イベントでは$(this)で要素が取得できないため、 'click'イベントを入れました。
  # これにより$(this)で'input'を取得することができ、inputの親要素である'li'まで辿れます。
  $(document).on('click', '.image_upload', function () {
    # inputの要素はクリックされておらず、inputの親要素であるdivが押されています。
    # だからdivのクラス名をclickした時にイベントが作動します。
    # div(this)から要素を辿ればinputを指定することが可能です。
    # $liに追加するためのプレビュー画面のHTMLです。
    var preview = $('<div class="image-preview__wapper"><img class="preview"></div><div class="image-preview_btn"><div class="image-preview_btn_delete">削除</div></div>');
    # 次の画像を読み込むためのinputです。 
    var append_input = $(`<li class="input"><label class="upload-label"><div class="upload-label__text"><i class="fa fa-camera fa-4x"></i><div class="input-area display-none"><input class="hidden image_upload" type="file"></div></div></label></li>`)
    $ul = $('#previews')
    $li = $(this).parents('li');
    $label = $(this).parents('.upload-label');
    $inputs = $ul.find('.image_upload');
    # inputに画像を読み込んだら、"プレビューの追加"と"新しいli追加"処理が動きます。
    $('.image_upload').on('change', function (e) {
      # inputで選択した画像を読み込みます。
      var reader = new FileReader();
      # プレビューに追加させるために、inputから画像ファイルを読み込みます。
      reader.readAsDataURL(e.target.files[0]);
      # 画像ファイルが読み込んだら、処理が実行されます。 
      reader.onload = function (e) {
        # previewをappendで追加する前に、プレビューできるようにinputで選択した画像を<img>に'src'で付与させます。
        # つまり、<img>タグに画像を追加させます。
        $(preview).find('.preview').attr('src', e.target.result);
      }
      # inputの画像を付与した,previewを$liに追加します。
      $li.append(preview);
      # 生成したliの横幅を決めます。
      $('#previews li').css({
        'width': `80px`
      })
      # プレビュー完了後は、inputを非表示にさせます。これによりプレビューだけが残ります。
      $label.css('display', 'none'); # inputを非表示にします。
      $li.removeClass('input');     # inputのクラスはjQueryで数を数える時に邪魔なので除去します。
      $li.addClass('image-preview'); # inputのクラスからプレビュー用のクラスに変更しました、
      $lis = $ul.find('.image-preview'); # クラス変更が完了したところで、プレビューの数を数えます。 
      # 画像が9枚以内なら文字とインプットを追加させます。
      if ($lis.length < 10) {
        $ul.append(append_input)
        $('#previews li:last-child').css({
          'width': `80px`
        })
      }
      # inputの最後の"data-image"を取得して、input nameの番号を更新させています。
      # これをしないと、それぞれのinputの区別ができず、最後の1枚しかDBに保存されません。
      # 全部のプレビューの番号を更新することで、プレビューを削除して、新しく追加しても番号が1,2,3,4,5,6と綺麗に揃います。そのため、全部の番号を更新させます。
      $inputs.each(function (num, input) {
        # nameの番号を更新するために、現在の番号を除去します。
        $(input).removeAttr('name');
        $(input).attr({
          name: "product[images_attributes][" + num + "][image]",
          id: "images_attributes_" + num + "_image"
        });
      });

    });
  });
  # 削除ボタンをクリックしたとき、処理が動きます。
  $(document).on('click', '.image-preview_btn_delete', function () {
    var append_input = $(`<li class="input"><label class="upload-label"><div class="upload-label__text"><i class="fa fa-camera fa-4x"></i><div class="input-area display-none"><input class="hidden image_upload" type="file"></div></div></label></li>`)
    $ul = $('#previews')
    $lis = $ul.find('.image-preview');
    $li = $(this).parents('.image-preview');
    # "li"ごと削除して、previewとinputを削除させます。
    $li.remove();
    $lis = $ul.find('.image-preview'); # クラス変更が完了したところで、プレビューの数を数えます。 
    # 画像が10枚以内なら文字とインプットを追加させます
    if ($lis.length == 9) {
      $ul.append(append_input)
    }
    $('#previews li:last-child').css({
      'width': `80px`
    })
  });
});

jsファイルで追加されたセレクタをcssで編集させていきます。

_new.scss
.content-bg-gray{
  background-color: #EFEFEF;
  padding-top: 80px;
  padding-bottom: 100px;
  .shadowed-rounded-rectangle{
    width: 700px;
    box-shadow: 1px 1px 10px 1px rgba(0,0,0,0.1);
    margin: 100px auto 0;
    background-color: #ffffff;
    border-radius: 60px;
    padding: 30px;
    .product-new-form{
      width: 434px;
      margin: auto;
      .product__block__form{
        .product-new__field__uploader{
          margin-bottom: 50px;
          width: 100%;
          #previews{
            list-style: none;
            display: flex;
            flex-wrap: wrap;
            .image-preview__wapper{
              width: 80px;
              height: 80px;
              .preview{
                width: 80px;
                height: 80px;
              }
            }
            .image-preview_btn{
              text-align: center;
              padding: 8px;
              border-top: 1px solid #cccccc;
              cursor: pointer;
              &:hover{
                transition: 0.5s;
                background-color: #cccccc;
              }
            }
            .input{
              display: flex;
              flex-wrap: wrap;
              width: 100%;
              .upload-label{
                width: 100%;
                height: 121px;
                background-color: rgb(245, 245, 245);
                .upload-label__text{
                  text-align: center;
                  position: absolute;
                  top: 50%;
                  z-index: 0;
                  width: 100%;
                  transform: translate(0, -50%);
                }
                .input-area{
                  display: none;
                }
              }
            }
          }
        }
      }
    }
  }
}
.mainbox-product-header{
  margin: 30px 0 8px;
  width: 434px;
  &__text{
    margin-bottom: 20px;
  }
}
.require{
  display: inline;
  width: 32px;
  height: 15px;
  padding: 2.5px 5.5px;
  background-color: #68C7CC;
  color: #ffffff;
  border-radius: 7px;
}

#previewsの中に.preview.inputを横並べにし、flex-wrap: wrap;をかけます。5枚×2段になるようにサイズを調整しています。

以上で画像の複数投稿の実装は終わりです。

おわりに

リファクタリングが出来ていなかったり、BEMに従って命名出来ていなかったり、他にも課題はいくつかあると思います。機会があれば、画像の枚数に応じてフォームの大きさを小さくしていくするような実装をしてみたいと思います。質問や間違っている点がございましたら、コメントで指摘してくださると幸いです。

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

Node.jsを使用してWebアプリケーションからAlibaba Cloud Object Storage Serviceにコンテンツをアップロード

このチュートリアルでは、Node.jsを使ってWebアプリケーションからAlibaba Cloud Object Storage Serviceにコンテンツをアップロードする方法を検討します。

本ブログは英語版からの翻訳です。オリジナルはこちらからご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。

必要条件

このチュートリアルを実行するには、以下のものが必要です。

1、Alibaba Cloud Object Storage Serviceに登録し、秘密のアクセスキーを取得します。
2、マシン上でNode.jsとnpmを実行します。Node.js Downloadsには、Nodeのインストール方法についての詳細な情報が記載されています。
まとめると、Alibaba Cloudのアカウント、Object Storage Service、Node.js、npm、OSSサービスのアクセスキーが必要になります。

アクセスキーをクレデンシャルファイルに追加する方法

そもそも、Alibaba Cloud OSSはRESTfulなAPI操作をサポートしており、ほとんどの言語に対応したSDKを提供しています。このチュートリアルでは、オープンソースのOSS JavaScript SDK for node.jsを使用します。まずはこのように ali-oss をインストールする必要があります。

npm install ali-oss

注目すべきは、同期モードと非同期モードの2つのモードから選択できることです。

同期モードで使用するには、さらにcoと組み合わせて使用することができます。 coをインストールするようにしてください。

npm install co

非同期モードはコールバックをサポートしています。

クライアントの初期化

このメソッドは同期モードで適用されます。このメソッドでは、以下のような app.js オブジェクトを作成します。

var co = require('co');
var OSS = require('ali-oss');

var client = new OSS({
  region: '<Your region>',
  accessKeyId: '<Your AccessKeyId>',
  accessKeySecret: '<Your AccessKeySecret>'
});

Alibaba Cloudでは、OSSサービスに加入している場合、地域を指定しなければならなかったので、リージョンフィールドは非常に重要です。OSSノードの完全なリストは以下の通りです。

さらに、OSS Nodesのリストにないエンドポイントもありますが、その場合は以下のパラメータを設定する必要があります。

1、secure: リージョンと組み合わせて使用します。secureをtrueに指定した場合はHTTPを使ってアクセスします。

2、endpoint: エンドポイントを指定した場合、リージョンを無視しても大丈夫です。endpointにHTTPSを指定してもOKです。

3、bucket:バケットが指定されていない場合は、まず useBucket インターフェースを呼び出す必要があります。

4、timeout: デフォルトでは60秒です。OSS APIのタイムアウトを指定するためのパラメータです。

見た目は問題なさそうですが、次のステップに進みます。

依存関係のインストール

このステップではnode.jsの依存関係をインストールして、ノードアプリケーションが置かれるディレクトリを作成します。このチュートリアルでは、OSS-node-appで以下のようにアプリを作成します。

mkdir sites/OSS-node-app && cd sites/OSS-node-app

次に、package.jsonファイルを作成し、そこに以下のコードを貼り付けます。

{
  "name": "OSS-node-app",
  "version": "1.0.0",
  "main": "server.js",
  "scripts": {
    "start": "node server.js"
  },
  "license": "MIT"
}

上記のサンプルでは、アプリの名前、バージョン、ライセンスを記述した package.json ファイルを作成しています。scriptsパラメータは、node server.jsではなく、npm startを使用してnode serverを実行できるようにします。

インストールする必要のあるすべての依存関係は、npm installコマンドを使用してインストールされます。次に4つの依存関係が続き、このプロジェクトでは以下のようにインストールされます。

npm install ali-oss express 

私たちの依存関係は、OSSのAPIを使って作業したり、Webサーバーを立ち上げたり、ファイルのアップロードを処理したりすることを可能にしています。上記のコマンドはまた、package.jsonを更新します。

1、ali-OSS - 私たちがJavaScript APIにアクセスできるようにするためのJavaScript用のAlibaba SDKです。
2、express - Expressは、サーバーの迅速かつ効率的なセットアップを可能にします。
プロジェクトの場所と依存関係がすべてセットアップされたので、サーバーを起動してフロントエンドのビューをセットアップすることができます。すべての依存関係は、それ以降のすべてのNode.jsバージョンではデフォルトでpackage.jsonファイルに保存されることに注意してください。

フロントエンドアプリの作成

アプリケーションのパブリックビュー用のファイルを作成することから始めましょう。index.html、succeed.html、error.htmlを使ってパブリックフォルダを作成します。これら3つのファイルは、以下に示すようなHTMLの骨格を持っていますが、内容が異なるだけです。全てのファイルに以下のコードを貼り付けます。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <title>OSS Node Tutorial</title>

  <link rel="stylesheet" href="./style.css">
</head>

<body>

  <!—your content here -->

</body>

</html>

このように、それぞれのファイルにエラーメッセージを書き込んでください。

<h1>Something went wrong!</h1>
<p>Uploading the file was unsuccessful.</p>

このように、それぞれのファイルに成功メッセージを書き込んでください。

<h1>Success!</h1>
<p>File uploaded successfully.</p>

ここでは、index.htmlにmultipart/form-dataを含むHTMLフォームを作成します。フォームには、送信ボタンも用意しなければなりません。

<h1>OSS Tutorial</h1>

<p>Please select a file and submit form to upload.</p>

<form method="post" enctype="multipart/form-data" action="/upload">
  <label for="file">Upload a file</label>
  <input type="file" name="upload">
  <input type="submit" class="button">
</form>

では、インターフェイスを見やすくするためにstyle.cssを作成します。

html {
  font-family: sans-serif;
  line-height: 1.5;
  color: #333;
}

body {
  margin: 0 auto;
  max-width: 500px;
}

label,
input {
  display: block;
  margin: 5px 0;
}

無事にアップロード用のファイル、成功ページとエラーページの作成が完了しましたので、弊社のNode.jsアプリをご紹介します。

Expressを使ったサーバーの設定

今回はExpressフレームワークを使ってnode.jsサーバーを濡らしていきます。まず、プロジェクトのルートディレクトリにserver.jsファイルを作成します。次に、require()を使用して4つの依存関係をロードし、以下のようにExpressのアプリインスタンスを経由してアプリをルーティングする必要があります。

server.js
// Load dependencies
const ali = require(ali-oss');
const express = require('express');
const app = express();

アプリケーションのフロントエンドは公開リポジトリにあるので、依存関係の設定は以下のように設定しなければなりません。

// Views in public directory
app.use(express.static('public'));

そして、3つのファイルをサーバのルートに相対的にルーティングするように進めます。

// Main, error and success views
app.get('/', function (request, response) {
  response.sendFile(__dirname + '/public/index.html');
});
app.get("/success", function (request, response) {
  response.sendFile(__dirname + '/public/success.html');
});
app.get("/error", function (request, response) {
  response.sendFile(__dirname + '/public/error.html');
});

ここでは、リスニングに使用するポートを設定する必要があります。

server.js
app.listen(5000, function () {
  console.log('Server listening on port 5000.');
});

これを保存して、npm startを使ってサーバーを起動します。

npm start
Output
> node server.js
Server listening on port 5000.

ブラウザのアドレスバーにhttp://localhost:5000と入力すると、アップロードフォームにアクセスできるはずです。また、http://localhost:5000/successhttp://localhost:5000/error にアクセスして反応を試してみてください。

ファイルのアップロード

サーバーはすべてセットアップされているので、最初のファイルをAlibaba OSSにアップロードできるようにするために、フォームをali-ossに統合しなければなりません。

server.js
const app = express();
// Set endpoint to Alibaba OSS
const spacesEndpoint = new OSS.Endpoint(' http://oss-cn-hangzhou.aliyuncs.com.');
const s3 = new OSS({
  endpoint: spacesEndpoint
});

新しい OSS() を使用して OSS クライアントに接続する必要があります。

この最初のインスタンスでは、単純なローカルファイルのアップロードを実装しています。

var co = require('co');
var OSS = require('ali-oss')

var client = new OSS({
  region: '<Your region>',
  accessKeyId: '<Your AccessKeyId>',
  accessKeySecret: '<Your AccessKeySecret>',
  bucket: 'Your bucket name'
});

co(function* () {
  var result = yield client.put('object-key', 'local-file');
  console.log(result);
}).catch(function (err) {
  console.log(err);
});

2 番目のインスタンスでは、putStream インターフェイスを使用してアクセスされたストリームのアップロードを使用します。固有の読み取り可能なストリームを持つ任意のオブジェクトは、このメソッドで有効になります(オブジェクトとネットワーク ストリームの両方)。putStream インターフェースが使用されると、SDK は自動的にチャンク化されたエンコーディングの HTTP PUT を開始します。

var co = require('co');
var OSS = require('ali-oss');
var fs = require('fs');

var client = new OSS({
  region: '<Your region>',
  accessKeyId: '<Your AccessKeyId>',
  accessKeySecret: '<Your AccessKeySecret>',
  bucket: 'Your bucket name'
});

co(function* () {
  // use 'chunked encoding'
  var stream = fs.createReadStream('local-file');
  var result = yield client.putStream('object-key', stream);
  console.log(result);

  // do not use 'chunked encoding'
  var stream = fs.createReadStream('local-file');
  var size = fs.statSync('local-file').size;
  var result = yield client.putStream(
    'object-key', stream, {contentLength: size});
  console.log(result);
}).catch(function (err) {
  console.log(err);
});

バッファ内のオブジェクトの内容をアップロードするには、put インターフェースを介してアクセスします。

var co = require('co');
var OSS = require('ali-oss');

var client = new OSS({
  region: '<Your region>',
  accessKeyId: '<Your AccessKeyId>',
  accessKeySecret: '<Your AccessKeySecret>',
  bucket: 'Your bucket name'
});

co(function* () {
  var result = yield client.put('object-key', new Buffer('hello world'));
  console.log(result);
}).catch(function (err) {
  console.log(err);
});

非常に大きなファイルをアップロードする場合、multipartUploadインターフェースはリクエストをより小さな実行可能なリクエストに分割することができます。この方法の利点は、ファイル全体を修正するのとは対照的に、失敗した部分だけをアップロードする必要があることです。以下のパラメータを使用します。

1、name {String}:オブジェクト名
2、file {String|File}:ファイルパスまたは HTML5 Web ファイル
3、[options] {Object}:オプションのパラメータ

1、[checkpoint] {Object}: 再開可能なアップロードで使用するエンドポイントのチェックポイントです。このパラメータを設定すると、エンドポイントからアップロードが開始されます。設定されていない場合は、アップロードを再開します。
2、[partSize] {Number}: パーツサイズ
3、[progress] {Funtion}: ジェネレーター機能。3つのパラメータを含む。
-----1.(percentage {Number}: アップロードの進捗状況 (0から1までの10進数)
-----2.(checkpoint {Object}.:エンドポイントチェックポイント
-----3.(res {Object}):単一のパーツが正常にアップロードされた後に返されるレスポンス
4、[meta] {Object}: x-oss-meta-という接頭辞を持つユーザによって定義されたヘッダメタ情報。
5、[headers] {Object}: 余分なヘッダ。詳細は RFC 2616 を参照してください。
-----1.'Cache-Control':HTTP リクエストとレスポンスでコマンドを指定してキャッシュメカニズムを実装するために使用される一般的なヘッダ。例えば、以下のようになります。Cache-Control: public, no-cache
-----2.'Content-Disposition':これは、内部参照(ウェブページやページの一部)や、ローカルにダウンロードして保存された添付ファイルである可能性があります。例えば、以下のようになります。Content-Disposition: somename
-----3.'Content-Encoding':特定のメディアタイプのデータを圧縮するために使用されます。例えば Content-Encoding: gzip
-----4.'Expires':有効期限。例えば Expires. 3600000

var co = require('co');
var OSS = require('ali-oss')

var client = new OSS({
  region: '<Your region>',
  accessKeyId: '<Your AccessKeyId>',
  accessKeySecret: '<Your AccessKeySecret>',
  bucket: 'Your bucket name'
});

co(function* () {
  var result = yield client.multipartUpload('object-key', 'local-file', {
    progress: function* (p) {
      console.log('Progress: ' + p);
    }
 meta: {
      year: 2017,
      people: 'test'
    }
  });
  console.log(result);
  var head = yield client.head('object-key');
  console.log(head);
}).catch(function (err) {
  console.log(err);
});

上記のprogressパラメータは、アップロードの進捗状況を取得するためのcalback関数です。

var progress = function (p) {
  return function (done) {
    console.log(p);
    done();
  };
};

結論

おめでとうございます。Alibaba Cloud Object Storage Service (OSS) スペースにオブジェクトをアップロードするためのExpressアプリケーションの設定に成功しました! Alibaba Cloudのアカウントをお持ちでない方は、アカウントを登録してください。アカウントにサインアップして、最大1200ドル相当の40以上の製品を無料でお試しください。Alibaba Cloudの詳細については、「Get Started with Alibaba Cloud」を参照してください。

アリババクラウドは日本に2つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
アリババクラウドジャパン公式ページ

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

【JavaScript】プログラミングクイズでよく使う記法

結論

  • プログラミングクイズでよく使う記法と知見をメモしています。
  • 初〜中級者向けの内容です。
  • 手短に結論だけ欲しい方は、各タイトルの見出し〜ソースまでをお読みいただければ十分です。

はじめに

プログラミングのクイズを200問解いてみました。
難易度は文を読む時間を含めて1分〜3分ほどで解ける簡単な問題が多かったですが、解き方を調べるうちに

こうしたら短く書けるんだ! 」  とか、

この関数、こんな使い方できたんだ・・・

などといった気づきを得ました。
基本的な文法も改めて学習することができて非常に有意義でした。
その時の知見をここにメモしようと思います。

けっこう好みがわかれる書き方もしてると思います。
暇つぶし感覚で読んでみてください。


1.console.log(値1,値2)

console.log()にカンマ区切りで複数の値を渡すと、記述した順番通りにスペース区切りで値を出力してくれます。

const month = 6;
const date = 27;

console.log(month, date);
// 6 27

console.log(month, '/', date);
// 6 / 27

一応、MDNを読んでみました。
カンマ区切りで渡した値を出力する旨がきちんと書いてありました。

console.log(obj1 [, obj2, ..., objN]);
obj1 ... objN
出力する JavaScript オブジェクトのリスト。
各オブジェクトの文字列表現が記述順で出力されます。
MDN -console.log-

これを知るまでは複数の値を出力する際は 変数1 + " " + 変数2としたり、テンプレート文字${変数1} {変数2}列を使っていましたが、どう頑張ってもカンマで区切るのが一番楽です!

この書き方は超常識なのかもしれませんが、今まで読んだJavaScriptの学習教材には出てきませんでした。もっと早く知りたかった・・・。

2. 配列.map(Number)

配列.map(Number)とすると、配列内の要素をすべてNumberオブジェクトに変換できます。

['1','2','3'].map(Number)
// [ 1, 2, 3 ]

配列内の要素がNumberオブジェクトにできない値であれば、NaNが設定されます。

['1', 'a', '3'].map(Number)
// [ 1, NaN, 3 ]

mapといえばarr.map(x => x * 2);のように引数を受け取って処理したものを返す使い方しか知らなかったのですが、Numberを渡すだけで処理してくれるみたいです。

余談: .map(parseInt)について

.map(Number)と同じ要領でparseIntを使ってもNumberオブジェクトになりそうですが・・・そうはなりません。parseIntをそのまま渡すと思わぬ結果が返ってくるので注意してください。

["1", "2", "3"].map(parseInt);
// [1, NaN, NaN] 

parseIntは引数を2つとるため、このような結果になります。
以下のように、引数を1つだと明示すれば想定通りの結果が得られます。

['1', '2', '3'].map((str) => parseInt(str));
// [ 1, 2, 3 ]

詳しくはMDNにわかりやくす書いてあるのでそちらを参照してください。
MDN - Array.prototype.map() トリッキーな使用例-

3. 配列.map(Math.メソッド)

配列.map(Math.メソッド)とすると、配列内の要素すべてに渡したメソッドを実行してくれます。
例えば小数点以下を切り捨てて整数にしてくれるMath.floorを渡すと・・・

[1.1, '2.2', 3.3].map(Math.floor)
// [1, 2, 3];

きちんと計算した配列を生成します。
こちらはStringオブジェクトでも数字であればよしなにNumberとして扱ってくれます。

4.[,変数名] = 配列

分割代入で[,変数名] = 配列とすると、不必要な値を定義せずに無視することができます。
例えば[, str] = ['a', 'b']とすると、配列の0番目の要素('a')を無視し、配列1番目の要素('b')のみ左辺の変数(str)に格納されます。

const [, str] = ['a', 'b'];
console.log(str);
// b

これは何番目に何の値が入っているのかがわかりきっている場合によく使います。
例えば日時情報などです。
String(new Date()).split(' ') には日付や時間などの情報がたくさん入っています。

String(new Date()).split(' ')
// [ 'Fri', 'Jun', '26', '2020', '29:39:55', 'GMT+0900', '(GMT+09:00)' ]

ここから時間だけを取り出したい場合、const [, , , , time]に代入します。

const [, , , , time] = String(new Date()).split(' ');
console.log(time);
// 19:39:55

このように欲しい変数だけを定義すると、コードの見通しがよくなるのでオススメです。

詳しくはこちらを参照してください。
MDN -分割代入 返値の無視-

余談: 残余パターン(...arr)との併用について

この書き方は残余パターンと併用して使うことが結構ありました。
例えば[人数, 値1, 値2, 値3 ・・・]という入力から、値1以降を変数に格納する場合は以下のように書きます。

const [, ...arr] = ['人数', '値1', '値2', '値3'];
console.log(arr);
// [ '値1', '値2', '値3' ]

ちなみにこれはslice()を使っても同じ結果が得られますが・・・

const arr = ['人数', '値1', '値2', '値3'].slice(1);
console.log(arr);
// [ '値1', '値2', '値3' ]

slice()は文の末尾に出てくるので、そこまで読まないとどこで値を区切るのかがわかりません。
その点分割代入なら、変数定義の時点で「いらない要素があるのか」とすぐに察することができるので、個人的にはこっちの方が好きです。

まとめ

私が取り組んでいたクイズ問題の入力値は、数値なのにStringやスペース区切りの文字列として渡ってくることがほとんどでした・・・。
それを200問解いたら、文字列や配列を操作するお決まり文をスラスラ書けるようになりました。
また、最適な書き方を追求する過程で、この記事に書いたような注意点や良い記法があることを学べました。

知見はまだあるような気がするので、思い出したら追記しようと思います。

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

CentOS 7にOpenProjectをインストールする

今回は、Alibaba Cloud ECS CentOS 7サーバーにOpenProjectをインストールします。

本ブログは英語版からの翻訳です。オリジナルはこちらからご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです 。

前提条件

1、Alibaba Cloud Elastic Compute Service (ECS)インスタンスが有効化されており、有効な支払い方法を確認している必要があります。新規ユーザーの場合は、Alibaba Cloudアカウントで無料アカウントを取得することができます。ECSインスタンスのセットアップ方法がわからない場合は、このチュートリアルまたはクイックスタートガイドを参照してください。ECSインスタンスは、少なくとも1GBのRAMと1つのCoreプロセッサを搭載している必要があります。
2、Alibaba Cloudから登録されたドメイン名。すでにAlibaba Cloudまたは他のホストからドメインを登録している場合は、そのドメインネームサーバーレコードを更新することができます。

システムのアップグレード

このチュートリアルでは、インストールプロセス全体にrootユーザ権限を使用しています。以下のコマンドを使用して、非 root ユーザから root ユーザに切り替えることができます。

sudo -i

OpenProjectをインストールする前に、利用可能なリポジトリやパッケージをアップグレードすることをお勧めします。アップグレードは以下のコマンドで行うことができます。

yum -y update

OpenProjectのインストール

まずはCentOS 7用のOpenProjectリポジトリを追加します。以下のコマンドを実行してください。

sudo wget -O /etc/yum.repos.d/openproject-ce.repo  https://dl.packager.io/srv/opf/openproject-ce/stable/7/installer/el/7.repo

リポジトリが追加されたら、OpenProjectをインストールします。以下のコマンドを実行してインストールします。

yum -y install openproject

インストールが完了すると、ターミナルにInstalled as resultが表示されるはずです。

OpenProjectの設定

OpenProjectのインストールが完了したら、設定をしなければなりません。MySQLを使ったデータベースの設定、Apacheを使ったウェブサーバの設定、ドメイン名の設定、GitとSVNのサポート追加、メール通知の設定、OpenProjectのパフォーマンスを向上させるためのMemcachedの有効化を行います。以下のコマンドを実行してOpenProjectを設定します。

openproject configure

コマンドを実行すると、以下のようなインターフェースが表示されます。

image.png

セットアップウィザードで必要なMySQLデータベースを自動的に作成するかどうか聞かれるので、インストールオプションを選択してMySQLサーバをローカルにインストールして設定します。OpenProjectで使用するPostgreSQLデータベースを使用したい場合は、スキップを選択します。既存のデータベースを使用したい場合は、再利用オプションを選択します。手動でデータベースを設定する手間を省くために、インストールオプションを選択してください。

次のインターフェースでは、Webサーバの設定を求められます。単に'Install apache2 server'を選択して'OK'をクリックしてください。これで自動的にapache2ウェブサーバーがインストールされ、OpenProjectアプリケーションのための仮想ホストが設定されます。

image.png

次の画面では、Openprojectアプリケーションのドメイン名の入力を求められますので、ドメイン名を入力してOKボタンをクリックしてください。

image.png

次のステップでは、サーバーパスのプレフィックスを聞かれますが、空白のままでも構いません。アプリケーションへのパスを提供したい場合は、ここに入力してください。それ以外の場合は、インストールを進めてください。

image.png

次に、SSLの設定を求められます。SSL証明書を持っている場合は「Yes」を選択し、そうでない場合は「No」を選択して先に進みます。

image.png

次に、SubversionとGitサポートの設定を求められます。Yesを選択してこれらの機能をインストールすると、Apacheを使ってアプリケーションにSubversionとGitのリポジトリを作成してホストできるようになります。

image.png

image.png

次に、アプリケーションのメール設定を尋ねられます。アプリケーションにメールを送信させたくない場合は、「Skip」を選択し、必要に応じて選択して「OK」ボタンをクリックして先に進みます。

image.png

最後にmemcached serverのインストールを求められます。memcached serverは強力で高性能な分散メモリオブジェクトキャッシングシステムです。OpenProjectのパフォーマンスを向上させるためにインストールしておきましょう。

image.png

openpojectインストールのためのすべてのパッケージを自動的にインストールして設定します。

ウェブインタフェース

お気に入りのWebブラウザを開き、OpenProjectアプリケーションへのパスを入力するか、Fully qualified domain nameにアクセスしてください。以下のようなOpenProjectのデフォルトのWebページが表示されます。

image.png

ログインボタンをクリックして管理画面にアクセスし、ユーザー名とパスワードに「admin」を使用します。

次に、現在のログインパスワードの変更を求められますので、お好きなように変更して、保存ボタンをクリックして先に進みます。

image.png

最後に、以下のようなOpenProjectの管理画面が表示されます。

image.png

おめでとうございます。これで、Alibaba Cloud Elastic Compute Service (ECS) CentOS 7サーバーにOpenProjectをインストールし、設定することができました。これで、VPSまたはクラウドサーバー上でOpenProjectを簡単に設定・設定できるようになりました。

アリババクラウドは日本に2つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
アリババクラウドジャパン公式ページ

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

JavaScript FizzBuzz問題の作り方

目次

  • はじめに
  • FizzBuzz問題の作り方
  • HTML
  • JavaScript
  • 学んだこと
  • おわりに

はじめに

今回はJavascriptの基本的な概念DOMを操作して、FizzBuzz問題を作っていきます。
イチから全部作り方を書くというよりは、作っていく流れを書き、
実際に作る中で僕が躓いたり、ここは大事だなと感じた箇所などを説明していきます。
同じようなことで行き詰まっている初心者の方のお力になれれば幸いです。

FizzBuzz問題の作り方

基本的にHTMLとJavaScriptを使って作成します。
CSSは特に使いません。
HTMLの要素も至ってシンプルです。
なぜなら、今回の実装ではJavaScriptを使ってDOMを操作することが目的だからです。
HTMLで作った画面を作り、それに対してJavaScriptで色々操作していく、という流れになります。

HTML

まずはHTMLから作っていきます。
HTMLの作りはとてもシンプルで簡単なものです。

見出し h1タグ
2つの数値入力ボックス pタグ
実行ボタン buttonタグ
「出力」 pタグ

それだけです。
見た目としては以上になります。

そして、大事なscriptタグ
このタグがあることで簡単にHTMLとJavaScriptを繋げることが出来ます。
このタグがないとJavaScriptを発動出来ません。
これだけは忘れずに書いてください。

色々決まりはありますが、
基本的にscriptタグはbodyの末尾辺りに置く慣習になっています。

JavaScriptは違うページに専用ファイルとして書いていきます。
(文が長くなって読みにくくなるのを防ぐためであったり、それもまた慣習となっています。)
そのためsrc属性を使って、リンク先だけを書いておきます。

<body>
  <h1>FizzBuzz問題</h1>
  <p>FizzNum: <input type="text" name="fizzes" id="fizz" placeholder="整数値を入力してください"></p>
  <p>BuzzNum:<input type="text" name="buzzes" id="buzz" placeholder="整数値を入力してください"></p>
  <button value="実行" id="btn">実行</button>
  <div id="output">
    <p>【出力】</p>
  </div>

  <script src="main.js"></script>
</body>

またJavaScriptを使ってDOMを操作するのに必要になるのがidです。
何箇所にidがセットされているのがわかるかと思います。
このidが後々非常に役に立ちます。

HTMLは以上で完了です。
ブラウザに映る元々の表示の役割を担ってくれています。
出来ることはここまでです!
これから先の実装はJavaScriptに任せることとします。

JavaScript

//FizzNum, BuzzNumに数値を入力し、実行を押したときの挙動//


//HTMLの中からIdがbtnの要素を取得
const btn = document.getElementById('btn');

//ボタン要素のクリックイベントをトリガーにコールバック処理を作成
btn.addEventListener('click', () => {

次にJavaScriptでの処理です。
別ファイルに作ったmain.jsに書き込んでいきます。

基本的にコードは始まりはこれを書かなければいけないという厳密な決まりはないので、なにから書いてもいいはずです。
ただ、通常コードは上から下に読み込まれていくので、その点は考える必要があります。

まず、要素をidから取得します。そのためにhtmlで要素にidを付与する訳です。
その要素に対してイベント(何かしらの処理を行う)を発動する行為が、EventListenerです。
この流れはJavaScriptでは鉄板です。何回も使います。
指が覚えるくらい繰り返してもいいと思います。

//入力値の取得//

  //HTMLの中からIdがfizzの要素を取得
  const elemFizzNumber = document.getElementById('fizz');

  //fizzNumのinputに入力された数字から値を取得
  const fizzNumber = elemFizzNumber.value;

  //HTMLの中からIdがbuzzの要素を取得
  const elemBuzzNumber = document.getElementById('buzz');

  //BuzzNunのinputに入力された数字から値を取得
  const buzzNumber = elemBuzzNumber.value;

この次も要素の取得の流れは続きます。
要素を取得し、更に要素に入力された値を取得しています。
valueが登場してきます。
取得した要素を後でたくさん使うので、先に色々と取得しておく必要があります。
この辺りの考え方もプログラミングならでは、ですね。

//結果情報のベース作成//

  //HTMLの中からIdがoutputの要素を取得
  const outputArea = document.getElementById('output');

  //結果要素の子要素を取得
  outputArea.innerHTML = '';

  //pタグの要素を作成
  const ptag = document.createElement('p');

  //pタグの要素に結果情報ヘッダーの固定値をセット
  ptag.textContent = '【出力】';

  //結果要素の子要素としてpタグの要素を追加
  outputArea.appendChild(ptag);

必要な要素が取得できたら、次はその要素をブラウザに表示するための処理が必要です。
次にcreateElementが出てきます。
要素を取得したり、要素を作り出したり、なんせ要素は大事です。
この辺りも一連の流れになっているので、繰り返せば頭に自然に入ってくるはずです。

一文一文に意味があり、
やること(必要な処理)を流れで考えている、のがわかるかと思います。

//結果情報の中身を作成//

  //fizzbuzz問題のループ文

  // 変数iを定義し、iが100より小さい場合、iに1ずつ足していく処理を実行
  for (let i = 1; i < 100; i++) {

    //バリューを取得するための変数を定義
    let value = '';

    //iがfizzes、buzzesの両方の数値の倍数である場合の処理
    if (i % fizzNumber === 0 && i % buzzNumber === 0) {

      //ブラウザに表示するための文字列と取得した値をバリューとして用意
      value = "FizzBuzz" + " " + i;

      //がiがfizzesの倍数である場合の処理
    } else if (i % fizzNumber === 0) {

      //ブラウザに表示するための文字列と取得した値をバリューとして用意
      value = "Fizz" + " " + i;

      //がiがbuzzesの倍数である場合の処理
    } else if (i % buzzNumber === 0) {

      //ブラウザに表示するための文字列と取得した値をバリューとして用意
      value = "Buzz" + " " + i;

      //iがどちらの倍数でもない場合の処理
    } else {

      //何も表示させないものとして用意
      value = '';
    }

次は肝心のFizzBuzz文です。
for文の中でif文を用います。
最初に取得した要素もここで使用する訳です。
必要なものを先に準備して、後で使う。それがイメージできれば、構文も考えやすくなります。

言葉にはできてもコードで書くのは難しいです。
for文にしろ、if文にしろ、プログラミングには書き方(法則)があるので、それを自分の脳内に落とし込む作業が必要になります。
頭に入ってしまえば、後は必要なときに取り出すだけです。

これだけあれば、FizzBuzz文をブラウザに表示できます。
後は、エラーが出たときの処理を付け足したりして、より強固な機能を付け加えるといいかと思います。

以上が、FizzBuzz問題の作り方です。

以下、全体のコード

`use strict`

//FizzNum, BuzzNumに数値を入力し、実行を押したときの挙動//


//HTMLの中からIdがbtnの要素を取得
const btn = document.getElementById('btn');

//ボタン要素のクリックイベントをトリガーにコールバック処理を作成
btn.addEventListener('click', () => {

  //入力値の取得//

  //HTMLの中からIdがfizzの要素を取得
  const elemFizzNumber = document.getElementById('fizz');

  //fizzNumのinputに入力された数字から値を取得
  const fizzNumber = elemFizzNumber.value;

  //HTMLの中からIdがbuzzの要素を取得
  const elemBuzzNumber = document.getElementById('buzz');

  //BuzzNunのinputに入力された数字から値を取得
  const buzzNumber = elemBuzzNumber.value;

  //結果情報のベース作成//

  //HTMLの中からIdがoutputの要素を取得
  const outputArea = document.getElementById('output');

  //結果要素の子要素を取得
  outputArea.innerHTML = '';

  //pタグの要素を作成
  const ptag = document.createElement('p');

  //pタグの要素に結果情報ヘッダーの固定値をセット
  ptag.textContent = '【出力】';

  //結果要素の子要素としてpタグの要素を追加
  outputArea.appendChild(ptag);

  //結果情報の中身を作成//

  //fizzbuzz問題のループ文

  // 変数iを定義し、iが100より小さい場合、iに1ずつ足していく処理を実行
  for (let i = 1; i < 100; i++) {

    //バリューを取得するための変数を定義
    let value = '';

    //iがfizzes、buzzesの両方の数値の倍数である場合の処理
    if (i % fizzNumber === 0 && i % buzzNumber === 0) {

      //ブラウザに表示するための文字列と取得した値をバリューとして用意
      value = "FizzBuzz" + " " + i;

      //がiがfizzesの倍数である場合の処理
    } else if (i % fizzNumber === 0) {

      //ブラウザに表示するための文字列と取得した値をバリューとして用意
      value = "Fizz" + " " + i;

      //がiがbuzzesの倍数である場合の処理
    } else if (i % buzzNumber === 0) {

      //ブラウザに表示するための文字列と取得した値をバリューとして用意
      value = "Buzz" + " " + i;

      //iがどちらの倍数でもない場合の処理
    } else {

      //何も表示させないものとして用意
      value = '';
    }

    //要素を追加するためのpタグ要素を作成
    const fizzbuzz = document.createElement('p');

    //テキストの内容に、ループ文で取得した値を入力する
    fizzbuzz.textContent = value;

    //親要素であるdivタグの要素を取得
    const div = document.querySelector('div')

    //div要素の子要素として追加
    outputArea.appendChild(fizzbuzz);
 }
});

学んだこと

一から何かを作るのは大変です。
私は一ヶ月ほどかかって今回のFizzBuzz問題の作り方を理解しました。
時間はかかりましたが、考えを頭に落とし込むにはそのくらいの時間をかけてもいいと思います。

例え簡単なFizzBuzz文であっても、本当にわからないことだらけでした。
わからないながらでも作っていき、その中で学んでいくことが大半です。

コード自体は学習したことをそのまま応用しているだけなので、難しいことは書いていません。
しかし、学んだことを応用する作業が非常に難しいです。

何を書けばいいかわからない(頭に浮かんでこないから)という問題は、私も例外なく陥りました。
そのくらい、プログラミングは生活から切り離されているもの(馴染みのないもの)なんだなと感じました。
こればっかりは繰り返して学んでいくしかないので、
プログラミングという概念を頭に埋め込んでいく時間が必要です。

また、DOM操作も初心者が躓く概念だと思います。
普段の生活でDOMのような考え方はありませんから、最初はよくわからないのも当然です。
しかし、JavaScriptを学ぶ上でDOMの概念は切っても切れないので、しっかり脳内に刻み込んでおく必要があります。

ここは踏ん張りどころです。一つ一つ乗り越えていくしかありません。
その過程で出来ないことが出来るようになっていくのはとても嬉しいものです。

教材で学習するのは最低限必要ですが、その先は何かを作れ、という先人達のアドバイスは本当にその通りだと思いました。

おわりに

今回は私が初めて行った課題について説明しました。
今振り返ると簡単なことでも、振り返ると当初は全然わからず、頭を抱えていました。
初歩的なことでも初心者はわからないものです。

振り返ると、自分も少しは理解できていることに気づきます。
きっとプログラミング学習は、そんな小さな喜びの連続なんだと思っています。

毎日勉強して、新しいことを吸収していきたいものです。

人に説明するのも難しいものです。
と同時に、自身の学びにも繋がるので、随時アウトプットしていきます。

ありがとうございました。

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

varとletとconstの違いメモ

自分的なメモです。
varとletとconstの違いを説明してみろと言われた時のための

  • varはES6より前で使われていた宣言方法。
  • letとconstは、ES6から採用された、新しい宣言方法。

再宣言、再代入に関する違い

var

再宣言、再代入が可能。

//再代入できる
var hoge = 'りんご';
hoge = 'みかん';

console.log(hoge); // 結果:みかん

// 同じ変数名を使って別のデータを再宣言できる
var hoge = 'すいか';

console.log(hoge); // 結果:すいか

let

letは代入し直すことはできるけど、同じ変数名を使って別のデータを再宣言するとエラーが出る。

// 再代入できる
let hoge = 'りんご';
hoge = 'みかん';

console.log(hoge); // 結果:みかん

let hoge = 'すいか';

console.log(hoge); // 結果:シンタックスエラー

再宣言はできないので

Uncaught SyntaxError: Identifier 'hoge' has already been declared
っていうシンタックスエラーが出ますよ

const

constは変数の中でも一番厳格で、再代入も再宣言もできない。constのことを変数ではなく「定数」と定義する場合もある
一度しか代入できないので、長いプログラムを書いた時に変数名が被らない。

// 再代入できる
const hoge = 'りんご';
hoge = 'みかん';

console.log(hoge); // 結果:シンタックスエラー

const hoge = 'すいか';

console.log(hoge); // 結果:シンタックスエラー

こちらもシンタックスエラー
Uncaught SyntaxError: Identifier 'hoge' has already been declared
が出ます

スコープの違い

var,let,contはそれぞれ影響範囲(スコープ)が異なります。

varの場合

関数スコープなので、関数の外にある変数に格納されているデータを関数内で出力することができません。

var hoge = 'りんご';
console.log(hoge); // 結果:りんご

function fugafuga(){

  console.log(hoge); // 結果:エラー

  var hoge = 'みかん';
  console.log(hoge); // 結果:みかん
}
fugafuga();

letの場合

  • 関数の外から呼び出すことは可能。外から呼び出した値を上書きすることもできる。
  • だけど再宣言は禁止されているのでエラーがでる
  • if{}やfor{}内で宣言したらその中でしか使用できなくなるブロックスコープなので、影響範囲が少なくなる
let hoge = 'りんご';
console.log(hoge); // 結果:りんご

function fugafuga(){

  console.log(hoge); // 結果:りんご

  hoge = 'みかん';
  console.log(hoge); // 結果:みかん

  let hoge = 'みかん';
  console.log(hoge); // 結果:Uncaught ReferenceError: Cannot access 'hoge' before initialization 

}
fugafuga();

constの場合

  • 関数の外から呼び出すことは可能。
  • 再代入もできず再宣言もできないのでエラーがでる。より厳格に定数内に収められた値を扱うことができる。
  • if{}やfor{}内で宣言したらその中でしか使用できなくなるブロックスコープなので、影響範囲が少なくなる
const hoge = 'りんご';
console.log(hoge); // 結果:りんご

function fugafuga(){

  console.log(hoge); // 結果:りんご

  hoge = 'みかん';
  console.log(hoge); // 結果:Uncaught TypeError: Assignment to constant variable. 

  const hoge = 'みかん';
  console.log(hoge); // 結果:Uncaught ReferenceError: Cannot access 'hoge' before initialization  

}
fugafuga();

とりとめのない感じになったけど、頭の中では整理できたので良しとする。
結論としては、今後はvarできるだけ使わないほうが良いよということでしょうか。以上です。

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

AltJS製のNodeモジュールをGitHubから直接インストールすると同時に、そのライブラリのビルドを自動化するpackage.jsonの設定

この投稿では、NodeモジュールをGitHubから直接インストールする際、そのライブラリをインストールのタイミングでビルドする方法を紹介します。

この手法はどういうときに使うか?

普段、yarn add ライブラリ名をすると、npmjs.orgのレジストリにアップロードされたモジュールが、手元のnode_modulesに入ってきます。仮に、ライブラリのソースコードがTypeScriptやBebelなどのAltJSであったとしても、node_modulesにインストールされるのは普通ビルド後のJavaScriptです。これはライブラリ開発者がnpmjs.orgに公開するとき、AltJSからJavaScriptにビルドしたものをアップロードしてくれているからです。

なので、モジュール利用者は、ライブラリのソースコードがもともとどんな言語で書かれているかを気にする必要がありませんし、ましてや、モジュール利用者がnpm installしするたびに、わざわざライブラリのソースコードからビルドする必要は普通ありません。

どうしてもソースコードからインストール&ビルドしたい場合に使う

しかし、場合によっては「普通」じゃないインストールをしたい場合があります。どうしてもソースコードからインストールする必要がある場合です。

例えば、「ライブラリのGitHubにプルリクエストを送って取り込まれたけれど、npmjs.orgには未公開。でも、すぐにそのリビジョンのコードを使いたい」といったケースです。

このケースでは、どこにもビルド済みのコードがなく、TypeScriptやBebelなどのAltJSのコードがGitHub上にあるだけ、という状況になります。

YarnやNPMでは、GitHubのURLを指定してmasterブランチをnode_modulesにインストールすることができます:

npm install 'https://github.com/suin/isObject.js.git#master'
# or
yarn add 'https://github.com/suin/isObject.js.git#master'

しかし、この方法ではAltJSのコードしかインストールされず、必要となるビルド後のJSファイルが手に入りません:

ls -a1 node_modules/@suin/is-object
CHANGELOG.md
index.ts # TypeScriptしかない!
LICENSE
package.json

このままでは、モジュールをimport/require()しても使えません。

モジュールのインストールと同時にビルドを実行する必要があるのです。

モジュールインストール時にビルドを実行するためのpackage.jsonの設定

では、npm installyarn addと同時に、インストールしたモジュールのビルドを実行するにはどうしたらいいか、どういう設定をpackage.jsonに施したらいいかについて説明します。

ステップ1: モジュールのビルド方法を調べる

まず、自プロジェクトのpackage.jsonをいじる前に、ビルドするパッケージのpackage.jsonを開いて情報収集します。

例えば、@suin/is-objectパッケージのpackage.jsonは以下のようになっています:

package.json
{
  "name": "@suin/is-object",
  "version": "1.1.3",
  "description": "TypeScript friendly isObject function",
  ...
  "main": "index.js",
  ...
  "scripts": {
    "build": "tsc",
    ...
  },
  "devDependencies": {
    ...
  }
}

この中で注目するのは、mainフィールドとscriptsフィールドです。

1つ目のmainフィールドはビルド後の成果物のファイル名です。ライブラリ利用者の我々としては、これが欲しいのでこのファイル名をメモしておきます。

2つ目のscriptsフィールドには、たいていビルドするためのコマンドが定義されています。多くの場合、buildという名前で定義することが多いです。ここはライブラリの開発者次第なので、よく調べてみる必要があります。ライブラリによってはREADME.mdやCONTRIBUTING.mdにビルドの手順が書いてあるかもしれないので、そこも目を通しておきます。この@suin/is-objectパッケージの例では、buildコマンドだけで良いことがわかります。

また、パッケージがYarnとNPMどちらでビルドしているのかも調べておきます。これは、yarn.lockとpackage-lock.jsonどちらがあるかで判断できます。

ステップ2: postinstallにビルドのスクリプトを組む

上の調査で、ビルド後の成果物ファイル名とビルド方法が分かったら、自プロジェクトのpackage.jsonに手を加えて、モジュールのインストール時にビルドを実行するように設定します。

yarn addnpm install後に何かコマンドを実行するには、postinstallscriptsフィールドに追加します:

package.json
{
  "name": "my-app",
  "scripts": {
    "postinstall": "echo インストール後になにかするよ"
  }
}

CleanShot 2020-07-01 at 09.23.12@2x.png

postinstallにはシェルスクリプトが書けるので、ここにモジュールをビルドするためのコマンドを書いていきます。@suin/is-objectパッケージの例では、次のようなステップでビルドします。

  1. 成果物ファイルのindex.jsがあるか確認する。
  2. あればビルドしない。→ 終了
  3. なければ、
    1. モジュールが依存するパッケージをインストールする。
    2. モジュールをビルドする。 → 終了

これをシェルスクリプトに起こすと、次のようになります:

set -eux
# 成果物ファイルの`index.js`があるか確認する。
if [ ! -f ./node_modules/@suin/is-object/index.js ]
then # なければ、
  cd ./node_modules/@suin/is-object
  # モジュールが依存するパッケージをインストールする。
  yarn install --frozen-lockfile
  # モジュールをビルドする。
  yarn build
fi

このシェルスクリプトをpostinstallに書いておきます:

package.json
{
  "scripts": {
    "postinstall": "set -eux; if [ ! -f ./node_modules/@suin/is-object/index.js ]; then cd ./node_modules/@suin/is-object; yarn install --frozen-lockfile; yarn build; fi"
}

この状態で、モジュールをGitHubからインストールしてみます:

yarn add 'https://github.com/suin/isObject.js.git#master'

実行結果は次のようになります。ライブラリのインストール後に、ライブラリのビルドが行われました:

CleanShot 2020-07-01 at 09.49.27@2x.png

以上で、package.jsonの設定は完了です。

これにより、AltJS製のNodeモジュールをGitHubから直接インストールすると同時に、そのライブラリのビルドが自動化されます。

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

共通jsの発火位置を秒で特定する

はじめに

.js-accordionとクラス名に当てるとアコーディオンになるみたいな、
良く使われるjs処理がどこで記述されてるかを一瞬で見つける方法です。

結論を先に書くと
Networkタブで検索かけたら一瞬で見つかりました!

使い所

サイト全体で共通のjsファイルなんかを読み込んでおいて、
その中で良く使われるアコーディオンなり、アンカーリンクなりの処理を書いておいて、どこでも呼び出せるようにみたいなことって良くあるかと思います。

ただ、ある程度長く運用を続けているサイトになってくると、
共通で読み込んでるjsファイルが数が増えてきちゃって

  • base.js
  • share.js
  • utilty.js

みたいな後から入った人からするとどこに何が書かれてるか分かんない状況になっちゃうんですよね。
で、アコーディオンの挙動に問題があった場合とかまず調査するためにその処理がどこに書かれてるか探すところから始めるかと思いますが、これを僕は検証機能の「source」タブを使って読み込まれたjsファイルを一つ一つ開いて「.js-accordion」で検索かけたりして探してたんですよね。

これを「Network」タブの方でCtrl+fしたら左側にこんな検索メニューでてくるんですよ。
スクリーンショット 2020-07-01 8.35.18.png
ここで「.js-accordion」とかって検索すると、
それが書かれてるファイルと行数がパッと出てくるんでそれで解決です。

誤操作でたまたまこの探し方発見したんですけど感動しました。

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