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

React Nativeをいちから始める<公式ドキュメントを読んでいく> Part.2 Environment setup

こんにちは。

スマホアプリを作成するフレームワークReact Nativeの公式ドキュメントを読んでいくシリーズ第2回は、環境設定です。

本記事の位置づけ

基本的に、公式ドキュメントの解説を上から順番に見て、ざっとまとめた記事です。
また、本ページは、下記公式ドキュメントの一番最初「The Basics」の部分をまとめた記事となります。

https://reactnative.dev/docs/getting-started
- Part1. The Basics
- Part2. Environment setup ←今ココ★
- Part3. Workflow
- Part4. Design
- Part5. Interaction
- Part6. Inclusion
- Part7. Performance
- Part8. JavaScript Runtime
- Part9. Connectivity
- Part10. Native Components and Modules
- Part11. Guides (Android)
- Part12. Guides (iOS)

なお、本ページに記載のソースコードはすべて上記サイトから引用したものとなります。

開発環境のセットアップ

  • モバイル開発経験がない方は、ExpoCLIを使うのが便利
  • ブラウザでぱっと動作を確認したい場合は、Snackを使うのが便利
  • モバイル開発の経験がある方は、React Native CLIを使うのが便利(ここでは解説しない)

Expo CLIのセットアップ

1.Node.js(バージョン12 LTS以降)をインストール
2.npmでExpo CLIをインストール

npm install -g expo-cli

3.以下コマンドで"AwesomeProject"という名前のプロジェクトを作る

expo init AwesomeProject
cd AwesomeProject
npm start

npm startの代わりにexpo startでもよい

Expo CLI上でのアプリ実行

1.Expoアプリをスマホにインストールする(iOS、Android両方あり)
2.PCとスマホを同じWifiネットワーク上に接続する
3.Android:Expoアプリ上でPCのターミナル上のQRコードを読む
iOS:カメラアプリのQRコードリーダーでPCのターミナル上のQRコードを読む
4.アプリがスマホで実行される
5.メインファイルであるApp.jsの修正からしていきましょう!コードを修正すると自動的にスマホに反映されます。

困ったときのリンク

Expo CLI利用時の注意事項

  • Expo CLIはネイティブコードを実行しているわけではないため、各プラットフォームのネイティブコードは実行できない
  • 各デバイスのシミュレータや実機で動かしたい場合もExpo CLIは使えない
  • 上記のような場合は、React Native CLI Quickstartを読んでネイティブ環境をセットアップするなり、ネイティブコードを"eject"するなどの必要がある。
  • Expo CLIは最新のReact Nativeをフォローするため、どのバージョンをサポートしているかはバージョン依存のページを見てね。

既存アプリとの統合

すみませんが、この章は割愛します。
原文を読んでください。

TVデバイスとの統合

すみませんが、この章は割愛します。
原文を読んでください。

Android、iOS、Web以外のプラットフォーム

下記のプラットフォームに対応しているので、気になるものはぐぐってみてください。
実験的に対応しているだけのものもあるため、よく読んでから使うようにするとよいです。

  • React Native Windows
  • React Native DOM
  • React Native Turbolinks
  • React Native Desktop
  • React Native macOS
  • React Native tvOS
  • alita
  • Proton Native

また、好きなプラットフォームに独自に対応させることもできるようなので、こちらもドキュメントを参照してください。

Creating your own React Native platform

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

【Nuxt.js】Nuxt文法編:component②動的コンポーネント

? この記事はWP専用です
https://wp.me/pc9NHC-FE

前置き

今回はcomponent①で解説した
自作コンポーネントを動的に変えられる
componentタグについての解説です✨?‍♀️

⬇️公式ガイドはこちら
https://jp.vuejs.org/v2/guide/components.html#動的なコンポーネント
https://jp.vuejs.org/v2/guide/components-dynamic-async.html

ボタンで表示させる
コンポーネントを切り替えています??
条件つきのキャッシュ保存もやりますよ?‍♀️

非同期コンポーネントは
NuxtならasyncDataが使えるので
そちらをご覧ください?

【Nuxt.js】Nuxt文法編:asyncData

ただasyncDataは
pageコンポーネントでしか使えないので
通常のコンポーネントで使うなら
こちらを参考にすると良いかと思います??‍♀️
https://qiita.com/hiroyukiwk/items/b83f52e6d899b06506cb

簡単な使い方

clear.gif

component v-bind:is
動的に複数のコンポーネントを
切り替えることができます?

切り替える度に
新しいインスタンスが作成され
キャッシュはクリアされてしまいます?
クリアされないようにするには
componentタグを
keep-aliveタグで囲むのですが、
まずは簡単な使い方を理解しましょう?

切り替えできることが分かれば良いので
コンポーネント名は安直に命名してます。

❓キャッシュ
保存する仕組みのことです?
キャッシュのクリア
=保存ができない状態です?
Input1に入力した文字が
別のコンポーネントを表示させると
なくなってしまっていますね?

コード

index.vue
<template>
 <div class="page">
   <button @click="changeComponent = 'Input1'">Input1</button>
   <button @click="changeComponent = 'Input2'">Input2</button>
   <button @click="changeComponent = 'Input3'">Input3</button>
   <component
     v-bind:is="changeComponent"
     class="box"
    >
     ここにコンポーネントが表示されます
    </component>
 </div>
</template>

<script>
import Input1 from '~/components/Input1.vue'
import Input2 from '~/components/Input2.vue'
import Input3 from '~/components/Input3.vue'

export default {
 data () {
   return {
     changeComponent: 'Input1',
   }
 },
 components: {
   Input1,
   Input2,
   Input3,
 },
}
</script>

<style lang="scss" scoped>
 .page {
   .box  {
     border: 1px solid orange;
   }
 }
</style>

【Input1.vue】
数字だけ変えたinputのコンポーネントを
3つ作成しています。

Input.vue
<template>
 <div class="input1">
   <p>Input1</p>
   <input type="text">
 </div>
</template>

値の受け渡し

スクリーンショット 2020-09-08 14.37.14.png

propsなども使用できます⭕?‍♀️
inputでやるとややこしいので
ただのテキストをpropsで渡してみます。
画像Input1の
「親からテキストを渡す」の部分です。

? 続きはWPでご覧ください?
https://wp.me/pc9NHC-FE

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

【Nuxt.js】Nuxt文法編:v-model②

? この記事はWP専用です
https://wp.me/pc9NHC-kO

前置き

前回はinputやtextareaなどで
基本的な使い方を解説しました!
今回は親子間での使い方がメインです?

【Nuxt.js】Nuxt文法編:v-model①

基本的な使い方

親子間でv-modelをやるには工夫が必要です!

v-modelはこれと同じです?
v-bind:value, v-on:input

値は親から受け取るprops
イベントは子自体のイベントなので
親に通知するための$emitを使用します?

picture_pc_c46c90ce95ad7e5d800c2a7ea9aa5be8.gif

親で入力した値を
子のinputにも反映させています?

components/
--| atom/
----| inputs/
-----| InputDefault.vue

pages/
--| index.vue

解説

【InputDefault.vue】

・v-bind:value="value"
 value属性をvalueプロパティにバインド
 valueはpropsで値を親からもらう

@input="$emit('input', $event.target.value)"
 └@input:inputした時(イベントハンドラ )
 └$emit('input'):子のイベント名inputを親に伝える
  https://note.com/aliz/n/nd6e771724cd7
 └\$event.target.value:
  イベントが起きる(入力イベント)要素(input)の値を取得
  入力した値を取得できるってことです?
  $emitの第二引数で使えます!

コード

InputDefault.vue
<template>
<input
  :value="value"
  @input="$emit('input', $event.target.value)"
>
</template>

<script lang="ts">
export default {
props: {
  value: {
    type: String,
    // ここは影響しない
    default: 'ハロー'
  }
},
}
</script>

または
v-modelのような属性自体を親に渡す
v-bind="$attrs"でもOK⭕️

InputDefault.vue
<template>
<input
  v-bind="$attrs"
  @input="$emit('input', $event.target.value)"
>
</template>

<script lang="ts">
export default {
props: {
  value: {
    type: String,
    // ここは影響しない
    default: 'ハロー'
  }
},
}
</script>
input.vue
<template>
<div class="page">
  <form @submit.prevent>
    <InputDefault
      v-model="message"
    />
    <button
      @click="submit(message)"
    >
      送信
    </button>
  </form>
</div>
</template>

<script>
import InputDefault from '~/components/atom/inputs/InputDefault.vue'

export default {
components: {
  InputDefault,
},
data () {
  return {
    message: 'メッセージ',
  }
},
methods: {
  submit (message) {
    console.log(message)
  },
},
}
</script>

コンポーネントにネイティブイベントをバインディング

子にあるinputのイベントを
親で使いたい時にどうするのか、
という話です!

? 続きはWPでご覧ください?
https://wp.me/pc9NHC-kO

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

Discord.jsとbotで簡易スケジュール回答機能を実装する

はじめに

Discordにはスケジュール回答機能があるけれど、
URL開くのめんどくさいのでテキスト+リアクションで済ませられないかな、
さらにはそれをbotが設定してくれないかなということで簡易的なものを作ってみました。

挑戦したのでメモ。Qiita初投稿!

Discord botは導入されているものとします。bot導入についてはこちらを参考にしました。

できたもの

キャプチャ.JPG
イメージとしては上記となります。

機能としては「YYYY/MM/DD」形式でスケジュール回答したい日付をテキストチャンネルに流すと、
botがそれを拾えば該当日付から n 日分の日付と指定したリアクションをつけてくれます。

あとはスケジュール回答の通りにリアクションをぽちぽちすれば良いので
送信者も毎度メッセージにリアクションをつけなくていいですね。

書いたもの

index.js
//ログイン処理
const Discord = require('discord.js');
const client = new Discord.Client();
const token = 'ここにBotのToken';
client.on('ready', () => {
    console.log('起動時のメッセージ');
});

