effective_bootstrap 0.9.6 → 0.9.11

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,6 +3,7 @@
3
3
  @import 'effective_checks/input';
4
4
  @import 'effective_datetime/input';
5
5
  @import 'effective_file/input';
6
+ @import 'effective_has_many/input';
6
7
  @import 'effective_rich_text_area/input';
7
8
  @import 'effective_select/input';
8
9
  @import 'effective_select_or_text/input';
@@ -4,5 +4,5 @@
4
4
  font-size: 0.75rem;
5
5
  margin: 0.25rem 0;
6
6
 
7
- a { padding: 0.25rem 0; }
7
+ a { padding: 0.5rem 0; }
8
8
  }
@@ -0,0 +1,42 @@
1
+ body.dragging,
2
+ body.dragging * {
3
+ cursor: move !important;
4
+ }
5
+
6
+ .form-has-many {
7
+ .has-many-placeholder {
8
+ position: relative;
9
+ height: 2rem;
10
+
11
+ &:before {
12
+ position: absolute;
13
+ content: '';
14
+ background-image: asset-data-url('icons/arrow-right-circle.svg');
15
+ background-repeat: no-repeat;
16
+ height: 2rem;
17
+ width: 2rem;
18
+ }
19
+ }
20
+
21
+ .has-many-fields.dragged {
22
+ position: absolute;
23
+ opacity: 0;
24
+ z-index: 2000;
25
+ .has-many-move { display: none; }
26
+ }
27
+
28
+ .has-many-move svg { margin-top: 6px; }
29
+ .has-many-move { display: none; }
30
+
31
+ .has-many-remove { margin-top: 1.5rem; }
32
+ .has-many-move { margin-top: 1.5rem; }
33
+ }
34
+
35
+ .form-has-many.reordering {
36
+ .has-many-move { display: inline-block; }
37
+ }
38
+
39
+ .form-has-many.tight {
40
+ .has-many-remove { margin-top: 0; }
41
+ .has-many-move { margin-top: 0; }
42
+ }
@@ -2,7 +2,7 @@ module EffectiveFormBuilderHelper
2
2
  def effective_form_with(**options, &block)
3
3
  # Compute the default ID
4
4
  subject = Array(options[:scope] || options[:model]).last
5
- class_name = subject.class.name.underscore
5
+ class_name = subject.class.name.parameterize.underscore
6
6
  unique_id = options.except(:model).hash.abs
7
7
 
8
8
  html_id = if subject.kind_of?(Symbol)
@@ -56,6 +56,17 @@ module EffectiveFormBuilderHelper
56
56
  # Assign default ID
57
57
  options[:id] ||= (options[:html].delete(:id) || html_id) unless options.key?(:id)
58
58
 
59
+ # Assign url if engine present
60
+ options[:url] ||= if options[:engine] && options[:model].present?
61
+ resource = Effective::Resource.new(options[:model])
62
+
63
+ if subject.respond_to?(:persisted?) && subject.persisted?
64
+ resource.action_path(:update, subject)
65
+ elsif subject.respond_to?(:new_record?) && subject.new_record?
66
+ resource.action_path(:create)
67
+ end
68
+ end
69
+
59
70
  without_error_proc do
60
71
  form_with(**options.merge(builder: Effective::FormBuilder), &block)
61
72
  end
@@ -203,5 +203,19 @@ module Effective
203
203
  Effective::FormLogics::ShowIfAny.new(*args, builder: self).to_html(&block)
204
204
  end
205
205
 
206
+ # Has Many
207
+ def has_many(name, collection = nil, options = {}, &block)
208
+ association = object.class.reflect_on_all_associations.find { |a| a.name == name }
209
+ raise("expected #{object.class.name} to has_many :#{name}") if association.blank?
210
+
211
+ nested_attributes_options = (object.class.nested_attributes_options || {})[name]
212
+ raise("expected #{object.class.name} to accepts_nested_attributes_for :#{name}") if nested_attributes_options.blank?
213
+
214
+ options = collection if collection.kind_of?(Hash)
215
+ options.merge!(collection: collection) if collection && !collection.kind_of?(Hash)
216
+
217
+ Effective::FormInputs::HasMany.new(name, options, builder: self).to_html(&block)
218
+ end
219
+
206
220
  end
207
221
  end
@@ -50,18 +50,29 @@ module Effective
50
50
  end
51
51
 
