hot-glue 0.5.26 → 0.6.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +2 -2
- data/README.md +78 -50
- data/lib/generators/hot_glue/field_factory.rb +2 -3
- 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 +67 -0
- data/lib/generators/hot_glue/templates/typeahead_views/typeahead_results_controller.js +85 -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: 8ffc84538112521ab7f15e7c0d234de7ffc5e5231e49a6ffb3cb3fd77c4f0545
|
4
|
+
data.tar.gz: f9e7c8e6b171aace305d5fae08ee24d08f6304c0127e5044579e8d3c30cc6ca6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 53da97a3e0c570c41594ae47ed50149a5519e512d1677c38dc70fb118e69893d67165b7f7de89e0e8f56325f766934225f810c52fba501c9b9a1a71943cdc365
|
7
|
+
data.tar.gz: f6fd036dd5abeb33724927a6a25e2fab560f553f4df23cf186438f09036e68f147aef50ce356b7c9c3858cd3fbe81c02bc9649b46076562da3a3cf43f0cf0530
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
hot-glue (0.
|
4
|
+
hot-glue (0.6.0)
|
5
5
|
ffaker (~> 2.16)
|
6
6
|
kaminari (~> 1.2)
|
7
7
|
rails (> 5.1)
|
@@ -139,7 +139,7 @@ GEM
|
|
139
139
|
mini_mime (1.1.2)
|
140
140
|
mini_portile2 (2.8.4)
|
141
141
|
minitest (5.16.3)
|
142
|
-
net-imap (0.4.
|
142
|
+
net-imap (0.4.4)
|
143
143
|
date
|
144
144
|
net-protocol
|
145
145
|
net-pop (0.1.2)
|
data/README.md
CHANGED
@@ -64,25 +64,23 @@ _If you are on Rails 6, see [LEGACY SETUP FOR RAILS 6](https://github.com/jasonf
|
|
64
64
|
|
65
65
|
## The Super-Quick Setup
|
66
66
|
|
67
|
-
https://jasonfleetwoodboldt.com/courses/stepping-up-rails/rails-
|
67
|
+
https://jasonfleetwoodboldt.com/courses/stepping-up-rails/jason-fleetwood-boldts-rails-cookbook/
|
68
68
|
|
69
|
-
Copy & paste the whole code block from each section into your terminal.
|
69
|
+
Copy & paste the whole code block from each section into your terminal. Remember, there is a small "Copy" button at the top-right of each code block to help you copy & paste the script into your terminal.
|
70
70
|
|
71
|
+
These are the sections you need, you can ignore any others:
|
71
72
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
Sectoin #6 is for Hot Glue itself, and Section #7 is for Kaminari
|
83
|
-
|
84
|
-
You will also need section #8 to setup Devise if you want authentication.
|
73
|
+
* Section 1A for a new JS Bundling app, then skip down to
|
74
|
+
* Section 2B: Rspec + Friends
|
75
|
+
* Section 2B-Capy: Capybara for Rspec, then skip down to
|
76
|
+
* Section 3 for a welcome controller
|
77
|
+
( you can skip everything in Section 4 )
|
78
|
+
* Section 5 for debugging tools
|
79
|
+
* _Section 6 is the Hot Glue installer itself_ (this gem) - for Bootstrap, choose section 6A
|
80
|
+
* Section 7A to install Bootstrap along with CSSBundling
|
81
|
+
* Section 8 to set up Devise if you want authentication. (See how Hot Glue interacts with Devise below.)
|
85
82
|
|
83
|
+
If you do this through the quick setup above, you can then skip down past the next section to the "HOT GLUE DOCS" below.
|
86
84
|
|
87
85
|
## Step-By-Step Setup
|
88
86
|
|
@@ -294,10 +292,21 @@ Alternatively, you can define your own driver like so:
|
|
294
292
|
|
295
293
|
# HOT GLUE DOCS
|
296
294
|
|
295
|
+
Remember: Use `bin/rails generate model Thing` to generate models. Then add `has_many`, `belongs_to`, and _migrate your database_ before building the scaffold with Hot Glue.
|
296
|
+
|
297
|
+
You will also need every Rails model to contain _either_ a database column _or_ an object-level method named one of these five things:
|
298
|
+
`name`
|
299
|
+
`to_label`
|
300
|
+
`full_name`
|
301
|
+
`display_name`
|
302
|
+
`email`
|
303
|
+
|
304
|
+
If your database doesn't contain one of these five, add a method to your model using `def to_label`. This will be used as the default label for the object throughout the Hot Glue build system.
|
305
|
+
|
297
306
|
## First Argument
|
298
307
|
(no double slash)
|
299
308
|
|
300
|
-
TitleCase class name of the thing you want to build a
|
309
|
+
TitleCase class name of the thing you want to build a scaffolding for.
|
301
310
|
|
302
311
|
```
|
303
312
|
./bin/rails generate hot_glue:scaffold Thing
|
@@ -772,7 +781,7 @@ Note that Hot Glue still generates a singular partial (`_form`) for both actions
|
|
772
781
|
<% end %>
|
773
782
|
```
|
774
783
|
|
775
|
-
This works for both regular fields, association fields
|
784
|
+
This works for both regular fields, association fields.
|
776
785
|
|
777
786
|
|
778
787
|
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 +963,6 @@ This happens using two interconnected mechanisms:
|
|
954
963
|
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
964
|
|
956
965
|
|
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
966
|
## "Thing" Label
|
990
967
|
|
991
968
|
Note that on a per model basis, you can also globally omit the label or set a unique label value using
|
@@ -1256,7 +1233,7 @@ end
|
|
1256
1233
|
```
|
1257
1234
|
|
1258
1235
|
|
1259
|
-
|
1236
|
+
be sure your factory code creates a local variable that follows this name
|
1260
1237
|
|
1261
1238
|
**<downcase association name>**_factory.<downcase association name>
|
1262
1239
|
|
@@ -1410,6 +1387,33 @@ end
|
|
1410
1387
|
|
1411
1388
|
```
|
1412
1389
|
|
1390
|
+
### Typeahead Foreign Keys
|
1391
|
+
|
1392
|
+
Let's go back to the first Books & Authors example.
|
1393
|
+
assuming you have created
|
1394
|
+
`bin/rails generate model Book title:string author_id:integer`
|
1395
|
+
and
|
1396
|
+
`bin/rails generate model Author name:string`
|
1397
|
+
and also added `has_many :books` to Author and `belongs_to :author` to Book
|
1398
|
+
|
1399
|
+
|
1400
|
+
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.
|
1401
|
+
|
1402
|
+
You will do these three things:
|
1403
|
+
|
1404
|
+
1. As a one-time setup step for your app, run
|
1405
|
+
`bin/rails generate hot_glue:typeahead_install`
|
1406
|
+
2. When generating a scaffold you want to make a typeahead association, use `--modify='parent_id{typeahead}'` where `parent_id` is the foreign key
|
1407
|
+
`bin/rails generate hot_glue:scaffold Book --include=title,author_id --modify='author_id{typeahead}'`
|
1408
|
+
3. Within each namespace, you will generate a special typeahead controller (it exists for the associated object to be searched on
|
1409
|
+
`bin/rails generate hot_glue:typehead Author`
|
1410
|
+
This will create a controller for `AuthorsTypeaheadController` that will allow text search against any *string* field on the `Author` model.
|
1411
|
+
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.
|
1412
|
+
|
1413
|
+
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
|
1414
|
+
puts the value into the search box and the id into a hidden field.
|
1415
|
+
|
1416
|
+
You need to making a selection *and* click "Save" to update the record.
|
1413
1417
|
|
1414
1418
|
### TinyMCE
|
1415
1419
|
1. `bundle add tinymce-rails` to add it to your Gemfile
|
@@ -1469,6 +1473,30 @@ bin/rails generate Thing --include=my_story --modify='my_story{tinymce}'
|
|
1469
1473
|
|
1470
1474
|
# VERSION HISTORY
|
1471
1475
|
|
1476
|
+
#### 2023-11-03 - v0.6.0
|
1477
|
+
|
1478
|
+
Typeahead Associations
|
1479
|
+
|
1480
|
+
You can now use a typeahead when editing any foreign key.
|
1481
|
+
|
1482
|
+
Instead of displaying the foreign key in a drop-down list, the associated record is now selectable
|
1483
|
+
from a searchable typehead input.
|
1484
|
+
|
1485
|
+
The typeahead is implemented with a native Stimulus JS pair of controllers and is a modern & clean replacement to the old typeahead options.
|
1486
|
+
|
1487
|
+
1. As a one-time setup step for your app, run
|
1488
|
+
`bin/rails generate hot_glue:typeahead_install`
|
1489
|
+
2. When generating a scaffold you want to make a typeahead association, use `--modify='parent_id{typeahead}'` where `parent_id` is the foreign key
|
1490
|
+
`bin/rails generate hot_glue:scaffold Book --include=title,author_id --modify='author_id{typeahead}'`
|
1491
|
+
3. Within each namespace, you will generate a special typeahead controller (it exists for the associated object to be searched on
|
1492
|
+
`bin/rails generate hot_glue:typehead Author`
|
1493
|
+
This will create a controller for `AuthorsTypeaheadController` that will allow text search against any *string* field on the `Author` model.
|
1494
|
+
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.
|
1495
|
+
|
1496
|
+
The example Books & Authors app with typeahead is here:
|
1497
|
+
|
1498
|
+
https://github.com/hot-glue-for-rails/BooksAndAuthorsWithTypeahead2
|
1499
|
+
|
1472
1500
|
|
1473
1501
|
#### 2023-10-23 - v0.5.26
|
1474
1502
|
|
@@ -44,7 +44,6 @@ class FieldFactory
|
|
44
44
|
AttachmentField
|
45
45
|
end
|
46
46
|
@class_name = class_name
|
47
|
-
|
48
47
|
@field = field_class.new(name: name,
|
49
48
|
layout_strategy: generator.layout_strategy,
|
50
49
|
form_placeholder_labels: generator.form_placeholder_labels,
|
@@ -53,7 +52,6 @@ class FieldFactory
|
|
53
52
|
hawk_keys: generator.hawk_keys,
|
54
53
|
auth: generator.auth,
|
55
54
|
class_name: generator.singular_class,
|
56
|
-
alt_lookups: generator.alt_lookups,
|
57
55
|
singular: generator.singular,
|
58
56
|
self_auth: generator.self_auth,
|
59
57
|
update_show_only: generator.update_show_only,
|
@@ -61,6 +59,7 @@ class FieldFactory
|
|
61
59
|
sample_file_path: generator.sample_file_path,
|
62
60
|
modify_as: generator.modify_as[name.to_sym] || nil,
|
63
61
|
display_as: generator.display_as[name.to_sym] || nil,
|
64
|
-
default_boolean_display: generator.default_boolean_display
|
62
|
+
default_boolean_display: generator.default_boolean_display,
|
63
|
+
namespace: generator.namespace_value)
|
65
64
|
end
|
66
65
|
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
|
+
"<div class='typeahead typeahead--#{assoc.name}_id'
|
85
|
+
data-controller='typeahead'
|
86
|
+
data-typeahead-url-value='<%= #{search_url} %>'
|
87
|
+
data-typeahead-typeahead-results-outlet='#search-results'>
|
88
|
+
<%= text_field_tag :#{assoc.plural_name}_query, '', placeholder: 'Search #{assoc.plural_name}', class: 'search__input',
|
89
|
+
data: { action: 'keyup->typeahead#fetchResults keydown->typeahead#navigateResults', typeahead_target: 'query' },
|
90
|
+
autofocus: true,
|
91
|
+
autocomplete: 'off',
|
92
|
+
value: #{singular}.try(:#{assoc.name}).try(:name) %>
|
93
|
+
<%= f.hidden_field :#{assoc.name}_id, value: #{singular}.try(:#{assoc.name}).try(:id), 'data-typeahead-target': 'hiddenFormValue' %>
|
94
|
+
<div data-typeahead-target='results'></div>
|
95
|
+
<div data-typeahead-target='classIdentifier' data-id=\"typeahead--#{assoc_name}_id\"></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
|
+
@namespace = 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
|
+
@typeahead_identifier = params[:typeahead_identifier]
|
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_identifier %>"
|
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,67 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
2
|
+
|
3
|
+
export default class extends Controller {
|
4
|
+
static targets = [ "query", "results", "hiddenFormValue",
|
5
|
+
"classIdentifier"]
|
6
|
+
static values = { url: String }
|
7
|
+
static outlets = [ "typeahead-results" ]
|
8
|
+
|
9
|
+
disconnect() {
|
10
|
+
this.reset()
|
11
|
+
}
|
12
|
+
|
13
|
+
fetchResults() {
|
14
|
+
|
15
|
+
|
16
|
+
var typeaheadIdentifier = this.classIdentifierTarget.dataset.id
|
17
|
+
|
18
|
+
if(this.query == "") {
|
19
|
+
this.reset()
|
20
|
+
return
|
21
|
+
}
|
22
|
+
|
23
|
+
if(this.query == this.previousQuery) {
|
24
|
+
return
|
25
|
+
}
|
26
|
+
this.previousQuery = this.query
|
27
|
+
|
28
|
+
const url = new URL(this.urlValue)
|
29
|
+
url.searchParams.append("query", this.query)
|
30
|
+
url.searchParams.append("typeahead_identifier", typeaheadIdentifier)
|
31
|
+
|
32
|
+
this.abortPreviousFetchRequest()
|
33
|
+
|
34
|
+
this.abortController = new AbortController()
|
35
|
+
|
36
|
+
fetch(url, { signal: this.abortController.signal })
|
37
|
+
.then(response => response.text())
|
38
|
+
.then(html => {
|
39
|
+
this.resultsTarget.innerHTML = html
|
40
|
+
})
|
41
|
+
.catch(() => {})
|
42
|
+
}
|
43
|
+
|
44
|
+
navigateResults(event) {
|
45
|
+
if(this.hasSearchResultsOutlet) {
|
46
|
+
this.searchResultsOutlet.navigateResults(event)
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
// private
|
51
|
+
|
52
|
+
reset() {
|
53
|
+
this.resultsTarget.innerHTML = ""
|
54
|
+
this.queryTarget.value = ""
|
55
|
+
this.previousQuery = null
|
56
|
+
}
|
57
|
+
|
58
|
+
abortPreviousFetchRequest() {
|
59
|
+
if(this.abortController) {
|
60
|
+
this.abortController.abort()
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
get query() {
|
65
|
+
return this.queryTarget.value
|
66
|
+
}
|
67
|
+
}
|
@@ -0,0 +1,85 @@
|
|
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
|
+
const allElements = this.resultTarget.querySelectorAll(".search-result-item");
|
16
|
+
|
17
|
+
allElements.forEach((element, index) => {
|
18
|
+
element.addEventListener("click", () => {
|
19
|
+
// Call the searchItemClicked member function when the element is clicked
|
20
|
+
this.searchItemClicked(element, index);
|
21
|
+
});
|
22
|
+
})
|
23
|
+
this.selectCurrentResult()
|
24
|
+
}
|
25
|
+
|
26
|
+
searchItemClicked(element, index) {
|
27
|
+
const result_value = element.dataset.value;
|
28
|
+
const result_id = element.dataset.id;
|
29
|
+
|
30
|
+
// how to pass this to the search controller, set the field value and clear out the search
|
31
|
+
|
32
|
+
this.typeaheadOutlets.forEach(outlet => {
|
33
|
+
outlet.hiddenFormValueTarget.value = result_id;
|
34
|
+
outlet.queryTarget.value = result_value;
|
35
|
+
})
|
36
|
+
|
37
|
+
this.resultTarget.innerHTML = "";
|
38
|
+
}
|
39
|
+
|
40
|
+
navigateResults(event) {
|
41
|
+
if(!navigationKeys.includes(event.keyCode)) {
|
42
|
+
return
|
43
|
+
}
|
44
|
+
|
45
|
+
event.preventDefault()
|
46
|
+
|
47
|
+
switch(event.keyCode) {
|
48
|
+
case downKey:
|
49
|
+
this.selectNextResult()
|
50
|
+
break;
|
51
|
+
case upKey:
|
52
|
+
this.selectPreviousResult()
|
53
|
+
break;
|
54
|
+
case enterKey:
|
55
|
+
this.goToSelectedResult()
|
56
|
+
break;
|
57
|
+
}
|
58
|
+
}
|
59
|
+
|
60
|
+
// private
|
61
|
+
|
62
|
+
selectCurrentResult() {
|
63
|
+
this.resultTargets.forEach((element, index) => {
|
64
|
+
element.classList.toggle(this.currentClass, index == this.currentResultIndex)
|
65
|
+
})
|
66
|
+
}
|
67
|
+
|
68
|
+
selectNextResult() {
|
69
|
+
if(this.currentResultIndex < this.resultTargets.length - 1) {
|
70
|
+
this.currentResultIndex++
|
71
|
+
this.selectCurrentResult()
|
72
|
+
}
|
73
|
+
}
|
74
|
+
|
75
|
+
selectPreviousResult() {
|
76
|
+
if(this.currentResultIndex > 0) {
|
77
|
+
this.currentResultIndex--
|
78
|
+
this.selectCurrentResult()
|
79
|
+
}
|
80
|
+
}
|
81
|
+
|
82
|
+
goToSelectedResult() {
|
83
|
+
this.resultTargets[this.currentResultIndex].firstElementChild.click()
|
84
|
+
}
|
85
|
+
}
|
@@ -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.1
|
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-11 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
|