From 1191aab340707242c2ec879ca73073ea1d047b18 Mon Sep 17 00:00:00 2001 From: Danyil Shkoropad <31440971+anko20094@users.noreply.github.com> Date: Sun, 6 Nov 2022 14:34:09 +0200 Subject: [PATCH] Turbo Frames (#18) Turbo Frames in Rails. Dynamic adding, editing and deleting records. Some features of these frames. --- app/controllers/admin/users_controller.rb | 8 +-- app/controllers/questions_controller.rb | 74 +++++++++++++------- app/helpers/application_helper.rb | 4 ++ app/javascript/scripts/select.js | 59 ++++++++-------- app/views/layouts/application.html.erb | 40 +++++------ app/views/questions/_form.html.erb | 52 +++++++------- app/views/questions/_question.html.erb | 59 ++++++++-------- app/views/questions/create.turbo_stream.erb | 2 + app/views/questions/destroy.turbo_stream.erb | 2 + app/views/questions/index.html.erb | 18 +++-- app/views/questions/show.html.erb | 31 ++------ app/views/questions/update.turbo_stream.erb | 2 + app/views/shared/_flash.html.erb | 3 + app/views/tags/_tag.html.erb | 3 +- 14 files changed, 194 insertions(+), 163 deletions(-) create mode 100644 app/views/questions/create.turbo_stream.erb create mode 100644 app/views/questions/destroy.turbo_stream.erb create mode 100644 app/views/questions/update.turbo_stream.erb create mode 100644 app/views/shared/_flash.html.erb diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 915cb98..dae2c86 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -21,8 +21,6 @@ def index end end - def edit; end - def create if params[:archive].present? UserBulkImportJob.perform_later create_blob, current_user @@ -32,12 +30,14 @@ def create redirect_to admin_users_path end + def edit; end + def update if @user.update user_params flash[:success] = t '.success' redirect_to admin_users_path else - render :edit + render :edit, status: :unprocessable_entity end end @@ -71,4 +71,4 @@ def authorize_user! authorize(@user || User) end end -end +end \ No newline at end of file diff --git a/app/controllers/questions_controller.rb b/app/controllers/questions_controller.rb index 1f32397..793078c 100644 --- a/app/controllers/questions_controller.rb +++ b/app/controllers/questions_controller.rb @@ -7,45 +7,69 @@ class QuestionsController < ApplicationController before_action :authorize_question! after_action :verify_authorized - def index - @tags = Tag.where(id: params[:tag_ids]) if params[:tag_ids] - @pagy, @questions = pagy Question.all_by_tags(@tags) - @questions = @questions.decorate - end - def show load_question_answers end - def new - @question = Question.new - end - - def edit; end + def destroy + @question.destroy + respond_to do |format| + format.html do + flash[:success] = t('.success') + redirect_to questions_path, status: :see_other + end - def create - @question = current_user.questions.build question_params - if @question.save - flash[:success] = t('.success') - redirect_to questions_path - else - render :new, status: :unprocessable_entity + format.turbo_stream { flash.now[:success] = t('.success') } end end + def edit; end + def update if @question.update question_params - flash[:success] = t('.success') - redirect_to questions_path + respond_to do |format| + format.html do + flash[:success] = t('.success') + redirect_to questions_path + end + + format.turbo_stream do + @question = @question.decorate + flash.now[:success] = t('.success') + end + end else render :edit, status: :unprocessable_entity end end - def destroy - @question.destroy - flash[:success] = t('.success') - redirect_to questions_path, status: :see_other + def index + @tags = Tag.where(id: params[:tag_ids]) if params[:tag_ids] + @pagy, @questions = pagy Question.all_by_tags(@tags), link_extra: 'data-turbo-frame="pagination_pagy"' + @questions = @questions.decorate + end + + def new + @question = Question.new + end + + def create + @question = current_user.questions.build question_params + if @question.save + respond_to do |format| + format.html do + flash[:success] = t('.success') + redirect_to questions_path + end + + format.turbo_stream do + @question = @question.decorate + flash.now[:success] = t('.success') + end + end + else + render :new + end end private @@ -61,4 +85,4 @@ def set_question! def authorize_question! authorize(@question || Question) end -end +end \ No newline at end of file diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 50be8ce..5104889 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -3,6 +3,10 @@ module ApplicationHelper include Pagy::Frontend + def prepend_flash + turbo_stream.prepend 'flash', partial: 'shared/flash' + end + def pagination(obj) # rubocop:disable Rails/OutputSafety raw(pagy_bootstrap_nav(obj)) if obj.pages > 1 diff --git a/app/javascript/scripts/select.js b/app/javascript/scripts/select.js index e061a7a..b853f1e 100644 --- a/app/javascript/scripts/select.js +++ b/app/javascript/scripts/select.js @@ -12,41 +12,44 @@ document.addEventListener("turbo:before-cache", function() { const rerender = function() { const i18n = Translations[document.querySelector('body').dataset.lang] - document.querySelectorAll('.js-multiple-select').forEach((element) => { - let opts = { - plugins: { - 'remove_button': { - title: i18n['remove_button'] + document.querySelectorAll('select.js-multiple-select').forEach((element) => { + if(!element.classList.contains('tomselected')) { + let opts = { + plugins: { + 'remove_button': { + title: i18n['remove_button'] + }, + 'no_backspace_delete': {}, + 'restore_on_backspace': {} }, - 'no_backspace_delete': {}, - 'restore_on_backspace': {} - }, - valueField: 'id', - labelField: 'title', - searchField: 'title', - create: false, - load: function(query, callback) { - const url = element.dataset.ajaxUrl + '.json?term=' + encodeURIComponent(query) + valueField: 'id', + labelField: 'title', + searchField: 'title', + create: false, + load: function(query, callback) { + const url = element.dataset.ajaxUrl + '.json?term=' + encodeURIComponent(query) - fetch(url) - .then(response => response.json()) - .then (json => { - callback(json) - }).catch(() => { - callback() - }) - }, - render: { - no_results: function(_data, _escape){ - return '
' + i18n['no_results'] + '
'; + fetch(url) + .then(response => response.json()) + .then (json => { + callback(json) + }).catch(() => { + callback() + }) + }, + render: { + no_results: function(_data, _escape){ + return '
' + i18n['no_results'] + '
'; + } } } - } - const el = new TomSelect(element, opts) - selects.push(el) + const el = new TomSelect(element, opts) + selects.push(el) + } }) } document.addEventListener("turbo:load", rerender) +document.addEventListener("turbo:frame-render", rerender) document.addEventListener("turbo:render", rerender) \ No newline at end of file diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 8c2c384..b17168a 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -1,24 +1,24 @@ - - - <%= full_title(yield(:page_title)) %> - - <%= csrf_meta_tags %> - <%= csp_meta_tag %> - <%= stylesheet_link_tag "application", media: 'all' %> - <%= javascript_include_tag "application", "data-turbo-track": "reload", defer: true %> - + + + <%= full_title(yield(:page_title)) %> + + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + <%= stylesheet_link_tag "application", "data-turbo-track": "reload", media: 'all' %> + <%= javascript_include_tag "application", "data-turbo-track": "reload", defer: true %> + - - <%= yield :main_menu %> - -
- <% flash.each do |k, v| %> - <%= tag.div v, class: "alert alert-#{k}", role: 'alert' %> - <% end %> + +<%= yield :main_menu %> - <%= yield %> -
- - +
+
+ <%= render 'shared/flash' %> +
+ + <%= yield %> +
+ + \ No newline at end of file diff --git a/app/views/questions/_form.html.erb b/app/views/questions/_form.html.erb index e9cb21c..0039958 100644 --- a/app/views/questions/_form.html.erb +++ b/app/views/questions/_form.html.erb @@ -1,36 +1,38 @@ -<%= render 'shared/errors', object: question %> +<%= turbo_frame_tag question do %> + <%= form_with model: question do |f| %> + <%= render 'shared/errors', object: question %> -<%= form_with model: question do |f| %> -
-
- <%= f.label :title %> -
+
+
+ <%= f.label :title %> +
-
- <%= f.text_field :title, class: 'form-control' %> +
+ <%= f.text_field :title, class: 'form-control' %> +
-
-
-
- <%= f.label :body %> -
+
+
+ <%= f.label :body %> +
-
- <%= f.text_area :body, class: 'form-control' %> +
+ <%= f.text_area :body, class: 'form-control' %> +
-
-
-
- <%= f.label :tags %> -
+
+
+ <%= f.label :tags %> +
-
- <%= f.collection_select :tag_ids, question.tags, :id, :title, {}, multiple: true, - class: 'js-multiple-select js-ajax-select w-100', data: {'ajax-url': '/api/tags'} %> +
+ <%= f.collection_select :tag_ids, question.tags, :id, :title, {}, multiple: true, + class: 'js-multiple-select', data: {'ajax-url': '/api/tags'} %> +
-
- <%= f.submit t('global.button.submit'), class: 'btn btn-primary' %> + <%= f.submit t('global.button.submit'), class: 'btn btn-primary' %> + <% end %> <% end %> \ No newline at end of file diff --git a/app/views/questions/_question.html.erb b/app/views/questions/_question.html.erb index 982e18b..a6895a9 100644 --- a/app/views/questions/_question.html.erb +++ b/app/views/questions/_question.html.erb @@ -1,34 +1,35 @@ -
-
- <%= question.user.gravatar %> - <%= question.user.name_or_email %> -
+<%= turbo_frame_tag question do %> +
+
+ <%= question.user.gravatar %> + <%= question.user.name_or_email %> +
-
-

<%= link_to question.title, question_path(question) %>

+
+

<%= link_to question.title, question_path(question), + data: {turbo_frame: '_top'} %>

-
- <%= tag.time datetime: question.formatted_created_at do %> - <%= question.formatted_created_at %> - <% end %> - -
- <%= render question.tags %> -
+
+ <%= tag.time datetime: question.formatted_created_at do %> + <%= question.formatted_created_at %> + <% end %> -

- <%= truncate strip_tags(question.body), length: 150, omission: t('global.text.omission') %> -

-
+
+ <%= render question.tags %> +
- <%= link_to t('global.button.show'), question_path(question), class: 'btn btn-primary' %> +

+ <%= sanitize question.body %> +

+
- <% if policy(question).edit? %> - <%= link_to t('global.button.edit'), edit_question_path(question), class: 'btn btn-secondary' %> - <% end %> - <% if policy(question).destroy? %> - <%= link_to t('global.button.delete'), question_path(question), class: 'btn btn-danger', - data: {turbo_method: :delete, turbo_confirm: t('global.dialog.you_sure')} %> - <% end %> -
-
+ <% if policy(question).edit? %> + <%= link_to t('global.button.edit'), edit_question_path(question), class: 'btn btn-secondary' %> + <% end %> + <% if policy(question).destroy? %> + <%= link_to t('global.button.delete'), question_path(question), class: 'btn btn-danger', + data: {turbo_method: :delete, turbo_confirm: t('global.dialog.you_sure')} %> + <% end %> +
+ +<% end %> \ No newline at end of file diff --git a/app/views/questions/create.turbo_stream.erb b/app/views/questions/create.turbo_stream.erb new file mode 100644 index 0000000..0442f90 --- /dev/null +++ b/app/views/questions/create.turbo_stream.erb @@ -0,0 +1,2 @@ +<%= turbo_stream.prepend 'questions', render(@question) %> +<%= prepend_flash %> \ No newline at end of file diff --git a/app/views/questions/destroy.turbo_stream.erb b/app/views/questions/destroy.turbo_stream.erb new file mode 100644 index 0000000..50ab383 --- /dev/null +++ b/app/views/questions/destroy.turbo_stream.erb @@ -0,0 +1,2 @@ +<%= turbo_stream.remove @question %> +<%= prepend_flash %> \ No newline at end of file diff --git a/app/views/questions/index.html.erb b/app/views/questions/index.html.erb index b8e55e6..9b47ee4 100644 --- a/app/views/questions/index.html.erb +++ b/app/views/questions/index.html.erb @@ -18,8 +18,18 @@ <% if policy(:question).new? %> - <%= link_to t('.new'), new_question_path, class: 'btn btn-primary btn-lg mb-3' %> + <%= link_to t('.new'), new_question_path, + class: 'btn btn-primary btn-lg mb-3', + data: { turbo_frame: dom_id(Question.new) } %> + +
+ <%= turbo_frame_tag Question.new %> +
<% end %> -<%= pagination @pagy %> -<%= render @questions %> -<%= pagination @pagy %> +<%= turbo_frame_tag 'pagination_pagy' do %> + <%= pagination @pagy %> + <%= turbo_frame_tag 'questions' do %> + <%= render @questions %> + <% end %> + <%= pagination @pagy %> +<% end %> \ No newline at end of file diff --git a/app/views/questions/show.html.erb b/app/views/questions/show.html.erb index 5356932..06de063 100644 --- a/app/views/questions/show.html.erb +++ b/app/views/questions/show.html.erb @@ -1,36 +1,13 @@ <% provide :page_title, @question.title %> <% currently_at t('menu.questions') %> -

<%= @question.title %>

- - - -
- <%= sanitize @question.body %> -
- -
- <%= @question.user.gravatar %> - <%= @question.user.name_or_email %> -
+
+ <%= render @question %> +
<%= render 'comments/commentable', commentable: @question, comment: @comment, html_id: 'questionComments' %> -
-
- <% if policy(@question).edit? %> - <%= link_to t('global.button.edit'), edit_question_path(@question), class: 'btn btn-secondary' %> - <% end %> - <% if policy(@question).destroy? %> - <%= link_to t('global.button.delete'), question_path(@question), class: 'btn btn-danger', - data: {turbo_method: :delete, turbo_confirm: t('global.dialog.you_sure')} %> - <% end %> -
-
-

<%= t '.write_answer' %>

<%= render 'answers/form' %> @@ -40,4 +17,4 @@ <%= pagination @pagy %> <%= render partial: 'answers/answer', collection: @answers, as: :answer, locals: {question: @question} %> -<%= pagination @pagy %> +<%= pagination @pagy %> \ No newline at end of file diff --git a/app/views/questions/update.turbo_stream.erb b/app/views/questions/update.turbo_stream.erb new file mode 100644 index 0000000..8290705 --- /dev/null +++ b/app/views/questions/update.turbo_stream.erb @@ -0,0 +1,2 @@ +<%= turbo_stream.replace @question, render(@question) %> +<%= prepend_flash %> \ No newline at end of file diff --git a/app/views/shared/_flash.html.erb b/app/views/shared/_flash.html.erb new file mode 100644 index 0000000..f47ef8b --- /dev/null +++ b/app/views/shared/_flash.html.erb @@ -0,0 +1,3 @@ +<% flash.each do |k, v| %> + <%= tag.div v, class: "alert alert-#{k}", role: 'alert' %> +<% end %> \ No newline at end of file diff --git a/app/views/tags/_tag.html.erb b/app/views/tags/_tag.html.erb index 067239b..7153c21 100644 --- a/app/views/tags/_tag.html.erb +++ b/app/views/tags/_tag.html.erb @@ -1,4 +1,5 @@ <%= link_to questions_path(tag_ids: tag), - class: 'badge rounded-pill bg-light text-dark d-inline-block px-2 pt-1 pb-2 me-1' do %> + class: 'badge rounded-pill bg-light text-dark d-inline-block px-2 pt-1 pb-2 me-1', + data: {turbo_frame: '_top'} do %> <%= tag.title %> <% end %> \ No newline at end of file