//Bot自身の発言を無視する呪い
client.on('message', async message =>{
    if(message.author.bot){
        return;
    }

    // YYYY/MM/DDを取得する構文
    if (/\d{4}\/\d\d\/\d\d$/.test(message.content)) {

        var str = message.content;
        var imported_day = toDate(str, '/');

        function toDate (str, delim) {
            var arr = str.split(delim)
            return new Date(arr[0], arr[1] - 1, arr[2]);
        };

        let channel = message.channel;
        let author = message.author.username;

        // 取得した日付から7日後までの日付をメッセージにて送信
        for (let i = 0; i < 7; i++)    {
            var year = imported_day.getFullYear();
            var month = imported_day.getMonth()+1;
            var week = imported_day.getDay();
            var day = imported_day.getDate();

            var yobi= new Array('','','','','','','');

            let schedule = +month+''+' '+day+''+''+yobi[week]+'';
            let hyphen = '-------------------------------------------------';
            message.channel.send(hyphen)
            message = await message.channel.send(schedule)

            // つけたいリアクション
            message.react('')
            message.react('')
            message.react('')

            imported_day.setDate( imported_day.getDate() + 1 );

        }
        return;
    }

    // おあそび要素(こんばんはと入力すると「おやすみ」と返信してくれる)
    if (/^こんばんは$/.test(message.content))   {
        message.reply('おやすみなさい');
    }
});
client.login(token);

絵文字がサーバー内固有のものを使用したいときは特有の書き方があるようです。
あとはお好きにご活用ください。

ゲームのメンバーといつゲームをするか、といった予定をたてるのに
毎週画像のようにメッセージ+リアクションをぽちぽちしていたので
毎週3-5分ほど発生していた処理が10秒で終わるようになったのでもう文句なしです。

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

よく使うjqueryのメソッド

よく使うメソッド
$("#id").remove();
$("#id").show();
var.slice();

よく使う方式
・パラメータ
・配列
・key-value
・eventProgram(function(e,args){
});
・object.attribute.attribute..

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

ElectronをバージョンアップしたらMacアプリのCode Signができなくなった話

前回に引き続きElectron9のにバージョンアップした際に困ったことメモです

MacアプリのCode Sign

  • Macアプリは10.14.5以降、署名と公証されていないといけなくなっています参考
  • なのでアプリの配布には署名作業が必須
  • 使っているライブラリ
    • 署名用ライブラリ: electron-osx-sign
    • アプリのビルド: electron-builder

Electron9へバージョンアップした後.appへの署名に失敗

  • [ERROR] app is not signed [xxxxxx.app]
  • と言われた

対策

  • electron-osx-signを最新化 v0.4.11 --> v0.4.17
  • electron-builderを最新化 v22.1.0 --> v22.7.0

これで署名できた!

参考

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

吾輩はバグを出す 〜Mapped typesでミス対策〜

ある日、某システム会社にて

ワイ「カタカタカタカタ・・・」

ハスケル子「やめ太郎さん」
ハスケル子「さっきから一生懸命、何を書いてるんですか?」

ワイ「小説や」
ワイ「これで一発当てて、会社なんて辞めてやるんや・・・!」

ハスケル子「へえ〜、凄いですね(棒読み)」
ハスケル子「ちょっと読ませてくださいよ」

ワイ「ええで」

ハスケル子「ええと、タイトルは」
ハスケル子「・・・吾輩はバグを出す・・・?」
ハスケル子「なるほど」
ハスケル子「ノンフィクション小説ですね?」

ワイ「どういう意味やねん
ワイ「フィクションや、フィクション」

仕事をしろ

ハスケル子「それよりやめ太郎さん」
ハスケル子「今日から新しい仕事が始まりますよ」
ハスケル子「株式会社ジャンケンポンさんの、コーポレートサイトを作るお仕事です」

ワイ「ああ、せやったな」
ワイ「どんなサイトを作るんやったっけ」
ワイ「仕様書を読んでみよか」

  • トップページで「ジャンケンゲーム」ができるようにして欲しい。

ワイ「なるほどな」
ワイ「社名にちなんで、って事やな」

ハスケル子「じゃあ、早速ペアプロしていきましょうか」

ワイ「せやな」

ペアプロ開始

ワイ「ジャンケンと言えば、グー・チョキ・パーやな」
ワイ「ワイはよく、チョキをチャカって打ち間違えてしまうから」
ワイ「それを防ぐために、ちゃんと型をつけておかんとな」

ハスケル子「そうですね」
ハスケル子「チャカを出したらまずいですもんね」

ワイ「そういうことや」
ワイ「せやから・・・」

type Hands = "グー" | "チョキ" | "パー";

ワイ「↑これでええな」
ワイ「これで、Hands型の変数には"グー""チョキ""パー"しか代入できひん」

type Hands = "グー" | "チョキ" | "パー";

const hand: Hands = "チャカ"; // コンパイルエラー!

ワイ「↑こんな感じで」
ワイ「チャカの混入を防げるってわけや」
ワイ「テキストエディタ上でも・・・」

スクリーンショット 2020-09-15 22.29.29.png

ワイ「↑こんな感じで、ちゃんと赤い波線が表示されて」
ワイ「ミスに気付けるで」

ハスケル子「いいですね」

ワイ「やっぱ静的型付け言語ってええなぁ」
ワイ「ブラウザ上で実際にコードを動かす前に」
ワイ「テキストエディタでコードを書いてる段階でエラーに気づけるから、便利やな」

ハスケル子「そうですね」
ハスケル子「矛盾が生じるようなコードを書いてしまった時に」
ハスケル子「事前に気づけるから効率的ですよね」

ワイ「そうそう」

ハスケル子「間違えないように人間が気をつけてプログラミングをするより」
ハスケル子「間違えたらコンパイラが怒ってくれる方が良いですもんね」

ワイ「せやせや」

ところが

ハスケル子「でもやめ太郎さん」
ハスケル子「このサイト、日本語だけじゃなくて」
ハスケル子「英語での表示もしないといけないみたいですよ」

ワイ「マジかいな」
ワイ「どれどれ、仕様書には何てかいてあんねん」

  • 英語での表示もできること。
  • 英語モードのときは「グー・チョキ・パー」を「Rock・Scissors・Paper」と表示すること。

ワイ「Oh...」
ワイ「どうすればええねん・・・」

閃いた

ワイ「分かったで」
ワイ「こういうオブジェクトを作ればええんや」

// 日本語用
const handsJa = {
    gu: "グー",
    choki: "チョキ",
    pa: "パー",
};

// 英語用
const handsEn = {
    gu: "Rock",
    chaka: "Scissors",
    pa: "Paper",
};

ワイ「↑こうしておけば・・・」

console.log(handsJa["gu"]); // グー

console.log(handsEn["gu"]); // Rock

ワイ「↑こんな感じで、"gu"という文字列を」
ワイ「日本語の"グー"に変換して表示したり」
ワイ「英語の"Rock"に変換して表示したりできるわけや」

ハスケル子「なるほど」
ハスケル子「対応表っぽいオブジェクトを作るわけですね」

ワイ「せや」
ワイ「基本的には"gu""choki""pa"という文字列を使って処理を行って」
ワイ「画面に表示するときだけは対応表オブジェクトを通して日本語や英語に変換する感じや」
ワイ「そうすれば、アルゴリズムの部分は日本語と英語を分けずに、共通化できるで!」

ハスケル子「良さげですね」
ハスケル子「でもやめ太郎さん」

    chaka: "Scissors",

ハスケル子「↑さっそくチャカが混入してますよ」

ワイ「ファッ!?
ワイ「chokichakaって打ってもうたんか」

ワイ「またチャカを打ってもうたぁ〜!!!」

ハスケル子「やめ太郎さん、語弊がエグいです
ハスケル子「通報されますよ」

プロパティ名をタイポしないために

ワイ「ワイはすぐタイポしてしまうなぁ」
ワイ「これじゃTypeScriptやなくてタイポスクリプトやで」
ワイ「・・・言うてる場合ちゃうわ」
ワイ「ほんま、気をつけんとなぁ・・・」

ハスケル子「やめ太郎さん」
ハスケル子「いつもそう言って」
ハスケル子「気をつけられた試しがないじゃないですか」

ワイ「ぐぬぬ」

ハスケル子「ここはTypeScriptの力を頼りましょう

ワイ「と言いますと・・・」

ハスケル子「Mapped typesを使いましょう」

Mapped typesを使ってみる

ハスケル子「まずは・・・」

type Hands = "gu" | "choki" | "pa";

ハスケル子「↑こうです」

ワイ「Hands型の変数には、"gu""choki""pa"しか代入できませんよ、ていう」
ワイ「そういう感じやな」

ハスケル子「はい」
ハスケル子「ここでMapped typesを使います」

type HandsDisplay = {
    [P in Hands]: string
};

ハスケル子「↑こうです」

ワイ「え?」
ワイ「ワイはてっきり・・・」

type HandsDisplay = {
    gu: string
    chaka: string
    pa: string
};

ワイ「↑こうかと思ってたわ」

ハスケル子「同じことですね」

ワイ「そうなんや」
ワイ「Hands型の値と対応したプロパティ名・・・」
ワイ「つまり"gu""choki""pa"というプロパティ名を持つ」
ワイ「そんなオブジェクト型を、サクッと書けるわけか」
ワイ「短く書けて便利やね」

ハスケル子「短いのも良いんですけど、それよりも」
ハスケル子「Hands型を元に、オブジェクトの型を自動っぽく生成できるのがメリットですね」

ワイ「ふーん」
ワイ「そこ自動化するくらいでメリットある?」

ハスケル子「あります」
ハスケル子「その証拠に、やめ太郎さんが手動で書いたHandsDisplay型には・・・」

    chaka: string

ハスケル子「↑チャカが混入しています」

ワイ「ぐぬぬ」

type HandsDisplay = {
    [P in Hands]: string
};

ハスケル子「↑こうやって」
ハスケル子「Mapped typesを使って書けば」
ハスケル子「guchokipaというプロパティを持つオブジェクト型を」
ハスケル子「確実に生成できます」
ハスケル子「仮に、Hands型の値が数百種類だったとしてもミスりません」
ハスケル子「chakaも混入しません」

スクリーンショット 2020-09-15 22.28.30.png

ハスケル子「ほら」
ハスケル子「ちゃんとchakaの部分にエラーが出てます」

ワイ「なるほどな」
ワイ「ある型に対応した、オブジェクト型を定義する・・・」
ワイ「Mapped typesを使うとそんなことができるんやな」

