20210827のJavaScriptに関する記事は19件です。

Vue + Laravelでformの入力情報をparamsから受け取り、復元する方法

はじめに 一週間前から初めて触るVue + Laravelの環境で新規案件を開発しはじめました。 入力フォームの情報を確認画面に渡し、入力ミスなどで入力フォームに戻る際に、Laravelでフォームの情報を受け渡ししようかと奮闘しましたができず、結局Javascriptの力技で解決しました。。。多分 本来のやり方ではないと思いますが、良かったら参考にしてください。 環境 バックエンド:PHP - Laravel8 フロントエンド:Vue.js DB:Mysql ver8.0 Docker使用 最初にやろうとしたこと 新規入力フォーム userCreate.vue <template> <form @submit.prevent="submit"> <label for="name">名前</label> <input type="text" id="name" name="name" v-model="form.name" /> <label for="email">メールアドレス</label> <input type="text" id="email" name="email" v-model="form.email" /> <button name="test" type="submit">送信</button> </form> </template> <script> //中略 export default {      props: { user: Object, }, data() { return {              // 戻った時に予め入力していた値を受け取り、フォームに復元する form: this.$inertia.form({ name: this.user.name, email: this.user.email, }), }; }, methods: { submit() { this.form.post(this.route("userconfirm")); }, }, }; </script> 入力確認フォーム userConfirm.vue <template> <form @submit.prevent="submit"> <table> <tbody> <tr> <td>名前</td> <td>{{ user.name }}</td> </tr> <tr> <td>email</td> <td>{{ user.email }}</td> </tr> </tbody> </table> <button name="back" type="submit">戻る</button> <button name="send" type="submit">送信</button> </form> </template> <script> //中略 export default {      props: { user: Object, }, data() { return { form: this.$inertia.form({ name: this.user.name, email: this.user.email, }), }; }, methods: { submit() { // data()内のformデータをコントローラに送る this.form.post(this.route("usercomplate")); }, }, }; </script> laravel側コントローラー userController.php <?php namespace App\Http\Controllers; use Inertia\Inertia; use Illuminate\Http\Request; use App\Models\User; class UserController extends Controller { public function create() { return Inertia::render('Mypage/UserCreate'); } public function confirm(Request $request) { $user = $request->all(); return Inertia::render('Mypage/UserConfirm', ['user' => $user]); } public function complete(Request $request) { if($request->has('send')){ // ここに保存の処理 }elseif($request->has('back')){ // ここに新規入力フォームに戻る処理 $user = $request->all(); return Inertia::render('Mypage/UserConfirm', ['user' => $user]); } } } ボタンのnameを受け取りif文で条件分岐処理を行うのがLaravelのベストプラクティスかと思われますが、どうやってもname属性を受け渡しできずにLaravelでの処理は諦めました。。。。。 URLから受け取る処理をVueで実装する 新規入力フォーム userCreate.vue <template> <form @submit.prevent="submit"> <label for="name">名前</label> <input type="text" id="name" name="name" v-model="form.name" /> <label for="email">メールアドレス</label> <input type="text" id="email" name="email" v-model="form.email" /> <button type="submit">送信</button> </form> </template> <script> //中略 export default {      props: { user: Object, }, data() {         // ①ここを追加         let url = new URL(window.location.href); let params = url.searchParams; return {              // ②パラメータを受け取り、フォームに復元する form: this.$inertia.form({ name: params.get("name"), email: params.get("email"), }), }; }, methods: { submit() { this.form.post(this.route("userconfirm")); }, }, }; </script> 入力確認フォーム userConfirm.vue <template> <form @submit.prevent="submit"> <table> <tbody> <tr> <td>名前</td> <td>{{ user.name }}</td> </tr> <tr> <td>email</td> <td>{{ user.email }}</td> </tr> </tbody> </table> <div @click="backpage">戻る</div> <button type="submit">送信</button> </form> </template> <script> //中略 export default {      props: { user: Object, }, data() { return { form: this.$inertia.form({ last_name: this.user.name, email: this.user.email, }), }; }, methods: { submit() { this.form.post(this.route("usercomplete")); },         // 入力値が含まれるパラメーターを送る backpage() { this.form.get(this.route("usercreate")); }, }; </script> [戻る]をクリックしbackpageメソッド実行されると、下記のような入力データが入ったパラメータが送られるため①でURLを取得し、②で各項目をGetしてからdataに初期値を渡す。 http://localhost:8080//user/create?name=foobar&email=test@test.com 参考サイト URLに含まれるGETパラメータを取得する Laravelにて1つのFormで複数ボタンを実装する際の備忘録 正しい方法ではないかもしれませんがご参考まで
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue + Laravelでformの入力情報をパラメーターから受け取り、復元する方法

はじめに 一週間前から初めて触るVue + Laravelの環境で新規案件を開発しはじめました。 入力フォームの情報を確認画面に渡し、入力ミスなどで入力フォームに戻る際に、Laravelでフォームの情報を受け渡ししようかと奮闘しましたができず、結局Javascriptの力技で解決しました。。。多分 本来のやり方ではないと思いますが、良かったら参考にしてください。 環境 バックエンド:PHP - Laravel8 フロントエンド:Vue.js DB:Mysql ver8.0 Docker使用 最初にやろうとしたこと 新規入力フォーム userCreate.vue <template> <form @submit.prevent="submit"> <label for="name">名前</label> <input type="text" id="name" name="name" v-model="form.name" /> <label for="email">メールアドレス</label> <input type="text" id="email" name="email" v-model="form.email" /> <button name="test" type="submit">送信</button> </form> </template> <script> //中略 export default {      props: { user: Object, }, data() { return {              // 戻った時に予め入力していた値を受け取り、フォームに復元する form: this.$inertia.form({ name: this.user.name, email: this.user.email, }), }; }, methods: { submit() { this.form.post(this.route("userconfirm")); }, }, }; </script> 入力確認フォーム userConfirm.vue <template> <form @submit.prevent="submit"> <table> <tbody> <tr> <td>名前</td> <td>{{ user.name }}</td> </tr> <tr> <td>email</td> <td>{{ user.email }}</td> </tr> </tbody> </table> <button name="back" type="submit">戻る</button> <button name="send" type="submit">送信</button> </form> </template> <script> //中略 export default {      props: { user: Object, }, data() { return { form: this.$inertia.form({ name: this.user.name, email: this.user.email, }), }; }, methods: { submit() { // data()内のformデータをコントローラに送る this.form.post(this.route("usercomplate")); }, }, }; </script> laravel側コントローラー userController.php <?php namespace App\Http\Controllers; use Inertia\Inertia; use Illuminate\Http\Request; use App\Models\User; class UserController extends Controller { public function create() { return Inertia::render('Mypage/UserCreate'); } public function confirm(Request $request) { $user = $request->all(); return Inertia::render('Mypage/UserConfirm', ['user' => $user]); } public function complete(Request $request) { if($request->has('send')){ // ここに保存の処理 }elseif($request->has('back')){ // ここに新規入力フォームに戻る処理 $user = $request->all(); return Inertia::render('Mypage/UserConfirm', ['user' => $user]); } } } ボタンのnameを受け取りif文で条件分岐処理を行うのがLaravelのベストプラクティスかと思われますが、どうやってもname属性を受け渡しできずにLaravelでの処理は諦めました。。。。。 URLから受け取る処理をVueで実装する 新規入力フォーム userCreate.vue <template> <form @submit.prevent="submit"> <label for="name">名前</label> <input type="text" id="name" name="name" v-model="form.name" /> <label for="email">メールアドレス</label> <input type="text" id="email" name="email" v-model="form.email" /> <button type="submit">送信</button> </form> </template> <script> //中略 export default {      props: { user: Object, }, data() {         // ①ここを追加         let url = new URL(window.location.href); let params = url.searchParams; return {              // ②パラメータを受け取り、フォームに復元する form: this.$inertia.form({ name: params.get("name"), email: params.get("email"), }), }; }, methods: { submit() { this.form.post(this.route("userconfirm")); }, }, }; </script> 入力確認フォーム userConfirm.vue <template> <form @submit.prevent="submit"> <table> <tbody> <tr> <td>名前</td> <td>{{ user.name }}</td> </tr> <tr> <td>email</td> <td>{{ user.email }}</td> </tr> </tbody> </table> <div @click="backpage">戻る</div> <button type="submit">送信</button> </form> </template> <script> //中略 export default {      props: { user: Object, }, data() { return { form: this.$inertia.form({ last_name: this.user.name, email: this.user.email, }), }; }, methods: { submit() { this.form.post(this.route("usercomplete")); },         // 入力値が含まれるパラメーターを送る backpage() { this.form.get(this.route("usercreate")); }, }; </script> [戻る]をクリックしbackpageメソッド実行されると、下記のような入力データが入ったパラメータが送られるため①でURLを取得し、②で各項目をGetしてからdataに初期値を渡す。 http://localhost:8080//user/create?name=foobar&email=test@test.com 参考サイト URLに含まれるGETパラメータを取得する Laravelにて1つのFormで複数ボタンを実装する際の備忘録 正しい方法ではないかもしれませんがご参考まで
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【React Native】下から引っ張るタイプのモーダルを作る

