Skip to content

Instantly share code, notes, and snippets.

@tomasc
Created April 29, 2018 15:24
Show Gist options
  • Save tomasc/f192749f7c52e5ad33dd501fcda119a5 to your computer and use it in GitHub Desktop.
Save tomasc/f192749f7c52e5ad33dd501fcda119a5 to your computer and use it in GitHub Desktop.
dependent fields
import _ from 'lodash'
export default class DependentFields extends Modulor.Plugin
@defaults =
debug: false
name: 'Modulor__DependentFields'
# add the <%= Bem.bems :modulor_dependent_fields, :scope %> class on parent element
# in order to prevent dependent fields influencing for example nested fields
scope_selector: "bems(modulor_dependent_fields, scope), bems(modulor_nested_fields, item), form"
@selector: "bems(modulor_dependent_fields)"
on_init: ->
@scope_element = @get_scope_element()
@form_update_handler = (e) =>
return unless @$element.is(':visible')
return unless @is_dependent_on_input($(e.target))
return unless $(e.target).closest(@options.scope_selector).is(@scope_element)
@update_dependent_fields()
@scope_element.on "change.#{@options.name}", 'input,select', @form_update_handler
on_destroy: ->
# we need to be specific to remove only handler of this updated fields
# otherwise we might accidentally remove all handlers of all dependent fields
# in the same form
@scope_element.off ".#{@options.name}", 'input,select', @form_update_handler
# ---------------------------------------------------------------------
depends_on: -> @$element.data('depends-on')
depends_on_any: -> @depends_on()['depends_on_any']
depends_on_all: -> @depends_on()['depends_on_all']
depends_on_none: -> @depends_on()['depends_on_none']
# ---------------------------------------------------------------------
get_form: -> @$element.closest('form')
get_input: (name) -> @get_inputs().filter("[name$='[#{name}]']")
get_input_names: -> Object.keys(@depends_on_any() || {}).concat Object.keys(@depends_on_all() || {}).concat Object.keys(@depends_on_none() || {})
get_inputs: -> @scope_element.find('input,select').not(':hidden').filter (i, el) => $(el).closest(@options.scope_selector).is(@scope_element)
get_scope_element: -> @$element.closest(@options.scope_selector)
# ---------------------------------------------------------------------
is_dependent_on_input: ($input) ->
_.filter(@get_input_names(), (name) -> $input.is("[name$='[#{name}]']")).length > 0
# ---------------------------------------------------------------------
update_dependent_fields: -> if @is_condition_valid() then @show_content() else @hide_content()
show_content: ->
return if @$element.children().length > 0
return unless html = @$element.data('template-html')
@$element.append($(html))
hide_content: ->
template_html = @$element.children().remove()
@$element.data('template-html', template_html) unless @$element.data('template-html')
# ---------------------------------------------------------------------
is_condition_valid: ->
switch
when @depends_on_any() then @is_any_valid()
when @depends_on_all() then @is_all_valid()
when @depends_on_none() then @is_none_valid()
is_any_valid: ->
res = false
for name, values of @depends_on_any()
for value in _.flatten([values])
input_value = @is_valid(name, value)
res = (res || input_value)
res
is_all_valid: ->
res = true
for name, values of @depends_on_all()
for value in _.flatten([values])
input_value = @is_valid(name, value)
res = (res && input_value)
res
is_none_valid: ->
res = true
for name, values of @depends_on_none()
for value in _.flatten([values])
input_value = @is_valid(name, value)
res = (res && !input_value)
res
is_valid: (name, value) ->
$input = @get_input(name)
value = value.toString()
switch value
when 'true' then value = '1'
when 'false' then value = '0'
switch $input.prop('type')
when 'radio' then $input.filter(':checked').val() == value
when 'checkbox'
switch value
when '1' then $input.filter(':checked').length > 0
else $input.not(':checked').length > 0
when 'select-one' then $input.val() == value
when 'select-multiple' then value in $input.val()
else $input.val() == value
DependentFields.register()
# = form.input :center, collection: MapModule::CENTER, as: :radio_buttons
#
# = form.dependent_fields depends_on_all: { center: :address } do
# = form.input :address
#
# = form.dependent_fields depends_on_all: { center: :coordinates } do
# = form.input :center_lat
# = form.input :center_lng
module ActionView
module Helpers
class FormBuilder
def dependent_fields(options = {}, &block)
Modulor::DependentFieldsBuilder.new(self, @template, options).dependent_fields(&block)
end
end
end
end
module Modulor
class DependentFieldsBuilder < Struct.new(:builder, :template, :options)
def initialize(builder, template, options = {})
super(builder, template, options)
end
delegate :object,
to: :builder
def dependent_fields(&block)
html = template.capture(&block)
tag_html = condition_valid? ? html : nil
data_template_html = condition_valid? ? {} : { template_html: CGI.escapeHTML(html).html_safe }
template.content_tag :div, tag_html, class: [dom_class, Bem.bem(:modulor_dependent_fields)].reject(&:blank?).flatten, data: dom_data.merge(data_template_html)
end
private
def dom_class
options.fetch(:class, nil)
end
def dom_data
{ depends_on: options.slice(:depends_on_any, :depends_on_all, :depends_on_none) }
end
# ---------------------------------------------------------------------
def depends_on_any
options.fetch(:depends_on_any, nil)
end
def depends_on_all
options.fetch(:depends_on_all, nil)
end
def depends_on_none
options.fetch(:depends_on_none, nil)
end
# ---------------------------------------------------------------------
def condition_valid?
case
when depends_on_any.present? then any_valid?
when depends_on_all.present? then all_valid?
when depends_on_none.present? then none_valid?
end
end
def any_valid?
return unless depends_on_any.present?
depends_on_any.any? do |name, values|
Array(values).any? do |value|
is_valid?(name, value)
end
end
end
def all_valid?
return unless depends_on_all.present?
depends_on_all.all? do |name, values|
Array(values).all? do |value|
is_valid?(name, value)
end
end
end
def none_valid?
return unless depends_on_none.present?
depends_on_none.all? do |name, values|
Array(values).none? do |value|
is_valid?(name, value)
end
end
end
def is_valid?(name, value)
return false unless object.respond_to?(name)
value = nil if value == 'null'
object.send(name) == value
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment