Last active
April 29, 2018 15:33
-
-
Save tomasc/50e876ba53774339dddac56c144dc2fa to your computer and use it in GitHub Desktop.
nested_fields
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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')) | |
) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export default class Link__NestedFields extends Modulor.Plugin | |
@defaults = | |
debug: false | |
new_item_class_name: "bem(modulor_nested_fields, item, is_new)" | |
regexp: new RegExp("__INDEX_PLACEHOLDER__", 'g') # regexp: new RegExp("<%= Modulor::NestedFieldsBuilder::CHILD_INDEX_STRING %>", 'g') | |
@selector: "bems(modulor_link, nested_fields)" | |
on_init: -> | |
@$element.on "click.#{@options.name}", (e) => | |
e.preventDefault() | |
return unless @$element.is("bems(modulor_link, nested_fields, add)") | |
@add_new_item() | |
@$element.on "click.#{@options.name}", (e) => | |
e.preventDefault() | |
return unless @$element.is("bems(modulor_link, nested_fields, remove)") | |
@remove_item() | |
get_index: -> new Date().getTime() | |
get_item: -> @$element.closest("bems(modulor_nested_fields, item)") | |
get_items: -> @$element.find("bems(modulor_nested_fields, item)") | |
get_items_container: -> @$element.closest("bems(modulor_nested_fields)") | |
get_links: -> @$element.closest("bems(modulor_nested_fields, links)") | |
get_template: -> @$element.data('template').replace(@options.regexp, @get_index()) | |
add_new_item: -> | |
$template = $(@get_template()) | |
$template.addClass(@options.new_item_class_name) | |
@get_links().before($template) | |
remove_item: -> | |
$item = @get_item() | |
if $item.hasClass(@options.new_item_class_name) | |
$item.remove() | |
else | |
@$element.prev("input[type=hidden]").val("1") | |
$item.hide() | |
Link__NestedFields.register() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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