-
Notifications
You must be signed in to change notification settings - Fork 71
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Q&AのAnswer部分を非Vue化 #8033
Q&AのAnswer部分を非Vue化 #8033
Conversation
5eda859
to
91648bb
Compare
変更点です。 |
@@ -31,7 +31,7 @@ def create | |||
if @answer.save | |||
Newspaper.publish(:answer_create, { answer: @answer }) | |||
Newspaper.publish(:answer_save, { answer: @answer }) | |||
render :create, status: :created | |||
render partial: 'questions/answer', locals: { question:, answer: @answer, user: current_user }, status: :created |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
answer.js
内のcreateAnswer
で新規回答を非同期で追加するため、直接要素を取得しています。
@@ -11,7 +11,7 @@ def create | |||
Newspaper.publish(:answer_save, { answer: @answer }) | |||
Newspaper.publish(:correct_answer_save, { answer: @answer }) | |||
ChatNotifier.message("質問:「#{@answer.question.title}」のベストアンサーが選ばれました。\r#{url_for(@answer.question)}") | |||
render json: @answer | |||
head :ok |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
以前は、Vue.jsのコード内で、answersという配列に回答を保持し、その中からベストアンサーにした@answer
と一致する回答のtypeをcorrect_answerに変更することで、ベストアンサーを判定していました。
今回、Vue.jsを使用しない実装に変更するため、@answer
を直接受け取る必要がなくなり、その処理を変更しました。
app/javascript/answer.js
Outdated
} | ||
}) | ||
|
||
export function initializeAnswer(answer) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
新規回答追加した際に受け取ったパーシャルにもJSを適用する必要があります。
そのため、new-answer.jsでも使えるようにexportしています。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@reckyy exportして外からも使うのであればその共通の部分だけ別ファイルにした方がいいと思います。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@komagata
おっしゃる通りです。
修正します!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
修正しました〜。
1f4ea05
answerDiv.innerHTML = html | ||
const newAnswerElement = answerDiv.firstElementChild | ||
answersList.appendChild(newAnswerElement) | ||
initializeAnswer(newAnswerElement) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
新規回答にJSを適用しています。
6a4cb49
to
7b3c62d
Compare
@motohiro-mm |
承知しました! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@reckyy
おつかれさまです!
気になった部分をコメントさせていただきました!
コメントの他に以下の点をご確認いただければ幸いです↓
-
途中のページで新規回答をすると、そのページの最下部(16個目)に表示されます。これは正しい挙動ですか?
リロードするとそのページでは追加した回答は表示されなくなり、最終ページの一番最後に回答が追加されています。
途中ページで回答を追加する場合、「コメントする」をクリックしたあと最終ページに飛んで追加した回答が正しい位置に表示される、というのが正しい挙動なのかと思ったのですが、いかがでしょうか? -
1に関連してそうなのですが、新規追加した回答に対してリアクションボタンをクリックすると反応がありません。
リロードするとリアクションボタンのクリックが反応するようになります。
ご確認をお願いいたします。 -
こちらのコメントに関連した質問なのですが、2ページ目以降の回答の日付から取得できるURLに
?page=2
などページ数が含まれています。これは、後々回答数が増減して表示ページが変わった際にはリンクとして機能するのでしょうか?
現在リンクがジャンプできていないので試せていないのですが、気になったので質問させていただきました🙏
よろしくおねがいいたします!
app/javascript/new-answer.js
Outdated
import CSRF from 'csrf' | ||
import TextareaInitializer from 'textarea-initializer' | ||
import MarkdownInitializer from 'markdown-initializer' | ||
import { toast } from 'toast_react' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
toast_react.js
は元々React化していくときに作成されたもののようです。
今回React化ではないためそちらからimportするのはあまり適していないのではないかと思いました。
同じtoastを表示するjsファイルにtoast.js
がありますが、そちらで実装は可能でしょうか?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
toast.js
はVueコンポーネント内でのみ使用できるものだと理解していましたが、もしかすると私の認識に誤りがあるかもしれません。背景について十分に理解できていない部分もあるので、どのように実装すれば良いかアドバイスをいただけると助かります。 🙇
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
そうなのですね!toast.js
はたしかにVue.jsで書かれていて、私が調べた限りでは外から使うのは煩雑そうな感じがしました(何かやり方があるのかもですが私にはわかりませんでした🙏)
ですのでできないのであればそのままで大丈夫です!
単純にtoast_react.js
にはなぜreact
という文言が入っているのかなと思い調べたらこのファイルはこちらのPRで作られていて、「React化じゃないしこのファイル以外で実装できる方が良いのかな、toast.js
というファイルがあるな」という気持ちでコメントしてました。
ちょっとずれるのですが、今回の実装は非Vue化にあたり、apiではない通常のコントローラを作成しそれに伴うviewを作成する、ではなく、コントローラはapiのものを使ったままですべてJSでCRUD処理を実装する形にしている、という認識であっていますか?
通常のコントローラに書くflashメッセージ(notice)はtoastで表示されるようにたしかなっているはずなので、その辺りが気になりました。ただ実装できるかは現時点で私はわかっておらず、できない理由があって現在の実装になっているのかと思ったので、仕様の認識の確認とそのようにしている理由を伺えればと思い質問させていただきました。
私の根本的な認識間違いや理解不足もあるかと思いますので、ご指摘いただければ幸いです。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@motohiro-mm
ご返信ありがとうございます!toastについての回答は以下に含めています。
コントローラはapiのものを使ったままですべてJSでCRUD処理を実装する形にしている、という認識であっていますか?
通常のコントローラに書くflashメッセージ(notice)はtoastで表示されるようにたしかなっているはずなので、その辺りが気になりました。
ユーザーがリロードなしで操作できる現在の仕様を維持するために、すべての処理を非同期で実行する形にしていますが、通常のコントローラを新たに作成して同期処理に移行することも可能です。具体的には、createとdeleteの処理を同期で行い、form_withを用いて移行できると考えています。
ただ、個人的には処理の一貫性とUXの観点から、すべての操作をなるべく統一したほうが良いと考えています。そのため、全操作を非同期処理で統一することが望ましいと考えています。(flashメッセージを用いる場合、画面のリロードを伴う実装になる可能性があるので、仕様が変わってしまうことを懸念しています)
form_with
を用いて非同期処理を実装することも可能ですが、コントローラを増やして処理を分散させるメリットはあまりないと考え、現状のapi/answers_controller.rb
を引き続き使用する形で実装しました。
以上が私の仕様の認識です。
もし分かりづらい所があれば、教えていただけますと幸いです!(よくコンテキストを省略してしまうので。。)
よろしくお願いいたします。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ユーザーがリロードなしで操作できる現在の仕様を維持するために、すべての処理を非同期で実行する形にしていますが
ただ、個人的には処理の一貫性とUXの観点から、すべての操作をなるべく統一したほうが良いと考えています。そのため、全操作を非同期処理で統一することが望ましいと考えています。
なるほど!腹落ちしました!
たしかに現行の仕様はリロードしてないですもんね。
仕様を維持するためであれば、コントローラ追加等せずにこのままJSで実装していただいて良いと思いました!
importファイルもこのままで良いと思います!
詳しくご説明くださりありがとうございました!🙏
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@komagata (cc: @motohiro-mm )
返信が遅れて申し訳ないです。
すみません、僕はまだなんでtoast_reactという名前がついたものを使っているのかの理由がわかりませんでした。
みた人がおかしいと思うのは当然だと思うので、元のファイルがreactと関係ないのであれば、ファイル名の修正をお願いしたいです。
toast_react.js
とtoast.js
の他にvanillaToast.js
というバニラJS向けのファイルを見つけました。ファイル名がtoastで始まっていなかったので、見落としていました。申し訳ありません。
現在の実装ではReactやVueに依存しないため、toast関数のimport先をvanillaToast.js
に変更する修正を行います。
以下のファイルを利用する形で進めます:
bootcamp/app/javascript/vanillaToast.js
Lines 1 to 13 in a621a30
import Swal from 'sweetalert2' | |
export function toast(title, status = 'success') { | |
Swal.fire({ | |
title: title, | |
toast: true, | |
position: 'top-end', | |
showConfirmButton: false, | |
timer: 3000, | |
timerProgressBar: true, | |
customClass: { popup: `is-${status}` } | |
}) | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -0,0 +1,84 @@ | |||
.thread-comment.answer data-question_id="#{question.id}" data-answer_id="#{answer.id}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
回答ごとのHTMLのid
がなくなっているので、日付からURLを取得しても該当する回答にジャンプすることができないようです。
確認をお願いいたします🙏
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
失念していました!ありがとうございます。
追加しました。
commentPlaceholder(v-for='num in placeholderCount', :key='num') | ||
#comments.thread-comments(v-else) | ||
header.thread-comments__header | ||
h2.thread-comments__title 回答・コメント |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
この「回答・コメント」の表示がなくなっています。
追加をお願いいたします。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ありがとうございます!
追加しました。
@machida (cc: @motohiro-mm ) 相談したいことPagerの追加を行わない方向で検討したいと考えています。 現在のPagerに関する課題
こちらは @motohiro-mm さんからのご指摘により、発覚しました。
例えば、回答が16件ある場合を想定します(1ページの上限は15件です)。
この問題に対して、現時点では適切な対応策が見つかっておりません。
こちらも同様に、 @motohiro-mm さんからのご指摘です。
これを正しい仕様とする場合、次のような動作が期待されます。
前者のシチュエーションでは、回答数をカウントし、15件なら2ページ目に遷移させることで対応可能です。しかし、これでは操作の一貫性が失われる可能性があり、ユーザーにとって良い体験ではないと感じています。 Pagerを追加しない理由pagerの追加は、こちらのバグを防ぐためと認識しています。 お手隙の際に、ご確認いただけますと幸いです。 |
a0e92d5
to
90751f6
Compare
}) | ||
}) | ||
|
||
export function initializeReaction(reaction) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@motohiro-mm
新規追加した回答でも、リアクションを有効化するためにexportしてnew-answer.js
で用いられるようにしています。
}) | ||
|
||
document.querySelectorAll('.js-reaction-dropdown').forEach((dropdown) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
なぜか元のコードでは reactions.each
の外で実行していましたが、まとめられる処理だったためループの内部に移動しました。
なるほど!ありがとうございます。 読み込みができなくなるエラーは、以前起こっていたのですが、 見返したところ、原因はJSで不要なリクエストが発生していたからっぽいです。なので、今回は相当な数がコメント(今回の場合はAnswer)がつかないとエラーは起きないと思うので、エラーが出るまでは対応はしない、という方針にしたいと思います。 なので、今回はページャーも数件ずつ読み込みをするのも無しでお願いします🙏 |
@machida |
@motohiro-mm お手隙の際に、再度ご確認いただけますと幸いです。 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
確認させていただきました!
コメントさせていただいたのに加え、気になったところを1つ以下に記載します。
id属性をつけて特定の場所にジャンプできるURLを入力して一度そこにジャンプした後、同じページで移動し再度リロードしても元いた場所(移動した後の場所)に戻ってしまいURLで指定している場所にはジャンプされませんでした。
動画は、「sotugyou」ユーザーの回答のURLを入力して一度ジャンプし、他の回答を読んだ後(「machida」ユーザーを目印にしました)、Chromeのリロードをクリックする、という動作を行っています。
- mainブランチ
URLで指定している「sotugyou」ユーザーの回答にジャンプしました。
2024-08-31.23.01.35.mov
- 現在のブランチ
URLで指定している「sotugyou」ユーザーの回答にジャンプせず、リロード前の画面(「machida」ユーザー付近)にジャンプしました。
2024-08-31.23.00.14.mov
なぜこうなるのか調べましたが、原因はわかりませんでした。
(turbo-linksとかcacheとかですかね…?)
元々の仕様と同じようにとのことでしたし、URLで指定した場所にジャンプしないのはなんだか不思議だなと思い気になりました。
ですがそもそも同じページ内のジャンプは仕様として気にする必要があるのかないのかは私には判断できなかったので、まずはご確認いただこうと思いました。
文章だとわかりづらいと思い動画もつけましたがそれでもわかりづらいと思います🙇♀️
ご確認をお願いいたします🙏
app/javascript/new-answer.js
Outdated
import CSRF from 'csrf' | ||
import TextareaInitializer from 'textarea-initializer' | ||
import MarkdownInitializer from 'markdown-initializer' | ||
import { toast } from 'toast_react' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ユーザーがリロードなしで操作できる現在の仕様を維持するために、すべての処理を非同期で実行する形にしていますが
ただ、個人的には処理の一貫性とUXの観点から、すべての操作をなるべく統一したほうが良いと考えています。そのため、全操作を非同期処理で統一することが望ましいと考えています。
なるほど!腹落ちしました!
たしかに現行の仕様はリロードしてないですもんね。
仕様を維持するためであれば、コントローラ追加等せずにこのままJSで実装していただいて良いと思いました!
importファイルもこのままで良いと思います!
詳しくご説明くださりありがとうございました!🙏
app/javascript/answer.js
Outdated
}) | ||
|
||
export function initializeAnswer(answer) { | ||
TextareaInitializer.initialize('.a-markdown-input__textarea') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Q&Aのページを開く動作をChromeのコンソールで見てみたのですが、例えばこちらのQ&Aですと以下の警告が3024回、毎回出ていました。(mainブランチでは出ていませんでした)
textarea-initializer.js:48 Tribute was already bound to TEXTAREA
調べると、同じtextareaに対して複数回Tribute
の初期化処理を行ってしまっているという警告のようです。
元々のanswer.vue:180
では
TextareaInitializer.initialize(`#js-comment-${this.answer.id}`)
となっていたので、ここ(今コメントしている)の行を以下のようにすると警告が消えました。
TextareaInitializer.initialize(`#js-comment-${answer.id}`)
正直詳細は分かりかねているのですが、一緒に出ていた[Violation] 'DOMContentLoaded' handler took 4554ms
といった警告も消えたので(時間がかかっているという警告のようです:参考)、こちらの方が良いのかなと思いました。
reckyyさんの環境でもコンソールで警告が出ているかご確認いただければと思います🙏
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ありがとうございます!
自分の環境でも同じ警告が出現していました。原因が分かりましたので、修正しました〜。
原因
今回、各answerに対して、answer.js
でinitializeAnswerを行っていますが、その中で @motohiro-mm さんがおっしゃっている以下の操作を行なっていました。
TextareaInitializer.initialize('.a-markdown-input__textarea')
このクラス指定が良くなかったみたいです。
このinitializeを各answerにそれぞれ実行しているつもりでしたが、実際は全てのアンサー + 新規回答フォームに対してanswerの数だけ実行されていました。
.a-markdown-input__textarea
は全てのanswer + 新規回答フォームにあるので。
motohiroさんのケースで言うと
- Q&Aにアクセス
- 回答が55個ある。
- つまりこのanswer.jsのinitializeAnswer()が55回実行される。
- ただ最初のanswerの時点で、全てのanswer + 新規回答フォームにTextareaInitializer.initialize()が実行される。
- それ以降のanswerのinitializeAnswer()でも、同様にTextareaInitializer.initialize()が実行される。(重複している)
つまり、
3024 = 56(回答の総数55 + 新規回答フォーム) × 54(重複の回数)
3024回重複した処理が発生してしまっていた、というわけでした。
なので、idに変更し各answerに対して一意にバインドするようにしました。
前回のコードから変更していなければ、このようなことは起こりませんでした。。
お手数をおかけして、申し訳ございませんでした。 🙇
ちなみに、Tributeは Tribute.jsです。
メンションに関する機能などを提供しているようです。あまり詳しくは見れていませんが。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
なるほど、そういうことだったのですね!
詳しくわかっていなかったので、解説していただいて何が起きていたのか理解できました!
ありがとうございます!
app/javascript/answer.js
Outdated
const createdAtElement = answer.querySelector('.thread-comment__created-at') | ||
if (createdAtElement && navigator.clipboard) { | ||
createdAtElement.addEventListener('click', () => { | ||
const answerURL = `${location.href}#answer_${answerId}` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
元々answer.vue:248
で以下のようになっていました。
const answerURL = location.href.split('#')[0] + '#answer_' + answerId
同じようにlocation.href
はsplit
で#
より前のみをURLにする方が良いと思いました!
現在のコードだと、一度日付URLをクリックしてそのURLページへジャンプ後、再度日付URLをクリックするとidが重複してしまいます。
2024-08-31.22.51.42.mov
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ご指摘ありがとうございます。
おっしゃる通りです。。修正しました!(修正というよりは、前回の形に戻しました。)
ありがとうございます!
こちらですが、answer.vueの仕様をまたまた引き継げていませんでした。。
なので、同様の処理を追加しました。 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
確認させていただきました!
ブラウザはリロードした際スクロールしている位置を記憶し、その部分を表示します。
そのため、アンカーリンクにページ内ジャンプさせたい場合処理を書く必要がありました。
スクロールしている位置を記憶するのが本来の挙動で、ジャンプさせるための処理があったのですね。気付けずすみません🙏
追加修正ありがとうございます!
動作確認も問題ありませんでしたので、私からはapproveさせていただきます!
勉強になりました!ありがとうございました!
app/javascript/answer.js
Outdated
}) | ||
|
||
export function initializeAnswer(answer) { | ||
TextareaInitializer.initialize('.a-markdown-input__textarea') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
なるほど、そういうことだったのですね!
詳しくわかっていなかったので、解説していただいて何が起きていたのか理解できました!
ありがとうございます!
@motohiro-mm @komagata |
@reckyy conflictの修正お願いします〜 |
updateAnswerCountを2つのファイルでほとんど同じ処理で書いてたので、フラグを作成し統一
Watchableの更新を忘れていた。また、アドバイザーの会社の表示が正しくできていなかった
その一環で、reaction.jsの処理をまとめexportした
自分の書いたコードでは、もしanswerURLへジャンプした後別の回答のURLをコピーすると、#answer_url#answer_urlのように正しくないURLが作成されてしまうため
8d9ab48
to
fe71953
Compare
@komagata 指摘されていた修正点 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
遅くなってすみません。
確認させて頂きました。OKです〜🙆♂️
Issue
概要
answer.vue
,answer.vue
、またこれらに関係があるファイルをまとめて非Vue化しました。question-answer.vue
(上記2つのファイルを内部で使用していたファイル)ai-answer.vue
(question-answer.vueの中で使用されていたファイル)comment-placeholder.vue
もquestion-answer.vue
の中で使用されていましたが、他の場所(comment.vue
)で使用されていたので、削除はしていません。変更確認方法
chore/answer-non-vue-conversion
をローカルに取り込むkomagata
でログインする。Screenshot
画面上の変更点はありません。