无需额外Schema渲染多记录表单:多Paper记录同时编辑技术问询
解决Ecto Embedded Schema多Paper记录编辑表单的常见问题
嘿,我来帮你搞定用Ecto Embedded Schema做多条Paper记录同时编辑的表单问题!结合你给出的代码,我整理了几个最常遇到的坑和对应的解决方案:
问题1:没法动态添加新的Paper表单字段
默认的inputs_for只会渲染已经存在的Paper记录,没法直接添加新条目,得手动实现动态添加的逻辑:
后端小调整
先给你的EmbSchema加个初始化空Paper的函数,同时确保changeset能处理空条目:
defmodule EmbSchema do embedded_schema do has_many :papers, Paper end def changeset(emb_schema, attrs) do emb_schema |> cast(attrs, []) # 加上empty_values: []确保空条目能被处理,on_replace: :delete是为了后续删除功能准备的 |> cast_assoc(:papers, with: &Paper.classify_changeset/2, empty_values: [], on_replace: :delete) end # 初始化时可以带指定数量的空Paper,方便页面加载时显示一个空表单 def new_with_empty_papers(amount \\ 1) do %__MODULE__{} |> put_assoc(:papers, Enum.map(1..amount, fn _ -> %Paper{} end)) end end
前端动态添加逻辑
在模板里加个“添加新论文”按钮,点击时克隆现有表单并修改字段的索引(Ecto靠索引区分不同条目):
<%= form_for @changeset, @action, fn f -> %> <%= inputs_for f, :papers, fn p -> %> <div class="paper-form"> <%= text_input p, :title, placeholder: "论文标题" %> <%= text_input p, :author, placeholder: "作者" %> <button type="button" class="delete-paper">删除</button> </div> <% end %> <button type="button" id="add-paper">添加新论文</button> <%= submit "保存所有记录" %> <% end %> <script> // 添加新论文逻辑 document.getElementById('add-paper').addEventListener('click', () => { const paperForms = document.querySelectorAll('.paper-form'); const lastForm = paperForms[paperForms.length - 1]; const newForm = lastForm.cloneNode(true); // 提取最后一个表单的索引,生成新索引 const lastIndex = parseInt(lastForm.querySelector('input').name.match(/papers\[(\d+)\]/)[1]); const newIndex = lastIndex + 1; // 更新新表单所有字段的name和id,同时清空输入值 newForm.querySelectorAll('input, select, textarea').forEach(input => { input.name = input.name.replace(/papers\[(\d+)\]/, `papers[${newIndex}]`); input.id = input.id.replace(/papers_(\d+)_/, `papers_${newIndex}_`); input.value = ''; }); lastForm.parentNode.appendChild(newForm); }); // 删除论文逻辑(先隐藏,提交时标记删除) document.addEventListener('click', (e) => { if (e.target.classList.contains('delete-paper')) { const form = e.target.closest('.paper-form'); // 如果要彻底删除,直接remove;如果要标记删除(需要后端配合),加个_delete隐藏字段 form.style.display = 'none'; // 可选:添加隐藏的_delete字段,后端会处理删除 const deleteInput = document.createElement('input'); deleteInput.type = 'hidden'; deleteInput.name = form.querySelector('input').name.replace(/\[title\]$/, '[_delete]'); deleteInput.value = 'true'; form.appendChild(deleteInput); } }); </script>
问题2:提交后部分Paper记录没保存
如果遇到提交后只有部分记录生效,先检查这两点:
- 确保
Paper的changeset没有漏掉字段:比如你的Paper.classify_changeset要把需要编辑的字段(比如title、author)都放到cast里:
defmodule Paper do use Ecto.Schema import Ecto.Changeset schema "papers" do field :title, :string field :author, :string # 其他字段... end def classify_changeset(paper, attrs) do paper |> cast(attrs, [:title, :author]) # 必须包含所有要编辑的字段 |> validate_required([:title]) # 根据业务需求加验证 end end
- 检查前端提交的参数结构:确保所有Paper条目都在
papers数组里,比如提交的参数应该是类似%{"emb_schema" => %{"papers" => [%{"title" => "Elixir入门"}, %{"title" => "Ecto实战"}]}}这样的结构。
问题3:删除Paper记录不生效
要让删除功能正常工作,需要后端和前端配合:
后端配置
在EmbSchema的cast_assoc里加上on_replace: :delete(前面已经加过了),这样Ecto会处理标记为删除的条目。
前端完善删除逻辑
在每个Paper表单里添加隐藏的_delete字段,点击删除按钮时设置其值为true,这样提交时后端就会知道要删除这条记录:
<%= inputs_for f, :papers, fn p -> %> <div class="paper-form"> <%= hidden_input p, :_delete, class: "delete-flag" %> <%= text_input p, :title %> <%= text_input p, :author %> <button type="button" class="delete-paper">删除</button> </div> <% end %> <script> document.addEventListener('click', (e) => { if (e.target.classList.contains('delete-paper')) { const form = e.target.closest('.paper-form'); form.querySelector('.delete-flag').value = 'true'; form.style.display = 'none'; // 隐藏表单,用户看不到但参数会提交 } }); </script>
内容的提问来源于stack exchange,提问作者Jakub Markos
相关产品推荐
相关产品推荐