最初からプルタブが露出しているタイプのモーダルが作りたかったので、ライブラリを使って作ってみました。 完成はこんな感じ   ライブラリを使用 Reanimated Bottom Sheetというライブラリを使用します。リファレンスはこちら ライブラリのインストール npm install reanimated-bottom-sheet react-native-gesture-handler react-native-reanimated リファレンスのサンプルコードから引っ張ってきたものを以下で改良していきます。 プルタブの作成 App.js import * as React from 'react'; import { StyleSheet, Text, View, Button } from 'react-native'; import BottomSheet from 'reanimated-bottom-sheet'; export default function App() { const renderContent = () => ( <View style={styles.modalWrapper}> <View style={styles.modalHeader}> <View style={styles.modalPulltab}/> </View> <View style={styles.modalContents}> <View style={{height: '100%'}}> <Text>modal contents here</Text> </View> </View> </View> ); const sheetRef = React.useRef(null); return ( <> <View style={{ flex: 1, backgroundColor: 'papayawhip', alignItems: 'center', justifyContent: 'center', }} > <Button title="Open Bottom Sheet" onPress={() => sheetRef.current.snapTo(0)} /> </View> <BottomSheet ref={sheetRef} snapPoints={['90%', '50%', 50]} initialSnap={2} // モーダルの初期位置を決める。snapPointsのインデックス番号を指定 borderRadius={20} renderContent={renderContent} /> </> ); } const styles = StyleSheet.create({ modalWrapper: { backgroundColor: 'rgba(221,221,221,0.3)', }, modalHeader: { backgroundColor: 'rgba(153,153,153,0.7)', height: 50, // 引っ張り出す部分を露出させたいので、初期位置のsnapPointsと同じ高さを指定する alignItems: 'center', justifyContent: 'center' }, modalPulltab: { backgroundColor: 'gray', height: 5, width: 40, }, modalContents: { backgroundColor: 'rgba(230,230,255,0.8)', paddingLeft: 16, paddingRight: 16, flexGrow: 1 } }) BottomSheetのsnapPointsというプロパティはモーダルが固定される位置を指定できます。また、initialSnapで初期状態でのsnapPointsを指定できるため、今回は50を指定してモーダルの上部50を露出させています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

EJSで何が入ってるかわからない文字列をJavascriptの変数に渡したい

はじめに 基本的なEJS構文についてはmiwashutaro0611様の記事を参考にしてください。 先に結論 次のように変換します。 <%# markdown は何が入っているかわからない文字列です。%> <% var json_ejs = JSON.stringify({markdown:encodeURIComponent(markdown)}) %> <script language="javascript" type="text/javascript"> const json = <%-json_ejs%>; var markdown = decodeURIComponent(json.markdown); </script> 以下 説明(試したこと) 普通の渡し方 普通にやると var text = <%= markdown %> ですが改行や"などに対応していません。 JSON.stringify と <%- %> json形式に変換した後、<%- %>でオブジェクトとして出力します。 <%# markdown は何が入っているかわからない文字列です。%> <% var json_ejs = JSON.stringify({markdown:markdown}) %> <script language="javascript" type="text/javascript"> const json = <%-json_ejs%>; var markdown = json.markdown; </script> ですが<%- %>で出力するため var content = 'console.log("?");</script>'; のようにscriptタグを含まれると?だけ残して強制終了します。 encodeURIComponentとdecodeURIComponent 一時的にencodeURIComponentを通してタグをエスケープします。 参考: https://shanabrian.com/web/javascript/escape-encode-uri-component.php 一番上のコード おわりに 間違いやもっと良い方法がありましたらコメントをお願いします。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

EJSで何が入ってるかわからない文字列をJavaScriptの変数に渡す

はじめに 基本的なEJS構文についてはmiwashutaro0611様の記事をご参考ください。 変換 次のように変換します。 <%# markdown は何が入っているかわからない文字列です。%> <% var json_ejs = JSON.stringify({markdown:encodeURIComponent(markdown)}) %> <script language="javascript" type="text/javascript"> const json = <%-json_ejs%>; var markdown = decodeURIComponent(json.markdown); </script> 以下 説明(試したこと) 普通の渡し方 普通にやると var text = <%= markdown %> ですが改行や"などに対応していません。 JSON.stringify と <%- %> json形式に変換した後、<%- %>でオブジェクトとして出力します。 <%# markdown は何が入っているかわからない文字列です。%> <% var json_ejs = JSON.stringify({markdown:markdown}) %> <script language="javascript" type="text/javascript"> const json = <%-json_ejs%>; var markdown = json.markdown; </script> ですが<%- %>で出力するため var markdown = 'console.log("?");</script>'; のようにscriptタグを含まれると?だけ残して強制終了します。 encodeURIComponentとdecodeURIComponent 一時的にencodeURIComponentを通してタグをエスケープします。 参考: https://shanabrian.com/web/javascript/escape-encode-uri-component.php 一番上のコード おわりに 間違いやもっと良い方法がありましたらコメントをお願いします。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

"/"を忘れたかっただけなのに

こんにちは。つんあーです。 みなさん、JS(あるいはTS)でファイルパスを操作する時、どうしていますか? (紛らわしいのですが、今回はブラウザが対象の話です。Node.jsはそんなに悩まないですからね) パスの末尾の/の扱いって、なかなか困りますよね。 こんなのとか、 const url = location.href; fetch(url + '/data.json').then(... // /は必要なんだっけ? // => 404 : http://localhost/page//data.json // 違う、そうじゃないの あるいはこんなのとか、 const url = location.hostname; fetch(url + 'data.json').then(... // /は必要なんだっけ? // => 404 : http://localhostdata.json // お゛お゛お゛お゛ん みたいなやつです。 私はよくこんなことをやってます。きっとあなたも1度はやったことあるはず。 1度と言わずに週1回くらいやってるはず。きっと。 ところでPythonのpathlibは、便利ですよね。 import pathlib path = pathlib.Path('Users/hoge') filepath = path / 'data.csv' print(str(filepath)) # => "Users/hoge/data.csv" JSでもこんな感じに/の有無とか無視したい! ってかNode.jsにはpathモジュールとかあるんだからイケるっしょ! と思って調べたのですが、 「js パス 解決」 とか 「js パス モジュール」 とかでググろうもんなら、 「Webpackでimportのパスを解決する方法」やら 「TypeScriptでWebpackのimportがエラーになる」とか 「Webpack5からはNode.jのspolyfillが標準装備されなくなったからpathモジュールを使うには云々...」 みたいなものばっかりでなかなか必要な情報(=ブラウザでさくっと使えるパス関係モジュール)にたどり着けませんでした。 (ってかimport関連詰まりやすいよねぇ) 結論(Webpack5以上) $ npm i -S path-browserify import path from 'path-browserify'; const url = path.join('hoge/', '/fuga'); console.log(url); // => 'hoge/fuga' なお、TypeScriptの場合は型定義ファイルに以下を追加する必要があります。 index.d.ts declare module 'path-browserify' { import path from 'path' export default path } おしまい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[JS]簡単なフォームバリデーション

この記事について JavaScriptの学習の記録。 学習している書籍の演習問題をヒントにBMI測定プログラムを作りました。 (掲載のコードは書籍に記載のコードではありません。) フォームのバリデーションや正規表現について記載しています。 学習書籍 いちばんやさしいJavaScriptの教本 第2版 ECMAScript 2017(ES8)対応 人気講師が教えるWebプログラミング入門 「いちばんやさしい教本」 作ったもの BMI測定 機能 ・全角入力してもjs側で半角にしてから計算する ・数値以外の文字が入力されていた場合は注意文言を表示 バリデーションチェック部分JS 文字列の判定は「JS 正規表現 数値」などで検索すると参考サイト出てきます。 記号付きや桁数の指定もできるようです。 // 全角→半角変換(数字のみ) function changeStrHalf(str) { return str.replace(/[0-9]/g, function(s) { return String.fromCharCode(s.charCodeAt(0) - 0xFEE0); }); } // 数値で入力されているかチェック function isNum(num){ var pattern = /^[0-9]+$/; return pattern.test(num); } 参考サイト: [JavaScript] 全角⇔半角の変換を行う(英数字、カタカナ) 正規表現を用いて JavaScript で数値チェックを行う方法
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Nest-js Headerデコレータのカスタム

tags: nest-js import { createParamDecorator, ExecutionContext,BadRequestException } from '@nestjs/common' import * as admin from 'firebase-admin' export const CustomHeaders = createParamDecorator( (data: string, ctx: ExecutionContext) => { const req = ctx.switchToHttp().getRequest() const value = req.headers[data].split('Bearer ')[1] return admin.auth().verifySessionCookie(value, true).then((decodedClaims) => { return decodedClaims.user_id }).catch(err => { console.log("err:",err) throw new BadRequestException('Error:Validation failed. Is the authorization appropriate?'); }) } ) @Post() async sample(@CustomHeaders('authorization') uid) { const result = await this.sampleService.sample(uid) return result } 解説 firebaseのセッションを受け取って(ヘッダーに添付),userIdを返すPipeを作ろうとしたが、既存のデコレータ(@Header())はPipeに互換性がなかったため、セッションを受け取ってuseridを返すデコレータを作った。 参照
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.js(axios)からDirectCloud-BOX APIでファイル一覧の取得とnode話

一連の記事の流れになります。 紙で申請を出す居酒屋店員のシフト希望と管理業務をちょっとスマートにする Node.js(axios)からDirectCloud-BOX APIでファイルのDL #linedc Node.js(axios)からDirectCloud-BOX APIでファイルアップロード 同じような書き方にはなるのですが、DirectCloudのAPIのドキュメントは注意といった記事です。 また、タイトルにあるnodeはNode.jsの話ではないです。 ファイル一覧取得を試す(GET/POST) /openapp/v1/files/index/{node}でファイルの一覧を照会できます APIリファレンス - ファイルの照会 以下のコードで実行します。前の記事で書いたように、{node}は1に指定しています。 このAPIはこの指定がないとうまく動きません。(後述) また、 このAPIはフォルダリストのAPI(/openapp/v1/folders/index/{node})と同様にドキュメント上だとGETと書いてますが、何故かPOSTも利用できます。 GETの書き方 'use strcit'; const axios = require('axios'); const BASE_URL = `https://api.directcloud.jp`; const URL = `${BASE_URL}/openapp/v1/files/index/1?lang=eng` const token = `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` const main = async () => { const res = await axios.get(URL,{ headers: { access_token: token, } }); console.log(res.data); }; main(); 何故か動くPOSTの書き方 'use strcit'; const axios = require('axios'); const FormData = require('form-data'); const BASE_URL = `https://api.directcloud.jp`; const URL = `${BASE_URL}/openapp/v1/files/index/1?lang=eng` const token = `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` const main = async () => { const bodyFormData = new FormData(); const res = await axios.post(URL, bodyFormData, { headers: { access_token: token, ...bodyFormData.getHeaders() } }); console.log(res.data); }; main(); 実行 どちらも正常に動きます。 実行するとこんな感じの内容になりました。 $ node ls.js { success: true, total: 5, lastpage: true, lists: [ { file_seq: '287745501', name: 'image.png', extension: 'png', size: '2184218', creator: 'xxxxxxxxxxxx', datetime: '2021-08-26 05:29:48', sizetext: '2.08MB' }, 省略 出来てみるとなんてことないですが、後述するように、動くまでに若干詰まりました。 node指定問題 ドキュメントのミスかAPIの設計ミスがありそうFolders API問題 このAPI扱う時にやっかいな nodeという概念があります。 フォルダの場所を示す値っぽいやつです。 FoldersのAPIは、 ドキュメントに必須と書いているのに、何故かnodeを指定しなくても使えてしまってた問題がありました。 フォルダリストの照会のドキュメントを見ると フォルダリストの紹介はnodeは必須と書いてますがなぜか無しでも動きます。 それだけだったら、まぁそういうこともあるかー優しさかなぁ?って感じだったのですが、同じようなAPIの先ほどサンプルを書いた ファイルリストの照会のほうは本当にnodeが必須っぽいです。 入り方が良くなかった可能性もありますが、どっちかに統一してほしいですね。。 "node指定必須と書いているが、node無しで動く"フォルダリストAPI ドキュメント準拠してないAPIなのか、APIにドキュメントが追いついてないのか。 完全に裏をかいてくるAPIです。 'use strcit'; const axios = require('axios'); const FormData = require('form-data'); const BASE_URL = `https://api.directcloud.jp`; const URL = `${BASE_URL}/openapp/v1/folders/index/?lang=eng` //node指定がない const token = `xxxxxxxxxxxxxxxxxxxxxxxx` const main = async () => { const bodyFormData = new FormData(); const res = await axios.post(URL, bodyFormData, { headers: { access_token: token, ...bodyFormData.getHeaders() } }); console.log(res.data); }; main(); 実行結果は正常 $ node folder.js { success: true, lists: [ { dir_seq: '38605003', node: '1', 省略 "node指定必須と書いているが、node無しで動く"と思いきや動いてくれないファイルリストAPI 完全に裏をかいてくるAPIです。と先ほど書きましたが、 さらに裏を書いてくるという。 裏の裏で表ですね。はい。 こちらはドキュメント通りにnode必須ですw 'use strcit'; const axios = require('axios'); const FormData = require('form-data'); const BASE_URL = `https://api.directcloud.jp`; const URL = `${BASE_URL}/openapp/v1/files/index/?lang=eng` //node指定がない const token = `xxxxxxxxxxxxxxxxxxxxxxxx` const main = async () => { const bodyFormData = new FormData(); const res = await axios.post(URL, bodyFormData, { headers: { access_token: token, ...bodyFormData.getHeaders() } }); console.log(res.data); }; main(); 実行結果はエラー $ node file.js { success: false, all: 'There is no required input value.', result_code: '00' } うーん、、統一するかドキュメントどうにかしてほしい。 まぁフォルダーの概念を考えるとnode指定がない場合には、ルート的な場所を表示するようにしてるってのは分かるんですけど、そのルート的な場所にはファイルは置けないってことでエラーにするようにしてるんですかね? まとめ ドキュメントが疑わしい場合もある まぁドキュメント更新とAPIの機能更新の整合性が取れないタイミングってあるのは分かるので、どこかのタイミングで改善してもらえると嬉しいなと感じました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby on rails】JavaScript 非同期通信のコメント機能でエラーメッセージを出す バリデーション

初めに 非同期通信でのコメント機能は実装ずみで、「401字以上のコメント」はNGというバリデーションは、 かけていたものの、エラーメッセージが出るようにしていませんでした・・・。 少し実装に苦労はしたものの、非同期通信への理解が深まったのでまとめていきます!! バリデーションをかけておく app/models/post_comment.rb class PostComment < ApplicationRecord default_scope -> { order(created_at: :desc) } # あるコメントに紐づくユーザーも記事も1 belongs_to :user belongs_to :post validates :comment, presence: true, length: { maximum: 400 } end 一応マイグレーションファイルも。 db/migrate/20210801122808_create_post_comments.rb class CreatePostComments < ActiveRecord::Migration[5.2] def change create_table :post_comments do |t| t.text :comment, null: false t.integer :user_id,null: false t.integer :post_id,null: false t.timestamps end end end app/views/post_comments/_comment.html.erb <%= form_with(model:[post, post_comment], remote: true) do |f| %> <%= f.text_area :comment, rows:'2',placeholder: "感想や疑問点をコメントで伝えましょう",required: true,class:"form-control" %> <%=f.submit "コメントする",class:"mt-2 btn btn-outline-secondary btn-block btn-sm"%> とりあえず、ここまでやると空欄での投稿に対しては以下のように出てきます。 ただ、401文字打ってもバリデーションには引っかかるものの何も出てこないという状況です。 コントローラーで保存できなかった場合の記述を書く! controllers/post_comments_controller.rb def create post = Post.find(params[:post_id]) @comment = current_user.post_comments.new(post_comment_params) @comment.post_id = post.id @post = Post.find(params[:post_id]) if @comment.save # ユーザーステータス無効で、投稿者とコメント者が等しいとき if current_user != @post.user && @post.user.is_valid == true @post.create_notification_by(current_user) end @post_comment = PostComment.new else render 'error' end end 通知機能も実装してるのでわかりにくくなっているかもしれません・・・・すみません。 コメントがバリデーションに引っかかってしまい保存されなかったら、 render 'error'というところがポイントです!!! さてrender先にいきます。 error.js.erb views/post_comments/error.js.erb $("#comments-error").html("<%= j(render 'layouts/errors', obj: @comment) %>"); コメントが401字でバリデーションに引っかかってるので、@commentの持ってる値としては ”false"になっています。 そしてここの記述としては、idがcomments-errorとなっているどこかに、 <%= j(render 'layouts/errors', obj: @comment) %>を渡すという意味 です。 ちなみに'layouts/errors'の中身はこんな感じになっています。 <% if obj.errors.any?%> <div class="text-center" style="color:red;"> <%=obj.errors.count %>件のエラーが発生しました。<br> <% obj.errors.full_messages.each do |message| %> <%= message %> <% end %> </div> <% end %> さてこのエラー文を差し込む、id = comments-errorの箇所を作りましょう。 エラーメッセージを出す app/views/post_comments/_comment.html.erb #ここです!!!! <div id ="comments-error"></div> <%= form_with(model:[post, post_comment], remote: true) do |f| %> <%= f.text_area :comment, rows:'2',placeholder: "感想や疑問点をコメントで伝えましょう",required: true,class:"form-control" %> <%=f.submit "コメントする",class:"mt-2 btn btn-outline-secondary btn-block btn-sm"%> <% end %> <div id ="comments-error"></div>という箇所を作成しました。 これでコメント欄のすぐ上にエラーメッセージができます。 まとめ:非同期通信のエラーメッセージの流れ コメントがバリデーションに引っ掛かり保存されない ↓ errorのjsファイルに飛ぶ ↓ jsファイルは指定されてるidに、html以下を差し込む ↓ 該当のidが記載されてる箇所で、html以下を受け取り表示する。 要するに、普段は <div id ="comments-error"></div>なってるところが、 <div id ="comments-error">render 'layouts/errors', obj: @comment</div> になってくれて、エラー文が出てくると! そもそも非同期通信とは.. 最後に書くことはでなはいですが、簡単にまとめていきます。 簡単にいうと「データを送信したら、もう画面を書き換えてもらう」ということです。 サーバーからの結果は待ちません。 通常であればちゃんと応答待ちますが、非同期通信は待ちません! メリットとしては待つ必要が無いから、早いということです。 サーバーが処理してる間に、操作ができます。 「あとやっといて〜」って感じでしょうか。 最後に 非同期通信がうまくいかないときって、大体スペルミスのような気がしますので、 スペルミスにご注意ください!!!!!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

動画IDからサムネイル、nocookieのurlを取得する

前回の記事で動画のIDを取得する方法をご紹介しました。 今回はこの動画IDでどの様なことができるかを実際に見ていきます。 index.js //ex) console.log(change_to_img_url("-mMmOKHzuWc"); //前回の get_video_id 関数を使用すると // console.log(change_to_img_url(get_video_id("https://www.youtube.com/watch?v=-mMmOKHzuWc"))); // といったことができる const change_to_img_url = (id) => { id = "http://img.youtube.com/vi/" + id + "/mqdefault.jpg"; return id; } const change_to_nocookie_url = (id) => { id = "https://www.youtube-nocookie.com/embed/" + id; return id; } なお、この関数を使用して作った僕のサイトがありますので、 よかったら見ていってください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

YouTubeの動画のIDを取得する

動画のIDを取得することで、 動画のurlに応じてサムネイルを変更したり、 YouTubeが提供しているyoutube.nocookie.comにて動画を見ることが容易になります。 (説明下手くそ・・・) 動画のIDの使い方は下の記事で紹介しています。 get_id.js //------/// // ex) console.log(get_video_id("https://www.youtube.com/watch?v=-mMmOKHzuWc&list=PLRBp0Fe2GpgmsW46rJyudVFlY6IYjFBIK&index=1")); //PC版サンプルurl //https://www.youtube.com/watch?v=-mMmOKHzuWc&list=PLRBp0Fe2GpgmsW46rJyudVFlY6IYjFBIK&index=1 //短縮url版サンプルurl //https://youtu.be/-mMmOKHzuWc?list=PLRBp0Fe2GpgmsW46rJyudVFlY6IYjFBIK //モバイル版サンプルurl //https://m.youtube.com/watch?v=-mMmOKHzuWc&list=PLRBp0Fe2GpgmsW46rJyudVFlY6IYjFBIK&index=1 //結果 //-mMmOKHzuWc const get_video_id = (url) => { //[0]・・・PC版urlの一部    [1]・・・短縮url    [2]・・・モバイル版url let pattern = ["www.youtube.com", "youtu.be", "m."]; //[0]・・・PC版再生リスト    [1]・・・短縮url版再生リスト    [2]・・・モバイル版再生リスト let list_values = ["&list", "?list", "&list"]; if (url.includes(pattern[0])) { url = src.replace("https://www.youtube.com/watch?v=", "").trim(); if (url.includes(list_values[0])) { url = url.split(list_values[0])[0]; } } if (url.includes(pattern[1])) { url = url.replace("https://youtu.be/", "").trim(); if (url.includes(list_values[1])) { url = url.split(list_values[1])[0]; } } if (url.includes(pattern[2])) { url = url.replace("https://m.youtube.com/watch?v=", "").trim(); if (url.includes(list_values[2])) { url = url.split(list_values[2])[0]; } } return url; }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】データ型とは

はじめに こんにちは。JavaScriptを学習中の佐藤です。 今日は、データ型についてアウトプットしていきます! データ型とは データの分類のことで、数値型や文字列型などのデータ型がある。 また、JavaScriptは「動的型付け言語」とも呼ばれ、変数に格納された値によってデータ型が定義される。実際のコードでみてみましょう。 JavaScript let width = 'wide'; //文字列型で定義 width = 1200; //数値型に変更される というように、後から代入された値によってデータ型が変更される。 また、変数の型はtypeof演算子を使って確認できる。 JavaScript let width = 'wide'; console.log(typeof width); //string(文字列型) width = 1200; console.log(typeof width); //number(数値型) 一方、「動的型付け言語」とは反対に、1度定義したデータ型を変更できない「静的型付け言語」というものも存在する。それぞれメリット・デメリットがあるので目的に合ったものを使用していきます。 データ型は、今回紹介したもの以外にも複数あるので、ご興味のある方は調べてみてくださいね。 最後に ここまでJavaScriptのデータ型について記事にしました。 静的型付け言語についても、学習を進めながら記事にしていきたいと思います!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LINE BotのNode.js SDKでプロフィール取得のメモ #linedc

userIdからプロフィールを取得したいってのをちょくちょく使うけど忘れるのでメモ SDKのサンプルコード client.getProfile()を利用します。 const profile = await client.getProfile(event.source.userId); console.log(profile); { userId: 'Ubxxxxxxxxxxxxxxxxxxxxxx', displayName: 'n0bisuke', pictureUrl: 'https://profile.line-scdn.net/ch/v2/p/xxxxxxxxxxxxxxxxxxxxxxxx/exist', statusMessage: 'こんにちは', language: 'ja' } コピペ用: リプライ 1時間でLINE BOTを作るハンズオンの記事から引用したコードです。 'use strict'; const express = require('express'); const line = require('@line/bot-sdk'); const PORT = process.env.PORT || 3000; const config = { channelSecret: '作成したBOTのチャンネルシークレット', channelAccessToken: '作成したBOTのチャンネルアクセストークン' }; const app = express(); app.get('/', (req, res) => res.send('Hello LINE BOT!(GET)')); //ブラウザ確認用(無くても問題ない) app.post('/webhook', line.middleware(config), (req, res) => { console.log(req.body.events); Promise .all(req.body.events.map(handleEvent)) .then((result) => res.json(result)); }); const client = new line.Client(config); async function handleEvent(event) { let msg = ``; if (event.type !== 'message' || event.message.type !== 'text') { return Promise.resolve(null); } //☆ココがプロフ取得箇所 const profile = await client.getProfile(event.source.userId); console.log(profile); msg = `${profile.displayName}さんこんにちは。 あなたのユーザーIDは${profile.userId}です。` return client.replyMessage(event.replyToken, { type: 'text', text: msg //実際に返信の言葉を入れる箇所 }); } app.listen(PORT); console.log(`Server running at ${PORT}`); コピペ用: プッシュメッセージ 友達全員にメッセージを送るシンプルな送信専用LINE BOTを作る【Node.js】 #linedcの記事から引用したコードです。 ただブロードキャストだと他の人にUserIdなど送るのはあまりよくないのでPushメッセージで自分だけに送信するようにしてます。 'use strict'; const line = require('@line/bot-sdk'); const config = { channelSecret: 'チャンネルシークレット', channelAccessToken: 'チャンネルアクセストークン' }; const client = new line.Client(config); let msg = ``; //☆ココがプロフ取得箇所 const userId = `Ubxxxxxxxxxxxxxxxxxxxxxxxx`; //管理画面や他APIなどから取得しておく const profile = await client.getProfile(userId); console.log(profile); msg = `${profile.displayName}さんこんにちは。あなたのユーザーIDは${profile.userId}です。` const main = async () => { const messages = [{ type: 'text', text: msg }]; try { const res = await client.pushMessage(userId, messages); console.log(res); } catch (error) { console.log(`エラー: ${error.statusMessage}`); console.log(error.originalError.response.data); } } main(); こんな感じに なります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

シンプルアナログ時計

本当にシンプルな時計が欲しくて作ってみました。 画像群は Google図形描画 で作成。 jsなのでローカルPC時刻に依存。 サンプル アナログ時計 ソース analogue.htm <html> <head> <title> <style> img{width: 860px;height: 860px;} #base {width: 860px;height: 860px;position: relative;} .clock { position: absolute; top: 0px; left: 0px; } .clock_hour_shd { position: absolute; top: 15px; left: 15px; } .clock_minute_shd { position: absolute; top: 30px; left: 30px; } .clock_second_shd { position: absolute; top: 45px; left: 45px; } </style> </head> <body style="background-color:#ffffff;"> <div id="base"> <img src="img/dial.png" class="clock"> <img src="img/hour_shd.png" id="hour_shd" class="clock_hour_shd"> <img src="img/minute_shd.png" id="minute_shd" class="clock_minute_shd"> <img src="img/second_shd.png" id="second_shd" class="clock_second_shd"> <img src="img/dial_1.png" class="clock"> <img src="img/hour.png" id="hour" class="clock"> <img src="img/minute.png" id="minute" class="clock"> <img src="img/second.png" id="second" class="clock"> </div> <script> var time; var hour = document.getElementById("hour"); var minute = document.getElementById("minute"); var second = document.getElementById("second"); var second_shd = document.getElementById("second_shd"); function main() { time = new Date(); hour.style.transform = "rotate("+(time.getHours()*30+time.getMinutes()*0.5)+"deg)"; minute.style.transform = "rotate("+(time.getMinutes()*6)+"deg)"; second.style.transform = "rotate("+(time.getSeconds()*6)+"deg)"; hour_shd.style.transform = "rotate("+(time.getHours()*30+time.getMinutes()*0.5)+"deg)"; minute_shd.style.transform = "rotate("+(time.getMinutes()*6)+"deg)"; second_shd.style.transform = "rotate("+(time.getSeconds()*6)+"deg)"; document.title = time.getHours()+":"+("00"+time.getMinutes()).substr(-2,2) + ":" + ("00" + time.getSeconds()).substr(-2,2) setTimeout(main, 1000-time.getMilliseconds()); } main(); </script> </body> </html>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【個人開発】地域の魅力を発信できるデジタルパンフレットサービス「Openパンフレット」を作った

はじめに デジタルパンフレット作成公開サービス Openパンフレット をリリースしました。 ​ 個人開発です。 ​ バックエンドはだいたいAWSです。 ​ Android未対応?​ ​バックエンドの技術者がフロントやらインフラやらアプリやら色んな所に手を出して作ったシステムになっています。 記事本文はZennで書いているのでこちらです。 宣伝 Android版の開発のためクラウドファンディング募集しています。 よかったら見て下さい。励みになります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Slackの古い投稿を自動定期削除する

やりたいこと Slackの特定のチャンネルに投稿された発言を定期的に削除したい 仕様とか 投稿から一定時間経過したSlackの発言を削除する 一度に削除できる投稿数には上限がある (https://api.slack.com/lang/ja-jp/rate-limit) サーバーレス (GASを利用) パブリック・プライベートチャンネル両方対象 ユーザー権限で実行するためSlackAppのTokenを発行するユーザー自身に投稿削除権限が必要 手順 Slack側の設定 1) Appの作成 Slack API のサイトに行き、右上の Your apps からApp管理ページを開く https://api.slack.com/ Create New App からAppの作成画面を開く From scratch を選択 App Name は適当に Pick a workspace to develop your app in: でAppを追加したいワークスペースを選択 2) 必要な権限の追加 Basic Information の Add features and functionality のプルダウンの中に Permissions というのがあるので選択する Scopes の項で User Token Scopes の Add an OAuth Scope を選択する ※Botではない方 プルダウンから必要な権限を選んで追加していく channels:history channels:read chat:write groups:history groups:read 権限が追加されたことを確認 3) Appのインストール 権限が追加できたら左カラムのメニューから Basic Information を選択して基本情報画面に戻ります Install your app のプルダウンを開き Install to Workspace を選択 確認画面にリダイレクトされるので 許可する を選択 Install your app の項目にチェックが付けばOK 4) Tokenの取得 Appのインストールが完了したら左カラムのメニューから OAuth & Permissions へ遷移する OAuth Tokens for Your Workspace という項目に先ほど作成した User Token Scopes の OAuth Token があるのでエディタなどにコピーして控えておく Google Spreadsheet側の設定 1) 自動削除したいチャンネル一覧を作成する Google Spreadsheetで新しいスプレッドシートを作成する https://docs.google.com/spreadsheets/ 自動削除をしたいチャンネルを記入する シート名はなんでもOK 2) スクリプトの作成 メニューから ツール > スクリプト エディタ を選択 スクリプトに適当なファイル名をつける 以下のコードを貼り付ける 1行目の token にはSlackの設定で作成した User OAuth Token をセットする cleaner.gs const token = 'xoxp-23********177-23********317-23********648-e6bd********e7********a********c'; const hours = 8; // 何時間遡るか const limit = 3; // 最大追加取得回数 const second = 0.8; // 何秒ごとに削除を実行するか /** * main */ function main() { const values = SpreadsheetApp.getActiveSheet().getDataRange().getValues(); const channelNames = []; for (const value of values) { channelNames.push(value[0]); } for (const channelName of channelNames) { Logger.log("[%s]",channelName) cleanChannel(token, channelName); } } /** * チャンネル内投稿削除 */ function cleanChannel(token, channelName) { Logger.log('*** cleanChannel start'); const channelId = getChannelId(token, channelName); Logger.log("channelId: #" + channelName + ' | ' + channelId) if (channelId.length === 0) { return; } const date = new Date(); const timestamp = Math.round(date.setHours(date.getHours()-hours) / 1000); // 設定した時間前 Logger.log("timestamp: " + timestamp + ' | ' + new Date(timestamp * 1000).toString()); let result; let count = 0; do { result = getChannelHistory(token, channelId, timestamp); count++; if (result.ok) { for (const message of result.messages.reverse()) { const deleteResult = deleteChat(token, channelId, message.ts); if (deleteResult.ok) { Logger.log("delete success"); } Utilities.sleep(second * 1000); // error429回避のため一定時間sleep } } } while (result.ok && result.has_more && count < limit) } /** * チャンネル名 -> チャンネルID取得 */ function getChannelId(token, channelName) { Logger.log('*** getChannelId start'); const result = getChannelList(token); if (result.ok) { for (const channel of result.channels) { if (channel.name === channelName) { return channel.id; } } } return ''; } /** * チャンネル一覧取得 */ function getChannelList(token) { Logger.log('*** getChannelList start'); const endpoint = 'https://slack.com/api/conversations.list'; const options = { method: 'post', payload: { token: token, types: 'public_channel,private_channel' } }; const response = UrlFetchApp.fetch(endpoint, options); Logger.log(JSON.parse(response)); return JSON.parse(response); } /** * チャンネル内投稿取得 */ function getChannelHistory(token, channelId, timestamp) { Logger.log('*** getChannelHistory start'); const endpoint = 'https://slack.com/api/conversations.history'; const options = { method: 'post', payload: { token: token, channel: channelId, latest: timestamp + '.00000', inclusive: true } }; const response = UrlFetchApp.fetch(endpoint, options); Logger.log(JSON.parse(response)); return JSON.parse(response); } /** * 投稿削除 */ function deleteChat(token, channelId, timestamp) { Logger.log('*** deleteChat target: ' + timestamp); const endpoint = 'https://slack.com/api/chat.delete'; const options = { method: 'post', payload: { token: token, channel: channelId, ts: timestamp } }; let response = UrlFetchApp.fetch(endpoint, options); Logger.log(JSON.parse(response)); // error429が出た場合は指定秒数sleepさせる while (!response.ok && response.status == 429) { const retryAfter = response.headers['retry-after']; Utilities.sleep(retryAfter * 1000); response = UrlFetchApp.fetch(endpoint, options); Logger.log(JSON.parse(response)); } return JSON.parse(response); } 3) トリガーの設定 左のメニューから トリガー を選択する 画面右下の + トリガーを追加 ボタンを押す 定期実行の設定を行う 参考にした情報 基本的なロジックは以下を参考にさせていただき、現在のAPI仕様に対応していなかったり一部動作しない箇所を修正しました。 https://qiita.com/hideo-works/items/21c2a6784b7f6caa4c06 https://tech.naturalmindo.com/notwork_slackapi/ 以上
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ページ上にDOMを大量に複製したいとき

自分用備忘録。 結論 ① 複製したいDOMをChromeのdevツール Elementタブで特定 ② 1でみつけた要素を右クリック→Copy > Copy Element ③ devツールのConsoleで以下スクリプト実行 console.js const newElm = document.createElement('p'); // 適当なタグでおk const target = document.getElementById('**任意のid**'); // 大量に複製する要素のブチ込み先 newElm.innerHTML = '**②でコピーしたやつ**'; // 1000件ブチ込む for(let i=0;i<1000;i++){target.appendChild(newElm.firstChild.cloneNode(true))} あとがき cloneNode(true)だと内包する要素までコピーしてくれる これに気づかなくて地味にハマった 特定のページだけゴリゴリに重いなーってことがあったので、検証用に作った。 もっといい感じのやり方があったら教えて下さい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

devツールで、ページ上にDOMを大量に複製したいとき

自分用備忘録。 結論 ① 複製したいDOMをChromeのdevツール Elementタブで特定 ② 1でみつけた要素を右クリック→Copy > Copy Element ③ devツールのConsoleで以下スクリプト実行 console.js const newElm = document.createElement('p'); // 適当なタグでおk const target = document.getElementById('**任意のid**'); // 大量に複製する要素のブチ込み先 newElm.innerHTML = '**②でコピーしたやつ**'; // 1000件ブチ込む for(let i=0;i<1000;i++){target.appendChild(newElm.firstChild.cloneNode(true))} あとがき cloneNode(true)だと内包する要素までコピーしてくれる これに気づかなくて地味にハマった 特定のページだけゴリゴリに重いなーってことがあったので、検証用に作った。 もっといい感じのやり方があったら教えて下さい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む