ハスケル子「そうですね」
ハスケル子「mapという単語には」
ハスケル子「対応づけるとか対応表って意味もありますからね」

ワイ「おお、対応表か」
ワイ「何かに対応したプロパティ名を持つオブジェクト型、って」
ワイ「割と作りたいときあるから」
ワイ「そんなときにはMapped typesを使わしてもらうわ!」
ワイ「値の種類が多い場合なんか、特に便利やな!」

ハスケル子「Hands型の値の種類が数百に増えた場合にも」
ハスケル子「HandsDisplay型は自動で追従してくれますしね!」

ワイ「おお、良さげやな!」

社長「(ジャンケンの手、そんな増えへんやろ・・・)」
社長「(片手でどうやって表現すんねん・・・)」

まとめ

ワイ「吾輩はバグを出す」
ワイ「知能はまだない」
ワイ「どこでバグらせたか、とんと見当がつかぬ」
ワイ「だから、TypeScriptを導入して」
ワイ「コンパイラに見つけてもらおう」

ハスケル子「そういうことですね」

〜おしまい〜

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

ASP.NET Core で QR コードの生成

はじめに

ここでは、Visual Studio 2019 での ASP.NET Core 3.1 プロジェクトにおいて、クライアント サイド、 JavaScript による QR コードの生成方法を説明します。
今回は、以下のライブラリを使用します。

QR コード生成ライブラリの追加

qrcode.js からダウンロードして、プロジェクトに追加しても良いですが、今回は、Visual Studio のクライアント ライブラリ マネジャー使って、プロジェクトにライブラリを追加します。

[ソリューション エクスプローラー] - [対象プロジェクト] - [wwwroot] - [lib] で、右クリック メニューから、[追加] - [クライアント側のライブラリ] を選択します。
クライアント側のライブラリ

[ライブラリ] に "qrcode.js" と入力して、[Enter] とすると、CDNJS から、最新のライブラリが検索されます。[インストール] を選択します。
qrcode.js ライブラリの追加

"wwww\lib" 以下に "qrcode" というフォルダーが作成され、同フォルダー以下にライブラリが追加されます。
wwww\lib\qrcode

ここで、プロジェクト直下の "libman.json" を開いてみてやると、"qrcodejs" のエントリが追加されていることが分かります。
libman.json
もちろん手動でプロジェクトに qrcode.js を追加してもよいのですが、パッケージ マネジャーを使ってインストールすると、削除や更新が容易になります。

QR コード生成コードの実装

QR コード生成のスクリプトを実装していきます。ここでは、Visual Studio から生成した ASP.NET Core プロジェクト テンプレート内の index.cshtml への実装例を説明します。

まず、QR コードを表示したい箇所に div 要素を定義します。この時、div 要素に任意の id を付与しておきます。
次に、Scripts セクションに、先にインストールしたライブラリへの参照と、QR コード生成のコードを記述します。
コード記述では、QRCode クラスのインスタンス生成時のコンストラクタの第一引数で、QR コードを表示したい箇所に定義した div 要素の id を指定します。

Index.cshtml
@page
@model IndexModel
@{
    ViewData["Title"] = "Home page";
}

<div class="text-center">
    <h1 class="display-4">Welcome</h1>
    <p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
    @*QR コード表示箇所*@
    <div class="qrCodeImg" id="qrCode"></div>
</div>


@section Scripts{
    @*qr.code.js への参照*@
    <script type="text/javascript" src="~/lib/qrcodejs/qrcode.js"></script>
    <script type="text/javascript">

        $(function () {
            //id=qrCode の要素に、QR コードを表示
            new QRCode('qrCode',
                {
                    text: 'https://qiita.com/hiromasa-masuda',
                    width: 150,
                    height: 150,
                    colorDark: '#8b0000',
                    colorLight: '#ffffff',
                    correctLevel: QRCode.CorrectLevel.H
                });
        });

    </script>
}

実行結果は、以下となります。
Index.cshtml

Tips としては、QR コードをセンタリングしたい場合、動的に生成される img 要素が block 要素として定義されるので、スタイル設定で、".qrCodeImg > img { margin: 0 auto; }" といったように、子要素の img を block 要素として扱ってセンタリングすればうまくいきます。

qrcode.js を使えば容易にクライアント サイドで QR コードを生成できます。
Visual Studio のライブラリ マネージャを使うことで、Java Script ライブラリの管理も容易になります。

参考サイト

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

discord.js スタートガイド(スマホ可)

どうも すえきゅーです
今回はdiscordのbotを作りたい方向けのスタートガイドをここに書きたいと思います

使うもの
・Heroku
・Github
・discord developer portal

まずdiscord developer portalでbotを登録しましょう

Botを登録する こ↑こ↓ でNew applicationを押します 50668E8C-5E1C-44C5-BB36-55B786D314F6.jpeg
訳:Botの名前を決めてね!
Botの名前はわかりやすくて他人に見られてもいいようにしましょう
名前を決めたら↓のような画面になります7DB887CC-2067-4D7B-8CC8-AA2624CC83D7.jpeg
アイコンは決めておきましょう
次は三本線を押してBotというところを押します
なんか下のようなものが出てくるのでAdd Botを押しましょうF2AD21CC-86F8-40C5-BC5F-26CDF04BEF74.jpeg
本当にするの?(下)が出てくるのでYes, do it!を押しましょう。6F1A339A-B53C-4206-AAB6-3E50FFC353AE.jpeg
こいつはBotを動かすために使うパスワードをコピーするボタンですね
注意!これを人に教えると乗っ取られます!絶対に教えないようにしましょう。
もし教えてしまったら隣のRegenerateというボタンを押しましょう。
C7667304-2B2E-4D8F-B532-4B1B2A6EC03E.jpeg
登録作業はこれで終わり!

Botの中身をつくる
まずこ↑こ↓にアクセスしUse this Templateを押す
そしたら68B32773-5822-4E82-B3DC-4993C9E22498.jpeg
こんな感じにリポジトリを作る
これで中身は完成
こいつ自体にライセンスはないです
index.jsがbotの中身だよ!
これで中身を作る作業は終わり

これで最後の作業だ!頑張れ!

Herokuで稼働させる
herokuアカウンk…アカウントを登録しましょう
職業の欄は学生でいいと思います
では次!
Newってところを押してcreate a New appを押します
そしてSettings(歯車のマーク)を押しここに行きます
こんな画面になると思うのでF1961DFE-342E-467E-A793-C6A3F9EAE518.jpeg
まずは Reveal Config Varsを押し
KEYにDISCORD_BOT_TOKENと入力したらVALUEにBOT登録作業で手に入れたTOKENをぶち込みます
次はAdd bulid packを押しNodejsを押します
なんか警告文見たいのが出ますが気にしないでください
最後!!!!
Deployで下にスクロールするとGithubとかよくわかんないのが並んでますがGithubを押しましょう それでリポジトリを作ったGithubアカウントでログインします
それでなんか下のようなところが出てくるので(語彙力0)
さっき作ったリポジトリの名前をぶち込んでサーチを押したらリポジトリが出てくるのでconnectします。
そしたら Enable Automatic Deployを押して Deploy Branchを押す
これで動くはずです…

動かなかったら discordで index#7595 にフレンド申請して質問するか 自鯖で質問してください。

最後まで見てくれてありがとうございます!
初めての記事なのであまり質が良くないかも知れません。

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

【JS】戻り値returnについて説明してみた

returnとは?

関数内で処理をした結果を「戻り値」として返す

戻り値には、
数値、文字列、真偽値、配列、オブジェクト
などが指定できます。
JavaScriptでreturnを使って関数から値を返す方法を現役エンジニアが解説【初心者向け】

JavaScriptでの『return文』の98%くらいは『関数の処理を停止するため』に書かれている

returnを使う意味って?

【returnなしの場合】

sample.js
function sample() {

    // ①処理のみを記述

}
undefined

処理のみ

関数内で処理を行って終了になる。

関数の呼び出した後→戻り値が見つからないのでundefinedを返す

【return使うの場合】

sample.js
function sample() {

    // ①処理を記述
    // ↓
    // ②return 戻り値; (結果を返す)

}

const sample = sample();

処理結果返す

処理の結果を返すことが出来る。
そのため、処理結果を別の処理で使い回すことが出来る。

sample()で、関数の呼び出しを。
定数sampleには、呼び出した後に、returnで返した値を格納しています。

console.log()との違い

console.log()

console.log()は、そもそもどういったときに使うのか?

デバックや、情報の出力を行う時に使います。

return

対して、returnは、処理結果を返します。

似たような意味だが、違いは?

データを提示すると言った意味では、同じです。

簡単にすると、

console.log()は、データの情報を表示して確認するためのモノ。

return:プログラムを結果として返す処理。

ですかね?(間違っていたらすみません)

訂正

コメント(@felisさん)より

・returnが返すものは、『値』そのものではなく、関数の呼び出した場所に戻すという『動作』
return 返り値;:returnの後ろに記述されているのが、その結果として返ってきた返り値。

参考

【JavaScript入門】returnの使い方と戻り値・falseのまとめ!

関数から呼び出し元へ値を返す

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

【JS】returnについて説明してみた

returnとは?

関数内で処理をした結果を「戻り値」として返す

戻り値には、
数値、文字列、真偽値、配列、オブジェクト
などが指定できます。
JavaScriptでreturnを使って関数から値を返す方法を現役エンジニアが解説【初心者向け】

JavaScriptでの『return文』の98%くらいは『関数の処理を停止するため』に書かれている

returnを使う意味って?

【returnなしの場合】

sample.js
function sample() {

    // ①処理のみを記述

}
undefined

処理のみ

関数内で処理を行って終了になる。

関数の呼び出した後→戻り値が見つからないのでundefinedを返す

【return使うの場合】

sample.js
function sample() {

    // ①処理を記述
    // ↓
    // ②return 戻り値; (結果を返す)

}

const sample = sample();

処理結果返す

処理の結果を返すことが出来る。
そのため、処理結果を別の処理で使い回すことが出来る。

sample()で、関数の呼び出しを。
定数sampleには、呼び出した後に、returnで返した値を格納しています。

console.log()との違い

console.log()

console.log()は、そもそもどういったときに使うのか?

デバックや、情報の出力を行う時に使います。

