From 96aebb13465f2cdc0efff9eed5c1f044fc3dec3f Mon Sep 17 00:00:00 2001 From: f Date: Mon, 3 Jun 2024 17:52:53 -0300 Subject: [PATCH] feat: new_has_one --- app/controllers/posts_controller.rb | 27 ++++++---- .../controllers/modal_controller.js | 14 +++++ app/models/metadata_new_has_one.rb | 6 +++ app/views/posts/_htmx_form.haml | 4 +- app/views/posts/_new_has_one.haml | 2 + .../posts/attribute_ro/_new_has_one.haml | 6 +++ app/views/posts/attributes/_new_has_one.haml | 53 +++++++++++++++++++ app/views/posts/new_has_one.haml | 1 + app/views/posts/new_has_one_value.haml | 1 + config/routes.rb | 1 + 10 files changed, 104 insertions(+), 11 deletions(-) create mode 100644 app/models/metadata_new_has_one.rb create mode 100644 app/views/posts/_new_has_one.haml create mode 100644 app/views/posts/attribute_ro/_new_has_one.haml create mode 100644 app/views/posts/attributes/_new_has_one.haml create mode 100644 app/views/posts/new_has_one.haml create mode 100644 app/views/posts/new_has_one_value.haml diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 6d199cd7..282a784a 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -40,10 +40,18 @@ class PostsController < ApplicationController render layout: false end - # El formulario de un Post, si pasamos el uuid, estamos editando, sino + def new_has_one + @uuid = params.require(:value).strip + + @indexed_post = site.indexed_posts.find_by!(post_id: @uuid) + + render layout: false + end + + # El formulario de un Post, si pasamos el UUID, estamos editando, sino # estamos creando. def form - uuid = params.permit(:uuid).try(:[], :uuid) + uuid = params.permit(:uuid).try(:[], :uuid).presence locale @post = @@ -111,7 +119,7 @@ class PostsController < ApplicationController params: params) @post = service.create - if @post.persisted? + if post.persisted? site.touch forget_content end @@ -119,11 +127,11 @@ class PostsController < ApplicationController # @todo Enviar la creación a otro endpoint para evitar tantas # condiciones. if htmx? - if @post.persisted? + if post.persisted? swap_modals - @value = @post.title.value - @uuid = @post.uuid.value + @value = post.title.value + @uuid = post.uuid.value @name = params.require(:name) render render_path_from_attribute, layout: false @@ -133,8 +141,8 @@ class PostsController < ApplicationController render 'posts/form', layout: false, post: post, site: site, **params.permit(:form, :base, :dir, :locale) end - elsif @post.persisted? - redirect_to site_post_path(@site, @post) + elsif post.persisted? + redirect_to site_post_path(site, post) else render 'posts/new' end @@ -235,7 +243,7 @@ class PostsController < ApplicationController # @param triggers [Hash] Otros disparadores def swap_modals(triggers = {}) params.permit(:show, :hide).each_pair do |key, value| - triggers["modal:#{key}"] = { id: value } + triggers["modal:#{key}"] = { id: value } if value.present? end headers['HX-Trigger'] = triggers.to_json if triggers.present? @@ -247,6 +255,7 @@ class PostsController < ApplicationController when 'new_has_many' then 'posts/new_has_many_value' when 'new_belongs_to' then 'posts/new_belongs_to_value' when 'new_has_and_belongs_to_many' then 'posts/new_has_many_value' + when 'new_has_one' then 'posts/new_has_one_value' else 'nothing' end end diff --git a/app/javascript/controllers/modal_controller.js b/app/javascript/controllers/modal_controller.js index 20154f79..0f8deeca 100644 --- a/app/javascript/controllers/modal_controller.js +++ b/app/javascript/controllers/modal_controller.js @@ -18,9 +18,23 @@ export default class extends Controller { window.removeEventListener("modal:hide", this.hideEvent); } + /* + * Abrir otro modal, enviando el ID a toda la ventana. + */ + showAnother(event = undefined) { + event?.preventDefault(); + + if (!event.target?.dataset?.modalShowValue) return; + + window.dispatchEvent(new CustomEvent("modal:show", { detail: { id: event.target.dataset.modalShowValue } })); + } + /* * Podemos enviar la orden de apertura como un click o como un * CustomEvent incluyendo el id del modal como detail. + * + * El elemento clicleable puede tener un valor que se refiera a otro + * modal también. */ show(event = undefined) { event?.preventDefault(); diff --git a/app/models/metadata_new_has_one.rb b/app/models/metadata_new_has_one.rb new file mode 100644 index 00000000..642273e3 --- /dev/null +++ b/app/models/metadata_new_has_one.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +# Nueva interfaz para relaciones 1:1 +class MetadataNewHasOne < MetadataHasOne + include Metadata::UnusedValuesConcern +end diff --git a/app/views/posts/_htmx_form.haml b/app/views/posts/_htmx_form.haml index 707b9c51..f4f4a845 100644 --- a/app/views/posts/_htmx_form.haml +++ b/app/views/posts/_htmx_form.haml @@ -61,8 +61,8 @@ = errors.first -# Parámetros para HTMX - %input{ type: 'hidden', name: 'hide', value: params.require((post.errors.empty? ? :show : :hide)) } - %input{ type: 'hidden', name: 'show', value: params.require((post.errors.empty? ? :hide : :show)) } + %input{ type: 'hidden', name: 'hide', value: params.permit((post.errors.empty? ? :show : :hide)).try(:values).try(:first) } + %input{ type: 'hidden', name: 'show', value: params.permit((post.errors.empty? ? :hide : :show)).try(:values).try(:first) } %input{ type: 'hidden', name: 'name', value: params.require(:name) } %input{ type: 'hidden', name: 'base', value: params.require(:base) } %input{ type: 'hidden', name: 'form', value: options[:id] } diff --git a/app/views/posts/_new_has_one.haml b/app/views/posts/_new_has_one.haml new file mode 100644 index 00000000..54a370cd --- /dev/null +++ b/app/views/posts/_new_has_one.haml @@ -0,0 +1,2 @@ += render 'posts/new_related_post', post: post +%input{ type: 'hidden', name: name, value: value } diff --git a/app/views/posts/attribute_ro/_new_has_one.haml b/app/views/posts/attribute_ro/_new_has_one.haml new file mode 100644 index 00000000..425e659e --- /dev/null +++ b/app/views/posts/attribute_ro/_new_has_one.haml @@ -0,0 +1,6 @@ +%tr{ id: attribute } + %th= post_label_t(attribute, post: post) + %td{ dir: dir, lang: locale } + - p = metadata.has_one + - if p + = link_to p.title.value, site_post_path(site, p.id) diff --git a/app/views/posts/attributes/_new_has_one.haml b/app/views/posts/attributes/_new_has_one.haml new file mode 100644 index 00000000..5a614990 --- /dev/null +++ b/app/views/posts/attributes/_new_has_one.haml @@ -0,0 +1,53 @@ +-# + Genera un listado de radios entre los que se puede elegir solo uno para + guardar. Podemos elegir entre los artículos ya cargados o agregar uno + nuevo. + + Al agregar uno nuevo, se abre un segundo modal que carga el formulario + correspondiente vía HTMX. El formulario tiene que cargarse por fuera + del formulario principal porque no se pueden anidar. + +:ruby + id = random_id + name = "#{base}[#{attribute}]" + target_id = random_id + form_id = random_id + modal_id = random_id + post_id = random_id + post_form_id = random_id + post_modal_id = random_id + post_form_loaded_id = random_id + value_list_id = random_id + layout = metadata.filter[:layout] + +%div{ data: { controller: 'modal' }} + .form-group + = hidden_field_tag name, '' + .d-flex.align-items-center.justify-content-between + = label_tag id, post_label_t(attribute, post: post), class: 'h3' + = render 'bootstrap/btn', content: t('.edit'), action: 'modal#showAnother', data: { 'modal-show-value': modal_id } + + -# Aquí se reemplaza por la tarjeta y el UUID luego de guardar + .row.row-cols-1.no-gutters.placeholder-glow{ id: target_id } + -# @todo issue-7537 + - if !metadata.empty? && (indexed_post = site.indexed_posts.find_by(post_id: metadata.value)) + = render 'posts/new_has_one', post: indexed_post, name: name, value: metadata.value + +-# + El modal se genera por fuera del formulario, para poder enviar los + datos y recibir su UUID en respuesta. +- content_for :post_form do + %div{ id: modal_id, data: { controller: 'modal' }} + - if layout.is_a?(String) + = render 'bootstrap/modal', id: id, modal_content_attributes: { class: 'h-100' } do + - content_for :"#{id}_body" do + -# @todo ocultar el modal después de guardar + .placeholder-glow{ 'hx-get': site_posts_form_path(site, layout: layout, base: id, name: name, form: form_id, swap: 'innerHTML', target: target_id, attribute: 'new_has_one', hide: modal_id, uuid: metadata.value), 'hx-trigger': 'load' } + %span.placeholder.w-100.h-100 + + - content_for :"#{id}_footer" do + = render 'bootstrap/btn', form: form_id, content: t('.save'), type: 'submit', class: 'm-0 mt-1 mr-1' + = render 'bootstrap/btn', content: t('.cancel'), action: 'modal#hide', class: 'm-0 mt-1 mr-1' + + - else + Nada diff --git a/app/views/posts/new_has_one.haml b/app/views/posts/new_has_one.haml new file mode 100644 index 00000000..e32f191b --- /dev/null +++ b/app/views/posts/new_has_one.haml @@ -0,0 +1 @@ += render 'posts/new_has_one', post: @indexed_post, name: params.require(:name), value: @uuid diff --git a/app/views/posts/new_has_one_value.haml b/app/views/posts/new_has_one_value.haml new file mode 100644 index 00000000..9f2b660a --- /dev/null +++ b/app/views/posts/new_has_one_value.haml @@ -0,0 +1 @@ += render 'posts/new_has_one', post: @post.to_index, name: params.require(:name), value: @uuid diff --git a/config/routes.rb b/config/routes.rb index 45410782..711e3f24 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -103,6 +103,7 @@ Rails.application.routes.draw do get :'posts/new_array', to: 'posts#new_array' get :'posts/new_array_value', to: 'posts#new_array_value' 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' resources :posts do