20210908のJavaScriptに関する記事は21件です。

Googleフォームの回答を画面キャプチャで共有するときに便利なスクリプト

ちょっとした小技です。 Googleフォームの回答ですが、グラフが見やすいので、そのまま画面キャプチャして共有したいですよね。 ただ、回答数が多かったり、長い回答があるとテキストが見切れてしまうのが難点です...。 そんなときは、ChromeならCtrl+Shift+Iで開発者ツールのコンソールを開いて、次のスクリプトを実行します。 for (const e of document.querySelectorAll('.m2 .freebirdCommonAnalyticsTextResponsesContainer')) { e.style.maxHeight = '1200px'; } すると、スクロールバーが消えていい感じになります。 あとは、Chromeなら開発者ツールでコマンドメニューを使い、Ctrl+Shift+Pのあとにfullを入力して、フルサイズの画面キャプチャーをすればOKです! 一応、ブックマークレットもつけておきます。 javascript:(function(){for(const e of document.querySelectorAll('.m2 .freebirdCommonAnalyticsTextResponsesContainer')) {e.style.maxHeight = '1200px';}})();
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavascriptのDateオブジェクトのタイムゾーンは何を参照しているの?

概要 Javascriptでよく使う?Dateのタイムゾーンはどこを参照しているか気になったので調べてみました。 MDN Web Docs Dateに詳細を書いてくれているのですが、、、地方時 (ホストシステムなど) のタイムゾーンなんて書き方だから、クライアントPCなの?サーバーなの?か結局確信が持てませんでした。 (注:私の読解力は結構低いですw) 結論 クライアントPC(デバイス)のシステム設定を参照しているようです。 検証 1. 現在時刻をコンソール出力させる 以下のJSでブラウザのコンソールに現在時刻を出力できます。 function showClock() { var nowTime = new Date(); var nowHour = nowTime.getHours(); var nowMin = nowTime.getMinutes(); var nowSec = nowTime.getSeconds(); var msg = "現在時刻は、" + nowHour + ":" + nowMin + ":" + nowSec + " です。"; console.log(msg); } window.onload = () => { showClock(); }; JSが動くなら、サーバーでもローカル環境でもどちらでもOK 2. ブラウザで確認 ブラウザのデベロッパーツールのコンソールを確認します。 現在時刻は、19:48:58 です。 3. システム設定にてタイムゾーンの変更 今回はMacで検証します。 Macは、システム環境設定 > 日付と時刻 > 時間帯 にて変更できます。 変更前(東京) 変更後(アメリカ) 変更後に、PCの時計を確認してみてください。 時計の時刻が変わっていれば変更はうまくできています。 4. ブラウザで再確認 ブラウザ側でも時刻が変わっていることが確認できます。 現在時刻は、6:48:58 です。 新たな疑問(真面目) 日本とアメリカで遠距離恋愛した場合うまくいくのだろうか?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【CSS】ボックスモデルとは 〜マージンとパディングの違い〜

 はじめに CSSを勉強してて、ボックスモデルを説明できるように簡単にまとめてみました。 コンテントボックスとは 実際の横幅と高さ 基本はwidthとheight html <div class="box">ボックススタイルブロック</div> css .box { /* background-color: green; */ /* Content Box*/ width: 200px; height: 200px; } パディングボックスとは コンテントボックスから外側までどのくらい空けるか padding:上 右 左 下の順で書く(ショートハンドという) html <div class="box">ボックススタイルブロック</div> css .box { background-color: green; /* Padding Box */ padding: 8px 10px 15px 30px; } マージンボックスとは 他の要素からどのくらい空けるか html <div class="box">ボックススタイルブロック</div> css .box { background-color: green; /* Margin Box */ margin: 8px 10px 15px 30px;; } マージンの相殺とは マージが重なると値が大きいほうが優先される 下記のコードの場合margin-topの値が大きいため、margin-top: 40px;が適用される html <div class="box">ボックススタイルブロック</div> <div class="margin">マージン相殺用の説明のブロック</div> css .box { background-color: green; margin-bottom: 20px; } .margin { background-color: blue; margin-top: 40px; } ボーダーボックスとは border: 先の太さ 線の種類 線の色と書く html <div class="box">ボックススタイルブロック</div> css .box { background-color: green; /* Content Box*/ width: 200px; height: 200px; /* Border Box */ border: 5px solid black; } Box-Sizing プロパティについて ボーダーとパディングの値が加わって、実際の指定した値よりも大きくなることがある。これは、直感的ではないのでBox-Sizing プロパティを使うと良い Box-Sizingプロパティをつかうとwidthとheightで指定した大きさになる MDNのドキュメント CSS ボックスモデルの代替 html <div class="box">ボックススタイルブロック</div> css .box { background-color: green; /* Content Box*/ width: 200px; height: 200px; /* Padding Box*/ padding: 8px 10px 15px 30px; /* Margin Box */ margin: 8px 10px 15px 30px; box-sizing: border-box; }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【React Native】AlertやらDialogやらmodalやら似たようなやつのそれぞれ特徴をまとめました。