52
52
  def build_label
53
- return BLANK if options[:label] == false
53
+ return BLANK if options[:label] == false && !actions?
54
54
  return BLANK if name.kind_of?(NilClass)
55
55
 
56
- text = (options[:label].delete(:text) || (object.class.human_attribute_name(name) if object) || BLANK).html_safe
56
+ text = begin
57
+ if options[:label] == false
58
+ nil
59
+ elsif options[:label].key?(:text)
60
+ options[:label].delete(:text)
61
+ elsif object.present?
62
+ object.class.human_attribute_name(name)
63
+ end || BLANK
64
+ end.html_safe
65
+
66
+ actions = if !disabled? && actions?
67
+ content_tag(:div, class: 'effective-checks-actions text-muted') do
68
+ link_to('Select All', '#', 'data-effective-checks-all': true) + ' - ' + link_to('Select None', '#', 'data-effective-checks-none': true)
69
+ end
70
+ end
57
71
 
58
72
  content_tag(:label, options[:label]) do
59
- text + content_tag(:div, class: 'effective-checks-actions text-muted') do
60
- unless disabled? || !actions?
61
- link_to('Select All', '#', 'data-effective-checks-all': true) + ' - ' + link_to('Select None', '#', 'data-effective-checks-none': true)
62
- end
63
- end
73
+ [text, actions].compact.join.html_safe
64
74
  end
75
+
65
76
  end
66
77
 
67
78
  def build_item(builder)
@@ -91,7 +102,7 @@ module Effective
91
102
 
92
103
  def actions? # default true
93
104
  return @actions unless @actions.nil?
94
- @actions = (options.delete(:actions) != false)
105
+ @actions = (options[:input].delete(:actions) != false)
95
106
  end
96
107
 
97
108
  end
@@ -15,11 +15,12 @@ module Effective
15
15
 
16
16
  def input_js_options