return

対して、returnは、処理結果を返します。

似たような意味だが、違いは?

データを提示すると言った意味では、同じです。

簡単にすると、

console.log()は、データの情報を表示して確認するためのモノ。

return:プログラムを結果として返す処理。

ですかね?(間違っていたらすみません)

訂正

コメント(@felisさん)より

・returnが返すものは、『値』そのものではなく、関数の呼び出した場所に戻すという『動作』
return 返り値;:returnの後ろに記述されているのが、その結果として返ってきた返り値。

参考

【JavaScript入門】returnの使い方と戻り値・falseのまとめ!

関数から呼び出し元へ値を返す

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

webpackerのしくみ

webpackerについて

前回webpackerをインストールする際、こんな記事を書きました。
Webpackerとは (Rails6で rails s してエラーが出た時の話)

webpackerの構成

webpackerのインストールで生成されたディレクトリapp/javascript/packsの中に
application.jsというファイルが作成されます。
webpackerはapp/javascript/packsディレクトリにあるファイルをエントリポイントとしてコンパイルを実行します。
それ以外のjsファイルはapp/javascript配下の他のディレクトリに置き、app/javascript/packs/application.jsから読み込みます。

(※エントリポイント・・・コンピュータプログラムを実行する際に、一番最初に実行することになっている箇所のこと。ここでは "コンパイルを開始するファイル" の意味。)

例:

app/
 ├ javascript/
 │ └channels/
 │ └ packs/
 │   └ application.js (エントリポイント)
 └ example/
   └ example.js

app/javascript/packs/application.js
// デフォルトの記述
require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")

// example.jsを読み込む
import '../example/example'

app/views/layouts/application.htmlに下記を記述することで、コンパイルしたjavascriptをビューで読み込むことができます。

app/views/layouts/application.html.slim
// slimの場合
= javascript_pack_tag 'application'

// ERBの場合
<%= javascript_pack_tag 'application' %>

もうちょっと詳しく

加筆予定です。
RailsアプリにReactを導入してみようと思います。

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

【初学者必見】"スライド1枚"でJavaScriptの「関数・引数・戻り値」の処理順序を解説

はじめに

私はプログラミング学習初期にJavaScriptの「関数・引数(ひきすう)・戻り値」の理解でつまずきました。その時に 「プログラムがどのような順番で処理されているかが一目で分かる」スライドがあったら理解しやすいと思ったため、実際にスライドを作成して図解してみました。

記事の見方

スライド1枚で「関数・引数・戻り値」の処理が理解出来ることを目指しているので、まずはスライドを見ていただければと思います。もし、理解がいまひとつだったり、違うアプローチでも学んでみたいという方に向けて、【補足】初学者の頃の自分に対して解説という項目で、より細かい文章に落とし込んでいますので、是非そちらの方も参考にしていただければと思います。

想定読者

JavaScriptの「関数・引数・戻り値」の理解に悩まれている方をイメージして執筆いたしました。
少しでもお力になれば幸いです。

今回解説するコード

main.js
function calculateSquareArea(length) {
    return length * length;
}
const result = calculateSquareArea(3);
console.log(result);

スライド1枚解説

関数_引数_戻り値.001.jpeg

【補足】初学者の頃の自分に対して解説

関数ってなに?

ひとまず「関数=プログラムの収納ボックス」と考えればいいと思います。
イメージとして、「普段は収納ボックスの中にプログラムをしまっておいて、必要な時に収納ボックスを開けてその中のプログラムを実行する」、という感覚を持っておけばいいのではないかと。

関数の作り方と呼び出し方を2ステップで解説

① 関数の定義 (収納ボックスを作ってプログラムを収納する)

まずは収納ボックス自体を作る必要があります。言い換えると「関数の定義」をする必要がある。
理由としては関数の定義をしなければ、プログラムを収納する場所もないからです。
実際に下記のコードを元に解説していきます。

【関数の定義方法】

「function」は関数を作ることの宣言を意味している。(今から収納ボックスを作るという宣言)
その横に収納ボックスの名前、つまり関数名(calcalateSquareArea)を書くことで関数の定義が出来るということになる。あとは{ }の中に処理したいプログラムを書いていけばOK。

main.js
function calculateSquareArea() {
   //収納ボックスの中身(プログラムをここに書いていく) 
}

今回はcalculateSquareAreaという関数を作ったわけですが、
関数の命名に関してはいくつか決まり事があるので、参考リンクまでに貼っておきます。

JavaScriptの命名規則▶︎https://techacademy.jp/magazine/21448
予約語▶︎https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Reserved_Words

② 関数の呼び出し (収納ボックスを開けて中のプログラムを実行する)

①で収納ボックス作ってその中にプログラムを準備しましたが、それだけだとプログラムは動きません。
理由としては収納ボックスを開ける必要があるからです。このボックスを開けて中のプログラムを動かすことを「関数の呼び出し」 といいます。

【関数の呼び出し方法】

下記の通り関数の外に関数名を書くだけで、
記述された関数が呼び出されてその中に書かれているプログラムを実行することが出来ます。

main.js
function calculateSquareArea() {
   //収納ボックスの中身(プログラムをここに書いていく)
   //ここに書かれたプログラムは関数が呼び出された時に実行される
}

//関数を呼び出すための記述(収納ボックスを開ける)
calcalateSquareArea();

引数と戻り値ってなに?

引数とは

関数への入力を意味しています。
関数で出てきた収納ボックスの例えを用いると、収納ボックス(関数)を開く時に、中に投入するもの(引数)というイメージです。
つまり、引数とは、関数を呼び出す時に、関数やプログラムの中に入力する「値」のこと。

戻り値とは

関数からの出力を意味しています。
関数で出てきた収納ボックスの例えを用いると、収納ボックス(関数)を開いて、中のものを取り出す(戻り値)というイメージです。
つまり、戻り値とは、関数を実行した結果、出力される「値」のこと。

身近なものに例えて解説

もう少し具体的な例えで解説してみます。

関数 = 洗濯機
引数 = 汚れた白シャツ
戻り値 = 綺麗な白シャツ

洗濯機を回す時(関数を呼び出す時)に、汚れた白シャツ(引数)を入れる。
洗濯機を回した結果(関数内部のプログラムを実行した結果)、綺麗な白シャツ(戻り値)が出てくる。

実際のコードを見ながら解説

スライドでも解説しているコードを用いて説明してみます。
ここからの説明や処理の順番で分からなくなったら一度スライドを見返してみて下さい。

解説するコードはこちら

main.js
                             //⬇︎仮引数
function calculateSquareArea(length) {

   return length * length; 
}
                                //⬇︎実引数
const result = calculateSquareArea(3);

console.log(result);

関数の呼び出し

最初に関数の呼び出しを行います。(スライド③-①参照)

呼び出しと同時に、定義された関数calculateSquareAreaに3という引数を渡そうとしています。

「洗濯機のふたを開けて、汚れた白シャツを中にいれようとしている状態」

main.js
              //⬇︎関数の呼び出し  //⬇︎実引数
const result = calculateSquareArea(3);

引数の引き渡し

呼び出された関数が下記のコードにあたります。
まず、実引数(3)を、仮引数(length)に引き渡します。結果「 length = 3 」となります。(スライド③-②参照)
引数は関数内のプログラムで使うことが出来るので、実際に内部の処理を見ていきます。

「洗濯機の中に、汚れた白シャツをいれた状態」

戻り値を呼び出し元に返す

まず、関数内のlength ✳︎ lengthという処理を実行すると9という結果になることが分かります。
次にreturnという記述がありますが、これは関数内の処理結果を関数の呼び出し元に返す(出力する)という意味があります。
今回でいうと9という結果を呼び出し元に返すことになります。(スライド③-⑤参照)

「洗濯機から、きれいな白シャツが出てきた状態」

main.js
                             //⬇︎仮引数(実引数から引き渡された値が入る)
function calculateSquareArea(length) {
   //⬇︎returnで処理結果を呼び出し元に返す
   return length * length; 
}

関数の戻り値を変数に代入

呼び出し元に返ってきた戻り値9を変数resultに代入されます。
代入された変数resultは最後の行でコンソールに出力されます。
これにてコード解説を終了とします。

main.js
              //⬇︎戻り値9が返ってきている
const result = calculateSquareArea(3);

console.log(result);  //9という結果をコンソールに出力

参考リンク

関数・引数・戻り値についてまとめて説明されています
https://www.sejuku.net/blog/31671

最後に

Qiita初投稿でしたが、いかがだったでしょうか?
誰が見ても理解出来る記事を目指して執筆いたしますので、間違いなどあればご指摘いただけると幸いです。
もし、「いいね!」と思われた方は「LGTM」を押していただけると大変励みになりますので宜しくお願いしますm(__)m

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

Riot.jsの特徴

プログラミング勉強日記

2020年9月15日
JSには色々なフレームワークがあって、開発の用途によって使用するフレームワークが違うと思うので、まとめてみようと思う。ReactについてAngularについてVue.jsについてNext.jsについてまとめたので、今回はRiot.jsについてまとめる。

Riot.jsとは

 Riot.js(読み方:ライオットジェーエス)はシンプルで軽量なコンポーネント指向のUIライブラリで、フロントエンドの開発で人気を集めている。
 Riot.jsはHTMLに似た文法を採用しているので、比較的使いやすいJSライブラリである。公式サイトは日本語に対応している。

特徴

  • コンポーネント指向
  • 軽量でシンプル
  • 学習コストが低い
  • 大規模開発向きではない
  • シェアが低い

コンポーネント指向である

 ReactとAngular、Vue.jsと同様で、Riot.jsもコンポーネント指向である。コンポーネント指向は、ソフトウェアを機能ごとに部品(コンポーネント)として分割して、必要に応じて組み合わせて使う考えのことである。
 機能を小さい部品にして持たせているので、状態の管理もしやすくて再利用も容易にできる。再利用性が高くなり、デザインと技術の共同作業を簡単にすることができ、開発スピードの向上ができる。
 Riot.jsではHTMLとJSを組み合わせることで、再利用可能なコンポーネントとして定義したものをカスタム要素・カスタムタグという。

軽量でシンプルである

 Riot.jsのすべては10KBに収まる容量で、ダウンロード速度が速くて、メンテナンスコストも低く済む。

