Skip to content

Instantly share code, notes, and snippets.

@tomasc
Last active April 29, 2018 15:33
Show Gist options
  • Save tomasc/50e876ba53774339dddac56c144dc2fa to your computer and use it in GitHub Desktop.
Save tomasc/50e876ba53774339dddac56c144dc2fa to your computer and use it in GitHub Desktop.
nested_fields
export default class IsSortable extends Modulor.Plugin
@defaults =
debug: false
name: 'Modulor__NestedFields__IsSortable'
@selector: "bems(modulor_nested_fields, is_sortable)"
on_init: ->
return if @sortable
Sortable = await `import('sortablejs' /* webpackChunkName: "sortablejs" */)`
@sortable = new Sortable(
@element,
animation: 150,
draggable: "bems(modulor_nested_fields, item)",
ghostClass: "bem(modulor_nested_fields, item, ghost)",
handle: "bems(modulor_nested_fields, item, handle)",
onAdd: (e) => @update_item_positions(),
onUpdate: (e) => @update_item_positions(),
onRemove: (e) => @update_item_positions(),
)
@$element.on "update_item_positions.#{@options.name}", (e) =>
e.stopPropagation()
@update_item_positions()
@update_item_positions()
on_destroy: ->
@sortable.destroy() if @sortable
get_items: -> @$element.find("bems(modulor_nested_fields, item)")
update_item_positions: ->
@get_items().each (i, el) =>
$(el).find('input[name*="position"]').val(i+1)
IsSortable.register()
# make sure positions are updated everytime items are added or removed
Modulor.MutationObserver.register(
"bems(modulor_nested_fields, item)",
((el) => $(el).trigger('update_item_positions')),
(=> $("bems(modulor_nested_fields, is_sortable)").trigger('update_item_positions'))
)
# FORM
# = form.nested_fields_for :locations
# = form.nested_fields_for :locations, filtered_locations
# = form.nested_fields_for :locations, nil, sortable: true
#
# FIELDS
# partial at `locations/fields`
module ActionView
module Helpers
class FormBuilder
def nested_fields_for(record_name, record_object = nil, options = {}, &block)
Modulor::NestedFieldsBuilder.new(self, @template, record_name, record_object, options).nested_fields_for(&block)
end
end
end
end
module Modulor
class NestedFieldsBuilder < Struct.new(:builder, :template, :record_name, :record_object, :options)
CHILD_INDEX_STRING = '__INDEX_PLACEHOLDER__'.freeze
delegate :object,
:object_name,
:simple_fields_for,
to: :builder
delegate :content_tag,
:hidden_field_tag,
:link_to,
:render,
to: :template
def initialize(builder, template, record_name, record_object = nil, options = {})
super(builder, template, record_name, record_object, options)
end
def nested_fields_for
dom_class = Bem.bem([:modulor_nested_fields, (:is_sortable if is_sortable?)])
content_tag(:div, class: dom_class) do
[
nested_fields_title,
simple_fields_for(record_name, record_object, options) do |fields|
dom_class = [Bem.bem(:modulor_nested_fields, :item), Bem.bem(relation.klass)]
dom_data = { id: fields.object.id.to_s }
content_tag(:div, class: dom_class, data: dom_data) do
nested_fields_item_handle.to_s.html_safe +
render(partial_path, fields: fields).html_safe +
link_to_remove(fields)
end.html_safe
end,
nested_fields_links
].reject(&:blank?).join.html_safe
end.html_safe
end
private
def is_sortable?
options[:sortable] == true
end
def partial_path
object.to_view_path("#{record_name}/fields")
end
def relation
object.reflect_on_association(record_name)
end
def nested_fields_title
dom_class = Bem.bem(:modulor_nested_fields, :title)
title = relation.klass.model_name.human.pluralize
content_tag(:div, title, class: dom_class).html_safe
end
def nested_fields_links
dom_class = Bem.bem(:modulor_nested_fields, :links)
content_tag(:div, link_to_add, class: dom_class).html_safe
end
def link_to_add
label = options.fetch(:label_add, ::I18n.t(:add, scope: %i(modulor/nested_fields_builder links), model_name: relation.klass.model_name.human))
dom_class = Bem.bem([:modulor_link, :nested_fields, :add])
dom_data = { template: CGI.escapeHTML(nested_fields_template).html_safe, turbolinks: 'false' }
link_to(label, '#', class: dom_class, data: dom_data).html_safe
end
def nested_fields_item_handle
return unless is_sortable?
dom_class = Bem.bem(:modulor_nested_fields, :item, :handle)
content_tag(:div, nil, class: dom_class).html_safe
end
def nested_fields_template
dom_class = [Bem.bem(:modulor_nested_fields, :item), Bem.bem(relation.klass)]
content_tag(:div, class: dom_class) do
nested_fields_template_string
end.html_safe
end
def nested_fields_template_string
new_object = object.send(record_name).respond_to?(:decorated?) ? object.send(record_name).object.build : object.send(record_name).build
simple_fields_for(record_name, new_object, child_index: CHILD_INDEX_STRING) do |fields|
nested_fields_item_handle.to_s.html_safe +
render(partial_path, fields: fields).html_safe +
link_to_remove(fields)
end.html_safe
end
def destroy_field_tag(fields)
return if fields.object.new_record?
hidden_field_tag("#{fields.object_name}[_destroy]", fields.object._destroy).html_safe
end
def link_to_remove(fields, options = {})
label = options.fetch(:label, ::I18n.t(:remove, scope: %i(modulor/nested_fields_builder links)))
dom_class = Bem.bem([:modulor_link, :nested_fields, :remove])
dom_data = { turbolinks: 'false' }
[
destroy_field_tag(fields),
link_to(label, '#', class: dom_class, data: dom_data)
].reject(&:blank?).join.html_safe
end
end
end
require 'test_helper'
describe 'WebModule – Nested Attributes', :capybara do
let(:current_user) { create :modulor_user, :can_create_pages, :can_update_pages, :can_create_web_modules, :can_update_web_modules }
let(:modulor_draft) { create :modulor_draft, editor: current_user }
before do
login_as current_user
visit modulor.edit_draft_path(modulor_draft.path)
end
describe 'create' do
let(:title) { 'New title' }
it 'creates embedded when valid', js: true do
app.draft(modulor_draft).tap do |draft|
draft.add_web_module(WebModuleWithEmbedded) do |wm|
within wm.nested_fields do
wm.link_to_add_embedded.click
wm.fill_in 'Title', with: title
end
end
draft.web_module(WebModuleWithEmbedded).must_have_content title
end
end
it 'redisplays form when not valid', js: true do
app.draft(modulor_draft).tap do |draft|
draft.modulor_dummy_module.add_web_module(WebModuleWithEmbedded).tap do |wm|
wm.must_be :edited?
within wm.nested_fields do
wm.link_to_add_embedded.click
end
wm.create_button.click
wm.must_be :edited?
within wm.nested_fields do
wm.must_have_content "can't be blank"
end
end
end
end
end
describe 'update' do
let(:title) { 'Updated title' }
let(:embedded_document) { WebModuleWithEmbedded::EmbeddedDocument.new(title: 'Existing title') }
let(:web_module_with_embedded) { WebModuleWithEmbedded.new(embedded_documents: [embedded_document]) }
let(:modulor_draft) { create :modulor_draft, editor: current_user, web_modules: [web_module_with_embedded] }
it 'updates when valid', js: true do
app.draft(modulor_draft).tap do |draft|
draft.update_web_module(web_module_with_embedded) do |wm|
within wm.nested_fields do
within :modulor_resource, embedded_document do
wm.fill_in 'Title', with: title
end
end
end
within :modulor_resource, embedded_document do
draft.web_module(web_module_with_embedded).must_have_content title
end
end
end
it 'redisplays form when not valid', js: true do
app.draft(modulor_draft).tap do |draft|
draft.web_module(web_module_with_embedded).tap do |wm|
wm.edit_button.click
wm.must_be :edited?
within wm.nested_fields do
within :modulor_resource, embedded_document do
wm.fill_in 'Title', with: nil
end
end
wm.update_button.click
wm.must_be :edited?
within :modulor_resource, embedded_document do
wm.must_have_content "can't be blank"
end
end
end
end
end
describe 'destroy' do
let(:embedded_document) { WebModuleWithEmbedded::EmbeddedDocument.new(title: 'Existing title') }
let(:web_module_with_embedded) { WebModuleWithEmbedded.new(embedded_documents: [embedded_document]) }
let(:modulor_draft) { create :modulor_draft, editor: current_user, web_modules: [web_module_with_embedded] }
it 'removes the document', js: true do
app.draft(modulor_draft).tap do |draft|
draft.update_web_module(web_module_with_embedded) do |wm|
within wm.nested_fields do
within :modulor_resource, embedded_document do
wm.link_to_remove_embedded.click
end
end
end
draft.wont_have_selector :modulor_resource, embedded_document
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment