abyme 0.2.2 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
data/abyme.gemspec CHANGED
@@ -28,7 +28,21 @@ Gem::Specification.new do |spec|
28
28
 
29
29
  spec.add_development_dependency "bundler", "~> 2.0"
30
30
  spec.add_development_dependency "rake", "~> 13.0"
31
+ # Tests
31
32
  spec.add_development_dependency "rspec-rails"
32
- spec.add_development_dependency "rails-dummy"
33
+ spec.add_development_dependency 'rails-controller-testing'
34
+ spec.add_development_dependency 'database_cleaner-active_record'
35
+ spec.add_development_dependency 'capybara'
36
+ spec.add_development_dependency 'webdrivers'
37
+ spec.add_development_dependency "generator_spec"
38
+
39
+ # Dummy app
33
40
  spec.add_development_dependency "sqlite3"
41
+ spec.add_development_dependency 'rails'
42
+ spec.add_development_dependency 'pry-rails'
43
+ spec.add_development_dependency 'web-console'
44
+
45
+ spec.add_development_dependency 'puma'
46
+ spec.add_development_dependency 'simplecov'
47
+ spec.add_development_dependency 'simplecov-lcov'
34
48
  end
@@ -1,27 +1,62 @@
1
1
  import { Controller } from 'stimulus';
2
2
 
3
3
  export default class extends Controller {
4
- static targets = ['template', 'associations', 'fields', 'newFields'];
4
+ // static targets = ['template', 'associations', 'fields', 'newFields'];
5
+ // Some applications don't compile correctly with the usual static syntax.
6
+ // Thus implementing targets with standard getters below
7
+
8
+ static get targets() {
9
+ return ['template', 'associations', 'fields', 'newFields'];
10
+ }
5
11
 
6
12
  connect() {
13
+ console.log("Abyme Connected")
14
+
7
15
  if (this.count) {
8
- this.addDefaultAssociations();
16
+ // If data-count is present,
17
+ // add n default fields on page load
18
+
19
+ this.add_default_associations();
9
20
  }
10
21
  }
11
22
 
23
+ // return the value of the data-count attribute
24
+
12
25
  get count() {
13
26
  return this.element.dataset.minCount || 0;
14
27
  }
15
28
 
29
+ // return the value of the data-position attribute
30
+ // if there is no position specified set end as default
31
+
16
32
  get position() {
17
33
  return this.associationsTarget.dataset.abymePosition === 'end' ? 'beforeend' : 'afterbegin';
18
34
  }
19
35
 
36
+ // ADD_ASSOCIATION
37
+
38
+ // this function is call whenever a click occurs
39
+ // on the element with the click->abyme#add_association
40
+ // <button> element by default
41
+
42
+ // if a data-count is present the add_association
43
+ // will be call without an event so we have to check
44
+ // this case
45
+
46
+ // check for limit reached
47
+ // dispatch an event if the limit is reached
48
+
49
+ // - call the function build_html that take care
50
+ // for building the correct html to be inserted in the DOM
51
+ // - dispatch an event before insert
52
+ // - insert html into the dom
53
+ // - dispatch an event after insert
54
+
20
55
  add_association(event) {
21
56
  if (event) {
22
57
  event.preventDefault();
23
58
  }
24
- // check for limit reached
59
+
25
60
  if (this.element.dataset.limit && this.limit_check()) {
26
61
  this.create_event('limit-reached')
27
62
  return false
@@ -33,8 +68,21 @@ export default class extends Controller {
33
68
  this.create_event('after-add');
34
69
  }
35
70
 
71
+ // REMOVE_ASSOCIATION
72
+
73
+ // this function is call whenever a click occurs
74
+ // on the element with the click->abyme#remove_association
75
+ // <button> element by default
76
+
77
+ // - call the function mark_for_destroy that takes care
78
+ // of marking the element for destruction and hiding it
79
+ // - dispatch an event before mark & hide
80
+ // - mark for descrution + hide the element
81
+ // - dispatch an event after mark and hide
82
+
36
83
  remove_association(event) {
37
84
  event.preventDefault();
85
+
38
86
  this.create_event('before-remove');
39
87
  this.mark_for_destroy(event);
40
88
  this.create_event('after-remove');
@@ -42,6 +90,12 @@ export default class extends Controller {
42
90
 
43
91
  // LIFECYCLE EVENTS RELATED
44
92
 
93
+ // CREATE_EVENT
94
+
95
+ // take a stage (String) => before-add, after-add...
96
+ // create a new custom event
97
+ // and dispatch at at the controller level
98
+
45
99
  create_event(stage, html = null) {
46
100
  const event = new CustomEvent(`abyme:${stage}`, { detail: {controller: this, content: html} });
47
101
  this.element.dispatchEvent(event);
@@ -69,9 +123,14 @@ export default class extends Controller {
69
123
  abymeAfterRemove(event) {
70
124
  }
71
125
 
72
- // UTILITIES
126
+ // BUILD HTML
127
+
128
+ // takes the html template and substitutes the sub-string
129
+ // NEW_RECORD for a generated timestamp
130
+ // then if there is a sub template in the html (multiple nested level)
131
+ // set all the sub timestamps back as NEW_RECORD
132
+ // finally returns the html
73
133
 
74
- // build html
75
134
  build_html() {
76
135
  let html = this.templateTarget.innerHTML.replace(
77
136
  /NEW_RECORD/g,
@@ -88,8 +147,15 @@ export default class extends Controller {
88
147
 
89
148
  return html;
90
149
  }
91
-
92
- // mark association for destroy
150
+
151
+ // MARK_FOR_DESTROY
152
+
153
+ // mark association for destruction
154
+ // get the closest abyme--fields from the remove_association button
155
+ // set the _destroy input value as 1
156
+ // hide the element
157
+ // add the class of abyme--marked-for-destroy to the element
158
+
93
159
  mark_for_destroy(event) {
94
160
  let item = event.target.closest('.abyme--fields');
95
161
  item.querySelector("input[name*='_destroy']").value = 1;
@@ -97,20 +163,29 @@ export default class extends Controller {
97
163
  item.classList.add('abyme--marked-for-destroy')
98
164
  }
99
165
 
100
- // check if associations limit is reached
166
+
167
+ // LIMIT_CHECK
168
+
169
+ // Check if associations limit is reached
170
+ // based on newFieldsTargets only
171
+ // persisted fields are ignored
172
+
101
173
  limit_check() {
102
174
  return (this.newFieldsTargets
103
175
  .filter(item => !item.classList.contains('abyme--marked-for-destroy'))).length
104
176
  >= parseInt(this.element.dataset.limit)
105
177
  }
106
178
 
107
- // Add default blank associations at page load
108
- async addDefaultAssociations() {
179
+ // ADD_DEFAULT_ASSOCIATION
180
+
181
+ // Add n default blank associations at page load
182
+ // call sleep function to ensure uniqueness of timestamp
183
+
184
+ async add_default_associations() {
109
185
  let i = 0
110
186
  while (i < this.count) {
111
187
  this.add_association()
112
188
  i++
113
- // Sleep function to ensure uniqueness of timestamp
114
189
  await this.sleep(1);
115
190
  }
116
191
  }
data/lib/abyme.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  require "abyme/version"
2
2
  require 'abyme/view_helpers'
3
3
  require 'abyme/engine'
4
+ require 'abyme/controller'
5
+ require 'abyme/action_view_extensions/builder'
4
6
 
5
7
  module Abyme
6
8
  class Error < StandardError; end
@@ -2,31 +2,42 @@ module Abyme
2
2
  class AbymeBuilder < ActionView::Base
3
3
  include ActionView
4
4
 
5
- def initialize(association:, form:, lookup_context:, partial:, &block)
5
+ # If a block is given to the #abymize helper
6
+ # it will instanciate a new AbymeBuilder
7
+ # and pass to it the association name (Symbol)
8
+ # the form object, lookup_context optionaly a partial path
9
+ # then yield itself to the block
10
+
11
+ def initialize(association:, form:, context:, partial:, &block)
6
12
  @association = association
7
13
  @form = form
8
- @lookup_context = lookup_context
14
+ @context = context
15
+ @lookup_context = context.lookup_context
9
16
  @partial = partial
10
17
  yield(self) if block_given?
11
18
  end
19
+
20
+ # RECORDS
21
+
22
+ # calls the #persisted_records_for helper method
23
+ # passing association, form and options to it
12
24
 
13
25
  def records(options = {})
14
26
  persisted_records_for(@association, @form, options) do |fields_for_association|
15
- render_association_partial(fields_for_association, options)
27
+ render_association_partial(@association, fields_for_association, @partial, @context)
16
28
  end
17
29
  end
30
+
31
+ # NEW_RECORDS
32
+
33
+ # calls the #new_records_for helper method
34
+ # passing association, form and options to it
18
35
 
19
36
  def new_records(options = {}, &block)
20
37
  new_records_for(@association, @form, options) do |fields_for_association|
21
- render_association_partial(fields_for_association, options)
38
+ render_association_partial(@association, fields_for_association, @partial, @context)
22
39
  end
23
40
  end
24
41
 
25
- private
26
-
27
- def render_association_partial(fields, options)
28
- partial = @partial || options[:partial] || "abyme/#{@association.to_s.singularize}_fields"
29
- ActionController::Base.render(partial: partial, locals: { f: fields })
30
- end
31
42
  end
32
43
  end
@@ -0,0 +1,17 @@
1
+ module Abyme
2
+ module ActionViewExtensions
3
+ module Builder
4
+ def abyme_for(association, options = {}, &block)
5
+ @template.abyme_for(association, self, options, &block)
6
+ end
7
+
8
+ alias :abymize :abyme_for
9
+ end
10
+ end
11
+ end
12
+
13
+ module ActionView::Helpers
14
+ class FormBuilder
15
+ include Abyme::ActionViewExtensions::Builder
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ module Abyme
2
+ module Controller
3
+ def abyme_attributes
4
+ return [] if resource_class.nil?
5
+
6
+ resource_class.abyme_attributes
7
+ end
8
+
9
+ private
10
+
11
+ def resource_class
12
+ self.class.name.match(/(.*)(Controller)/)[1].singularize.safe_constantize
13
+ end
14
+ end
15
+ end
data/lib/abyme/engine.rb CHANGED
@@ -4,9 +4,11 @@ module Abyme
4
4
 
5
5
  config.after_initialize do
6
6
  ActiveSupport.on_load :action_view do
7
- # ActionView::Base.send :include, Abyme::ViewHelpers
8
7
  include Abyme::ViewHelpers
9
8
  end
9
+ ActiveSupport.on_load :action_controller do
10
+ include Abyme::Controller
11
+ end
10
12
  end
11
13
  end
12
14
  end
data/lib/abyme/model.rb CHANGED
@@ -1,11 +1,87 @@
1
1
  module Abyme
2
2
  module Model
3
- extend ActiveSupport::Concern
4
-
5
- class_methods do
6
- def abyme_for(association, options = {})
3
+ module ClassMethods
4
+ def abymize(association, permit: nil, reject: nil, **options)
7
5
  default_options = {reject_if: :all_blank, allow_destroy: true}
8
- accepts_nested_attributes_for association, default_options.merge(options)
6
+ nested_attributes_options = default_options.merge(options)
7
+ accepts_nested_attributes_for association, nested_attributes_options
8
+ # Save allow_destroy value for this model/association for later
9
+ save_destroy_option(association, nested_attributes_options[:allow_destroy])
10
+ Abyme::Model.permit_attributes(self.name, association, permit || reject, permit.present?) if permit.present? || reject.present?
11
+ end
12
+
13
+ alias :abyme_for :abymize
14
+
15
+ def abyme_attributes
16
+ Abyme::Model.instance_variable_get(:@permitted_attributes)[self.name]
17
+ end
18
+
19
+ private
20
+
21
+ def save_destroy_option(association, value)
22
+ Abyme::Model.instance_variable_get(:@allow_destroy)[self.name][association] = value
23
+ end
24
+ end
25
+
26
+ @permitted_attributes ||= {}
27
+ @allow_destroy ||= {}
28
+
29
+ attr_accessor :allow_destroy
30
+ attr_reader :permitted_attributes
31
+
32
+ def self.permit_attributes(class_name, association, attributes, permit)
33
+ @permitted_attributes[class_name]["#{association}_attributes".to_sym] = AttributesBuilder.new(class_name, association, attributes, permit)
34
+ .build_attributes
35
+ end
36
+
37
+ def self.included(klass)
38
+ @permitted_attributes[klass.name] ||= {}
39
+ @allow_destroy[klass.name] ||= {}
40
+ klass.extend ClassMethods
41
+ end
42
+
43
+ class AttributesBuilder
44
+ def initialize(model, association, attributes, permit = true)
45
+ @model = model
46
+ @association = association
47
+ @attributes_list = attributes
48
+ @permit = permit
49
+ @association_class = @association.to_s.classify.constantize
50
+ end
51
+
52
+ def build_attributes
53
+ nested_attributes = @association_class.abyme_attributes if @association_class.respond_to? :abyme_attributes
54
+ authorized_attributes = build_default_attributes
55
+ if @permit && @attributes_list == :all_attributes
56
+ authorized_attributes = build_all_attributes(authorized_attributes, nested_attributes)
57
+ elsif @permit
58
+ @attributes_list << nested_attributes unless (nested_attributes.blank? || @attributes_list.include?(nested_attributes))
59
+ authorized_attributes += @attributes_list
60
+ else
61
+ authorized_attributes = build_all_attributes(authorized_attributes, nested_attributes)
62
+ authorized_attributes -= @attributes_list
63
+ end
64
+ authorized_attributes
65
+ end
66
+
67
+ def destroy_allowed?
68
+ Abyme::Model.instance_variable_get(:@allow_destroy).dig(@model, @association)
69
+ end
70
+
71
+ def add_all_attributes
72
+ @association_class.column_names.map(&:to_sym).reject { |attr| [:id, :created_at, :updated_at].include?(attr) }
73
+ end
74
+
75
+ def build_all_attributes(authorized_attributes, nested_attributes)
76
+ authorized_attributes += add_all_attributes
77
+ authorized_attributes << nested_attributes unless (nested_attributes.blank? || authorized_attributes.include?(nested_attributes))
78
+ authorized_attributes
79
+ end
80
+
81
+ def build_default_attributes
82
+ attributes = [:id]
83
+ attributes << :_destroy if destroy_allowed?
84
+ attributes
9
85
  end
10
86
  end
11
87
  end
data/lib/abyme/version.rb CHANGED
@@ -1,9 +1,11 @@
1
+ # :nocov:
1
2
  module Abyme
2
3
  module VERSION
3
4
  MAJOR = 0
4
- MINOR = 2
5
- PATCH = 2
5
+ MINOR = 5
6
+ PATCH = 1
6
7
 
7
8
  STRING = [MAJOR, MINOR, PATCH].join(".")
8
9
  end
9
10
  end
11
+ # :nocov:
@@ -1,28 +1,108 @@
1
+ require_relative "abyme_builder"
2
+
1
3
  module Abyme
2
4
  module ViewHelpers
3
5
 
4
- def abymize(association, form, options = {}, &block)
6
+ # ABYME_FOR
7
+
8
+ # this helper will generate the top level wrapper markup
9
+ # with the bare minimum html attributes (data-controller="abyme")
10
+ # it takes the Symbolized name of the association (plural) and the form object
11
+ # then you can pass a hash of options (see exemple below)
12
+ # if no block given it will generate a default markup for
13
+ # #persisted_records_for, #new_records_for & #add_associated_record methods
14
+ # if a block is given it will instanciate a new AbymeBuilder and pass to it
15
+ # the name of the association, the form object and the lookup_context
16
+
17
+ # == Options
18
+
19
+ # - limit (Integer)
20
+ # you can set a limit for the new association fields to display
21
+
22
+ # - min_count (Integer)
23
+ # set the default number of blank fields to display
24
+
25
+ # - partial (String)
26
+ # to customize the partial path by default #abyme_for will expect
27
+ # a partial to bbe present in views/abyme
28
+
29
+ # - Exemple
30
+
31
+ # <%= abyme_for(:tasks, f, limit: 3) do |abyme| %>
32
+ # ...
33
+ # <% end %>
34
+
35
+ # will output this html
36
+
37
+ # <div data-controller="abyme" data-limit="3" id="abyme--tasks">
38
+ # ...
39
+ # </div>
40
+
41
+ def abyme_for(association, form, options = {}, &block)
5
42
  content_tag(:div, data: { controller: 'abyme', limit: options[:limit], min_count: options[:min_count] }, id: "abyme--#{association}") do
6
43
  if block_given?
7
44
  yield(Abyme::AbymeBuilder.new(
8
- association: association, form: form, lookup_context: self.lookup_context, partial: options[:partial]
45
+ association: association, form: form, context: self, partial: options[:partial]
9
46
  )
10
47
  )
11
48
  else
12
49
  model = association.to_s.singularize.classify.constantize
13
50
  concat(persisted_records_for(association, form, options))
14
51
  concat(new_records_for(association, form, options))
15
- concat(add_association(content: options[:button_text] || "Add #{model}"))
52
+ concat(add_associated_record(content: options[:button_text] || "Add #{model}"))
16
53
  end
17
54
  end
18
55
  end
19
56
 
57
+ alias :abymize :abyme_for
58
+
59
+ # NEW_RECORDS_FOR
60
+
61
+ # this helper is call by the AbymeBuilder #new_records instance method
62
+ # it generates the html markup for new associations fields
63
+ # it takes the association (Symbol) and the form object
64
+ # then a hash of options.
65
+
66
+ # - Exemple
67
+ # <%= abyme_for(:tasks, f) do |abyme| %>
68
+ # <%= abyme.new_records %>
69
+ # ...
70
+ # <% end %>
71
+
72
+ # will output this html
73
+
74
+ # <div data-target="abyme.associations" data-association="tasks" data-abyme-position="end">
75
+ # <template class="abyme--task_template" data-target="abyme.template">
76
+ # <div data-target="abyme.fields abyme.newFields" class="abyme--fields task-fields">
77
+ # ... partial html goes here
78
+ # </div>
79
+ # </template>
80
+ # ... new rendered fields goes here
81
+ # </div>
82
+
83
+ # == Options
84
+ # - position (:start, :end)
85
+ # allows you to specify whether new fields added dynamically
86
+ # should go at the top or at the bottom
87
+ # :end is the default value
88
+
89
+ # - partial (String)
90
+ # to customize the partial path by default #abyme_for will expect
91
+ # a partial to bbe present in views/abyme
92
+
93
+ # - fields_html (Hash)
94
+ # allows you to pass any html attributes to each fields wrapper
95
+
96
+ # - wrapper_html (Hash)
97
+ # allows you to pass any html attributes to the the html element
98
+ # wrapping all the fields
99
+
20
100
  def new_records_for(association, form, options = {}, &block)
21
101
  options[:wrapper_html] ||= {}
22
102
 
23
103
  wrapper_default = {
24
104
  data: {
25
- target: 'abyme.associations',
105
+ abyme_target: 'associations',
26
106
  association: association,
27
107
  abyme_position: options[:position] || :end
28
108
  }
@@ -31,25 +111,69 @@ module Abyme
31
111
  fields_default = { data: { target: 'abyme.fields abyme.newFields' } }
32
112
 
33
113
  content_tag(:div, build_attributes(wrapper_default, options[:wrapper_html])) do
34
- content_tag(:template, class: "abyme--#{association.to_s.singularize}_template", data: { target: 'abyme.template' }) do
114
+ content_tag(:template, class: "abyme--#{association.to_s.singularize}_template", data: { abyme_target: 'template' }) do
35
115
  form.fields_for association, association.to_s.classify.constantize.new, child_index: 'NEW_RECORD' do |f|
36
116
  content_tag(:div, build_attributes(fields_default, basic_fields_markup(options[:fields_html], association))) do
37
117
  # Here, if a block is passed, we're passing the association fields to it, rather than the form itself
38
- block_given? ? yield(f) : render(options[:partial] || "abyme/#{association.to_s.singularize}_fields", f: f)
118
+ # block_given? ? yield(f) : render(options[:partial] || "abyme/#{association.to_s.singularize}_fields", f: f)
119
+ block_given? ? yield(f) : render_association_partial(association, f, options[:partial])
39
120
  end
40
121
  end
41
122
  end
42
123
  end
43
124
  end
125
+
126
+ # PERSISTED_RECORDS_FOR
127
+
128
+ # this helper is call by the AbymeBuilder #records instance method
129
+ # it generates the html markup for persisted associations fields
130
+ # it takes the association (Symbol) and the form object
131
+ # then a hash of options.
132
+
133
+ # - Exemple
134
+ # <%= abyme_for(:tasks, f) do |abyme| %>
135
+ # <%= abyme.records %>
136
+ # ...
137
+ # <% end %>
138
+
139
+ # will output this html
140
+
141
+ # <div>
142
+ # <div data-target="abyme.fields" class="abyme--fields task-fields">
143
+ # ... partial html goes here
144
+ # </div>
145
+ # </div>
146
+
147
+ # == Options
148
+ # - collection (Active Record Collection)
149
+ # allows you to pass an AR collection
150
+ # by default every associated records will be present
151
+
152
+ # - order (Hash)
153
+ # allows you to order the collection
154
+ # ex: order: { created_at: :desc }
155
+
156
+ # - partial (String)
157
+ # to customize the partial path by default #abyme_for will expect
158
+ # a partial to bbe present in views/abyme
159
+
160
+ # - fields_html (Hash)
161
+ # allows you to pass any html attributes to each fields wrapper
162
+
163
+ # - wrapper_html (Hash)
164
+ # allows you to pass any html attributes to the the html element
165
+ # wrapping all the fields
44
166
 
45
167
  def persisted_records_for(association, form, options = {})
46
168
  records = options[:collection] || form.object.send(association)
47
169
  options[:wrapper_html] ||= {}
48
- fields_default = { data: { target: 'abyme.fields' } }
170
+ fields_default = { data: { abyme_target: 'fields' } }
49
171
 
50
172
  if options[:order].present?
51
173
  records = records.order(options[:order])
52
- # Get invalid records
174
+ # by calling the order method on the AR collection
175
+ # we get rid of the records with errors
176
+ # so we have to get them back with the 2 lines below
53
177
  invalids = form.object.send(association).reject(&:persisted?)
54
178
  records = records.to_a.concat(invalids) if invalids.any?
55
179
  end
@@ -57,28 +181,54 @@ module Abyme
57
181
  content_tag(:div, options[:wrapper_html]) do
58
182
  form.fields_for(association, records) do |f|
59
183
  content_tag(:div, build_attributes(fields_default, basic_fields_markup(options[:fields_html], association))) do
60
- block_given? ? yield(f) : render(options[:partial] || "abyme/#{association.to_s.singularize}_fields", f: f)
184
+ block_given? ? yield(f) : render_association_partial(association, f, options[:partial])
61
185
  end
62
186
  end
63
187
  end
64
188
  end
189
+
190
+ # ADD & REMOVE ASSOCIATION
191
+
192
+ # these helpers will call the #create_button method
193
+ # to generate the buttons for add and remove associations
194
+ # with the right action and a default content text for each button
65
195
 
66
- def add_association(options = {}, &block)
196
+ def add_associated_record(options = {}, &block)
67
197
  action = 'click->abyme#add_association'
198
+ options[:content] ||= 'Add Association'
68
199
  create_button(action, options, &block)
69
200
  end
70
201
 
71
- def remove_association(options = {}, &block)
202
+ def remove_associated_record(options = {}, &block)
72
203
  action = 'click->abyme#remove_association'
204
+ options[:content] ||= 'Remove Association'
73
205
  create_button(action, options, &block)
74
206
  end
75
207
 
208
+ alias :add_association :add_associated_record
209
+ alias :remove_association :remove_associated_record
210
+
76
211
  private
212
+
213
+ # CREATE_BUTTON
214
+
215
+ # this helper is call by either add_associated_record or remove_associated_record
216
+ # by default it will generate a button tag.
217
+
218
+ # == Options
219
+ # - content (String)
220
+ # allows you to set the button text
221
+
222
+ # - tag (Symbol)
223
+ # allows you to set the html tag of your choosing
224
+ # default if :button
225
+
226
+ # - html (Hash)
227
+ # to pass any html attributes you want.
77
228
 
78
229
  def create_button(action, options, &block)
79
230
  options[:html] ||= {}
80
231
  options[:tag] ||= :button
81
- options[:content] ||= 'Add Association'
82
232
 
83
233
  if block_given?
84
234
  content_tag(options[:tag], { data: { action: action } }.merge(options[:html])) do
@@ -89,6 +239,11 @@ module Abyme
89
239
  end
90
240
  end
91
241
 
242
+ # BASIC_FIELDS_MARKUP
243
+
244
+ # generates the default html classes for fields
245
+ # add optional classes if present
246
+
92
247
  def basic_fields_markup(html, association = nil)
93
248
  if html && html[:class]
94
249
  html[:class] = "abyme--fields #{association.to_s.singularize}-fields #{html[:class]}"
@@ -99,17 +254,33 @@ module Abyme
99
254
  html
100
255
  end
101
256
 
257
+ # BUILD_ATTRIBUTES
258
+
259
+ # add optionals html attributes without overwritting
260
+ # the default or already present ones
261
+
102
262
  def build_attributes(default, attr)
103
- # ADD NEW DATA ATTRIBUTES VALUES TO THE DEFAULT ONES (ONLY VALUES)
263
+ # Add new data attributes values to the default ones (only values)
104
264
  if attr[:data]
105
265
  default[:data].each do |key, value|
106
266
  default[:data][key] = "#{value} #{attr[:data][key]}".strip
107
267
  end
108
- # ADD NEW DATA ATTRIBUTES (KEYS & VALUES)
268
+ # Add new data attributes (keys & values)
109
269
  default[:data] = default[:data].merge(attr[:data].reject { |key, _| default[:data][key] })
110
270
  end
111
- # MERGE THE DATA ATTRIBUTES TO THE HASH OF HTML ATTRIBUTES
271
+ # Merge data attributes to the hash of html attributes
112
272
  default.merge(attr.reject { |key, _| key == :data })
113
273
  end
274
+
275
+ # RENDER PARTIAL
276
+
277
+ # renders a partial based on the passed path, or will expect a partial to be found in the views/abyme directory.
278
+
279
+ def render_association_partial(association, form, partial = nil, context = nil)
280
+ partial_path = partial ||"abyme/#{association.to_s.singularize}_fields"
281
+ context ||= self
282
+ context.render(partial: partial_path, locals: {f: form})
283
+ end
284
+
114
285
  end
115
286
  end