学習コストが低い

 公式サイトが日本語に対応していることもあり、勉強しやすい。また、コードの量も少なくて学習量が他のライブラリと比べて低く抑えられる。

大規模開発向きではない

 Riot.jsはAngularのようにフルスタックじゃないので、データバインディング機能もない。また、双方向データバインディングがサポートされないので、自力で調べて実装する必要がある。

参考文献

Riot.jsの使い方を入門者向けに解説!他のライブラリと比較した際のメリットを紹介!フロントエンドにオススメの理由とは
「.js」選びに迷った時に役立つ!人気のJavaScriptライブラリ&フレームワークまとめ!
JavaScript

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

Microsoft TeamsのWebhook URLにNode.jsから情報を送ってみる

最近仕事のやりとりでTemasを使う機会が増えてきました。

Teamsのハックを出来ないかと調べてみているメモです。

Webhook URLの取得

自分が管理権限があるチームの左下にアプリボタンがあると思います。

スクリーンショット 2020-09-15 16.27.59.png
割とこの第1ステップで迷いました。

ここからIncomming Webhookを探してチームに追加します。

利用するチャンネルを選びます。

名前を適当に決めましょう。

作成ボタンを押すとhttps://outlook.office.com/webhook/~~~というアドレスが発行されます。

CURLで試す

cURLだと以下をコピペで実行できます。

$ curl -H 'Content-Type: application/json' -d '{"text": "Hello World"}' <YOUR WEBHOOK URL>

実行結果はこんな感じです。

スクリーンショット 2020-09-15 16.36.56.png

参考: cURL を使用してメッセージを Webhook に投稿する

ここまできたらこちらのものですね。

Node.jsから試す

axiosを利用します。

$ npm ini -y
$ npm i axios
app.js
'use strict'

const axios = require('axios');
const URL = `https://outlook.office.com/webhook/~~~`; //各自のWebhook URL

axios.post(URL, {text: 'Node.jsから送信'})
    .then(res => console.log(res.data));

シンプルなコードですね。
実行します。

$ node app.js
1

res.dataには1という数字が返ってくる模様。

スクリーンショット 2020-09-15 16.53.26.png

所感

Webhook URLの発行で結構迷いました。Slack, LINE, Discordなど様々なBOTを触りましたが今回はどんなパターンだろう......といった感じです。今のところの結論はSlack BOTに近いような印象です。Incoming WebhookのアプリというかBOTアカウントをチームに追加すると、連動しているWebhook URLが発行されるといった流れ。

他にもやり方ある気がするので引き続き調べてみます。

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

検索ワードが複数あるときまとめてグーグル検索する

検索したいワードが複数あるときまとめてグーグル検索する

searchGoogle.gif

何を作りましたか

検索したいワードが複数あるときにそれぞれでコピーしてグーグル検索するのは大変なので、まとめてコピーして検索できるようなコードを書きました。使うのはHTML、Javascriptファイルです。

注)ファイルをHTML、Javascriptに分割していますが、そうするとサーバーのコードが必要になるかもしれません。利用するときはHTMLファイルにコードをまとめて利用しています。

環境

Firefox Browser 80.0.1
Mac OS Catalina 10.15.3

HTML ファイル

sample.html
  <h1>まとめてグーグル検索</h1>
  <form>
    <div>検索したい文を入力してください。</div>
    <div>以下の例のように、改行するとそれぞれの行ごとに検索するよ(タブで開く)</div>
    <textarea name="textarea"
    rows="5" cols="30"
    placeholder="例)&#13;&#10;あいうえお&#13;&#10;かきくけこ&#13;&#10;東京 寒い"></textarea>
    <input type="submit" value="Subscribe!">
  </form>

  <h3>以下をコピーして検索してみよう</h3>
  <ul>
    <li>白日</li><li>白目</li><li>白曰</li><li>白臼</li><li>白自</li><li>白白</li><li>日白</li><li>日臼</li><li>日自</li><li>日日</li><li>曰白</li><li>曰臼</li><li>曰自</li><li>目曰</li><li>目臼</li><li>目自</li><li>臼白</li><li>臼日</li><li>臼目</li><li>臼曰</li><li>臼自</li><li>自白</li><li>自日</li><li>自目</li><li>自曰</li><li>自臼</li>
  </ul>
  <script type="module" src="sample.js">
  </script>

JSファイル

sample.js
    const searchInput = document.querySelector('textarea');
    const form = document.querySelector('form');

    form.addEventListener('submit', runEvent);

    function runEvent(e) {
      e.preventDefault(e);

      const searches = divideByLineBreak(searchInput.value);

      // 行ごとに検索(タブで開く)
      searches.forEach( search => {
        window.open('https://www.google.com/search?q=' + search, '_blank')
      })

      // 入力を改行ごとに分割してArrayに格納する
      function divideByLineBreak (str) {
        let pos = 0;
        const searches = [];
        for (let i = 0; i < str.length; i++) {

          if (str[i] === '\n') { 
            //console.log('\\n is found.');
            searches.push(str.slice(pos, i));
            pos = i+1;
          }

          // Add code here to replace white space with '+' (future work).

        }

        searches.push(str.slice(pos));
        return searches;          
       }
    }

その他メモ

  1. ポップアップがブロックされた(解除するとまとめて開くことができる)
  2. コンソールに「“(ファイルのURI)” の生成元 “https://www.google.com” へのストレージアクセスが自動的に許可されました。」という警告が出る
  3. '\n'が一文字として正しく認識されているかが不明。if文で正しく比較できているか。
  4. 通常、google検索ではand検索時の単語同士が+で繋がれているが、今回の検索ではURIのqに直接書き込んでいるためかホワイトスペースが+に置換されずに検索されていた。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

moment.jsはレガシープロジェクトになった

元の公式の記事:https://momentjs.com/docs/#/-project-status/

moment.jsの課題

momentオブジェクトがimmutableじゃない
サイズがデカい

今後のmoment.js

・新しい機能は追加されない
・immutableに変えることはしない
・サイズの大きさには対処しない
・長期に渡る既知のバグや問題のある動作は修正しないかも?
・重大なセキュリティ上の問題が発生すれば対応する

新しいプロジェクトでmoment.jsを使用することは避けるべきであると述べています。

代替ライブラリ

Luxon

moment.jsのコントリビューターが作った進化版
型がすべてimmutable
https://moment.github.io/luxon/

他のライブラリも元の公式には色々載ってます。
ちなみに僕はluxonがイチオシです。

所感

しょうがないですが、使い慣れたライブラリが役目を終えるというのは
中々寂しい気持ちになりますね
momentとそのコントリビューターには盛大な感謝を贈りたいですね。

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

逐次検索機能の実装

概要

今回は、逐次検索機能の実装について
カリキュラムをみながら自分で実装した時の理解度が低かったので、アウトプットの意味も込めて投稿しようと思います。

逐次検索機能とは、例えば「ruby」「python」「ruby on rails」というタグがすでにデータベースに存在する場合、rの文字が入力されると、rの文字と一致する「ruby」「ruby on rails」を候補として瞬時に画面上に表示する機能です。

一般的にインクリメンタルサーチと呼ばれます。

インクリメンタルサーチとは、
文字の入力の都度、自動的に検索が行われる検索機能です。
JSのAjaxを用いて実装します。

実装

jsファイルの記述

ターボリンクスをコメントアウトしてtag.jsを読み込めるようにします。

javascript/packs/application.js
require("@rails/ujs").start()
// require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")
require("../tag")

tag.jsを作成して編集します。

javascript/tag.js
if (location.pathname.match("posts/new")){
  window.addEventListener("load", (e) => {
  const inputElement = document.getElementById("post_tag_name");
    inputElement.addEventListener("keyup", (e) => {
      const input = document.getElementById("post_tag_name").value;
      const xhr = new XMLHttpRequest();
      xhr.open("GET", `search/?input=${input}`, true);
      xhr.responseType = "json";
      xhr.send();
      xhr.onload = () => {
        const tagName = xhr.response.keyword;
        const searchResult = document.getElementById('search-result')
        searchResult.innerHTML = ''
        tagName.forEach(function(tag){

          const parentsElement = document.createElement('div')
          const childElement = document.createElement('div')

          parentsElement.setAttribute('id', 'parents')
          childElement.setAttribute('id', tag.id )
          childElement.setAttribute('class', 'child' )

          parentsElement.appendChild(childElement)
          childElement.innerHTML = tag.name
          searchResult.appendChild(parentsElement)

          const clickElement = document.getElementById(tag.id)
          clickElement.addEventListener("click", () => {
            document.getElementById("post_tag_name").value = clickElement.textContent;
            clickElement.remove();
          })
        })
      }
    });
  })
};

ここからは、コードの詳細を確認していきます。

javascript/tag.js
const input = document.getElementById("post_tag_name").value;
      const xhr = new XMLHttpRequest();
      xhr.open("GET", `search/?input=${input}`, true);
      xhr.responseType = "json";
      xhr.send();

ここでは、post_tag_nameというID名の要素に入力された値を「input」に代入後、Ajaxの記述を行っています。

後の編集で、searchアクションと紐付けるルーティングを設定するので「openメソッド」でsearchアクションへのパスを設定します。この時、「input」に代入されたバリューをqueryパラメータとして設定します。また、レスポンスのデータ型は「json」と指定して、送信を行っています。

また、あとでsearchアクション(タグの検索)をコントローラーで行うための記述を行います。

keywordというキーに対応するバリューとしてセットして、jsonデータとして返す記述をあとでコントローラーにします。

javascript/tag.js
const parentsElement = document.createElement('div')
const childElement = document.createElement('div')

ここでは、インクリメンタルサーチの結果を画面上に表示させるために、div要素を作成しています。
作成したdiv要素の中に、インクリメンタルサーチの結果を加えていきます。

javascript/tag.js
parentsElement.setAttribute('id', 'parents')
childElement.setAttribute('id', tag.id )
childElement.setAttribute('class', 'child' )

先ほど作成したdiv要素にIDとクラス名を与えています。
childElementには、表示させるタグのIDを代入します。また、CSSを割り当てるためのクラス名も与えています。

javascript/tag.js
parentsElement.appendChild(childElement)
childElement.innerHTML = tag.name
searchResult.appendChild(parentsElement)