似たような使い方をするAlertとDialogとModalの違い ログインに失敗した時のエラーメッセージやユーザーに確認をとる時に使われるUIの作り方は何種類かあります。 その中で今回はReactNativeの標準に入っているAlert、ModalとライブラリのDialogの三つのそれぞれの違いをまとめました。 基本的にこの三つはほとんど同じUIで挙動や実装方法も同じです。ただ、Dialogじゃないとできないことなどもありましたので資料にしました。 ライブラリをインストール dialog yarn add react-native-dialog Github:https://github.com/mmazzarolo/react-native-dialog 実装 プロジェクトを作成し、各ライブラリをインポートします。 import React from 'react'; import {Alert, Button, View,Modal} from 'react-native'; import Dialog from "react-native-dialog"; 1、Alert 初めにAlertを作っていきます import React from 'react'; import {Alert, Button, View,Modal} from 'react-native'; import Dialog from "react-native-dialog"; export const AlertDialog = () => { const onPressAlert = () => { Alert.alert( 'アラートを出しました', 'テキストテキストテキストテキスト', [ { text: 'Cancel', onPress: () => console.log('アラートのcancelをタップした時の挙動を書く'), style: 'cancel', }, {text: 'OK', onPress: () => console.log('アラートのOKをタップした時の挙動を書く')}, ]); }; return ( <View> <Button title="Alert" onPress={onPressAlert} /> </View> ); }; Buttonをタップした時にAlertを表示させます。 配列の一つ目にstyle:'cancel'と書かれていますが、これは表示する文字の色、太さを変更することができます。 cancelだと太文字に、destructiveだと赤文字になります。 こんな感じで表示されます。  次に配列内のボタンを一つ増やしてみます。 Alert.alert('アラートを出しました', 'テキストテキストテキストテキスト', [ { text: 'setting', onPress: () => console.log('アラートのsettingをタップした時の挙動を書く'), style: 'destructive', }, { text: 'Cancel', onPress: () => console.log('アラートのcancelをタップした時の挙動を書く'), style: 'cancel', }, { text: 'OK', onPress: () => console.log('アラートのOKをタップした時の挙動を書く'), }, ]); すると。。。。 配列内が三つ以上の時は縦並びになります。 ここで一つ気になる点があります。 配列は一番目に「setting」、二番目に「Cancel」、三番目に「OK」としているのに実際はCancelが一番下になってます。 これは「Cancel」内のstyleに’cancel’としていると思いますが、cancelというstyleを当てると、例え一番目に入れていても表示する時は自動的に一番下になります。 便利っちゃー便利かな。 ちなみに配列を一つだけにするとこんな感じです Alertの特徴 1、アラートの上に出すタイトル(テキスト)のスタイリングができない →もし色やフォントサイズを変更したくても変えれないです。 2、タイトルに画像やアイコンを入れられない →基本テキストした入れないと思いますが、画像を入れたくても入れれないです。 3、手軽に実装できるので特に変わった実装が必要じゃない時はこれで十分かな →できることは少ないですが、Alertの内容だけで十分なことがほとんどだと思うので、基本的にはこれを使えばいい 補足ですが、実装するときにアラートの第二引数(テキスト)を入れたくなくて消してしまうと Alert.alert('アラート',[ { text: 'setting', onPress: () => console.log('アラートのsettingをタップした時の挙動を書く'), style: 'destructive', }, { text: 'Cancel', onPress: () => console.log('アラートのcancelをタップした時の挙動を書く'), style: 'cancel', type:'login-password', }, { text: 'OK', onPress: () => console.log('アラートのOKをタップした時の挙動を書く'), }, ], ); こんな感じに思ったUIにならないので、二行目にテキストが必要ない場合でも Alert.alert('アラート','',[ { text: 'setting', onPress: () => console.log('アラートのsettingをタップした時の挙動を書く'), style: 'destructive', }, { text: 'Cancel', onPress: () => console.log('アラートのcancelをタップした時の挙動を書く'), style: 'cancel', type:'login-password', }, { text: 'OK', onPress: () => console.log('アラートのOKをタップした時の挙動を書く'), }, ], ); こんな感じで空にしといてください! Dialog 実装していきます import React,{useState} from 'react'; import {Alert, Button, View,Modal} from 'react-native'; import Dialog from 'react-native-dialog'; export const AlertDialog = () => { const [visible,setVisible] = useState(false) return ( <View> <Button title="Alert" onPress={()=>setVisible(true)} /> <View> <Dialog.Container visible={visible}> <Dialog.Title>ダイアログを出しました</Dialog.Title> <Dialog.Description> あああああああああああああああああああああああああああああああああああ </Dialog.Description> <Dialog.Button label="Cancel" color='red' onPress={()=>setVisible(false)} /> <Dialog.Button label="OK" onPress={()=>setVisible(false)} /> </Dialog.Container> </View> </View> ); }; Alertではタップした時にAlertを出すようにしましたが、Dialogではvisibleがtrueの時に表示、falseで非表示にします。 ですのでuseStateを使ってtrueかfalseかを制御します。 Buttonをタップした時にtrueにし、ダイアログ内のボタンをタップすることでfalseとなり、非表示になります こんな感じで表示されます。Alertとほぼ同じですね。 では色々Dialogの特徴を紹介していこうと思います。 <View> <Dialog.Container visible={visible}> <Dialog.Title style={{fontSize:30,marginBottom:40}}>ダイアログを出しました</Dialog.Title> <Dialog.Description> あああああああああああああああああああああああああああああああああああ </Dialog.Description> <Dialog.Button label="Cancel" color='red' onPress={()=>setVisible(false)} /> <Dialog.Button label="OK" onPress={()=>setVisible(false)} /> <Dialog.Button label="Setting" bold={true} onPress={()=>setVisible(false)} /> </Dialog.Container> </View> まずスタイリングですがtitleにもstyleを当てることができます。またボタンの方はカラーや太さを調整することができます。 こんな感じに表示されます。 Alertの時は三つボタンがあると縦並びになりましたが、Dialogだと横並びに続いていきます。 さらにこんな事もできます <View> <Dialog.Container visible={visible}> <Dialog.Title style={{fontSize:30,marginBottom:40}}> <Icon name="user-o" size={70} /> </Dialog.Title> <Dialog.Input placeholder='名前' /> <Dialog.Button label="Cancel" color='red' onPress={()=>setVisible(false)} /> <Dialog.Button label="OK" onPress={()=>setVisible(false)} /> <Dialog.Button label="Setting" bold={true} onPress={()=>setVisible(false)} /> </Dialog.Container> </View> Dialog.title内をテキストではなく、アイコンを表示させています。 Alertではできませんでしたが、画像やアイコンも入れることができるんです。 そのほかにもDialog.inputでテキスト入力欄をダイアログにつけることができます! Dialogの特徴 1、Alertよりできることが多い 2、スタイルを当てることができる 3、画像やアイコンを入れることができる Modal 最後のModalです、実装していきます import React, {useState} from 'react'; import {Alert, Button, View, Modal, StyleSheet, Text} from 'react-native'; import Dialog from 'react-native-dialog'; import Icon from 'react-native-vector-icons/FontAwesome'; export const AlertDialog = () => { const [modalVisible, setModalVisible] = useState(false); return ( <View> <Button title="タップ" onPress={() => setModalVisible(true)} /> <View> <Modal animationType="slide" transparent={true} visible={modalVisible} onRequestClose={() => { Alert.alert('Modal has been closed.'); setModalVisible(!modalVisible); }}> <View style={styles.centeredView}> <View style={styles.modalView}> <Text style={styles.modalText}>Hello World!</Text> <Button title="閉じる" onPress={() => setModalVisible(false)} /> </View> </View> </Modal> </View> </View> ); }; const styles = StyleSheet.create({ centeredView: { flex: 1, justifyContent: 'center', alignItems: 'center', marginTop: 22, }, modalView: { margin: 20, backgroundColor: 'white', borderRadius: 20, width:300, padding: 35, alignItems: 'center', shadowColor: '#000', shadowOffset: { width: 0, height: 2, }, shadowOpacity: 0.25, shadowRadius: 4, elevation: 5, }, button: { borderRadius: 20, padding: 10, elevation: 2, }, buttonOpen: { backgroundColor: '#F194FF', }, buttonClose: { backgroundColor: '#2196F3', }, textStyle: { color: 'white', fontWeight: 'bold', textAlign: 'center', }, modalText: { marginBottom: 15, textAlign: 'center', }, }); 特徴はAlertやDialogと違い、ほぼほぼスタイリングで自分で実装しているところです。 よくも悪くも自分の理想のものを作ることができます。 UIはこんな感じになります。 表示方法はDialogと同様にtrueの時は表示、falseで非表示としています。 ボタンをタップした時、モーダルは下から出てきます。 これはModalタグ内のanimatedTypeで設定しています。 現在はslideにしていますが、noneにすることでAlertやDialogと同じような感じで表示されます。 二つ目のtransparentはtrueかfalseで設定します。 trueの場合:モーダルを出した時に背景をもともと設定している色のままにする falseの場合:モーダルを出すときは背景色を決めていても画面全体を真っ白にする modal内はViewタグを使って一からスタイリングしているため、自分の思うようなものを作ることができますが、AlertやDialogに比べてめんどくさいです。 Modalの特徴 1、UIは自分で一から構築 2、自由度は高いがAlertやDialogに比べて実装がめんどくさい。 結論 以上三つの似たUIを作ってみましたが、僕的にはDialogが一番使いやすいと思いました。 ただ、OKとキャンセルを出すだけだったらAlertで十分です。 Modalは使うことはないかなと思いました。Dialogを使えばやりたいことはできると思いますので。 ぜひ参考にしてください!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】JSONとStorage

JavaScript関連でJSONとStorageの基本についてのまとめています。 こいつらを使いこなせるとアプリ開発などの幅が広がることは間違いないです! JSON jsonはデータ交換フォーマットです。 異なる言語間でもデータをやりとりできるのがjsonです。 JavaScript→Rails→JavaScriptなどのようにデータを送るときにはjsonを使います。 JSONは{}の中にキーと値をコロンで区切って記述します。キーは必ずダブルクォーテーションで囲む必要があります。(シングルクォーテーションだとエラーになる) { "a": 0, "b": 1, "c": 2} JavaScriptでは、オブジェクトをJSON形式に変換したりその逆を行ったりすることが簡単にできます。 これにはstringifyとparseを使います。 const obj = {a: 0, b: 1, c: 2}; const json = JSON.stringify(obj); console.log(json); //{"a":0, "b":1,"c":2} ←json const obj2 = JSON.parse(json); console.log(obj2); //{a: 0, b: 1, c: 2} ←object JSON.stringify(obj)という記述でjson形式に変換しています。 ちなみにjsonに変換するときに特定のプロパティだけを指定することもできます。 const obj = {a: 0, b: 1, c: 2}; const json = JSON.stringify(obj, ["a", "c"]) //{"a":0,"c":2} JSON.stringify(abj, ["a", "c"])としています。 これで指定したプロパティのみをjson形式にすることができます。 Storage Storageは、ブラウザの特定領域にデータを格納するためのオブジェクトです。 JavaScriptでは、実際にはlocalStorageを使ってブラウザにデータを格納することができます。 localStorage.setItem('key', 1); const storageData = localStorage.getItem('key'); console.log(storageData); //1 localStorage.setItem('key', 1);という記述でブラウザにデータを格納できています。 検証ツールのApplicationのLocal Storageで確認できます。 この値を取得したいときは、localStorage.getItem('key')のようにしてあげれば取得できます。 もちろんjson形式でデータを格納することもできます。 const obj = {a: 0, b: 1}; const json = JSON.stringify(obj); localStorage.setItem('key', json); const storageData = localStorage.getItem('key'); console.log(storageData); //{"a":0,"b":1} まとめ JSONとStorageについて見てきました。 かなり実践的に活躍してくれる奴らだと思います。 手を動かしていじってみてください。 今回は以上です!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

FirebaseUIを使用してexport 'default' (imported as 'firebase') was not found in 'firebase/app' と表示された時の対処

Firebase UIはv9にまだ対応していない warning in ./node_modules/firebaseui/dist/esm.js "export 'default' (imported as 'firebase') was not found in 'firebase/app' Firebaseは2021年8月25日にv8→v9に正式にアップデートされ、npm install --save firebaseとすると自動的にv9がインストールされます。 ですが2021年9月8日現在、Firebase UIはFirebase JS SDK v9に対応していません。 そのため上記のようなエラーが発生します。 FirebaseUIを使う際は後述のcompat(互換)ライブラリを使用してv8以前のように書く必要があります。 compatライブラリ Firebase JS SDK v9以前のコードを使えるようにする一時的な措置としてcompat(互換)ライブラリがあります。 公式ドキュメント:バージョン8からモジュラーWebSDKにアップグレードします import firebase from 'firebase/compat/app'; import 'firebase/compat/auth'; import 'firebase/compat/firestore'; パスにcompatを挟むと、v8以前のままのコードで動くようです。 ただ、Composition APIを使用した高速化の恩恵は得られません Firebase UIを Firebase v9に対応させる まずエラーログにある./node_modules/firebaseui/dist/esm.jsのimportをcompatライブラリに書き換えます。 /node_modules/firebaseui/dist/esm.js import firebase from 'firebase/compat/app'; import 'firebase/compat/auth'; そして、FirebaseUIを設置しているファイルを以下の通りに書き換えます。 私の場合はこんな感じです。 signin.vue <!-- abbr --> import { getAuth } from "firebase/auth"; import * as firebaseui from "firebaseui" import "firebaseui/dist/firebaseui.css"; var ui = firebaseui.auth.AuthUI.getInstance() || new firebaseui.auth.AuthUI(getAuth()); <!-- abbr --> FirebaseUIインスタンスの初期化で注意する点は、AuthUI()の引数であるAuthインスタンスの取得はfirebase.auth()ではなくgetAuth()です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】クラスについて

はじめに こんにちは。 JavaScriptのクラスについてアウトプットしていきます! クラスとは クラスはオブジェクトを作成するためのテンプレートです。それらは、そのデータを処理するためのコードでデータをカプセル化します。 参照:https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Classes JavaScript class MyObj { //コンストラクタ constructor() { this.name = '佐藤'; this.age = 20; } //メソッド introduce() { console.log(`私の名前は${this.name}で、${this.age}歳です。`); } } //インスタンスを生成 const obj = new MyObj(); obj.introduce(); //私の名前は佐藤で、20歳です。 Myobjというクラスを宣言し、上記のようなコンストラクタとメソッドを定義する。コンストラクタは、クラスを初期化するときに呼ばれる関数のことです。クラスを定義した後、new演算子でオブジェクトを生成して処理を実行させる。 また、コンストラクタに引数を設定して、インスタンス生成時に値を受け取ることができる。 JavaScript class MyObj { constructor(name,age) { this.name = name; this.age = age; } introduce() { console.log(`私の名前は${this.name}で、${this.age}歳です。`); } } const sato = new MyObj('佐藤',20); const yamada = new MyObj('山田',25); sato.introduce(); //私の名前は佐藤で、20歳です。 yamada.introduce(); //私の名前は山田で、25歳です。 satoとyamadaをそれぞれ定数に代入して、クラスの処理を実行することができる。 最後に ここまでクラスについてまとめてみました。 クラスを定義することで、関数を使いまわすことができたり、コードの保守性を高めることができます。 まだ不十分な理解の部分もあるので、別の記事で詳細に書いていこうと思います!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

window.confirmのように手軽に使用できるReactの確認ダイアログコンポーネントを作成してみました

背景 ちょっと確認ダイアログを出したい際にwindow.confirmのように手軽に使用できるReactコンポーネントがあると便利だと思い、作ってnpmレジストリで公開してみました。 作ったもの GitHub: kamiya-kei/simple-confirm DEMO: https://kamiya-kei.github.io/simple-confirm/ npm: @kamiya-kei/simple-confirm オプションでダイアログタイトル・メッセージ・OK/キャンセルのテキスト・カラーを設定できるようにしてます。 インストール・使用方法 以下GitHubのREADMEにインストール・使用方法を記載していますので、よかったら使ってみてください。 kamiya-kei/simple-confirm/README.md 制作メモ window.confirmは以下の様に使用すると「OK」を選択するとtrue、キャンセルを選択するとfalseが返ってきます。 const is_ok = window.confirm('本当によろしいですか?'); 同じような感じで以下のように使用できるようにしました。 const is_ok = await simpleConfirmRef.current.confirm(); 子コンポーネント側の関数を呼び出せるようにする為にReact.useImperativeHandleを使用し、結果を同期的に受け取れるようにする為にPromise・Async/Awaitを使用しました。 上記コードを実行すると、下記コードのconfirm関数が呼び出されるようになっています。 useImperativeHandle(ref, () => ({ confirm: () => new Promise((resolve, reject) => { setHandleAgree(() => () => { resetHandle(); resolve(true); }); setHandleDisagree(() => () => { resetHandle(); resolve(false); }); setHandleClose(() => () => { resetHandle(); resolve(false); }); setOpen(true); }), })); コンポーネント全体のソースコードはこちらです。 https://github.com/kamiya-kei/simple-confirm/blob/main/src/index.js 最後に そもそもuseImperativeHandleは手続き的であり、あまり積極的に使用するべきではないといったことも書かれていたのですが、確認ダイアログを表示してその選択結果によって処理するといったことをする場合に宣言的かつシンプルで分かりやすいコードの書き方が思いつかず、手続き的な書き方をしました。 Reactは最近使い始めたばかりでまだ分かっていない部分も多いので、もっと勉強してより良い書き方ができるようにしていきたいと思います。 参考記事 React 16.8: 正式版となったReact Hooksを今さら総ざらいする Reactコンポーネントをnpmで公開する(GitHub Pages付き、Babel7、webpack4) async/awaitを使用して、"regeneratorRuntime is not defined "エラーが出た時の対処 npmとyarnのコマンド早見表
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

petite-vueを使ってみる

petite-vueとは? petite-vue Vueと同じテンプレート構文が使用できる サーバーフレームワークによってレンダリングされた既存のHTMLページに少量のインタラクションを「振りかける」ために最適化されているらしい とりえあず動かしてみる githubのサンプル petite-vueで操作するには、操作したいタグにv-scopeを付与する v-scopeには操作する変数や関数をオブジェクトで指定する スクリプトを分離してみる <div v-scope> {{count}} <button @click="increment">inc</button> </div> <script type="module"> import { createApp } from 'https://unpkg.com/petite-vue?module' createApp({ count: 0, increment() { this.count++; } }).mount() </script> createAppにpetite-vueから操作する変数や関数をオブジェクトで渡す mount()した値はルートスコープに指定され、v-scopeで指定しなくても関数や変数を使用できる ファイルを分割してみる 今回はwebpackからpetite-vueを使ってみる。 webpackの設定については省略。 $ npm i petite-vue index.html <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>hoge</title> </head> <body> <div v-scope> {{count}} <button @click="increment">inc</button> </div> <script src="main.js"></script> </body> </html> main.js import { createApp } from 'petite-vue' createApp({ count: 0, increment() { this.count++; } }).mount() railsで使ってみる 今回はwebpackerを使用するものとする。 petite-vueを追加する $ npm i petite-vue app/javascript/packs/count.js import {createApp} from 'petite-vue'; createApp({ count: 0, increment() { this.count++; } }).mount(); app/views/counts/index.html.erb <div v-scope> {{count}} <button @click="increment">inc</button> <%= javascript_pack_tag 'count' %> </div>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【React】関数コンポーネントで親のstateを子にpropsとして渡す方法

はじめに Reactで関数コンポーネントを使い、親のstateを子にpropsとして渡す方法(stateのリフトアップ)についてまとめました。 この記事では、1回クリックするとテキストがbananaからappleに変更されるボタンを作成します。 説明用に極力シンプルなコードにしています。 ご自身のコードに置き換えて頂いて、ご参考になればと思います。 また、この記事の内容に不備が御座いましたらご指摘頂けますと幸いです。 何の為に親のstateを子にpropsとして渡すのか 複数の子コンポーネントからデータを集めたり、コンポーネント同士を互いにやりとりさせる為には、 親コンポーネント内で共有のstateを宣言する必要があります。 環境 React:17.0.2 node.jsとnpmがインストールされていることが前提です。 準備 npx create-react-app my-app cd my-app npm start ターミナルの新しいウィンドウを開き、以下のコマンドを実行して必要なディレクトリとファイルを作成します。 mkdir src/components touch src/components/{Fruits.jsx,FruitsButton.jsx} 実装 まずは、stateのリフトアップをしないで書いてみます。 {/* Fruits.jsx */} import React, { useState } from "react"; const Fruits = () => { const [fruit, setFruit] = useState("banana"); return <button onClick={() => setFruit("apple")}>{fruit}</button>; }; export default Fruits; {/* App.js */} import "./App.css"; import Fruits from "./components/Fruits"; {/* 追加 */} function App() { return ( <div className="App"> <header className="App-header"> <Fruits /> {/* 追加 */} </header> </div> ); } export default App; stateのリフトアップをするとこうなります。 {/* Fruits.jsx(親コンポーネント) */} import React, { useState } from "react"; import FruitsButton from "./FruitsButton"; const Fruits = () => { const [fruit, setFruit] = useState("banana"); {/* 状態を"apple"に変える関数を定義*/} const ReplaceFruit = () => { setFruit("apple"); }; {/* propsとして子に渡すのは{fruit} は(現在の状態"banana")とReplaceFruit関数 */} return <FruitsButton fruit={fruit} onClick={ReplaceFruit} />; }; export default Fruits; {/* FruitsButton.jsx(子コンポーネント) */} const FruitsButton = (props) => { return <button onClick={() => props.onClick()}>{props.fruit}</button>; }; export default FruitsButton; 既に"apple"になっている方は一度ブラウザをリロードして下さい。 参考にしたサイト ステートフックの利用法 – React https://ja.reactjs.org/docs/hooks-state.html 06 新・日本一わかりやすいReact入門【基礎編】コンポーネントの状態管理 https://youtu.be/dTghhYtvPek
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Spline曲線に沿ってカードを動かす

See the Pen Frame Graphic with Spline path by iizuka (@112KA) on CodePen. ↑3D空間上のSpline曲線に沿ってオブジェクトを動かすというのを実装する機会があったので、記事化しておく。 See the Pen Spline path by iizuka (@112KA) on CodePen. 1スプライン1カードのサンプルはこれ↑ 白線 - Spline曲線 緑点 - Spline分割点 緑線 - Splineの外向きベクトル 実装手順 1. Spline曲線を分割 THREE.CatmullRomCurve3では、getPointsで分割してくれる サンプルでは25分割している let curve = new THREE.CatmullRomCurve3([...]) this.points = curve.getPoints(N_DIVIDE); 2. 各分割点で、外向きベクトルを算出 下図、点Bでの外向きベクトルを求めるために、線分ABと線分CBを足してnormalizeしている 3. 各分割点で、外向きベクターの方向に回転するためのQuaternionを算出 山場の実装。 参考にしたのはこのページ 方向をあわせる回転角度が時計回りかどうかを求める必要があったのがわからなくて、そこでほまって時間かかった。 カード(ローカルオブジェクト)のベクトル 姿勢を合わせるためのカードの法線(0,0,1)と進行方向(0,1,0) let cardNormal = new THREE.Vector3(0, 0, 1) , cardDirection = new THREE.Vector3(0, 1, 0) 姿勢合わせ回転 verticalVector = cardNormal.clone().cross(pB.outward) //カード面の法線と点Bの外向きベクトルに垂直なベクトル verticalVector.normalize() radians = Math.acos(cardNormal.dot(pB.outward)) //回転角度 attitudeQuaternion.setFromAxisAngle(verticalVector, radians) 方向合わせ回転 //点Bの外向きベクトルをカード平面に射影 //参考) http://www.thothchildren.com/chapter/5b670e772787593b86356103 pB.targetDirection = lineBC.clone() pB.targetDirection.sub(pB.outward.clone().multiplyScalar(pB.outward.dot(lineBC))) pB.targetDirection.normalize() let localDirection = cardDirection.clone() localDirection.applyQuaternion(attitudeQuaternion) radians = Math.acos(pB.targetDirection.dot(localDirection)) //回転角度 //このときの回転角度が時計回りかどうかを調べる localDirection.cross(pB.targetDirection); let code = localDirection.dot(pB.outward) > 0 ? 1 : -1; //点Bの外向きベクトルを軸に回転 directionQuaternion.setFromAxisAngle(pB.outward, code*radians) 姿勢と方向、それぞれのQuaternionを1つのQuaternionに合成 pB.quaternion = new THREE.Quaternion() pB.quaternion.multiply(directionQuaternion) pB.quaternion.multiply(attitudeQuaternion) 4. 各分割点の”位置”、”Quaternion”の補間値を移動させたいオブジェクトに設定する THREE.Vector3のlerpVectors THREE.QuaternionのslerpQuaternions で、”位置”、”Quaternion”をそれぞれ補間してくれるので、それをカードオブジェクトのmatrixに設定する 終わり! 参考 http://marupeke296.com/DXG_No16_AttitudeControl.html http://www.thothchildren.com/chapter/5b670e772787593b86356103
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TURNサーバーの動作をパケットをモニタリングして確認

試したこと AWSの異なるリージョンに webRTC のクライアントを設置 tcpdumpでパケットを確認して、 TURNサーバーが、パケットをリレーしていることを確認 環境 オファー側 AWS Region: Singapore Linux Ubuntu 20.04 node-webrtc データチャンネルで通信するサンプルを作成 *1 アンサー側 AWS Region: Ohio Linux Ubuntu 20.04 node-webrtc データチャンネルで通信するサンプルを作成 *1 TURNサーバー AWS Region: Tokyo Linux Ubuntu 20.04 Coturn-4.5.2 tcpdump version 4.9.3 データチャンネルで通信するサンプル(node-webrtc) *1 WebRTCのクライアントの設定 ブラウザのJavaScriptで実装する場合は、RTCPeerConnectionの第1引数に設定を渡す node-webrtc で実装する場合も、まったく同じなので、かなり実装が簡単 TURNサーバーの設定 const rtcPeerConnection = new RTCPeerConnection({ iceServers: [ { urls: "turn:{TURN Server Global IP}:{Port}", username: "username", credential: "password" } ] }); TURNサーバーの設定(ICE Candidatesのtypeをrelayだけにする)(必ずTURNサーバーを使うようになるはず) iceTransportPolicy: "relay" を指定 (2021/9/9 追記) const rtcPeerConnection = new RTCPeerConnection({ iceServers: [ { urls: "{TURN Server Global IP}:{Port}", username: "username", credential: "password" } ], iceTransportPolicy: "relay" }); STUNサーバーの設定(今回は無関係) const rtcPeerConnection = new RTCPeerConnection({ iceServers: [ { urls: "stun:stun.l.google.com:19302" } ] }); TURNサーバーのインストール バージョンなどのこだわりがなければ、apt でインストールできる 筆者は、ソースからビルドした sudo apt install coturn ソースコードからビルドする場合 テスト用にTURNサーバーの設定ファイルを編集(読み飛ばし可能) 起動時にコマンドのオプションとしても指定が可能なので、オプションで指定する場合は不要 設定ファイルの準備 /usr/local/etc/turnserver.conf.default にデフォルト設定があるので、リネームして適当な場所に保存 /etc/turnserver.conf に配置すると、自動で読み込んでくれそう(2021/9/9 パスが間違っていたので編集) 設定ファイルの設定項目 lt-cred-mech のコメントアウトを外して long-term credential mechanism を有効にする no-tls no-dtlsのコメントアウトを外して、TLSとDTLSを要求しないように設定 realm user listening-ip listening-port external-ip などを設定する user は、Static ユーザーアカウントを設定する、 long term credentials mechanism が有効のときに使える /etc/turnserver.conf # Uncomment to use long-term credential mechanism. # By default no credentials mechanism is used (any user allowed). # lt-cred-mech # Uncomment if no TLS client listener is desired. # By default TLS client listener is always started. # no-tls # Uncomment if no DTLS client listener is desired. # By default DTLS client listener is always started. # no-dtls # The default realm to be used for the users when no explicit # : # : realm=your_organization.jp # 'Static' user accounts for the long term credentials mechanism, only. # This option cannot be used with TURN REST API. # 'Static' user accounts are NOT dynamically checked by the turnserver process, # so they can NOT be changed while the turnserver is running. # user=username:password # TURN listener port for UDP and TCP (Default: 3478). # Note: actually, TLS & DTLS sessions can connect to the # "plain" TCP & UDP port(s), too - if allowed by configuration. # listening-port=3478 # Listener IP address of relay server. Multiple listeners can be specified. # If no IP(s) specified in the config file or in the command line options, # then all IPv4 and IPv6 system IPs will be used for listening. # listening-ip={Local IP} # For Amazon EC2 users: # # TURN Server public/private address mapping, if the server is behind NAT. # : # : external-ip={Global IP}/{Local IP} コマンド実行 設定ファイルを読み込む場合 必要に応じて、-v で Verbouseを指定してログを確認する /etc/turnserver.conf を利用 大文字のブイ-V にするとさらに詳細に確認できる(Extra verbose mode) Extra verboseにしてもパケットを受け取ったときにリレーする様子のログは表示はされないようだ sudo turnserver -v sudo turnserver -V 設定ファイルのパスを指定する場合(2021/9/9 編集) sudo turnserver -c /usr/local/etc/turnserver.conf 設定ファイルで設定しない場合(設定ファイルの項目を読み飛ばした場合) 手っ取り早くテストするならば、コマンドのオプションを指定した方が簡単 sudo turnserver --no-tls --no-dtls --lt-cred-mech -u username:password -r your_organization.jp -p 3478 -L {Local IP} -X {Global IP}/{Local IP} パケットの確認 tcpdump のインストール TURNサーバーのLinuxに tcpdump をインストールする sudo apt install -y tcpdump TURNサーバーのパケットのモニタリングのコマンド TURNサーバー上で、以下の tcpdump コマンドを実行 IPアドレスやポート番号でフィルタリングできるが 今回は、UDPでフィルタリングした(他にUDPのパケットが飛んでなさそうなので) sudo tcpdump udp シンガポールからのパケットが、 東京のTURNサーバーが受け取り USのオハイオにリレーする様子が確認できた 遭遇したエラーメッセージ 以下のエラーメッセージが出ていて、 本当にTURNサーバーが動いているか気になった 以下のリンクによると、ネゴシエーションのプロセスで、発生するとのことだが、理解を深める必要がある 44: : session 000000000000000001: realm <****> user <>: incoming packet message processed, error 401: Unauthorized 401 message is a legitimate message during negotiations. 感想 今回、TURNサーバーを設置して、 NAT配下にある、異なるリージョンにある、複数のEC2インスタンスでWebRTCの通信テストを行い、パケットをモニタリングして、TURNサーバーがパケットをリレーする様子を確認した リレーの負荷が高いと推測できるので、負荷対策などを今後、考えていく
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】【jQuery】ドロップダウン選択時その項目のvalue値をhidden項目のvalue値に渡す。

例) 下記ドロップダウンで 「東京」(value: "tokyo")を選択するとき、 hidden項目のvalueに "tokyo" を渡す。 index.html <head> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script> </head> <input id="result" type="hidden" value=""> <select name="都道府県" id="pref"> <option value="osaka">大阪</option> <option value="tokyo">東京</option> </select> <script> pref = jQuery("#pref") pref.on("change", function(){ var result = $(this).val() $("#result").val(result) pref_result = document.getElementById("result") console.log(pref_result.value) //tokyo }) </script>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

npm run watchを実行したらエラーが出て、localhost:3000が立ち上がらなかった時の話

いつも、laravel+Vue.jsでSPAを作る時、プロジェクトのあるディレクトリに移動してから$ php artisan serveでlocalhost:8000を立ち上げ、ターミナルの別ウィンドウでプロジェクトのあるディレクトリに移動してから$ npm run watchをしてlocalhost:3000を開いて、書いたコードがどの様に反映されるか確認しながらやってた。 でも突然、なぜかエラーがいっぱい出て、localhost:3000がブラウザで開かなかった。 今回は、そんな時の解決法を備忘録として書いておこうと思います。 エラーの詳細 入力後の出力結果はこちら。 $ npm run watch Error: Missing binding /Users/saya/Documents/laravel_Vue_SPA/node_modules/node-sass/vendor/darwin-x64-72/binding.node Node Sass could not find a binding for your current environment: OS X 64-bit with Node.js 12.x Found bindings for the following environments: - OS X 64-bit with Node.js 10.x This usually happens because your environment has changed since running `npm install`. Run `npm rebuild node-sass` to download the binding for your current environment. at module.exports (/Users/saya/Documents/laravel_Vue_SPA/node_modules/node-sass/lib/binding.js:15:13) at Object.<anonymous> (/Users/saya/Documents/laravel_Vue_SPA/node_modules/node-sass/lib/index.js:14:35) at Module._compile (/Users/saya/Documents/laravel_Vue_SPA/node_modules/v8-compile-cache/v8-compile-cache.js:194:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:1158:10) at Module.load (internal/modules/cjs/loader.js:986:32) at Function.Module._load (internal/modules/cjs/loader.js:879:14) at Module.require (internal/modules/cjs/loader.js:1026:19) at require (/Users/saya/Documents/laravel_Vue_SPA/node_modules/v8-compile-cache/v8-compile-cache.js:161:20) at Object.<anonymous> (/Users/saya/Documents/laravel_Vue_SPA/webpack.mix.js:11:21) at Module._compile (/Users/saya/Documents/laravel_Vue_SPA/node_modules/v8-compile-cache/v8-compile-cache.js:194:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:1158:10) at Module.load (internal/modules/cjs/loader.js:986:32) at Function.Module._load (internal/modules/cjs/loader.js:879:14) at Module.require (internal/modules/cjs/loader.js:1026:19) at require (/Users/saya/Documents/laravel_Vue_SPA/node_modules/v8-compile-cache/v8-compile-cache.js:161:20) at Object.<anonymous> (/Users/saya/Documents/laravel_Vue_SPA/node_modules/laravel-mix/setup/webpack.config.js:12:1) at Module._compile (/Users/saya/Documents/laravel_Vue_SPA/node_modules/v8-compile-cache/v8-compile-cache.js:194:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:1158:10) at Module.load (internal/modules/cjs/loader.js:986:32) at Function.Module._load (internal/modules/cjs/loader.js:879:14) at Module.require (internal/modules/cjs/loader.js:1026:19) at require (/Users/saya/Documents/laravel_Vue_SPA/node_modules/v8-compile-cache/v8-compile-cache.js:161:20) at WEBPACK_OPTIONS (/Users/saya/Documents/laravel_Vue_SPA/node_modules/webpack-cli/bin/utils/convert-argv.js:114:13) at requireConfig (/Users/saya/Documents/laravel_Vue_SPA/node_modules/webpack-cli/bin/utils/convert-argv.js:116:6) at /Users/saya/Documents/laravel_Vue_SPA/node_modules/webpack-cli/bin/utils/convert-argv.js:123:17 at Array.forEach (<anonymous>) at module.exports (/Users/saya/Documents/laravel_Vue_SPA/node_modules/webpack-cli/bin/utils/convert-argv.js:121:15) at /Users/saya/Documents/laravel_Vue_SPA/node_modules/webpack-cli/bin/cli.js:71:45 at Object.parse (/Users/saya/Documents/laravel_Vue_SPA/node_modules/webpack-cli/node_modules/yargs/yargs.js:576:18) at /Users/saya/Documents/laravel_Vue_SPA/node_modules/webpack-cli/bin/cli.js:49:8 at Object.<anonymous> (/Users/saya/Documents/laravel_Vue_SPA/node_modules/webpack-cli/bin/cli.js:366:3) at Module._compile (internal/modules/cjs/loader.js:1138:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:1158:10) at Module.load (internal/modules/cjs/loader.js:986:32) at Function.Module._load (internal/modules/cjs/loader.js:879:14) at Module.require (internal/modules/cjs/loader.js:1026:19) な…長い。 冷静に、エラーメッセージを読み解いてみる こんなに長いメッセージが出ると萎えちゃってやめたくなりますが、此処はひとつ冷静に読み解いてみます。 まず注目したのは序盤のメッセージです。 Error: Missing binding /Users/saya/Documents/laravel_Vue_SPA/node_modules/node-sass/vendor/darwin-x64-72/binding.node Node Sass could not find a binding for your current environment: OS X 64-bit with Node.js 12.x Found bindings for the following environments: - OS X 64-bit with Node.js 10.x これによると、どうやらNode.jsのバージョンが今は12系になっているけれど、前は10系使ってたじゃん! と言っている模様です。 あまり記憶はないのですが、そういえば最近、nvmで他のアプリケーションを作ろうとNode.jsの環境を変更しました。(浮気者ですね) 今はNode.jsをグローバルのバージョンを見に行く様になっているので、こういうことが起きるんですね。(多分) ローカルでバージョンをそれぞれ分けるとかやれる様になるといいんですがね。今後の課題です。。 さらにその下の行を読み進めます。 This usually happens because your environment has changed since running `npm install`. Run `npm rebuild node-sass` to download the binding for your current environment. nom installをしてから、環境が変わるとこれが起きますよ。 $ npm rebuild node-sassを実行して、現在の環境用のバインディングをダウンロードしてね、とのこと。 なるほど。 それだけでいいのですか。 $ npm rebuild node-sass Binary found at /Users/UserName/Documents/laravel_Vue_SPA/node_modules/node-sass/vendor/darwin-x64-72/binding.node Testing binary Binary is fine node-sass@4.14.1 /Users/UserName/Documents/laravel_Vue_SPA/node_modules/node-sass これで、また$ npm run watchすれば、ちゃんとlocalhost:3000の画面がブラウザで立ち上がりました! よかったよかった。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【kintone】レコード作成時に自動で任意の作業者を設定する

はじめに 本記事のレベル感 以下ができる・わかると進めやすいと思います。 JavaScriptの基本構文がわかる kintoneでアプリを作成できる kintoneの標準設定ができる kintoneカスタマイズを少しだけ理解できる とはいえやってみるなかで、できるようになることも多いと思うので参考になれば幸いです。 kintoneのプロセス管理とは kintoneにはプロセス管理の機能があります。詳しくはKintone ヘルプ参照 簡単に説明すると各アクション(ステータス)ごとに作業者を設定でき、レコードごとに誰が何をしたかを管理できるものです。利用イメージとしては、申請・承認作業で使われることが多いと思います。 最初のステータスに設定できる作業者について 最初のステータスの時に設定できる作業者は以下になっています。 作業者を設定しない 作成者 標準機能では作業者を作成者以外を自動で設定することができません。 ※レコードの作成後に手動で作業者を設定することは可能です。 そのため作業者を作成者以外で自動で設定する場合にはカスタマイズが必要になります。 今回の想定動作概要 アプリ内にユーザー選択を用意します。 新規レコード作成時にユーザー選択で選択された任意のユーザーを 最初のステータスの作業者に設定をする。 環境準備 今回はアプリに以下の設定をしています。 kintone フォーム設定 フォームから「ユーザー選択」フィールドを配置します。 配置したらフィールドに以下の設定をします。 フィールド名:「初期作業者」 フィールドコード:「初期作業者」 「必須項目にする」: ON kintone プロセス管理設定 一般設定のプロセス管理を設定します。 プロセス管理の有効化にチェックします。 ステータスを二つ用意します。今回は「未処理」と「完了」とします。 ステータスごとの設定をします。今回はカスタマイズで作業者を設定するので「作業者を設定しない」を設定します。 Javascriptカスタマイズ Javascriptでカスタマイズします。 JavascriptをPC用のJavaScriptファイルに設定してください。 以下のコードを利用します。 sample.js (function() { "use strict"; //新規作成の保存成功時イベント kintone.events.on('app.record.create.submit.success', function(event) { //eventオブジェクトのプロパティ const appId = event.appId; const recordId = event.recordId; const record = event.record; //ユーザー選択「初期作業者」:ログイン名取得 const worker = record['初期作業者']['value'][0]['code']; //本文 const body = { 'app': appId, 'id': recordId, 'assignees': [worker] }; //レコードの作業者の更新 return kintone.api(kintone.api.url('/k/v1/record/assignees.json', true), 'PUT', body, function(resp) { //console.log("成功"); }); }); })(); 設定後に保存を行い、アプリの更新を行ってください。 これで新規にレコードを作成した場合に、ユーザー選択で選択したユーザーが作成者に設定されます。 解説 レコード追加画面の保存成功後イベント //新規作成の保存成功時イベント kintone.events.on('app.record.create.submit.success', function(event) { }); 今回はレコード追加画面の保存成功後イベント(app.record.create.submit.success)を利用しました。 保存成功後を利用する理由としては、後述の作業者の更新時にレコード番号を必要とするためです。 ※モバイル版で利用する場合にはmobile.app.record.create.submit.successを利用します。 eventオブジェクトのプロパティ値取得 //eventオブジェクトのプロパティ const appId = event.appId; const recordId = event.recordId; const record = event.record; リクエストに利用する「アプリID」、「レコード番号」、 レコードの登録状況を取得する「レコードオブジェクト」を取得しています。 ユーザー選択に設定したユーザーを取得 //ユーザー選択「初期作業者」:ログイン名取得 const worker = record['初期作業者']['value'][0]['code']; ユーザー選択に設定したユーザーのユーザーコードを取得します。 ['初期作業者']はユーザー選択のフィールドコード名です。 ['value'][0]はユーザー選択のフィールド形式は配列で取得されるため、一つ目の値を指定しています。 ['code']はユーザーコードを指定しています。 フィールド形式については詳細に知りたい方はこちらを参考にしてください。 リクエストボディの作成 //本文 const body = { 'app': appId, 'id': recordId, 'assignees': [worker] }; リクエストボディの作成をしています。 先ほど取得した「アプリID」「レコード番号」「ユーザーコード」を設定しています。 レコードの作業者の更新 //レコードの作業者の更新 return kintone.api(kintone.api.url('/k/v1/record/assignees.json', true), 'PUT', body, function(resp) { //console.log("成功"); }); レコードの作業者を更新しています。 URIにはhttps://(サブドメイン名).cybozu.com/k/v1/record/assignees.json、 HTTPメソッドはPUT、先ほどのbody(リクエストボディ)をセットしリクエストを行います。 成功するとreturnが行われ、作業者の設定が完了します。 リクエストボディを含む詳細の設定はレコードの作業者の更新をご確認ください。 最後に 参考一覧 プロセス管理でできること レコード追加画面の保存成功後イベント フィールド形式 レコードの作業者の更新
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript初心者】QiitaAPIで投稿記事ごとのviews数、LGTM数、ストック数、コメント数を取得、表示

投稿記事が10件を超え、そろそろ各投稿記事のviews数、LGTM数、ストック数等を一覧で確認してみたくなりました。今現在、「views数については」Qiita上にて一覧で確認する方法はありません。調べてみたところ、皆さん同じことを思うようで参考になるものがありました。ありがとうございました。 以下の記事の「①HTML/JavaScript」を参考にQiitaAPI(api/v2/authenticated_user/items、api/v2/items/[記事のID])を使用して投稿記事ごとのviews数、LGTM数、ストック数を取得、表示しました。LIKE率、STOCK率は省略し、代わりにコメント数と作成日を追加しました。 本記事の環境 ※PCに環境構築を行う必要はありません。 WEBブラウザ(Google Chome) テキストエディタ 使用技術 HTML JavaScript jQuery Ajax Bootstrap 事前準備 Qiitaにて、「設定」から「アプリケーション」を選択する。 「個人用アクセストークン」を発行する。※今回は読み取り権限だけで良いです。 発行した「個人用アクセストークン」をメモする。※後で入力します。 使用方法 以下の成果物をWEBブラウザで開き、「個人用アクセストークン」を入力する。 「読込」ボタンを選択して、表示されるまで少し待つ。 表示されたら結果を確認する。※タイトルのリンクから記事に飛べるか等も。 成果物(画面) 所感 なるべくjQueryを使おうと努力した。使えたと思う。 Ajax、Bootstrapは初めて使った。できることが増えそうなので使っていきたい。 投稿記事ごとのviews数、LGTM数、ストック数、コメント数を一覧で確認できるのが楽でいい。 成果物(ソースコード) qiita_item_get.html <!DOCTYPE html> <html> <head> <title>Qiita Item</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous"> </head> <body> <template id="tdTemp"> <tr id="trTemp"> <th id="td_no" scope="row">1</th> <th id="td_create_datetime" scope="row">0000-00-00 00:00:00</th> <td id="td_title"><a id="td_link" href="temp" target="_blank">タイトル</a></td> <td id="td_views">1</td> <td id="td_lgtm">2</td> <td id="td_stock">3</td> <td id="td_comment">4</td> </tr> </template> <div class="container"> <br> <h3>Qiita投稿一覧</h3> <br> <div class="input-group flex-nowrap" id="main"> <div class="input-group-prepend"> <span class="input-group-text">Qiitaアクセストークン設定</span> </div> <input type="text" class="form-control" id="accesstoken" placeholder="Access Token"> </div> <br> <button type="button" class="btn btn-primary" onclick="getQiitaItem()">読込</button> <br> <br> <div id="result"> <table class="table table-dark" id="itemTable"> <thead id="itemThead" style="display: none;"> <tr> <th scope="col">No</th> <th scope="col">作成日</th> <th scope="col">タイトル</th> <th scope="col">views</th> <th scope="col">LGTM</th> <th scope="col">ストック</th> <th scope="col">コメント</th> </tr> </thead> <tbody id="itemTbody"></tbody> </table> <p id="loading" style="display: none;">Now Loading...</p> </div> </div> <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha384-vtXRMe3mGCbOeY7l30aIg8H9p3GdeSe4IFlP6G8JMa7o7lXvnz3GFKzPxzJdPfGK" crossorigin="anonymous"></script> <script> // Qiita投稿一覧取得処理 function getQiitaItem () { // 画面入力されたQiitaアクセストークンを取得する。 let token = $('#main > #accesstoken').val(); if (token == '') { alert('アクセストークンを入れてください'); return; } // 見出し行を表示する。 $("#result > #itemTable > #itemThead").show(); // ローディングを表示する。 $("#result > #loading").show(); // Qiitaアクセストークンに対応するユーザの記事の一覧を作成日時の降順で取得する。 let items = []; $.ajax({ type: "GET", url: "https://qiita.com/api/v2/authenticated_user/items", headers: { "Content-Type": "application/json", "Authorization": "Bearer " + token, }, dataType: "json", success: function(data) { $("#result > #loading").hide(); items = data; let sum_view = 0; let sum_lgtm = 0; let sum_stock = 0; let sum_comment = 0; items.forEach (function(element, index) { // 記事取得処理を呼び出す。 let item = []; item = getItemCore(element.id, token); // ストック数取得処理を呼び出す。 let cnt_stock = 0; cnt_stock = getQiitaStock(element.id, token); // 取得したデータを設定する。 let content_tdTemp = $("#tdTemp").contents(); let no = items.length - index; content_tdTemp.children("#td_no").text(no); let create_datetime = item.created_at.substring(0, 10); create_datetime = create_datetime.replace(/-/g, "/"); content_tdTemp.children("#td_create_datetime").text(create_datetime); content_tdTemp.children("#td_title").children("#td_link").attr("href", item.url); let title = item.title; if (title.trim().length >= 50) { // 記事タイトルが長すぎる場合は、一部を「 ...」に置換する。 title = title.substring(0, 50) + " ..."; } content_tdTemp.children("#td_title").children("#td_link").text(title); content_tdTemp.children("#td_views").text(item.page_views_count); content_tdTemp.children("#td_lgtm").text(item.likes_count); content_tdTemp.children("#td_stock").text(cnt_stock); content_tdTemp.children("#td_comment").text(item.comments_count); content_tdTemp.clone().appendTo("#result > #itemTable > #itemTbody"); $("#result").show(); sum_view += item.page_views_count; sum_lgtm += item.likes_count; sum_stock += cnt_stock; sum_comment += item.comments_count; }); // 合計行を設定する。 let content_tdTemp_sum = $("#tdTemp").contents(); content_tdTemp_sum.children("#td_no").text("計"); content_tdTemp_sum.children("#td_create_datetime").text("-"); content_tdTemp_sum.children("#td_title").text("-"); content_tdTemp_sum.children("#td_views").text(sum_view); content_tdTemp_sum.children("#td_lgtm").text(sum_lgtm); content_tdTemp_sum.children("#td_stock").text(sum_stock); content_tdTemp_sum.children("#td_comment").text(sum_comment); content_tdTemp_sum.clone().appendTo("#result > #itemTable > #itemTbody"); $("#result").show(); }, error: function(XMLHttpRequest, textStatus, errorThrown) { alert('getQiitaItem() ERROR : ' + errorThrown); } }); }; // 記事取得処理 function getItemCore(item_id, token) { let item = []; $.ajax({ type: "GET", url: 'https://qiita.com/api/v2/items/' + item_id, headers: { "Content-Type": "application/json", "Authorization": "Bearer " + token, }, dataType: "json", async: false, // 同期通信 success: function(data){ item = data; }, error: function(XMLHttpRequest, textStatus, errorThrown) { alert('getQiitaViews() ERROR : ' + errorThrown); } }); return item; }; // ストック数取得処理 function getQiitaStock(item_id, token) { // APIの仕様上、ストック数を瞬時には取得できないため、やや面倒な処理を実施。 let stock = []; let flg = true; let index_page = 1; let cnt_stock = 0; while (flg) { $.ajax({ type: "GET", url: 'https://qiita.com/api/v2/items/' + item_id + '/stockers?page=' + index_page + '&per_page=100', headers: { "Content-Type": "application/json", "Authorization": "Bearer " + token, }, dataType: "json", async: false, // 同期通信 success: function(data){ stock = data; cnt_stock += stock.length; if (stock.length < 100) { flg = false; } }, error: function(XMLHttpRequest, textStatus, errorThrown) { alert('getQiitaStock() ERROR : ' + errorThrown); flg = false; } }); index_page++; } return cnt_stock; }; </script> </body> </html>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【コピペ可】スプレッドシートからGoogleFormを自動生成

こんにちは,だるだるし です 会社の代表してたり音声作品投稿してたりします お金がありません,,,金か仕事をください,,, はい,ていうのはおいておいて Googleフォームの自動生成 目標 GoogleフォームをスプレッドシートとGASで自動生成する 前段 『 スタディステーション』という(うちの会社の)サービス(現在完全無料なので本当に”サービス”)がある. 各自がGoogleフォームで作成したテスト,問題を皆で共有,検索できるサービス. ここに小学二年生用の九九のサンプルを投稿したかったが,ポチポチ作るのはめんどくさかったので自動化したい. 【GAS】スプレッドシートからテスト用のGoogleFormを自動生成する 上記の記事を参照した結果,以下気になったので一応プログラマとして改良する 速度が遅い(多分毎回シートにアクセスしてるせい) 自由記述の解答形式に対応 汎用性を持たせる(選択肢や正答の数を無制限にする,セルを決め打ち指定するのを避ける) 冗長で複雑な表現を修正(配列をオブジェクトにするなど) その他軽微な機能追加 答え さっくり答え 九九作った時の「スプレッドシート」 で,Googleフォームを生成するGASのコード 正直変数名とかエラーハンドリングは適当にしてあるのでご了承 //データをオブジェクト形式に変換 function getDataObjects(keyRow, dataRow) { let dataObjects = []; let keys = []; for (let i in keyRow) { let key = keyRow[i]; if (key) { keys[key] = { key: key, i: i, }; } } for (let row of dataRow) { let obj = {}; for (let k in keys) { let key = keys[k]; obj[key.key] = row[key.i]; } dataObjects.push(obj); } return dataObjects; } //メイン関数 function myFunction() { //シート名 let sheet = SpreadsheetApp.getActive().getSheetByName('シート1'); let lastRow = sheet.getLastRow();//行 // let lastColumn = sheet.getLastColumn();//列 let formData = { title: "Googleフォーム", description: "", order: false, }; //全データ取得 let _sheet = sheet.getDataRange().getValues(); let _formData = getDataObjects(_sheet[0], [_sheet[1]])[0]; //フォーム名と説明文を設定 formData.title = _formData.title || formData.title; formData.description = _formData.description || formData.description; formData.order = (_formData.order == 1); Logger.log(formData); //フォームを作成 let form = FormApp.create(formData.title); form.setDescription(formData.description); form.setShuffleQuestions(formData.order); //クイズモードに設定 form.setIsQuiz(true); //問題データ let questions = getDataObjects(_sheet[3], _sheet.splice(5, lastRow - 1)); //問題文・選択肢を作成 for (let question of questions) { let choices; //正解の数によってラジオボタンまたは、チェックボックスに形式を変える let item; switch (question.format) { case 1: //数値解答 item = form.addTextItem(); let textValidation = FormApp.createTextValidation() .setHelpText('数値で入力してください') .requireNumber() .build(); item.setValidation(textValidation); break; case 2: //記述 item = form.addTextItem(); break; default: //選択 let n = 1; switch (question.multiple) { case 1: //複数解答 //チェックボックスの作成 item = form.addCheckboxItem(); choices = []; while (question["choices" + n] || question["choices" + n] === 0) { let correct = false; for (let i in question.numOfCorrect) { if (question["choices" + n] === question["answer" + i]); correct = true; break; } let choice = item.createChoice(question["choices" + n], correct);//text,isCorrect(booblean) choices.push(choice); n++; } break; default: //単一解答 //ラジオボタンの作成 item = form.addMultipleChoiceItem(); //選択肢と解答の番号を取得 choices = []; while (question["choices" + n] || question["choices" + n] === 0) { let choice = item.createChoice(question["choices" + n], question["choices" + n] === question.answer1); choices.push(choice); n++; } break; } break; } //問題文・選択肢・配点・必須をセット item.setTitle(question.statement || "問題"); if (choices) { item.setChoices(choices); } item.setPoints(question.points); // item.setRequired(true); //フィードバックをセット // Logger.log(question.feedback); // Logger.log(question.format); if (question.format != 2 && question.format != 1) { if (question.feedback) { item.setFeedbackForCorrect(FormApp.createFeedback().setText(question.feedback).build()); } if (question.feedbackForI) { item.setFeedbackForIncorrect(FormApp.createFeedback().setText(question.feedbackForI).build()); } } } } 取り敢えず使い方 元となるスプレッドシートを作成 見にくくて申し訳ないですが,以下のようにスプレッドシートを作成. スプレッドシートの作成方法は別の記事等を参照のこと ざっくり解説 一行目は全体に共通する「キー」 二行目は全体に共通する「値」 三行目は空(無視される) 四行目は問題に関する「キー」 五行目はキーの説明(無視される) 六行目以降は問題の「値」(一行で一問) 一行目の「title」の直下の値がGoogleフォームの「タイトル」, 一行目の「description」の直下の値がGoogleフォームの「概要」 になる 同様に 四行目の「statement」の列が問題の「問題文」の列 四行目の「format」の列が問題の「解答形式」の列 「キー」と「値」の列が対応していれば列の順序は自由 また,「キー」が空の列は無視されるので,計算やメモの列を確保可能 正解と選択肢はいくつでも登録可能 例えば選択肢は 「choices1」「choices2」「choices3」「choices4」「choices5」,,,と増やす 「choices1」から詰めて入力 スクリプトエディタ起動 「ツール」→「スクリプト エディタ」 許可等求められた場合は適宜対応 上記スクリプトをコピペ,実行 「実行」を押して実行 ただし「デバッグ」の右隣が「myFunction」になっていることを確認 完成 「実行完了」が出たら完成 Googleドライブの”トップ”に出来ている ポイント "シート1"のところはシート名に合わせる let sheet = SpreadsheetApp.getActive().getSheetByName('シート1'); 最初にシート全体を読み込んで読み込み回数を減らす let _sheet = sheet.getDataRange().getValues(); 「getDataObjects関数(自作)」で「question」オブジェクトにする for文では「of」を使用して「qestionList[i][5]」のようなわかりにくい表現を避ける //問題データ let questions = getDataObjects(_sheet[3], _sheet.splice(5, lastRow - 1)); //問題文・選択肢を作成 for (let question of questions) { ,,, } テキスト入力解答欄は addTextItem で作成 解答形式を指定する場合は createTextValidation を使用 以下は解答を数字に限定する例 item = form.addTextItem(); let textValidation = FormApp.createTextValidation() .setHelpText('数値で入力してください') .requireNumber() .build(); item.setValidation(textValidation); 問題順をランダムにするには setShuffleQuestions をtrueにする let form = FormApp.create("title"); form.setShuffleQuestions(true); 問題点 高速化したけど遅い(大量の問題を作る場合は結構待つ) 自動生成では自由記述型の解答形式の問題に正答を設定できない(手動か選択型なら可能,参考) おわり こんな感じです お付き合いいただきありがとうございました また,「スタディステーション」等についても記事にしていただきますので 今後ともよろしくお願いたします あと,仕事ください
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Next.js】 Vercelビルド時のエラー解決方法 'Page Without Valid React Component'

使用環境 ・MacOS BigSur(11.5.2) ・VScode(1.59.1) ・Node.js (16.1.1) ・React(17.0.2) ・Next.js(11.1.0) はじめに Vercelのビルド時に出たエラー’Page Without Valid React Component’の解決方法です。 04:50:30.440 > Build error occurred 04:50:30.442 Error: Build optimization failed: found pages without a React Component as default export in 04:50:30.442 pages/editPage 04:50:30.442 pages/profile 04:50:30.442 pages/search 04:50:30.442 pages/userAuth 04:50:30.443 See https://nextjs.org/docs/messages/page-without-valid-component for more info. 04:50:30.443 at /vercel/path0/node_modules/next/dist/build/index.js:597:19 04:50:30.444 at processTicksAndRejections (internal/process/task_queues.js:95:5) 04:50:30.444 at async Span.traceAsyncFn (/vercel/path0/node_modules/next/dist/telemetry/trace/trace.js:60:20) 04:50:30.473 error Command failed with exit code 1. 04:50:30.474 info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command. 04:50:30.493 Error: Command "yarn run build" exited with 1 結論/解決策 Vercelビルド時のエラーログだけではよく分からなかったので、エラーログに表示されていたNext.jsの公式ドキュメントのURLに飛んだら方法が書いてありました。 pagesの配下のReactコンポーネント(ページとして表示させたいコンポーネント)はexportではなく、export default を使用しなければいけないそうです。 If the file is meant to be a page, double check you have an export default with the React Component instead of an export. If you're already using export default, make sure the returned value is a valid React Component. - 公式DOCより (後々、エラーログみたらちゃんと書いてありました Error: Build optimization failed: found pages without a React Component as default export in) 最後に default exportだとimportする際のクラス名や関数名が一致していなくても呼び出せることから、予期せぬエラーが生じるのを回避するためにexportを使った方がいいという情報をどこかでみたので慣習的にそうしていました。 しかし今回のようにNext.jsでpagesのコンポーネントとして読み込ませる時など、default exportでなければValid React Componentとしてみなされないケースもあるみたいです。 なぜそうなのか気になって公式ドキュメントのPagesの解説をみましたが、詳しくは書いてありませんでした。これ以上深堀まではしてませんが、そういうものだと思っておきます。 もし詳しい方がいたら情報いただけると幸いです ? 参考資料 Next.js 公式ドキュメントPage Without Valid React Component Hatena Blog 【React/ES6】named exportsとdefault exportsの使い分け
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

node.js超入門ノート11(Sequelizeでのバリデーション編)

バリデーション モデルのバリデーション 以下のように修正します。 models/user.js 'use strict'; module.exports = (sequelize, DataTypes) => { const User = sequelize.define('User', { name: { type:DataTypes.STRING, validate: { notEmpty: true } }, pass: { type: DataTypes.STRING, validate: { notEmpty: true } }, mail: { type: DataTypes.STRING, validate: { isEmail: true } }, age: { type: DataTypes.STRING, validate: { isInt: true, min: 0 } } }, {}); User.associate = function(models) { // associations can be defined here }; return User; }; validateの例は以下のようになります。 validate 意味 notEmpty 未入力の禁止 isEmail メールアドレス形式のみ許可 isInt 整数値のみ許可 min 最小値の設定 User作成処理のバリデーション 以下を修正します。 routes/users.js router.get('/add',(req, res, next) => { var data = { title: 'Users/Add', form: new db.User(), err:null } res.render('users/add', data); }); router.post('/add',(req, res, next) => { const form = { name: req.body.name, pass: req.body.pass, mail: req.body.mail, age: req.body.age }; db.sequelize.sync() .then(() => db.User.create(form) .then(usr => { res.redirect('/users'); }) .catch(err => { var data = { title: 'Users/Add', form: form, err: err } res.render('users/add', data); }) ) }); テンプレートのエラー表示 テンプレートにエラーの表示を追加します。 views/users/add.ejs <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta http-equiv="content-type" content="text/html"> <title><%= title %></title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" crossorigin="anonymous"> <link rel="stylesheet" href="/stylesheets/style.css" /> </head> <body class="container"> <header> <h1 class="display-4"> <%= title %> </h1> </header> <div role="main"> <ol class="text-danger"> <% if (err!=null) {for (let i in err.errors) { %> <li><%= err.errors[i].message %></li> <% }} %> </ol> <form action="/users/add" method="post"> <div class="form-group"> <label for="name">NAME</label> <input type="text" name="name" id="name" value="<%=form.name %>" class="form-control"> <ul class="text-danger"> <% if (err!=null) {for (let i in err.get("name")) { %> <li><%= err.get("name")[i].message %></li> <% }} %> </ul> </div> <div class="form-group"> <label for="pass">PASSWORD</label> <input type="password" name="pass" id="pass" value="<%=form.pass %>" class="form-control"> <ul class="text-danger"> <% if (err!=null) {for (let i in err.get("pass")) { %> <li><%= err.get("pass")[i].message %></li> <% }} %> </ul> </div> <div class="form-group"> <label for="mail">MAIL</label> <input type="text" name="mail" id="mail" value="<%=form.mail %>" class="form-control"> <ul class="text-danger"> <% if (err!=null) {for (let i in err.get("mail")) { %> <li><%= err.get("mail")[i].message %></li> <% }} %> </ul> </div> <div class="form-group"> <label for="age">AGE</label> <input type="number" name="age" id="age" value="<%=form.age %>" class="form-control"> <ul class="text-danger"> <% if (err!=null) {for (let i in err.get("age")) { %> <li><%= err.get("age")[i].message %></li> <% }} %> </ul> </div> <input type="submit" value="作成" class="btn btn-primary"> </form> </div> </body> </html> エラーメッセージを日本語に変更 以下を修正します。 models/user.js 'use strict'; module.exports = (sequelize, DataTypes) => { const User = sequelize.define('User', { name: { type:DataTypes.STRING, validate: { notEmpty: { msg: "名前は必ず入力してください。" } } }, pass: { type: DataTypes.STRING, validate: { notEmpty: { msg: "パスワードは必ず入力してください。" } } }, mail: { type: DataTypes.STRING, validate: { isEmail: { msg: "メールアドレスを入力して下さい。" } } }, age: { type: DataTypes.STRING, validate: { isInt: { msg: "ゼロ以上の値が必要です。" }, min: { args: [0], msg: "ゼロ以上の値が必要です。" } } } }, {}); User.associate = function(models) { // associations can be defined here }; return User; }; バリデーション一覧は以下のPer-attribute validationsから見れます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Ajax]フォロー機能を非同期にした

はじめに 本記事では、フォロー機能を非同期通信にした方法を記述します。 以前にいいね機能について、非同期通信にした記事も投稿しておりますので、 ご参照ください。 前提 フォロー機能が同期通信できているものとします。 コード 早速ですが、該当箇所のコードを記載します。 コントローラー showページにフォローボタンやフォロワーを確認できるボタンがあります。 後述のモデルに合わせてfollowingsや`followersメソッドを作成します。 また、usersとfollowersなどに分けている方もいらっしゃいましたが、 私は、followingsや`followersに分けた方がわかりやすいと思ったため、こうしています。 users_controller.rb class UsersController < ApplicationController def show @user = User.find(params[:id]) @foods = @user.foods.order("created_at DESC") end def followings user = User.find(params[:id]) @users = user.followings end def followers user = User.find(params[:id]) @users = user.followers end 省略 end relationshipsテーブルは、userとuserの関係を示す、中間テーブルになります。 また、その関係を作る(create)、壊す(destroy)の2つが必要になります。 relationships_controller.rb class RelationshipsController < ApplicationController before_action :authenticate_user! def create @user = User.find(params[:user_id]) following = Relationship.create(follower_id: params[:user_id], following_id: current_user.id) end def destroy @user = User.find(params[:user_id]) following = Relationship.find_by(follower_id: params[:user_id], following_id: current_user.id) following.destroy end end モデル 同期通信と特に変わりはありません。 user.rb class User < ApplicationRecord # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable has_many :foods, dependent: :destroy has_many :likes, dependent: :destroy has_many :liked_foods, through: :likes, source: :food has_many :relationships, foreign_key: :following_id has_many :followings, through: :relationships, source: :follower has_many :reverse_of_relationships, class_name: 'Relationship', foreign_key: :follower_id has_many :followers, through: :reverse_of_relationships, source: :following has_one_attached :icon has_many :comments def already_liked?(food) self.likes.exists?(food_id: food) end def is_followed_by?(user) reverse_of_relationships.find_by(following_id: user.id).present? end end ここも同期通信と特に変わりはありません。 relationship.rb class Relationship < ApplicationRecord belongs_to :following ,class_name: "User" belongs_to :follower, class_name: "User" end ビュー show.html.erb 記述が長いので、部分テンプレートです。 ちなみに、クラス名を class="mypage-follow-contents-<%= @user.id %>" にしないと、誰をフォローするのかわからないままですので、 エラーが出続けることになります。 <div class="mypage-intro-topright"> <div class="mypage-follow-contents-<%= @user.id %>"> <%= render partial:"relationships/relationship", lacals: {user: @user} %> </div> </div> followers.html.erbフォロワーの一覧を確認できるページ。ここはあまり関係ないと思います。 <div class="followers-contents"> <div class="row d-flex justify-content-center"> <div class="col-10 mt-5"> <h1>FOLLOWERS</h1> <h2 class="followers-contents-title">Nickname</h2> <% @users.each do |user| %> <%= link_to user_path(user) do %> <%= user.nickname %> <% end %> <% if user != current_user %> <% if user.is_followed_by?(current_user) %> <%=link_to user_relationships_path(user), method: :delete do %> <button type="button" class="btn btn btn-danger">フォロー解除</button> <% end %> <% else %> <%=link_to user_relationships_path(user), method: :post do %> <button type="button" class="btn btn btn-primary">フォロー</button> <% end %> <% end %> <% end %> <% end %> </div> </div> </div> followings.html.erbフォローしている人を確認できるページ。ここはあまり関係ないと思います。 <div class="followings-contents"> <div class="row d-flex justify-content-center"> <div class="col-10 mt-5"> <h1>FOLLOWING</h1> <h2 class="followings-contents-title">Nickname</h2> <% @users.each do |user| %> <%= link_to user_path(user) do %> <%= user.nickname %> <% end %> <% if user != current_user %> <% if user.is_followed_by?(current_user) %> <%=link_to user_relationships_path(user), method: :delete do %> <button type="button" class="btn btn btn-danger">フォロー解除</button> <% end %> <% else %> <%=link_to user_relationships_path(user), method: :post do %> <button type="button" class="btn btn btn-primary">フォロー</button> <% end %> <% end %> <% end %> <% end %> </div> </div> </div> _relationship.html.erb show.html.erbにあった部分テンプレートです。 remote: trueこれが特に大事です。 というか非同期通信のスタートがこれなんではないかというぐらい重要です。 これにより、非同期通信をOKするものになり、リクエストがjs形式になります。 そのため、後ほど出てきますが、 ファイルはjs.erbファイルということになります。 <div class="mypage-followcount-btn"> <%= link_to followings_user_path(@user) do %> <button class="btn btn-mypage-follow">フォロー <i class="fas fa-angle-right fa-position-right"> <%= @user.followings.count %> </i> </button> <% end %> <%= link_to followers_user_path(@user) do %> <button class="btn btn-mypage-follow">フォロワー <i class="fas fa-angle-right fa-position-right"> <%= @user.followers.count %> </i> </button> <% end %> <% if user_signed_in? && @user == current_user %> <div class="mypage-introduction-edit"> <%= link_to edit_user_path(current_user) do %> <button class="btn btn-mypage-introduction-edit">プロフィール編集 <i class="fas fa-user-edit"></i> </button> <% end %> </div> <% else %> <%# <% unless @user == current_user %> <div class="mypage-follow-btn"> <% if @user.is_followed_by?(current_user) %> <%= link_to user_relationships_path(@user), method: :delete, remote: true do %> <button class="btn btn-follow-delete">フォロー中</button> <% end %> <% else %> <%= link_to user_relationships_path(@user), method: :post, remote: true do %> <button class="btn btn-follow-create">フォローする</button> <% end %> <% end %> </div> <% end %> </div> create.js.erbフォロー jについては、以下にも記載したので省略します。 $('.mypage-follow-contents-<%= @user.id %>').html("<%= j(render partial: 'relationships/relationship', locals: {user: @user}) %>") 部分テンプレートにより、 前述の以下の部分に進みます。 <% if @user.is_followed_by?(current_user) %> <%= link_to user_relationships_path(@user), method: :delete, remote: true do %> <button class="btn btn-follow-delete">フォロー中</button> <% end %> <% else %> <%= link_to user_relationships_path(@user), method: :post, remote: true do %> <button class="btn btn-follow-create">フォローする</button> <% end %> <% end %> destroy.js.erbフォロー解除 create.js.erbと同じです。 $('.mypage-follow-contents-<%= @user.id %>').html("<%= j(render partial: 'relationships/relationship', locals: {user: @user}) %>") 以上です。 いかがでしょうか。 終わりに 非同期通信、終わってみれば「簡単」だと思いますが、 できるまでは難しかったなというのが、印象でした。 アプリケーションにおいて非同期化してしまった方が良いものは全て非同期化した方が良さそうですね。 以下、参考サイトです。 [Rails]Ajaxを用いて非同期でフォロー機能の実装 【Rails】Ajaxを用いた非同期フォロー機能の実装 Railsで実装したフォロー機能の非同期化がうまくいかない。 Ruby on Rails チュートリアル 明日も頑張ります!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Axios使ってQiita投稿する

ドキュメント Qiita API v2ドキュメント - Qiita:Developer 準備 Qiita から個人用アクセストークンを取得する scopeでread_qiitaとwitre_qiitaにチェックを入れる。 axiosをインストールしておく 実装 import axios from 'axios' async function postQiita() { // TODO: ?取得したTokenをセットする。漏洩しないように注意 const QIITA_ACCESS_TOKEN = 'xxxxx' // POSTリクエスト const response = await axios.post('https://qiita.com/api/v2/items', { body: '# テストだよ', // 内容 group_url_name: null, coediting: false, private: false, // ? 限定公開 APIのテストは非公開&すぐに消しましょう // タグ必須だよ tags: [{ name: 'Qiita', versions: ['0.0.1'] }], title: 'テスト Qiita API', tweet: false // 投稿をTweetするか否か }, { headers: { Authorization: `Bearer ${QIITA_ACCESS_TOKEN}` // 認証 } }) // 確認 console.log('response:', response) }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む