From c1096871a6a44b8583b776b27c5ad1fb956ab1aa Mon Sep 17 00:00:00 2001 From: f Date: Thu, 11 Jul 2024 15:56:11 -0300 Subject: [PATCH] =?UTF-8?q?feat:=20poder=20modificar=20art=C3=ADculos=20si?= =?UTF-8?q?n=20salir=20del=20formulario=20principal=20#16665?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/posts_controller.rb | 49 ++++++++++++++++++++++- app/views/posts/_form.haml | 8 ++++ app/views/posts/_new_related_post.haml | 23 +++++++++-- app/views/posts/modal.haml | 55 ++++++++++++++++++++++++++ app/views/posts/new_related_post.haml | 2 +- config/routes.rb | 1 + 6 files changed, 131 insertions(+), 7 deletions(-) create mode 100644 app/views/posts/modal.haml diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 3ea29c55..4aa2c8d0 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -69,6 +69,30 @@ class PostsController < ApplicationController render layout: false end + # Genera un modal completo + # + # @todo recibir el atributo anterior + # @param :uuid [String] UUID del post (opcional) + # @param :layout [String] El layout a cargar (opcional) + def modal + uuid = pluck_param(:uuid, optional: true) + locale + + # @todo hacer que si el uuid no existe se genera un post, para poder + # pasar el uuid sabiendolo + @post = + if uuid.present? + site.indexed_posts.find_by!(post_id: uuid).post + else + # @todo Usar la base de datos + site.posts(lang: locale).build(layout: pluck_param(:layout)) + end + + swap_modals + + render layout: false + end + def index authorize Post @@ -158,6 +182,16 @@ class PostsController < ApplicationController breadcrumb 'posts.edit', '' end + # Este endpoint se encarga de actualizar el post. Si el post se edita + # desde el formulario principal, re-renderizamos el formulario si hay + # errores o enviamos a otro lado al guardar. + # + # Si los datos llegaron por HTMX, hay que regenerar el formulario + # y reemplazarlo en su modal (?) o responder con su tarjeta para + # reemplazarla donde sea que esté. + # + # @todo la re-renderización del formulario no es necesaria si tenemos + # validación client-side. def update authorize post @@ -179,9 +213,20 @@ class PostsController < ApplicationController @value = post.title.value @uuid = post.uuid.value - @name = pluck_param(:name) - render render_path_from_attribute, layout: false + if (result_id = pluck_param(:result_id, optional: true)) + headers['HX-Retarget'] = "##{result_id}" + headers['HX-Reswap'] = 'outerHTML' + + @indexed_post = site.indexed_posts.find_by_post_id(post.uuid.value) + + render 'posts/new_related_post', layout: false + # @todo Confirmar que esta ruta no esté transitada + else + @name = pluck_param(:name) + + render render_path_from_attribute, layout: false + end else headers['HX-Retarget'] = "##{params.require(:form)}" headers['HX-Reswap'] = 'outerHTML' diff --git a/app/views/posts/_form.haml b/app/views/posts/_form.haml index b767a28d..1c9c623e 100644 --- a/app/views/posts/_form.haml +++ b/app/views/posts/_form.haml @@ -37,3 +37,11 @@ -# Formularios usados por los modales = yield(:post_form) + +-# + Acumulador de formularios dinámicos, se van cargando a medida que se + necesitan en lugar de recursivamente. + + Nunca se eliminan los modales una vez que se cargan para poder tener + historial de cambios. +%div{ data: { controller: 'htmx', action: 'htmx:getUrl@window->htmx#beforeend' } } diff --git a/app/views/posts/_new_related_post.haml b/app/views/posts/_new_related_post.haml index 9e342a6c..7dcb264f 100644 --- a/app/views/posts/_new_related_post.haml +++ b/app/views/posts/_new_related_post.haml @@ -1,16 +1,31 @@ :ruby - local_assigns[:modal_id] ||= 'generic_modal' - image = nil description = nil + card_id = random_id if post.post.attribute?(:image) && (image = post.post.image.static_file) description = post.post.image.value['description'] end -.col.mb-3.p-1{ data: { controller: 'modal' } } +.col.mb-3.p-1{ id: card_id, data: { controller: 'modal' } } = render('bootstrap/card', image: image, description: description, title: post.title, class: 'h-100') do - if post.post.attribute?(:description) %p.card-text= post.post.description.value - = render 'bootstrap/btn', content: t('.edit'), data: { action: 'modal#showAnother', 'modal-show-value': local_assigns[:modal_id] }, id: random_id + -# + Si pasamos el ID del modal, asumimos que hay uno que ya existe y + lo llamamos. Sino, tenemos que abrir el modal genérico y cargarle + el formulario vía "HTMX". + + - if local_assigns[:modal_id].present? + = render 'bootstrap/btn', content: t('.edit'), data: { action: 'modal#showAnother', 'modal-show-value': local_assigns[:modal_id] }, id: random_id + - else + - form_params = {} + - form_params[:layout] = post.layout + - form_params[:uuid] = post.post_id + - form_params[:modal_id] = form_params[:show] = modal_id = random_id + -# Asociar un modal con una tarjeta + - form_params[:result_id] = card_id + + -# @todo Poder indicar en qué elemento queremos asociar lo descargado + = render 'bootstrap/btn', content: t('.edit'), data: { controller: 'htmx', action: 'modal#showAnother htmx#getUrlOnce', 'modal-show-value': modal_id, 'htmx-get-url-param': site_posts_modal_path(post.site, **form_params) }, id: random_id diff --git a/app/views/posts/modal.haml b/app/views/posts/modal.haml new file mode 100644 index 00000000..2174595c --- /dev/null +++ b/app/views/posts/modal.haml @@ -0,0 +1,55 @@ +-# + + Genera un modal completo con el formulario del post y sus botones de + guardado. + + Se comporta como "HTMX". + + +:ruby + post = @post + site = post.site + locale = @locale + base = random_id + dir = t("locales.#{locale}.dir") + modal_id = pluck_param(:modal_id) + result_id = pluck_param(:result_id) + form_id = random_id + except = %i[date] + options = { + id: form_id, + multipart: true, + class: 'form post' + } + + if post.new? + url = options[:'hx-post'] = site_posts_path(site, locale: locale) + options[:class] += ' new' + else + url = options[:'hx-patch'] = site_post_path(site, post.id, locale: locale) + options[:method] = :patch + options[:class] += ' edit' + end + +%div{ id: modal_id, data: { controller: 'modal' }} + = render 'bootstrap/modal', id: modal_id, modal_content_attributes: { class: 'h-100' } do + - content_for :"#{modal_id}_body" do + = form_tag url, **options do + = hidden_field_tag 'base', base + = hidden_field_tag 'result_id', result_id + = hidden_field_tag 'modal_id', modal_id + = hidden_field_tag "#{base}[layout]", post.layout.name + + = render 'errors', post: post + = render 'posts/attributes', site: site, post: post, dir: dir, base: base, locale: locale, except: except + -# @todo Volver obligatorios? + - except.each do |attr| + %input{ type: 'hidden', name: "#{base}[#{attr}]", value: pluck_param(attr, optional: true) } + + - content_for :"#{modal_id}_footer" do + -# = render 'posts/validation', site: site, invalid: { id: invalid_id }, submitting: { id: submitting_id } + -# = render 'bootstrap/alert', class: 'm-0 d-none fade', id: saved_id, data: { controller: 'notification', action: 'notification:show@window->notification#show', 'notification-hide-class': 'hide', 'notification-show-class': 'show' } do + = t('.saved') + = render 'bootstrap/btn', form: form_id, content: t('.save'), type: 'submit', class: 'm-0 mt-1 mr-1' + = render 'bootstrap/btn', content: t('.close'), action: 'modal#hide', class: 'm-0 mt-1 mr-1' + = yield(:post_form) diff --git a/app/views/posts/new_related_post.haml b/app/views/posts/new_related_post.haml index b0623039..11fdf3af 100644 --- a/app/views/posts/new_related_post.haml +++ b/app/views/posts/new_related_post.haml @@ -1 +1 @@ -= render 'posts/new_related_post', post: @indexed_post += render 'posts/new_related_post', post: @indexed_post, modal_id: pluck_param(:modal_id, optional: true) diff --git a/config/routes.rb b/config/routes.rb index 711e3f24..110641e2 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -105,6 +105,7 @@ Rails.application.routes.draw do get :'posts/new_related_post', to: 'posts#new_related_post' get :'posts/new_has_one', to: 'posts#new_has_one' get :'posts/form', to: 'posts#form' + get :'posts/modal', to: 'posts#modal' resources :posts do get 'p/:page', action: :index, on: :collection