この部分では、parentsElementの子要素としてchildElementを加えます。次にchildElementに表示させる、タグのHTMLを生成させます。最後に、searchResultの子要素にparentsElementを加えています。

javascript/tag.js
const searchResult = document.getElementById('search-result')
searchResult.innerHTML = ''

二文字目以降に重複して表示されないように、searchResultの中へ空文字の代入をしています。

javascript/tag.js
const clickElement = document.getElementById(tag.id)
          clickElement.addEventListener("click", () => {
            document.getElementById("post_tag_name").value = clickElement.textContent;
            clickElement.remove();

候補として表示させたタグがクリックされると、選択されたタグのテキスト要素を入力フォームのバリューとしてセットします。最後に、選択されたタグは、表示の一覧から削除します。

以上がインクリメンタルサーチの一連の動きになります。

ルーティングを設定

config/routes.rb
resources :posts, only: [:index, :new, :create] do
    collection do
      get 'search'
    end
  end

コントローラーを編集

controller/posts_controller.rb
class PostsController < ApplicationController

  def index
    @posts = Post.all.order(created_at: :desc)
  end

  ---省略---

  def search
    return nil if params[:input] == ""
    tag = Tag.where(['name LIKE ?', "%#{params[:input]}%"])
    render json:{ keyword: tag }
  end


  ---省略---
end

これで実装完了です。動作確認をしてみましょう。

エラーが起きた時

今回の実装で考えうるエラーの解決方法は、getElementByIdをした時のId名に注目してみることです。

今回の実装では、formオブジェクトのpost_tagモデルについてインクリメンタルサーチを実装しました。

また、jsの全文をみた時の一行目にパスの記述があります。
今回は新規投稿をする際のタグ入力で逐次検索をする機能を実装したかったので、posts/newとなっています。

この二点以外はコピペでもいけるのではないでしょうか。

あ、ターボリンクス切り忘れとか、tag.js読み込み忘れは流石にやめましょう。

感想

javascriptはカリキュラムでも触れる機会が少なく、苦手意識があるので頑張ろうと思います。

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

(小ネタ)DiscordにおけるChannelのpositionの話

はじめに

前回の記事に引き続き、備忘録も兼ねたDiscord APIのChannelposition関連の挙動についてまとめる。
本記事ではAPIラッパーライブラリとしてdiscord.jsを使用する。

Channelオブジェクトのpositionプロパティの値

DiscordのChannelオブジェクトのpositionの値は、0から始まる。
0はカテゴリ内のチャンネルであれば、そのカテゴリの先頭。
カテゴリ外のチャンネルであればサーバー内の先頭を指している。

チャンネル作成時のpositionの値

チャンネルを作成する際に、その位置にチャンネルを作成するか指定するのオプション値、positionパラメータについて。
チャンネル作成時のpositionパラメータの値は、プロパティと同じく0から始まる。
しかし、ここでの0は既存チャンネルの末尾にチャンネルを作成することを指す。
では先頭に作成したい場合はどうすればよいかと言えば、1を指定すればいよい。

要するに、Channelオブジェクトのpositionプロパティの値は先頭から0 1 2...とナンバリングされているが、チャンネル作成時のpositionパラメータの指定位置は先頭から1 2 3...とナンバリングされている訳である。

特定のチャンネルの下にチャンネルを作成する

少し困るのが特定のチャンネルの下にチャンネルを作成したいときである。
普通に下に追加するだけであれば特定のチャンネルのpositionプロパティの値に+2した値をpositionパラメータに渡せばよいだけだが、特定のチャンネルがカテゴリの末尾などにあると、これではエラーになるため0を指定しなければならない。

このとき役に立つのがカテゴリ内のチャンネル数である。
特定のチャンネルのpositionプロパティの値に+2した値が、このチャンネル数を超える場合0としてあげれば、上手く処理できる。

あとがき

以上、暫定的に備忘録として記事を残した。
今後、実際のコードなどを追加して記事を充実させていきたい。
ご参考になればと思います。

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

ブラウザで表示したSVGを画像として扱いたい

お昼ごはんを食べながら「ブラウザで表示したSVGを画像にしてダウンロードしたい」というのは需要がありそうだなと思ったので調べました。

やはりstackoverflowにありました。ありがたいですね。

https://stackoverflow.com/questions/3975499/convert-svg-to-image-jpeg-png-etc-in-the-browser

これをコピペして少しいじって試しました(Chromeで確認)

Demo: https://jsfiddle.net/r208on37/1/

画像をBlob URLで扱うついでに、1アクション挟んで「別タブ表示」「imgタグ」「ダウンロードリンクの生成」もできるか試しました。

仕組み

仕組みは大したことはやっていなくて、Blob URLとcanvas便利ですね、という感じです。

  • DOM上のSVGをXMLSerializerでSVGタグのテキストとして取り出す
  • SVGタグのテキストからBlob URLを生成
  • Blob URLをimgタグに読み込ませる(モダンブラウザはSVGをimgタグで扱える)
  • imgタグをcanvasに描画
  • canvasからBlobを得る
  • BLOBからBLOB URLを作り、よしなにする

なので、canvasで扱うタイミングでコピーライトを書き込んだり、いろいろできそうです。

反省

XMLSerializerを通していますが、ここはSVGの親タグからinnerHTMLでもいい気がします(svgタグ1つのみであれば)。

ページの使い方によりますが、revokeObjectURLはもう少しちゃんとしたほうがいいかもしれません。

「ダウンロードリンクの生成」はそもそも「最初から勝手に生成して表示してほしい」気はしますが、hrefはあらかじめ作る必要があるっぽいので動的に変化するSVGなどの場合は難しいかもしれません。もしかしたらclickイベントを拾ったタイミングで、Blob URLと<a>タグ生成してclickすればできる気がしますが、秋田ので試してないです。

あと、ここまで書いて思ったんですが、SVGのままで良い場合はわざわざ画像にする必要はなくimgやcanvasを経由せずに済むので、大幅な短縮ができるでしょう。チャートに書いておきます。

お昼ごはんは冷めました。

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

【JS学習その③】truthyとfalsy、そしてAND条件・OR条件とそのテクニック

JS学習シリーズの目的

このシリーズは、私ジャックが学んだJavaScriptのメカニズムについてアウトプットも兼ねて、
皆さんと知識や理解を共有するためのものです。
(理解に間違いがあればご指摘いただけると幸いです)

truthyとfalsyの定義

  • truthy・・・Booleanで真偽値に変換した場合にtrueになる値のこと
  • falsy・・・Booleanで真偽値に変換した場合にfalseになる値のこと

truthyとfalsyの見分け方

truthyとfalsyの見分け方は、基本的にfalsyのものを覚えて、falsy以外のものは全てtruthyだと判断することになります。
falsyのものはそれほど多くないので覚えるのは難しくないと思います。
それでは以下で説明しますね!

falsyな値

  • false
  • 0 (数字)
  • 0n (big int)
  • "" (空文字)
  • null
  • undefined
  • NaN (Not a Number)

※truthyはそれ以外の値

AND条件とOR条件

AND条件は"&&"で記述し、OR条件は"||"で記述します。
この条件演算子は主にif文で使用し、AND条件は左辺と右辺が両方ともtruthyならtrue、
OR条件は左辺と右辺のどちらかがtruthyならtrueを返します。

main.js
let a = 0;
let b = 1;
let c = 2;
let d = null;

console.log(a && b); /*false*/
console.log(b && c); /*true*/

console.log(a || b); /*true*/
console.log(a || d); /*false*/

AND条件とOR条件の返す値とそれを使用したテクニック

前述したAND条件とOR条件は、それぞれ
AND条件・・・左辺から見ていって、falsyなものがあれば、その値を返す
OR条件・・・左辺から見ていって、truthyなものがあればその値を返す

このことを利用して、例えば次のようなコードが書けます

main.js
function like(fruit) {
    fruit = fruit || apple;
    console.log('I like ' + fruit);
}

like(); /*'I like apple'*/
like(grape); /*'I like grape'*/

let fruit;

fruit && like(); /*何も表示されない*/

このように変数"fruit"がtruthyかfalsyかでlike関数の中のfruitの値を変更しています。
上記の場合、OR条件でfruitに値を指定していなければ'I like apple'、値が指定されていれば'I like "指定された値(今回はgrape)"'、となります。
また、AND条件で今回の場合fruitに値が指定されておらず、falsyなのでundefinedが返り、何も表示されません。

まとめ

いかがでしたでしょうか。truthyとfalsy及びAND条件とOR条件はJavaScriptにおいてとても大事なのでしっかりと理解しておきましょう!

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

もうBacklogやRedmineは要らない? Exmentでガントチャート付きタスク管理を実践する

どうも、業務改善が大好きな筆者です。

さて、連日Exmentの記事を書いているわけですが、そろそろタスク管理についても触れたいなと思っています。

タスク管理といえば、TrelloやAsana、BacklogやRedmineなど、SaaSのサービスが乱立してますよね。(Redmineはオンプレも可能ですが)

一方で、Excelやスプレッドシートに書き込んで、お手製のタスク管理をされている方もいらっしゃると思います。

ガントチャート好きな人、多いですよね

筆者個人が、タスク管理でどうしても外せないのがガントチャートです。ガントチャート機能付きのタスク管理サービスというと、どうしても選択肢がせばまってきます。

今はToggl Planという名前になってTogglファミリーとなった、かつてのTeamweekが、個人的には一番好きでした。

Redmineにもガントチャートがありますが、デフォルトのあれはダメでしたね。何が良くて何がダメなのか。それはマウスでガントチャートを伸ばしたり縮めたりできるかどうかだと思ってます。

Teamweekはそれが出来たのですが、Redmineはタスクの日付を変えない限り伸び縮みしてくれませんでした…(デフォルトの話です)

ガントチャート使えるサービス、有料SaaSが多い件

Backlogのガントチャートもマウスで伸び縮みできるそうなんですが、Backlogは一零細副業家(筆者は副業の管理にExmentを試験導入しています)にとっては手が出せない価格なんですよね。基本1人で使うし… Asanaも有料プランですし…

Redmineはオンプレで使えますが、ガントチャート自体の昨日が今一つなので、試してすぐやめました…

もしガントチャート使えるのなら、タスク管理もExmentでやってみたい…

というわけで、これまでタスク管理は色々なものを主に無料プランで使ってきましたが、どれも一長一短で馴染むものがありませんでした。ガントチャートでいえばTeamweek(現:Toggl Plan)でしたが、カンバンの良さでいえばTrelloですよね…

そこに来て、現在筆者は

をExmentに集約しているので、タスク管理もここで案件と紐づけつつやってみたいなあ、という思いに至りました。なので、今回はExmentでタスク管理しつつ、API経由でガントチャートビューも実装してみたいと思います。

フロントにFrappe Ganttを採用

色々なJavaScriptのガントチャートライブラリを一通り調べてみましたが、シンプルさと機能の両立で言うと、Frappe Ganttが良いな、と思い今回採用してみました。

2020-09-15_10h14_42.png

もちろんマウスで伸び縮み出来ますし、バーを横に動かすこともできます。しかも進捗率もマウスで変更できます。

それでは、実装していきましょう。

Exment側のテーブル設計

まずは、Exment側のカスタムテーブルを用意します。カスタムテーブル名は「タスク管理」としました。

テーブル設計は

列名 列のタイプ
タイトル 1行テキスト
開始日 日付と時刻
終了日 日付と時刻
担当者 ユーザー
案件名 選択肢 (他のテーブルの値一覧から選択)
進捗率 整数(0~100)
ステータス 選択肢(値・見出しを登録)

ステータスの値と見出しは以下の通りです。

1,未整理
2,未着手
3,進行中
4,チェック待ち
5,完了
6,対応しない

フロントはExpress + pugでHTMLを出力

コスト管理(原価計算)の時のように、Express + pugで出力します。この時のapp.jsを使いまわそうと思います。なので、Expressやpugはインストール済みという前提にしたいと思います。

app.js
app.get('/tasks', async (req, res) => {

  ;(async () => {
    const exmentToken = JSON.parse(await fs.readFileSync('./exment_tokens.txt')).access_token
    let tasksData = await axios.get('https://example.com/api/data/tasks/?orderby=start_at', {
      headers: {
        'Authorization': 'Bearer ' + exmentToken
      }
    })
    .then(res => { return res.data.data })
    .catch(err => { console.error(err) })

    let tasksArray = []
    tasksData.forEach(item => {
      const id = item.id
      const name = item.value.title
      const start = moment(item.value.start_at).format('YYYY-MM-DD')
      const end = moment(item.value.end_at).format('YYYY-MM-DD')
      const progress = item.value.progress
      const custom_class = 'uplift-task'
      tasksArray.push({
        id: String(id),
        name: name,
        start: start,
        end: end,
        progress: progress,
        custom_class: custom_class
      })
    })
    // console.log(tasksArray)
    res.render('tasks-gantt', { tasks: JSON.stringify(tasksArray) })
  })()
})

まずは、タスクデータの取得です。ExmentのAPIからGETで取得し、Frappe Ganttのtaskデータ形式に整形しています。

次に、viewです。

tasks-gantt.pug
<!DOCTYPE html>
html(lang="en")
  head
    meta(charset="UTF-8")
    meta(name="viewport", content="width=device-width, initial-scale=1.0")
    title Document
    link(rel="stylesheet", href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css", integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z", crossorigin="anonymous")
    .
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/frappe-gantt/0.5.0/frappe-gantt.min.css" integrity="sha512-j2JEichKxgq6udg7yAJpsSwGLrHtxeTinqv4kzc+SXCJEhYzDT/JclOOOU28pD1jk1IEdt2FCQ/CnqdOzt7e6Q==" crossorigin="anonymous" />
  body
    #gantt
    .btn-group(role='group', aria-label='Basic example')
      button.btn.btn-secondary(type='button' onClick="changeDay()") Day
      button.btn.btn-secondary(type='button' onClick="changeWeek()") Week
      button.btn.btn-secondary(type='button' onClick="changeMonth()") Month

    .
      <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.26.0/moment.min.js" integrity="sha512-QkuqGuFAgaPp3RTyTyJZnB1IuwbVAqpVGN58UJ93pwZel7NZ8wJOGmpO1zPxZGehX+0pc9/dpNG9QdL52aI4Cg==" crossorigin="anonymous"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/snap.svg/0.5.1/snap.svg-min.js" integrity="sha512-Gk+uNk8NWN235mIkS6B7/424TsDuPDaoAsUekJCKTWLKP6wlaPv+PBGfO7dbvZeibVPGW+mYidz0vL0XaWwz4w==" crossorigin="anonymous"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/frappe-gantt/0.5.0/frappe-gantt.min.js" integrity="sha512-5M8ejeX3DuiV4VGIFjHP5gpryPQb1dWYjzTUhBUKj81aT6ZZz6+wIG8k89nbjsiHFJHQbi/CByHQTe4mJOi3hw==" crossorigin="anonymous"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.20.0/axios.min.js" integrity="sha512-quHCp3WbBNkwLfYUMd+KwBAgpVukJu5MncuQaWXgCrfgcxCJAq/fo+oqrRKOj+UKEmyMCG3tb8RB63W+EmrOBg==" crossorigin="anonymous"></script>
    script.
      const tasks = !{tasks}
      const options = {
        header_height: 50,
        column_width: 30,
        step: 24,
        view_modes: ['Day', 'Week', 'Month'],
        bar_height: 50,
        bar_corner_radius: 3  ,
        arrow_curve: 5,
        padding: 18,
        view_mode: 'Day',   
        date_format: 'YYYY-MM-DD',
        custom_popup_html: null,
        on_date_change: function(task, start, end) {
          axios.put('/tasks/date',{
            id: task.id,
            start_at: moment(start).format('YYYY-MM-DD HH:mm:ss'),
            end_at: moment(end).format('YYYY-MM-DD HH:mm:ss')
          })
          .then(function(res) {
            console.log(res)
          })
          .catch(function(err) {
            console.log(err)
          })
        },
        on_progress_change: function(task, progress) {
          console.log(task, progress);
          axios.put('/tasks/progress',{
            id: task.id,
            progress: progress
          })
          .then(function(res) {
            console.log(res)
          })
          .catch(function(err) {
            console.log(err)
          })
        },
      }
      const gantt = new Gantt('#gantt', tasks, options)

      function changeDay() {
        gantt.change_view_mode('Day')
      }
      function changeWeek() {
        gantt.change_view_mode('Week')
      }
      function changeMonth() {
        gantt.change_view_mode('Month')
      }

とりあえず動く、までが目標なので、あんまりきれいにまとまっていません。

まず、Frappe GanttはCDNから読み込みました。Frappe Ganttのdependanciesである、momentとsnap-svgもCDNから。Frappe GanttのinitもHTMLにべた書きです。

on_date_changeと、on_progress_changeが、バーを伸ばしたり進捗率を変更したときのコールバックになります。ここで、CDNから読み込んだaxiosを呼び出して、Exment側へPUTしています。

ExmentのAPIをJSから直接叩いても良かったのですが、認証のこともありますし、一旦Express側にAPIを作って、そこからデータを送っています。フロントとバックエンドで2回axiosのお世話になるという、なんだかなー、ということになってますが…

下記が、app.jsのPUTの箇所です。

app.js
app.put('/tasks/date', async (req, res) => {
  ;(async () => {
    const exmentToken = JSON.parse(await fs.readFileSync('./exment_tokens.txt')).access_token

    // console.log(req.body)
    await axios.put('https://example.com/api/data/tasks/' + req.body.id, {
      value: {
        start_at: req.body.start_at,
        end_at: req.body.end_at
      }
    }, {
      headers: {
        'Authorization': 'Bearer ' + exmentToken
      }
    })
    .then(res => { return res })
    .catch(err => { console.error(err) })
  })()
})

app.put('/tasks/progress', async (req, res) => {
  ;(async () => {
    const exmentToken = JSON.parse(await fs.readFileSync('./exment_tokens.txt')).access_token

    // console.log(req.body)
    await axios.put('https://example.com/api/data/tasks/' + req.body.id, {
      value: {
        progress: req.body.progress
      }
    }, {
      headers: {
        'Authorization': 'Bearer ' + exmentToken
      }
    })
    .then(res => { return res })
    .catch(err => { console.error(err) })
  })()
})

さて、うまく行けば下記のようになっているはずです。

2020-09-15_10h37_40.png

いい感じですね。

では、ドラッグアンドドロップのテストをしたいと思います。

先にExmentを開いて、開始日終了日の日付を確認しておきましょう。

2020-09-15_10h39_31.png

↑まず、日付はこうなっています。

次に、ガントチャート側でバーをドラッグして長さも変更してみましょう。

2020-09-15_10h40_44.png

ついでに、進捗率も変えちゃいましょう。

2020-09-15_10h42_30.png

では、Exment側に戻り、同じタスクの詳細画面をリロードします。

2020-09-15_10h43_44.png

しっかりと変更されてました!

しかし、課題もあります

タスクの依存関係が作れない

Frappe Gantt側ではその機能はあるのですが、Exmentでバックエンドを実装すると、同じカスタムテーブル内のレコード同士を紐づける設定が今のところないので、タスク同士の紐づけができません。

無理やり整数のカラムを作って、レコードIDを手入力することも考えられますが、それも何だかなあといった感じです。

日本語化されてない

月の表記が英語です。月だけは日本語化できそうなので、暇なときにプルリク作って送っておきますか。

土日祝日の色変更ができない

なんかIssueにはそれっぽいのが上がってるのですが、結局どうやって変更すればいいのか分かりませんでした。

ただまあ、個人的にはこれらの課題は些末なことだったので…

一旦ガントチャートにできれば十分だったので、この結果は満足しています。

もし、高度なガントチャートが必要であれば、別のライブラリを使えばいいと思います。

まとめ

  1. ExmentのAPIを使って、タスクをガントチャート化できるよ!(BacklogとかRedmineいらないかも)
  2. ガントチャートライブラリは色々あるけど、マウスでバーを伸び縮みできるのがいいなら、Frappe Ganttがおすすめ(課題はあるけど)
  3. ガントチャートライブラリは、お好みのものを選べばいいと思う。もっと高機能なものもあるしね!

といったところでしょうか。

個人的には、MFクラウド請求書で受注した見積書とタスクが紐づくことによって、Togglの記録とも連動させて

  • 営業判断
  • 工数原価管理
  • タスク管理

が一元化したのが気持ちいいなー、と思っています。BacklogやRedmineも良いですが、Exmentでタスク管理始めてみませんか?

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

【Ruby on Rails】refileでの投稿画像プレビュー機能

目標

img_prev.gif

開発環境

ruby 2.5.7
Rails 5.2.4.3
OS: macOS Catalina

前提

※ ▶◯◯ を選択すると、説明等が出てきますので、
  よくわからない場合の参考にしていただければと思います。

投稿機能が既にできていると仮定して進めます。

流れ

1 gem refileの導入
2 カラムの追加
3 modelの編集
4 controllerの編集
5 viewの編集

gem refileの導入

Gemfile
gem 'refile', require: 'refile/rails', github: 'refile/refile'

補足
refileは、ファイルをアップロードできるようにするgemです。

ターミナル
$ bundle install

カラムの追加

ターミナル
$ rails g migration AddPostImageIdToPosts post_image_id:string
ターミナル
$ rails db:migrate

t.string "post_image_id"が追加されていればOKです。

db/schema
  create_table "posts", force: :cascade do |t|
    t.integer "user_id"
    t.string "title"
    t.string "body"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.string "post_image_id" # <--これがあればOK
    t.index ["user_id"], name: "index_posts_on_user_id"
  end

modelの編集

app/models/post.rb
attachment :post_image

補足【refile使用のルール】
1 画像アップロードは、<%= f.attachment_field :image %>で実装
2 モデルに画像アップ用のメソッド「attachment」を追加し、imageを指定
 ※今回はpost_image_idを追加したため、post_imageを指定。

controllerの編集

下記を追加することで、post_imageの変更も許可する。

app/controllers/posts_controller.rb
  def post_params
    params.require(:post).permit(:title, :body, :post_image)
  end

viewの編集

今回は先にデフォルトの画像を表示させておくため、
no-image.pngという画像ファイルを事前に用意しました。
保存場所は、app/assets/imagesの中になります。

app/views/posts/new.html.erb
<% form_with,...%>
...

  <div>
    <%= attachment_image_tag @post, :post_image, fallback: "no-image.png", id: "img_prev", style: "height: 250px; width:300px;" %><br>
    <%= f.attachment_field :post_image %>
  </div>

...

<% end %>

...

<script>
$(document).on("turbolinks:load", function(){
  function readURL(input) {
    if(input.files && input.files[0]){
      var reader = new FileReader();
      reader.onload = function (e) {
        $('#img_prev').attr('src', e.target.result);
      }
      reader.readAsDataURL(input.files[0]);
    }
  }
  $("#post_post_image").change(function(){
    readURL(this);
  });
});
</script>

補足【attachment_image_tag】
refileで用意されたヘルパーメソッドで、imgタグを作成。

補足【fallback】
何か問題が発生した際に表示する画像を指定。

補足【turbolinks:load】
初回読み込み、リロード、ページ切り替えで動くよう設定。

補足【Javascript動作について】
id=img_prevの属性を操作し、
changeメソッドでpostモデルのpost_imgの読み込みURLを変更

補足【Runtime Errorが表示された場合】
エラー画面のコード上に
```
Refile.secret_key = ...
```
と表示されています。
そのRefile.secret_key= を含む一行をコピーし、
config/initializers/application_controller_renderer.rbの
一番下に追加すればエラーは解消できます。

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

Vue.jsをCDNで使う (やることはコピペだけ!)

簡単にいろいろなアクションやイベントを実行することができるVue.js!

そのVue.jsを簡単に導入するには、CDN(コンテンツデリバリーネットワーク)を使うのが最も簡単かと思います。

CDN(コンテンツデリバリーネットワーク)とは

CDNとは、端的に言うと、コンテンツ(ここではVue.jsのこと)を早く簡単に使えるようにできるものです。

:point_down:詳しく知りたい方はこちら:point_down:
・[https://business.ntt-east.co.jp/content/cloudsolution/column-66.html]
・[https://www.kagoya.jp/howto/network/cdn/]

では実際に実装してみましょう(コピペだけですが!)

どのページでもVue.jsを使用できるようにするには、<head>にCDNを挿入するだけでOK!
もしくはVue.jsを使用するhtmlページに挿入するだけでOK!

application.html
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
</body>

:sunny:以上!超絶簡単ですね!:sunny:

上記のCDNはVue.jsの最新版を毎回採用

:warning:注意点:warning:

実際の開発現場では、バグやエラーを回避するために、バージョンを固定する場合が多い!
:point_right_tone2:本番環境での開発は、バージョンが指定されたCDNがおすすめです。

application.html
<head>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script>
</head>
<body>
</body>

Vue.jsのCDNをググる↓
[https://jp.vuejs.org/v2/guide/installation.html]

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

agora.io クイックスタートガイドWebSDK用(バージョン4:Next Generation)

概要

agora.io Web SDKのNext Generation(通称NG)をご紹介します。
(Web SDKのv4.0.0が2020/07/15にリリースしました。)

Agora Web SDK NGは、Web SDKの次世代バージョン(ver.4.x)です。

公式ページはこちら

特徴

1.Agora Web SDKの内部アーキテクチャを大幅にリファクタリングし、APIの利便性を向上しました。
 -Promise-based APIの採用
 -Typescriptサポート

2.従来のWeb SDKで利用していた"Stream"が、”LocalTrack”、”RemoteTrack”に変更されました。

3.従来のWeb SDKに引き続き、メジャーなブラウザをサポートしています。
image.png

参考リンク

概要(Agora Web SDK NGとは?)
デモ
Githubサンプル
APIリファレンス
マイグレーションガイド

STEP1. Agora Accountの作成とApp IDの取得

この手順は従来のWeb SDKと同じです。
参考記事:agora.io クイックスタートガイド WebSDK用(バージョン3)

STEP2. Agora Web SDKの取り込み

以下の3通りの方法があります。
Case1.npm経由
1.以下のコマンドを実行

npm install agora-rtc-sdk-ng --save

2.以下のコードを追加

index.js
import AgoraRTC from "agora-rtc-sdk-ng"
const client = AgoraRTC.createClient({ mode: "live", codec: "vp8" });

Case2.CDN経由
1.以下のコードを追加

index.html
<script src="https://download.agora.io/sdk/web/AgoraRTC_N-4.1.0.js"></script>

Case3.SDKダウンロード

1.こちらよりSDKの最新バージョンをダウンロード
2.ダウンロードしたSDKをディレクトリに保存
3.以下のコードを追加

index.html
<script src="./AgoraRTC_N-4.1.0.js"></script>

実装

サンプル(GitHub)のDemo\basicVideoCall\index.htmlを利用

1.通話開始まで
(1).createClient()をコール

basicVideoCall.js
client = AgoraRTC.createClient({ mode: "rtc", codec: "h264" });

(2).join()をコール

basicVideoCall.js
client.join(options.appid, options.channel, options.token || null)

(3).createMicrophoneAudioTrack()とcreateCameraVideoTrack()をコール

basicVideoCall.js
AgoraRTC.createMicrophoneAudioTrack(),
AgoraRTC.createCameraVideoTrack()

(4).publish()をコール

basicVideoCall.js
client.publish(Object.values(localTracks));

2.リモートユーザ側
(1).AgoraRTCClient.on("user-published")でAgoraRTCRemoteUserを取得
(2).subscribe()をコール
(3).remoteVideoTrack.play()とremoteAudioTrack.play()をコール

basicVideoCall.js
async function subscribe(user, mediaType) {
  const uid = user.uid;
  // subscribe to a remote user
  await client.subscribe(user, mediaType);
  console.log("subscribe success");
  if (mediaType === 'video') {
    const player = $(`
      <div id="player-wrapper-${uid}">
        <p class="player-name">remoteUser(${uid})</p>
        <div id="player-${uid}" class="player"></div>
      </div>
    `);
    $("#remote-playerlist").append(player);
    user.videoTrack.play(`player-${uid}`);
  }
  if (mediaType === 'audio') {
    user.audioTrack.play();
  }
}

function handleUserPublished(user, mediaType) {  
  const id = user.uid;
  remoteUsers[id] = user;
  subscribe(user, mediaType);
}

動作確認

サンプル(GitHub)のDemo\basicVideoCall\index.htmlを利用

1.index.htmlをクリック
image.png

2.APPID、Channel名を入力し、Joinクリック
image.png

3.Leaveクリックで退室

agora.io Web SDKのNext Generationの概要でした。

最後に

agora.ioに関するお問い合わせはこちらから
スクリーンショット 0001-08-15 13.41.56.png

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

JavaScriptを学ぶ

JavaScriptを学ぶ

出力

console.log("hogehoge");

変数の定義

基本の形

let 変数名 = 

変数strに文字列のhogehogeを代入

let str = "hogehoge"

変数の値を上書きすることができる
letは必要なく 変数名 = 値で定義できる

str = "huga"

上書きできない定数を定義する際にはconstを使用する
後から値を上書きすることができない

const num = 1

テンプレートリテラル

変数を文字列の中に埋め込むことができる。
その際には以下のルールがある
バッククォートで文字列を囲む
変数は${}で囲む

let name = "佐藤"
console.log(`私の名前は${name}です');
// 私の名前は佐藤です

条件分岐

if(条件式1){
  条件がtrueなら実行する処理
} else if(条件式2) {
  条件式2trueなら実行する処理
} else {
  条件式が全てfalseなら実行する処理
}

const num = 5
if(const > 0){
  console.log("0より大きい");
}

不等号

より大きい >
以上      >=
より少ない <
以下      <=

等しい    ===
等しくない !==

かつ      &&
または     |

switch文

値によって処理を分岐させたい場合に使用するのがswitch
breakは処理をそこで中断するためのもの
このbreakがないと次のcaseの処理も実行してしまう

defaultは当てはまるcaseがない場合に実行する処理
これもbreakが必要になるので注意

const num = 2;
switch(num){
  case 2:
    console.log("2です")
    break;
  case 3:
    console.log("3です")
    break;
  default:
    console.log("当てはまる数字はない")
    break;
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む