hot-glue 0.5.26 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +53 -34
- data/lib/generators/hot_glue/field_factory.rb +2 -2
- data/lib/generators/hot_glue/fields/association_field.rb +18 -10
- data/lib/generators/hot_glue/fields/attachment_field.rb +2 -2
- data/lib/generators/hot_glue/fields/field.rb +4 -3
- data/lib/generators/hot_glue/markup_templates/erb.rb +2 -3
- data/lib/generators/hot_glue/scaffold_generator.rb +31 -36
- data/lib/generators/hot_glue/templates/controller.rb.erb +5 -22
- data/lib/generators/hot_glue/templates/typeahead_controller.rb.erb +14 -0
- data/lib/generators/hot_glue/templates/typeahead_views/_thing.html.erb +5 -0
- data/lib/generators/hot_glue/templates/typeahead_views/index.html.erb +13 -0
- data/lib/generators/hot_glue/templates/typeahead_views/typeahead.scss +46 -0
- data/lib/generators/hot_glue/templates/typeahead_views/typeahead_controller.js +61 -0
- data/lib/generators/hot_glue/templates/typeahead_views/typeahead_results_controller.js +87 -0
- data/lib/generators/hot_glue/typeahead_generator.rb +105 -0
- data/lib/generators/hot_glue/typeahead_install_generator.rb +40 -0
- data/lib/hotglue/version.rb +1 -1
- metadata +10 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 822437dfda890941c29f2ff18b927c60a84279306c0937d354f69e18c67f51e5
|
4
|
+
data.tar.gz: 193b9bb92313ae7440c72d8d7598f918400136f7edabc70bf7ec7def1b4f96ed
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '0210383122eded31e46b9cc02bc6a7c230d95936f13635149c884774e762b178eb326e3722466a6ff8e365fd3d45782a05c0a60733d678499762ff49d6f7b58d'
|
7
|
+
data.tar.gz: 8cc9ed4dc85875d66dbd66ecac22908bfd6a8759588e37a2b26bb03522a16b8559ca0bc0ee0311a28a627f2f25af703ed744ea1aba6f8f1d48c5fbc35d28748a
|
data/README.md
CHANGED
@@ -772,7 +772,7 @@ Note that Hot Glue still generates a singular partial (`_form`) for both actions
|
|
772
772
|
<% end %>
|
773
773
|
```
|
774
774
|
|
775
|
-
This works for both regular fields, association fields
|
775
|
+
This works for both regular fields, association fields.
|
776
776
|
|
777
777
|
|
778
778
|
When mixing the show only, update show only, and Pundit features, notice that the show only + update show only will act to override whatever the policy might say.
|
@@ -954,38 +954,6 @@ This happens using two interconnected mechanisms:
|
|
954
954
|
please note that *creating* and *deleting* do not yet have a full & complete implementation: Your pages won't re-render the pages being viewed cross-peer (that is, between two users using the app at the same time) if the insertion or deletion causes the pagination to be off for another user.
|
955
955
|
|
956
956
|
|
957
|
-
### `--alt-foreign-key-lookup` (Foreign Key Lookups)
|
958
|
-
|
959
|
-
Example #1
|
960
|
-
`--alt-foreign-key-lookup=user_id{email}`
|
961
|
-
|
962
|
-
Let's assume a `Company` `has_many :company_users` and also a `Company` `has_many :users, through: :company_users`
|
963
|
-
|
964
|
-
Normally, you would be constructing a CompanyUsers downnest portal on the Company page. (Showing you only CompanyUsers associated with that company.)
|
965
|
-
|
966
|
-
A drop down of _all users in the_ database will be display on the screen where you create a new CompanyUser (join) record.
|
967
|
-
|
968
|
-
Let's say instead you don't want to expose the full list of all users to this controller, but instead make your user enter the full email address of the user to identify them.
|
969
|
-
|
970
|
-
Instead of a drop-down, the interface will present an input box for the user to supply an *search by* email.
|
971
|
-
|
972
|
-
Example #2
|
973
|
-
```
|
974
|
-
--alt-foreign-key-lookup='agent_id{email+}'
|
975
|
-
```
|
976
|
-
|
977
|
-
First, assume the current able has a `belongs_to` association for `agent_id` to a foreign model, `Agent`
|
978
|
-
|
979
|
-
Here, we would build a scaffold that would treat the foreign key `agent_id` as an alt lookup, allowing the user to search for foreign records by email (the agent's email).
|
980
|
-
|
981
|
-
The `+` symbol indicates to automatically make a new `agent` record (without it, the agent_id will be set to nil if there is no associated agent record found -- this may cause the update to fail, unless `optional: true` is on the belongs_to association.)
|
982
|
-
|
983
|
-
|
984
|
-
|
985
|
-
** Note: Current implementation does not work in conjunction with hawked associations to protect against users from accessing associated records not within scope.**
|
986
|
-
TODO: make it work with hawked associations to protect against users from accessing associated records not within scope
|
987
|
-
|
988
|
-
|
989
957
|
## "Thing" Label
|
990
958
|
|
991
959
|
Note that on a per model basis, you can also globally omit the label or set a unique label value using
|
@@ -1256,7 +1224,7 @@ end
|
|
1256
1224
|
```
|
1257
1225
|
|
1258
1226
|
|
1259
|
-
|
1227
|
+
be sure your factory code creates a local variable that follows this name
|
1260
1228
|
|
1261
1229
|
**<downcase association name>**_factory.<downcase association name>
|
1262
1230
|
|
@@ -1410,6 +1378,33 @@ end
|
|
1410
1378
|
|
1411
1379
|
```
|
1412
1380
|
|
1381
|
+
### Typeahead Foreign Keys
|
1382
|
+
|
1383
|
+
Let's go back to the first Books & Authors example.
|
1384
|
+
assuming you have created
|
1385
|
+
`bin/rails generate model Book title:string author_id:integer`
|
1386
|
+
and
|
1387
|
+
`bin/rails generate model Author name:string`
|
1388
|
+
and also added `has_many :books` to Author and `belongs_to :author` to Book
|
1389
|
+
|
1390
|
+
|
1391
|
+
You can now use a typeahead when editing the book. Instead of displaying the authors in a drop-down list, the authors will appear in a searchable typehead.
|
1392
|
+
|
1393
|
+
You will do these three things:
|
1394
|
+
|
1395
|
+
1. As a one-time setup step for your app, run
|
1396
|
+
`bin/rails generate hot_glue:install_typeahead`
|
1397
|
+
2. When generating a scaffold you want to make a typeahead association, use `--modify='parent_id{typeahead}'` where `parent_id` is the foreign key
|
1398
|
+
`bin/rails generate hot_glue:scaffold Book --include=title,author_id --modify='author_id{typeahead}'`
|
1399
|
+
3. Within each namespace, you will generate a special typeahead controller (it exists for the associated object to be searched on
|
1400
|
+
`bin/rails generate hot_glue:typehead Author`
|
1401
|
+
This will create a controller for `AuthorsTypeaheadController` that will allow text search against any *string* field on the `Author` model.
|
1402
|
+
This special generator takes flags `--namespace` as the normal generator and also `--search-by` to let you specify the list of fields you want to search by.
|
1403
|
+
|
1404
|
+
Your new and edit views that were built on books now give you a search box for the author spot. Notice that just making the selection
|
1405
|
+
puts the value into the search box and the id into a hidden field.
|
1406
|
+
|
1407
|
+
You need to making a selection *and* click "Save" to update the record.
|
1413
1408
|
|
1414
1409
|
### TinyMCE
|
1415
1410
|
1. `bundle add tinymce-rails` to add it to your Gemfile
|
@@ -1469,6 +1464,30 @@ bin/rails generate Thing --include=my_story --modify='my_story{tinymce}'
|
|
1469
1464
|
|
1470
1465
|
# VERSION HISTORY
|
1471
1466
|
|
1467
|
+
#### 2023-11-03 - v0.6.0
|
1468
|
+
|
1469
|
+
Typeahead Associations
|
1470
|
+
|
1471
|
+
You can now use a typeahead when editing any foreign key.
|
1472
|
+
|
1473
|
+
Instead of displaying the foreign key in a drop-down list, the associated record is now selectable
|
1474
|
+
from a searchable typehead input.
|
1475
|
+
|
1476
|
+
The typeahead is implemented with a native Stimulus JS pair of controllers and is a modern & clean replacement to the old typeahead options.
|
1477
|
+
|
1478
|
+
1. As a one-time setup step for your app, run
|
1479
|
+
`bin/rails generate hot_glue:install_typeahead`
|
1480
|
+
2. When generating a scaffold you want to make a typeahead association, use `--modify='parent_id{typeahead}'` where `parent_id` is the foreign key
|
1481
|
+
`bin/rails generate hot_glue:scaffold Book --include=title,author_id --modify='author_id{typeahead}'`
|
1482
|
+
3. Within each namespace, you will generate a special typeahead controller (it exists for the associated object to be searched on
|
1483
|
+
`bin/rails generate hot_glue:typehead Author`
|
1484
|
+
This will create a controller for `AuthorsTypeaheadController` that will allow text search against any *string* field on the `Author` model.
|
1485
|
+
This special generator takes flags `--namespace` like the normal generator and also `--search-by` to let you specify the list of fields you want to search by.
|
1486
|
+
|
1487
|
+
The example Books & Authors app with typeahead is here:
|
1488
|
+
|
1489
|
+
https://github.com/hot-glue-for-rails/BooksAndAuthorsWithTypeahead2
|
1490
|
+
|
1472
1491
|
|
1473
1492
|
#### 2023-10-23 - v0.5.26
|
1474
1493
|
|
@@ -53,7 +53,6 @@ class FieldFactory
|
|
53
53
|
hawk_keys: generator.hawk_keys,
|
54
54
|
auth: generator.auth,
|
55
55
|
class_name: generator.singular_class,
|
56
|
-
alt_lookups: generator.alt_lookups,
|
57
56
|
singular: generator.singular,
|
58
57
|
self_auth: generator.self_auth,
|
59
58
|
update_show_only: generator.update_show_only,
|
@@ -61,6 +60,7 @@ class FieldFactory
|
|
61
60
|
sample_file_path: generator.sample_file_path,
|
62
61
|
modify_as: generator.modify_as[name.to_sym] || nil,
|
63
62
|
display_as: generator.display_as[name.to_sym] || nil,
|
64
|
-
default_boolean_display: generator.default_boolean_display
|
63
|
+
default_boolean_display: generator.default_boolean_display,
|
64
|
+
namespace: generator.namespace_value)
|
65
65
|
end
|
66
66
|
end
|
@@ -5,12 +5,12 @@ class AssociationField < Field
|
|
5
5
|
|
6
6
|
attr_accessor :assoc_name, :assoc_class, :assoc
|
7
7
|
|
8
|
-
def initialize(
|
8
|
+
def initialize( class_name: , default_boolean_display:, display_as: ,
|
9
9
|
name: , singular: ,
|
10
10
|
update_show_only: ,
|
11
11
|
hawk_keys: , auth: , sample_file_path:, ownership_field: ,
|
12
12
|
attachment_data: nil , layout_strategy: , form_placeholder_labels: nil,
|
13
|
-
form_labels_position:, modify_as: , self_auth: )
|
13
|
+
form_labels_position:, modify_as: , self_auth: , namespace: )
|
14
14
|
super
|
15
15
|
|
16
16
|
@assoc_model = eval("#{class_name}.reflect_on_association(:#{assoc})")
|
@@ -48,10 +48,7 @@ class AssociationField < Field
|
|
48
48
|
|
49
49
|
def spec_setup_and_change_act(which_partial)
|
50
50
|
if which_partial == :update && update_show_only.include?(name)
|
51
|
-
|
52
|
-
elsif alt_lookups.keys.include?(name.to_s)
|
53
|
-
lookup = alt_lookups[name.to_s][:lookup_as]
|
54
|
-
" find(\"[name='#{singular}[__lookup_#{lookup}]']\").fill_in( with: #{assoc}1.#{lookup} )"
|
51
|
+
|
55
52
|
else
|
56
53
|
" #{name}_selector = find(\"[name='#{singular}[#{name}]']\").click \n" +
|
57
54
|
" #{name}_selector.first('option', text: #{assoc}1.name).select_option"
|
@@ -82,10 +79,21 @@ class AssociationField < Field
|
|
82
79
|
assoc_name = name.to_s.gsub("_id","")
|
83
80
|
assoc = eval("#{class_name}.reflect_on_association(:#{assoc_name})")
|
84
81
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
82
|
+
if modify_as && modify_as[:typeahead]
|
83
|
+
search_url = "#{namespace ? namespace + "_" : ""}#{assoc.plural_name}_typeahead_index_url"
|
84
|
+
|
85
|
+
"<div class='typeahead typeahead--#{singular}--#{assoc.name}_id'
|
86
|
+
data-controller='typeahead'
|
87
|
+
data-typeahead-url-value='<%= #{search_url} %>'
|
88
|
+
data-typeahead-typeahead-results-outlet='#search-results'>
|
89
|
+
<%= text_field_tag :#{assoc.plural_name}_query, '', placeholder: 'Search #{assoc.plural_name}', class: 'search__input',
|
90
|
+
data: { action: 'keyup->typeahead#fetchResults keydown->typeahead#navigateResults', typeahead_target: 'query' },
|
91
|
+
autofocus: true,
|
92
|
+
autocomplete: 'off',
|
93
|
+
value: #{singular}.try(:#{assoc.name}).try(:name) %>
|
94
|
+
<%= f.hidden_field :#{assoc.name}_id, value: #{singular}.try(:#{assoc.name}).try(:id), 'data-typeahead-target': 'hiddenFormValue' %>
|
95
|
+
<div data-typeahead-target='results'></div>
|
96
|
+
</div>"
|
89
97
|
else
|
90
98
|
if assoc.nil?
|
91
99
|
exit_message = "*** Oops. on the #{class_name} object, there doesn't seem to be an association called '#{assoc_name}'"
|
@@ -1,10 +1,10 @@
|
|
1
1
|
class AttachmentField < Field
|
2
2
|
attr_accessor :attachment_data
|
3
|
-
def initialize(name:, class_name:,
|
3
|
+
def initialize(name:, class_name:, default_boolean_display: ,
|
4
4
|
display_as:,
|
5
5
|
singular:, update_show_only:, hawk_keys:, auth:,
|
6
6
|
sample_file_path: nil, attachment_data:, ownership_field:, layout_strategy: ,
|
7
|
-
form_placeholder_labels: , form_labels_position:, modify_as:, self_auth: )
|
7
|
+
form_placeholder_labels: , form_labels_position:, modify_as:, self_auth: , namespace: )
|
8
8
|
super
|
9
9
|
|
10
10
|
@attachment_data = attachment_data
|
@@ -5,11 +5,10 @@ class Field
|
|
5
5
|
:hawk_keys, :layout_strategy, :limit, :modify_as, :name, :object, :sample_file_path,
|
6
6
|
:self_auth,
|
7
7
|
:singular_class, :singular, :sql_type, :ownership_field,
|
8
|
-
:update_show_only
|
8
|
+
:update_show_only, :namespace
|
9
9
|
|
10
10
|
def initialize(
|
11
11
|
auth: ,
|
12
|
-
alt_lookups: ,
|
13
12
|
attachment_data: nil,
|
14
13
|
class_name: ,
|
15
14
|
default_boolean_display: ,
|
@@ -24,7 +23,8 @@ class Field
|
|
24
23
|
sample_file_path: nil,
|
25
24
|
singular: ,
|
26
25
|
update_show_only:,
|
27
|
-
self_auth
|
26
|
+
self_auth:,
|
27
|
+
namespace:
|
28
28
|
)
|
29
29
|
@name = name
|
30
30
|
@layout_strategy = layout_strategy
|
@@ -43,6 +43,7 @@ class Field
|
|
43
43
|
|
44
44
|
@self_auth = self_auth
|
45
45
|
@default_boolean_display = default_boolean_display
|
46
|
+
@namesapce = namespace
|
46
47
|
|
47
48
|
# TODO: remove knowledge of subclasses from Field
|
48
49
|
unless self.class == AttachmentField
|
@@ -8,7 +8,7 @@ module HotGlue
|
|
8
8
|
:inline_list_labels, :layout_object,
|
9
9
|
:columns, :col_identifier, :singular,
|
10
10
|
:form_placeholder_labels, :hawk_keys, :update_show_only,
|
11
|
-
:
|
11
|
+
:attachments, :show_only, :columns_map, :pundit
|
12
12
|
|
13
13
|
|
14
14
|
def initialize(singular:, singular_class: ,
|
@@ -17,7 +17,7 @@ module HotGlue
|
|
17
17
|
ownership_field: , form_labels_position: ,
|
18
18
|
inline_list_labels: ,
|
19
19
|
form_placeholder_labels:, hawk_keys: ,
|
20
|
-
update_show_only:,
|
20
|
+
update_show_only:, attachments: , columns_map:, pundit: )
|
21
21
|
|
22
22
|
@singular = singular
|
23
23
|
@singular_class = singular_class
|
@@ -38,7 +38,6 @@ module HotGlue
|
|
38
38
|
@form_placeholder_labels = form_placeholder_labels
|
39
39
|
@hawk_keys = hawk_keys
|
40
40
|
@update_show_only = update_show_only
|
41
|
-
@alt_lookups = alt_lookups
|
42
41
|
@attachments = attachments
|
43
42
|
end
|
44
43
|
|
@@ -16,14 +16,16 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
16
16
|
hook_for :form_builder, :as => :scaffold
|
17
17
|
|
18
18
|
source_root File.expand_path('templates', __dir__)
|
19
|
-
attr_accessor :
|
19
|
+
attr_accessor :attachments, :auth, :big_edit, :button_icons, :bootstrap_column_width, :columns,
|
20
20
|
:default_boolean_display,
|
21
21
|
:display_as, :downnest_children, :downnest_object, :hawk_keys, :layout_object,
|
22
22
|
:modify_as,
|
23
23
|
:nest_with, :path, :plural, :sample_file_path, :show_only_data, :singular,
|
24
24
|
:singular_class, :smart_layout, :stacked_downnesting, :update_show_only, :ownership_field,
|
25
25
|
:layout_strategy, :form_placeholder_labels, :form_labels_position, :pundit,
|
26
|
-
:self_auth
|
26
|
+
:self_auth, :namespace_value
|
27
|
+
# important: using an attr_accessor called :namespace indirectly causes a conflict with Rails class_name method
|
28
|
+
# so we use namespace_value instead
|
27
29
|
|
28
30
|
class_option :singular, type: :string, default: nil
|
29
31
|
class_option :plural, type: :string, default: nil
|
@@ -160,6 +162,7 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
160
162
|
|
161
163
|
@plural = options['plural'] || @singular.pluralize # respects what you set in inflections.rb, to override, use plural option
|
162
164
|
@namespace = options['namespace'] || nil
|
165
|
+
@namespace_value = @namespace
|
163
166
|
use_controller_name = plural.titleize.gsub(" ", "")
|
164
167
|
@controller_build_name = ((@namespace.titleize.gsub(" ", "") + "::" if @namespace) || "") + use_controller_name + "Controller"
|
165
168
|
@controller_build_folder = use_controller_name.underscore
|
@@ -224,6 +227,10 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
224
227
|
@modify_as[key.to_sym] = {enum: :partials}
|
225
228
|
elsif $2 == "tinymce"
|
226
229
|
@modify_as[key.to_sym] = {tinymce: 1}
|
230
|
+
elsif $2 == "typeahead"
|
231
|
+
@modify_as[key.to_sym] = {typeahead: 1}
|
232
|
+
|
233
|
+
|
227
234
|
else
|
228
235
|
raise "unknown modification direction #{$2}"
|
229
236
|
end
|
@@ -251,26 +258,6 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
251
258
|
# instead of a drop-down for the foreign entity, a text field will be presented
|
252
259
|
# You must ALSO use a factory that contains a parameter of the same name as the 'value' (for example, `xyz_email`)
|
253
260
|
|
254
|
-
alt_lookups_entry = options['alt_foreign_key_lookup'].split(",")
|
255
|
-
@alt_lookups = {}
|
256
|
-
@alt_foreign_key_lookup = alt_lookups_entry.each do |setting|
|
257
|
-
setting =~ /(.*){(.*)}/
|
258
|
-
key, lookup_as = $1, $2
|
259
|
-
assoc = eval("#{class_name}.reflect_on_association(:#{key.to_s.gsub("_id", "")}).class_name")
|
260
|
-
|
261
|
-
data = { lookup_as: lookup_as.gsub("+", ""),
|
262
|
-
assoc: assoc,
|
263
|
-
with_create: lookup_as.include?("+") }
|
264
|
-
@alt_lookups[key] = data
|
265
|
-
end
|
266
|
-
|
267
|
-
puts "------ ALT LOOKUPS for #{@alt_lookups}"
|
268
|
-
|
269
|
-
@update_alt_lookups = @alt_lookups.collect { |key, value|
|
270
|
-
@update_show_only.include?(key) ?
|
271
|
-
{ key: value }
|
272
|
-
: nil }.compact
|
273
|
-
|
274
261
|
@label = options['label'] || (eval("#{class_name}.class_variable_defined?(:@@table_label_singular)") ? eval("#{class_name}.class_variable_get(:@@table_label_singular)") : singular.gsub("_", " ").titleize)
|
275
262
|
@list_label_heading = options['list_label_heading'] || (eval("#{class_name}.class_variable_defined?(:@@table_label_plural)") ? eval("#{class_name}.class_variable_get(:@@table_label_plural)") : plural.gsub("_", " ").upcase)
|
276
263
|
|
@@ -449,6 +436,25 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
449
436
|
@columns_map[col] = this_column_object.field
|
450
437
|
end
|
451
438
|
|
439
|
+
@columns_map.each do |key, field|
|
440
|
+
if field.is_a?(AssociationField)
|
441
|
+
if @modify_as && @modify_as[key] && @modify_as[key][:typeahead]
|
442
|
+
assoc_name = field.assoc_name
|
443
|
+
file_path = "app/controllers/#{namespace ? namspace + "/" : ""}#{assoc_name.pluralize}_typeahead_controller.rb"
|
444
|
+
|
445
|
+
if ! File.exist?(file_path)
|
446
|
+
|
447
|
+
assoc_model = eval("#{class_name}.reflect_on_association(:#{field.assoc_name})")
|
448
|
+
assoc_class = assoc_model.class_name
|
449
|
+
puts "##############################################"
|
450
|
+
puts "WARNING: you specified --modify=#{key}{typeahead} but there is no file at `#{file_path}`; please create one with:"
|
451
|
+
puts "bin/rails generate hot_glue:typeahead #{assoc_class} #{namespace ? " --namespace=\#{namespace}" : ""}"
|
452
|
+
puts "##############################################"
|
453
|
+
end
|
454
|
+
end
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
452
458
|
# create the template object
|
453
459
|
if @markup == "erb"
|
454
460
|
@template_builder = HotGlue::ErbTemplate.new(
|
@@ -464,7 +470,6 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
464
470
|
ownership_field: @ownership_field,
|
465
471
|
form_labels_position: @form_labels_position,
|
466
472
|
form_placeholder_labels: @form_placeholder_labels,
|
467
|
-
alt_lookups: @alt_lookups,
|
468
473
|
attachments: @attachments,
|
469
474
|
columns_map: @columns_map,
|
470
475
|
pundit: @pundit,
|
@@ -672,24 +677,18 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
672
677
|
end
|
673
678
|
|
674
679
|
def fields_filtered_for_email_lookups
|
675
|
-
@columns
|
680
|
+
@columns
|
676
681
|
end
|
677
682
|
|
678
683
|
def creation_syntax
|
679
|
-
merge_with = @alt_lookups.collect { |key, data|
|
680
|
-
"#{data[:assoc].downcase}: #{data[:assoc].downcase}_factory.#{data[:assoc].downcase}"
|
681
|
-
}.join(", ")
|
682
|
-
|
683
684
|
if @factory_creation == ''
|
684
|
-
"@#{singular } = #{ class_name }.create(modified_params
|
685
|
+
"@#{singular } = #{ class_name }.create(modified_params)"
|
685
686
|
else
|
686
687
|
"#{@factory_creation}\n" +
|
687
688
|
" @#{singular } = factory.#{singular}"
|
688
689
|
end
|
689
690
|
end
|
690
691
|
|
691
|
-
|
692
|
-
|
693
692
|
def formats
|
694
693
|
[format]
|
695
694
|
end
|
@@ -1285,11 +1284,7 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
1285
1284
|
}.join("")
|
1286
1285
|
end
|
1287
1286
|
|
1288
|
-
|
1289
|
-
@alt_lookups.collect { |key, data|
|
1290
|
-
".tap{ |ary| ary.delete('__lookup_#{data[:lookup_as]}') }"
|
1291
|
-
}.join("")
|
1292
|
-
end
|
1287
|
+
|
1293
1288
|
|
1294
1289
|
def nested_for_turbo_id_list_constructor
|
1295
1290
|
if @nested_set.any?
|
@@ -98,15 +98,10 @@ class <%= controller_class_name %> < <%= controller_descends_from %>
|
|
98
98
|
end
|
99
99
|
|
100
100
|
def create
|
101
|
-
<% if @
|
102
|
-
" #{data[:assoc].downcase} = #{data[:assoc]}.#{data[:with_create] ? "find_or_create_by" : "find_by"}(#{data[:lookup_as]}: #{ singular_name }_params[:__lookup_#{data[:lookup_as]}])\n"
|
103
|
-
}.join("/n") %><% end %> <% merge_lookups = @alt_lookups.collect{|key, data| "#{key.gsub("_id", "")}: #{key.gsub("_id", "")}" }.join(",") %>
|
104
|
-
modified_params = modify_date_inputs_on_params(<%= singular_name %>_params.dup<%= controller_update_params_tap_away_alt_lookups %>, <%= current_user_object %>, <%= datetime_fields_list %>) <% if @object_owner_sym && eval("#{class_name}.reflect_on_association(:#{@object_owner_sym})").class == ActiveRecord::Reflection::BelongsToReflection %>
|
101
|
+
modified_params = modify_date_inputs_on_params(<%= singular_name %>_params.dup, <%= current_user_object %>, <%= datetime_fields_list %>) <% if @object_owner_sym && eval("#{class_name}.reflect_on_association(:#{@object_owner_sym})").class == ActiveRecord::Reflection::BelongsToReflection %>
|
105
102
|
modified_params = modified_params.merge(<%= @object_owner_sym %>: <%= @object_owner_eval %>) <% elsif @object_owner_optional && any_nested? %>
|
106
103
|
modified_params = modified_params.merge(<%= @object_owner_name %> ? {<%= @object_owner_sym %>: <%= @object_owner_eval %>} : {}) <% elsif !@object_owner_eval.empty? %>
|
107
|
-
modified_params = modified_params.merge(<%= @object_owner_eval %>) <% end
|
108
|
-
modified_params = modified_params.merge(<%= merge_lookups %>)
|
109
|
-
<% end %>
|
104
|
+
modified_params = modified_params.merge(<%= @object_owner_eval %>) <% end %>
|
110
105
|
|
111
106
|
<% if @hawk_keys.any? %>
|
112
107
|
modified_params = hawk_params({<%= hawk_to_ruby %>}, modified_params)<% end %>
|
@@ -151,31 +146,19 @@ class <%= controller_class_name %> < <%= controller_descends_from %>
|
|
151
146
|
<% end %><% if @build_update_action %> def update
|
152
147
|
flash[:notice] = +''
|
153
148
|
flash[:alert] = nil
|
154
|
-
<% if @alt_lookups.filter{|key,d| ! @update_show_only.include?(key.to_sym) }.any? %><%= @alt_lookups.filter{|key,d| ! @update_show_only.include?(key.to_sym) }.collect{|key, data|
|
155
|
-
" #{data[:assoc].downcase} = #{data[:assoc]}.#{data[:with_create] ? "find_or_create_by" : "find_by"}(#{data[:lookup_as]}: #{ singular_name }_params[:__lookup_#{data[:lookup_as]}])\n"
|
156
|
-
}.join("\n") %><% end %> <% merge_lookups = @alt_lookups.filter{|key,d| ! @update_show_only.include?(key.to_sym) }.collect{|key, data| "#{key.gsub("_id", "")}: #{key.gsub("_id", "")}" }.join(",") %>
|
157
149
|
<% @magic_buttons.each do |button| %>
|
158
150
|
if <%= singular_name %>_params[:__<%= button %>]
|
159
151
|
@<%= singular_name %>.<%= button %>!
|
160
152
|
flash[:notice] << "<% singular %> <%= button.titlecase %>."
|
161
153
|
end
|
162
154
|
<% end %>
|
163
|
-
modified_params = modify_date_inputs_on_params(<% if @update_show_only %>update_<% end %><%= singular_name %>_params.dup<%=
|
155
|
+
modified_params = modify_date_inputs_on_params(<% if @update_show_only %>update_<% end %><%= singular_name %>_params.dup<%= controller_update_params_tap_away_magic_buttons %>, <%= current_user_object %>, <%= datetime_fields_list %>) <% if @object_owner_sym && eval("#{class_name}.reflect_on_association(:#{@object_owner_sym})").class == ActiveRecord::Reflection::BelongsToReflection %>
|
164
156
|
modified_params = modified_params.merge(<%= @object_owner_sym %>: <%= @object_owner_eval %>) <% elsif @object_owner_optional && any_nested? %>
|
165
157
|
modified_params = modified_params.merge(<%= @object_owner_name %> ? {<%= @object_owner_sym %>: <%= @object_owner_eval %>} : {}) <% elsif ! @object_owner_eval.empty? && !@self_auth%>
|
166
|
-
modified_params = modified_params.merge(<%= @object_owner_eval %>) <% end
|
167
|
-
modified_params = modified_params.merge(<%= merge_lookups %>)
|
168
|
-
<% end %>
|
158
|
+
modified_params = modified_params.merge(<%= @object_owner_eval %>) <% end %>
|
169
159
|
|
170
160
|
<% if @hawk_keys.any? %> modified_params = hawk_params({<%= hawk_to_ruby %>}, modified_params)<% end %>
|
171
|
-
|
172
|
-
unless @factory_creation.include?("#{data[:assoc].downcase} = ")
|
173
|
-
" #{data[:assoc].downcase} = #{data[:assoc]}.#{data[:with_create] ? "find_or_create_by" : "find_by"}(#{data[:lookup_as]}: #{ singular_name }_params[:__lookup_#{data[:lookup_as]}])\n"
|
174
|
-
end
|
175
|
-
}.join("/n") %><% end %><% if (@update_alt_lookups).any? %>
|
176
|
-
<%= @update_alt_lookups.collect{|key, data|
|
177
|
-
" @#{ singular_name }.#{key.gsub("_id", "")} = #{key.gsub("_id", "")}"
|
178
|
-
}.join("/n") %><% end %><%= controller_attachment_orig_filename_pickup_syntax %>
|
161
|
+
<%= controller_attachment_orig_filename_pickup_syntax %>
|
179
162
|
<% if @pundit %>
|
180
163
|
if @<%= singular_name %>.attributes = modified_params
|
181
164
|
authorize @<%= singular_name %>
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class <%= ((@namespace.titleize.gsub(" ", "") + "::" if @namespace) || "") + @plural.titleize.gsub(" ", "") + "TypeaheadController" %> < <%= controller_descends_from %>
|
2
|
+
# regenerate this controller with
|
3
|
+
<% if defined?(RuboCop) %># rubocop:disable Layout/LineLength
|
4
|
+
<% end %># <%= regenerate_me_code %><% if defined?(RuboCop) %>
|
5
|
+
# rubocop:enable Layout/LineLength <% end %>
|
6
|
+
|
7
|
+
def index
|
8
|
+
query = params[:query]
|
9
|
+
|
10
|
+
@<%= @plural %> = <%= @singular.titleize.gsub(" ", "") %>.where("<%= @search_by.collect{|search| "LOWER(#{search}) LIKE ?" }.join(" OR ") %>", <%= @search_by.collect{|search| "\"%\#{query.downcase}%\"" }.join(", ") %>).limit(10)
|
11
|
+
|
12
|
+
render layout: false
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
|
2
|
+
<div class="typeahead-results__<%= @plural %>"
|
3
|
+
data-controller="typeahead-results"
|
4
|
+
data-typeahead-results-typeahead-outlet=".typeahead--book--<%= @singular %>_id"
|
5
|
+
data-typeahead-results-current-class="search__result--current" >
|
6
|
+
<ul class="search__results" data-typeahead-results-target="result">
|
7
|
+
<\% if @<%= @plural %>.any? %>
|
8
|
+
<\%= render partial: "<%= @singular %>", collection: @<%= @plural %> %>
|
9
|
+
<\% else %>
|
10
|
+
<li class="search__result search__result--empty">No results</li>
|
11
|
+
<\% end %>
|
12
|
+
</ul>
|
13
|
+
</div>
|
@@ -0,0 +1,46 @@
|
|
1
|
+
.typeahead {
|
2
|
+
|
3
|
+
}
|
4
|
+
|
5
|
+
.search__input {
|
6
|
+
border: 1px solid gray;
|
7
|
+
border-radius: 2px;
|
8
|
+
padding: 10px;
|
9
|
+
font-size: 16px;
|
10
|
+
box-sizing: border-box;
|
11
|
+
}
|
12
|
+
|
13
|
+
[data-typeahead-target="results"] {
|
14
|
+
position: absolute;
|
15
|
+
}
|
16
|
+
|
17
|
+
.search__results {
|
18
|
+
border: 1px solid gray;
|
19
|
+
border-radius: 2px;
|
20
|
+
border-top: none;
|
21
|
+
margin: 0;
|
22
|
+
padding: 0;
|
23
|
+
list-style-type: none;
|
24
|
+
top: -1px;
|
25
|
+
}
|
26
|
+
|
27
|
+
.search__result {
|
28
|
+
span.search-result-item {
|
29
|
+
text-decoration: none;
|
30
|
+
color: black;
|
31
|
+
padding: 10px;
|
32
|
+
display: block;
|
33
|
+
cursor: pointer;
|
34
|
+
&:hover {
|
35
|
+
background: #86b7fe;
|
36
|
+
}
|
37
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
.search__result--current, .search__result:hover {
|
41
|
+
background: #e0e0e0;
|
42
|
+
}
|
43
|
+
|
44
|
+
.search__result--empty {
|
45
|
+
padding: 10px;
|
46
|
+
}
|
@@ -0,0 +1,61 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
2
|
+
|
3
|
+
export default class extends Controller {
|
4
|
+
static targets = [ "query", "results", "hiddenFormValue" ]
|
5
|
+
static values = { url: String }
|
6
|
+
static outlets = [ "typeahead-results" ]
|
7
|
+
|
8
|
+
disconnect() {
|
9
|
+
this.reset()
|
10
|
+
}
|
11
|
+
|
12
|
+
fetchResults() {
|
13
|
+
if(this.query == "") {
|
14
|
+
this.reset()
|
15
|
+
return
|
16
|
+
}
|
17
|
+
|
18
|
+
if(this.query == this.previousQuery) {
|
19
|
+
return
|
20
|
+
}
|
21
|
+
this.previousQuery = this.query
|
22
|
+
|
23
|
+
const url = new URL(this.urlValue)
|
24
|
+
url.searchParams.append("query", this.query)
|
25
|
+
|
26
|
+
this.abortPreviousFetchRequest()
|
27
|
+
|
28
|
+
this.abortController = new AbortController()
|
29
|
+
|
30
|
+
fetch(url, { signal: this.abortController.signal })
|
31
|
+
.then(response => response.text())
|
32
|
+
.then(html => {
|
33
|
+
this.resultsTarget.innerHTML = html
|
34
|
+
})
|
35
|
+
.catch(() => {})
|
36
|
+
}
|
37
|
+
|
38
|
+
navigateResults(event) {
|
39
|
+
if(this.hasSearchResultsOutlet) {
|
40
|
+
this.searchResultsOutlet.navigateResults(event)
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
// private
|
45
|
+
|
46
|
+
reset() {
|
47
|
+
this.resultsTarget.innerHTML = ""
|
48
|
+
this.queryTarget.value = ""
|
49
|
+
this.previousQuery = null
|
50
|
+
}
|
51
|
+
|
52
|
+
abortPreviousFetchRequest() {
|
53
|
+
if(this.abortController) {
|
54
|
+
this.abortController.abort()
|
55
|
+
}
|
56
|
+
}
|
57
|
+
|
58
|
+
get query() {
|
59
|
+
return this.queryTarget.value
|
60
|
+
}
|
61
|
+
}
|
@@ -0,0 +1,87 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
2
|
+
|
3
|
+
const upKey = 38
|
4
|
+
const downKey = 40
|
5
|
+
const enterKey = 13
|
6
|
+
const navigationKeys = [upKey, downKey, enterKey]
|
7
|
+
|
8
|
+
export default class extends Controller {
|
9
|
+
static classes = [ "current" ]
|
10
|
+
static targets = [ "result" ]
|
11
|
+
static outlets = [ "typeahead" ]
|
12
|
+
|
13
|
+
connect() {
|
14
|
+
this.currentResultIndex = 0
|
15
|
+
|
16
|
+
const allElements = this.resultTarget.querySelectorAll(".search-result-item");
|
17
|
+
|
18
|
+
allElements.forEach((element, index) => {
|
19
|
+
element.addEventListener("click", () => {
|
20
|
+
// Call the searchItemClicked member function when the element is clicked
|
21
|
+
this.searchItemClicked(element, index);
|
22
|
+
});
|
23
|
+
})
|
24
|
+
this.selectCurrentResult()
|
25
|
+
}
|
26
|
+
|
27
|
+
searchItemClicked(element, index) {
|
28
|
+
const result_value = element.dataset.value;
|
29
|
+
const result_id = element.dataset.id;
|
30
|
+
|
31
|
+
// how to pass this to the search controller, set the field value and clear out the search
|
32
|
+
console.log("search item clicked...", result_value, result_id)
|
33
|
+
|
34
|
+
this.typeaheadOutlets.forEach(outlet => {
|
35
|
+
outlet.hiddenFormValueTarget.value = result_id;
|
36
|
+
outlet.queryTarget.value = result_value;
|
37
|
+
})
|
38
|
+
|
39
|
+
this.resultTarget.innerHTML = "";
|
40
|
+
}
|
41
|
+
|
42
|
+
navigateResults(event) {
|
43
|
+
if(!navigationKeys.includes(event.keyCode)) {
|
44
|
+
return
|
45
|
+
}
|
46
|
+
|
47
|
+
event.preventDefault()
|
48
|
+
|
49
|
+
switch(event.keyCode) {
|
50
|
+
case downKey:
|
51
|
+
this.selectNextResult()
|
52
|
+
break;
|
53
|
+
case upKey:
|
54
|
+
this.selectPreviousResult()
|
55
|
+
break;
|
56
|
+
case enterKey:
|
57
|
+
this.goToSelectedResult()
|
58
|
+
break;
|
59
|
+
}
|
60
|
+
}
|
61
|
+
|
62
|
+
// private
|
63
|
+
|
64
|
+
selectCurrentResult() {
|
65
|
+
this.resultTargets.forEach((element, index) => {
|
66
|
+
element.classList.toggle(this.currentClass, index == this.currentResultIndex)
|
67
|
+
})
|
68
|
+
}
|
69
|
+
|
70
|
+
selectNextResult() {
|
71
|
+
if(this.currentResultIndex < this.resultTargets.length - 1) {
|
72
|
+
this.currentResultIndex++
|
73
|
+
this.selectCurrentResult()
|
74
|
+
}
|
75
|
+
}
|
76
|
+
|
77
|
+
selectPreviousResult() {
|
78
|
+
if(this.currentResultIndex > 0) {
|
79
|
+
this.currentResultIndex--
|
80
|
+
this.selectCurrentResult()
|
81
|
+
}
|
82
|
+
}
|
83
|
+
|
84
|
+
goToSelectedResult() {
|
85
|
+
this.resultTargets[this.currentResultIndex].firstElementChild.click()
|
86
|
+
}
|
87
|
+
}
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module HotGlue
|
2
|
+
class TypeaheadGenerator < Rails::Generators::Base
|
3
|
+
source_root File.expand_path('templates', __dir__)
|
4
|
+
class_option :namespace, type: :string, default: nil
|
5
|
+
class_option :search_by, type: :string, default: nil
|
6
|
+
|
7
|
+
def filepath_prefix
|
8
|
+
# todo: inject the context
|
9
|
+
'spec/dummy/' if $INTERNAL_SPECS
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
def initialize(*meta_args)
|
14
|
+
super
|
15
|
+
begin
|
16
|
+
@the_object = eval(meta_args[0][0])
|
17
|
+
rescue StandardError => e
|
18
|
+
message = "*** Oops: It looks like there is no object for #{meta_args[0][0]}. Please define the object + database table first."
|
19
|
+
puts message
|
20
|
+
raise(HotGlue::Error, message)
|
21
|
+
end
|
22
|
+
|
23
|
+
@singular = args.first.tableize.singularize
|
24
|
+
|
25
|
+
@plural = args.first.tableize.pluralize
|
26
|
+
@namespace = options['namespace']
|
27
|
+
|
28
|
+
if options['search_by']
|
29
|
+
@search_by = options['search_by'].split(",")
|
30
|
+
else
|
31
|
+
# todo: read the fields off the table
|
32
|
+
|
33
|
+
eligible_columns = @the_object.columns.filter{ |column|
|
34
|
+
column.sql_type == "character varying"
|
35
|
+
}
|
36
|
+
columns = eligible_columns.map(&:name).map(&:to_sym).reject { |field| [:id, :created_at, :updated_at].include?(field) }
|
37
|
+
@search_by = columns.collect(&:to_s)
|
38
|
+
|
39
|
+
end
|
40
|
+
@meta_args = meta_args
|
41
|
+
dest_file = "#{'spec/dummy/' if $INTERNAL_SPECS}app/controllers/#{@namespace ? @namespace + "/" : ""}#{@plural }_typeahead_controller.rb"
|
42
|
+
template "typeahead_controller.rb.erb", dest_file
|
43
|
+
|
44
|
+
|
45
|
+
dirname = "app/views/#{@namespace ? @namespace + "/" : ""}#{@plural}_typeahead"
|
46
|
+
|
47
|
+
if !Dir.exist?(dirname)
|
48
|
+
Dir.mkdir dirname
|
49
|
+
end
|
50
|
+
{"typeahead_views/_thing.html.erb" => "_#{@singular}.html.erb",
|
51
|
+
"typeahead_views/index.html.erb" => "index.html.erb"}.each do |source_filename, dest_filename|
|
52
|
+
|
53
|
+
dest_filepath = File.join("#{'spec/dummy/' if $INTERNAL_SPECS}app/views#{namespace_with_dash}",
|
54
|
+
"#{@plural}_typeahead", dest_filename)
|
55
|
+
|
56
|
+
template source_filename, dest_filepath
|
57
|
+
text = File.read(dest_filepath)
|
58
|
+
text.gsub!('<\\%=', '<%=' )
|
59
|
+
text.gsub!('<\\%', '<%' )
|
60
|
+
File.open(dest_filepath, "w") { |f| f.write text }
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
puts ""
|
65
|
+
puts "be sure to add this your config/routes.rb file:"
|
66
|
+
puts ""
|
67
|
+
if @namespace
|
68
|
+
puts "namespace :#{@namespace} do"
|
69
|
+
end
|
70
|
+
puts " resources :#{@plural}_typeahead, only: [:index]"
|
71
|
+
if @namespace
|
72
|
+
puts "end"
|
73
|
+
end
|
74
|
+
puts ""
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
def namespace_with_dash
|
80
|
+
if @namespace
|
81
|
+
"/#{@namespace}"
|
82
|
+
else
|
83
|
+
""
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def filepath_prefix
|
88
|
+
'spec/dummy/' if $INTERNAL_SPECS
|
89
|
+
end
|
90
|
+
|
91
|
+
def controller_descends_from
|
92
|
+
if defined?(@namespace.titlecase.gsub(" ", "") + "::BaseController")
|
93
|
+
@namespace.titlecase.gsub(" ", "") + "::BaseController"
|
94
|
+
else
|
95
|
+
"ApplicationController"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def regenerate_me_code
|
100
|
+
"bin/rails generate hot_glue:typeahead #{ @meta_args[0][0] } #{@meta_args[1].collect { |x| x.gsub(/\s*=\s*([\S\s]+)/, '=\'\1\'') }.join(" ")}"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module HotGlue
|
2
|
+
class TypeaheadInstallGenerator < Rails::Generators::Base
|
3
|
+
source_root File.expand_path('templates', __dir__)
|
4
|
+
class_option :namespace, type: :string, default: nil
|
5
|
+
|
6
|
+
def filepath_prefix
|
7
|
+
# todo: inject the context
|
8
|
+
'spec/dummy/' if $INTERNAL_SPECS
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
def initialize(*args) #:nodoc:
|
13
|
+
super
|
14
|
+
|
15
|
+
copy_file "typeahead_views/typeahead.scss", "#{'spec/dummy/' if $INTERNAL_SPECS}app/assets/stylesheets/typeahead.scss"
|
16
|
+
|
17
|
+
|
18
|
+
try = ["application.scss", "application.bootstrap.scss"]
|
19
|
+
try.each do |filename|
|
20
|
+
main_scss_file = "#{'spec/dummy/' if $INTERNAL_SPECS}app/assets/stylesheets/#{filename}"
|
21
|
+
if File.exist?(main_scss_file)
|
22
|
+
insert_into_file main_scss_file do
|
23
|
+
"@import 'typeahead';\n"
|
24
|
+
end
|
25
|
+
puts "Inserted @import 'typeahead'; into #{main_scss_file}"
|
26
|
+
break
|
27
|
+
else
|
28
|
+
# puts "Could not find #{main_scss_file}. Please add the following line to your main scss file: @import 'typeahead';"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
copy_file "typeahead_views/typeahead_controller.js", "#{'spec/dummy/' if $INTERNAL_SPECS}app/javascript/controllers/typeahead_controller.js"
|
32
|
+
copy_file "typeahead_views/typeahead_results_controller.js", "#{'spec/dummy/' if $INTERNAL_SPECS}app/javascript/controllers/typeahead_results_controller.js"
|
33
|
+
|
34
|
+
system("bin/rails stimulus:manifest:update")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
|
data/lib/hotglue/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hot-glue
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jason Fleetwood-Boldt
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-11-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -132,6 +132,14 @@ files:
|
|
132
132
|
- lib/generators/hot_glue/templates/themes/hotglue_scaffold_like_bootstrap.scss
|
133
133
|
- lib/generators/hot_glue/templates/themes/hotglue_scaffold_like_los_gatos.scss
|
134
134
|
- lib/generators/hot_glue/templates/themes/hotglue_scaffold_like_mountain_view.scss
|
135
|
+
- lib/generators/hot_glue/templates/typeahead_controller.rb.erb
|
136
|
+
- lib/generators/hot_glue/templates/typeahead_views/_thing.html.erb
|
137
|
+
- lib/generators/hot_glue/templates/typeahead_views/index.html.erb
|
138
|
+
- lib/generators/hot_glue/templates/typeahead_views/typeahead.scss
|
139
|
+
- lib/generators/hot_glue/templates/typeahead_views/typeahead_controller.js
|
140
|
+
- lib/generators/hot_glue/templates/typeahead_views/typeahead_results_controller.js
|
141
|
+
- lib/generators/hot_glue/typeahead_generator.rb
|
142
|
+
- lib/generators/hot_glue/typeahead_install_generator.rb
|
135
143
|
- lib/hot-glue.rb
|
136
144
|
- lib/hotglue/engine.rb
|
137
145
|
- lib/hotglue/version.rb
|