17
17
  {
18
- modules: {
19
- toolbar: toolbar,
20
- imageResize: imageResize,
18
+ modules: {
19
+ toolbar: toolbar,
20
+ imageResize: imageResize,
21
21
  imageDropAndPaste: imageDropAndPaste,
22
- syntax: (content_mode == :code)
22
+ magicUrl: magicUrl,
23
+ syntax: (content_mode == :code)
23
24
  },
24
25
  theme: 'snow',
25
26
  placeholder: "Add #{name.to_s.pluralize}...",
@@ -42,7 +43,7 @@ module Effective
42
43
  end
43
44
 
44
45
  def imageDropAndPaste
45
- active_storage && !(content_mode == :code)
46
+ active_storage && !(content_mode == :code)
46
47
  end
47
48
 
48
49
  def imageResize
@@ -57,6 +58,10 @@ module Effective
57
58
  }
58
59
  end
59
60
 
61
+ def magicUrl
62
+ true
63
+ end
64
+
60
65
  def active_storage
61
66
  return @active_storage unless @active_storage.nil?
62
67
 
@@ -0,0 +1,207 @@
1
+ module Effective
2
+ module FormInputs
3
+ class HasMany < Effective::FormInput
4
+ BLANK = ''.html_safe
5
+
6
+ def to_html(&block)
7
+ object.send(name).build() if build? && collection.blank?
8
+
9
+ errors = (@builder.error(name) if errors?) || BLANK
10
+
11
+ errors + content_tag(:div, options[:input]) do
12
+ has_many_fields_for(block) + has_many_links_for(block)
13
+ end
14
+ end
15
+
16
+ def input_html_options
17
+ { class: 'form-has-many mb-4' }
18
+ end
19
+
20
+ def input_js_options
21
+ { sortable: true }
22
+ end
23
+
24
+ def collection
25
+ Array(options[:input][:collection] || object.send(name))
26
+ end
27
+
28
+ # cards: false
29
+ def display
30
+ @display ||= (options[:input].delete(:cards) ? :cards : :rows)
31
+ end
32
+
33
+ # build: true
34
+ def build?
35
+ return @build unless @build.nil?
36
+
37
+ @build ||= begin
38
+ build = options[:input].delete(:build)
39
+ build.nil? ? true : build
40
+ end
41
+ end
42
+
43
+ # add: true
44
+ def add?
45
+ return @add unless @add.nil?
46
+
47
+ @add ||= begin
48
+ add = options[:input].delete(:add)
49
+ add.nil? ? true : add
50
+ end
51
+ end
52
+
53
+ # errors: true
54
+ def errors?
55
+ return @errors unless @errors.nil?
56
+
57
+ @errors ||= begin
58
+ errors = options[:input].delete(:errors)
59
+ errors.nil? ? true : errors
60
+ end
61
+ end
62
+
63
+ # remove: true
64
+ def remove?
65
+ return @remove unless @remove.nil?
66
+
67
+ @remove ||= begin
68
+ remove = options[:input].delete(:remove)
69
+
70
+ if remove != nil
71
+ remove
72
+ else
73
+ opts = (object.class.nested_attributes_options[name] || {})
74
+ opts[:update_only] != true && opts[:allow_destroy] != false
75
+ end
76
+ end
77
+ end
78
+
79
+ # reorder: true
80
+ def reorder?
81
+ return @reorder unless @reorder.nil?
82
+
83
+ @reorder ||= begin
84
+ reorder = options[:input].delete(:reorder)
85
+
86
+ if reorder != nil
87
+ reorder
88
+ else
89
+ build_resource().class.columns_hash['position']&.type == :integer
90
+ end
91
+ end
92
+ end
93
+
94
+ private
95
+
96
+ def has_many_fields_for(block)
97
+ collection.map { |resource| render_resource(resource, block) }.join.html_safe
98
+ end
99
+
100
+ def has_many_links_for(block)
101
+ return BLANK unless add? || reorder?
102
+
103
+ content_tag(:div, class: 'has-many-links text-center mt-2') do
104
+ [*(link_to_add(block) if add?), *(link_to_reorder(block) if reorder?)].join(' ').html_safe
105
+ end
106
+ end
107
+
108
+ def render_resource(resource, block)
109
+ remove = BLANK
110
+
111
+ content = @builder.fields_for(name, resource) do |form|
112
+ fields = block.call(form)
113
+
114
+ remove += form.super_hidden_field(:_destroy) if remove? && resource.persisted?
115
+ remove += form.super_hidden_field(:position) if reorder? && !fields.include?('][position]')
116
+
117
+ fields
118
+ end
119
+
120
+ remove += link_to_remove(resource) if remove?
121
+
122
+ content_tag(:div, render_fields(content, remove), class: 'has-many-fields')
123
+ end
124
+
125
+ def render_fields(content, remove)
126
+ case display
127
+ when :rows
128
+ content_tag(:div, class: 'form-row') do
129
+ (reorder? ? content_tag(:div, has_many_move, class: 'col-auto') : BLANK) +
130
+ content_tag(:div, content, class: 'col mr-auto') +
131
+ content_tag(:div, remove, class: 'col-auto')
132
+ end
133
+ when :cards
134
+ raise('unsupported')
135
+ else
136
+ content + remove
137
+ end
138
+ end
139
+
140
+ def render_template(block)
141
+ resource = build_resource()
142
+ index = collection.length
143
+
144
+ html = render_resource(resource, block)
145
+
146
+ unless html.include?("#{name}_attributes][#{index}]")
147
+ raise('unexpected index. unable to render resource template.')
148
+ end
149
+
150
+ html.gsub!("#{name}_attributes][#{index}]", "#{name}_attributes][HASMANYINDEX]")
151
+ html.gsub!("#{name}_attributes_#{index}_", "#{name}_attributes_HASMANYINDEX_")
152
+
153
+ html.html_safe
154
+ end
155
+
156
+ def link_to_add(block)
157
+ content_tag(
158
+ :button,
159
+ icon('plus-circle') + 'Add Another',
160
+ class: 'has-many-add btn btn-secondary',
161
+ title: 'Add Another',
162
+ data: {
163
+ 'effective-form-has-many-add': true,
164
+ 'effective-form-has-many-template': render_template(block)
165
+ }
166
+ )
167
+ end
168
+
169
+ def link_to_reorder(block)
170
+ content_tag(
171
+ :button,
172
+ icon('list') + 'Reorder',
173
+ class: 'has-many-reorder btn btn-secondary',
174
+ title: 'Reorder',
175
+ data: {
176
+ 'effective-form-has-many-reorder': true,
177
+ }
178
+ )
179
+ end
180
+
181
+ def link_to_remove(resource)
182
+ content_tag(
183
+ :button,
184
+ icon('trash-2') + 'Remove',
185
+ class: 'has-many-remove btn btn-danger',
186
+ title: 'Remove',
187
+ data: {
188
+ 'confirm': "Remove #{resource}?",
189
+ 'effective-form-has-many-remove': true,
190
+ }
191
+ )
192
+ end
193
+
194
+ def has_many_move
195
+ @has_many_move ||= content_tag(:span, icon('move'), class: 'has-many-move')
196
+ end
197
+
198
+ def build_resource
199
+ # Using .new() here seems like it should work but it doesn't. It changes the index
200
+ @build_resource ||= object.send(name).build().tap do |resource|
201
+ object.send(name).delete(resource)
202
+ end
203
+ end
204
+
205
+ end
206
+ end
207
+ end
@@ -8,7 +8,7 @@ module EffectiveBootstrap
8
8
  end
9
9
 
10
10
  initializer 'effective_bootstrap.assets' do |app|
11
- app.config.assets.precompile += ['icons/*']
11
+ app.config.assets.precompile += ['effective_bootstrap_manifest.js', 'icons/*']
12
12
  end
13
13
 
14
14
  end
@@ -1,3 +1,3 @@
1
1
  module EffectiveBootstrap
2
- VERSION = '0.9.6'.freeze
2
+ VERSION = '0.9.11'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: effective_bootstrap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.6
4
+ version: 0.9.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Code and Effect
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-09-29 00:00:00.000000000 Z
11
+ date: 2021-01-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: haml
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
97
111
  description: Everything you need to get set up with bootstrap 4.
98
112
  email:
99
113
  - info@codeandeffect.com
@@ -103,6 +117,7 @@ extra_rdoc_files: []
103
117
  files:
104
118
  - MIT-LICENSE
105
119
  - README.md
120
+ - app/assets/config/effective_bootstrap_manifest.js
106
121
  - app/assets/images/icons/activity.svg
107
122
  - app/assets/images/icons/airplay.svg
108
123
  - app/assets/images/icons/alert-circle.svg
@@ -414,9 +429,13 @@ files:
414
429
  - app/assets/javascripts/effective_editor/image-resize.js
415
430
  - app/assets/javascripts/effective_editor/initialize.js.coffee
416
431
  - app/assets/javascripts/effective_editor/input.js
432
+ - app/assets/javascripts/effective_editor/magic-url.js
417
433
  - app/assets/javascripts/effective_editor/quill.js
418
434
  - app/assets/javascripts/effective_file/initialize.js.coffee
419
435
  - app/assets/javascripts/effective_file/input.js
436
+ - app/assets/javascripts/effective_has_many/initialize.js.coffee
437
+ - app/assets/javascripts/effective_has_many/input.js
438
+ - app/assets/javascripts/effective_has_many/jquery.sortable.js
420
439
  - app/assets/javascripts/effective_integer/initialize.js.coffee
421
440
  - app/assets/javascripts/effective_integer/input.js
422
441
  - app/assets/javascripts/effective_number_text/initialize.js.coffee
@@ -582,6 +601,7 @@ files:
582
601
  - app/assets/stylesheets/effective_editor/overrides.scss
583
602
  - app/assets/stylesheets/effective_editor/quill.scss
584
603
  - app/assets/stylesheets/effective_file/input.scss
604
+ - app/assets/stylesheets/effective_has_many/input.scss
585
605
  - app/assets/stylesheets/effective_radio/input.scss
586
606
  - app/assets/stylesheets/effective_rich_text_area/input.scss
587
607
  - app/assets/stylesheets/effective_select/bootstrap-theme.css
@@ -611,6 +631,7 @@ files:
611
631
  - app/models/effective/form_inputs/file_field.rb
612
632
  - app/models/effective/form_inputs/float_field.rb
613
633
  - app/models/effective/form_inputs/form_group.rb
634
+ - app/models/effective/form_inputs/has_many.rb
614
635
  - app/models/effective/form_inputs/hidden_field.rb
615
636
  - app/models/effective/form_inputs/integer_field.rb
616
637
  - app/models/effective/form_inputs/number_field.rb
@@ -666,7 +687,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
666
687
  - !ruby/object:Gem::Version
667
688
  version: '0'
668
689
  requirements: []
669
- rubygems_version: 3.1.4
690
+ rubygems_version: 3.1.2
670
691
  signing_key:
671
692
  specification_version: 4
672
693
  summary: Everything you need to get set up with bootstrap 4.