headmin 0.1.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +0 -1
  3. data/Gemfile.lock +24 -2
  4. data/README.md +8 -43
  5. data/app/controllers/concerns/headmin/authentication.rb +0 -8
  6. data/app/controllers/concerns/headmin/pagination.rb +1 -1
  7. data/app/helpers/headmin/admin_helper.rb +4 -4
  8. data/app/models/concerns/headmin/block.rb +11 -0
  9. data/app/models/concerns/headmin/blockable.rb +10 -0
  10. data/app/models/concerns/headmin/field.rb +17 -0
  11. data/app/models/concerns/headmin/fieldable.rb +44 -0
  12. data/app/services/block_service.rb +68 -0
  13. data/app/views/{layouts → examples}/admin.html.erb +0 -0
  14. data/app/views/{layouts/admin → examples}/auth.html.erb +0 -0
  15. data/app/views/headmin/_blocks.html.erb +24 -0
  16. data/app/views/headmin/_breadcrumbs.html.erb +9 -5
  17. data/app/views/headmin/_card.html.erb +48 -0
  18. data/app/views/headmin/_dropdown.html.erb +18 -0
  19. data/app/views/headmin/_filters.html.erb +56 -34
  20. data/app/views/headmin/_form.html.erb +60 -6
  21. data/app/views/headmin/_heading.html.erb +8 -4
  22. data/app/views/headmin/_index.html.erb +9 -8
  23. data/app/views/headmin/_notifications.html.erb +8 -0
  24. data/app/views/headmin/_pagination.html.erb +11 -7
  25. data/app/views/headmin/_popup.html.erb +26 -0
  26. data/app/views/headmin/_table.html.erb +11 -4
  27. data/app/views/headmin/dropdown/_button.html.erb +13 -0
  28. data/app/views/headmin/dropdown/_devise.html.erb +21 -0
  29. data/app/views/headmin/{layout/dropdown → dropdown}/_divider.html.erb +1 -1
  30. data/app/views/headmin/dropdown/_item.html.erb +18 -0
  31. data/app/views/headmin/dropdown/_list.html.erb +11 -0
  32. data/app/views/headmin/dropdown/_locale.html.erb +17 -0
  33. data/app/views/headmin/filters/_date.html.erb +21 -16
  34. data/app/views/headmin/filters/_search.html.erb +18 -18
  35. data/app/views/headmin/filters/_select.html.erb +28 -23
  36. data/app/views/headmin/filters/filter/_button.html.erb +15 -6
  37. data/app/views/headmin/filters/filter/_menu_item.html.erb +2 -2
  38. data/app/views/headmin/filters/filter/_template.html.erb +2 -2
  39. data/app/views/headmin/forms/_actions.html.erb +4 -8
  40. data/app/views/headmin/forms/_base.html.erb +60 -0
  41. data/app/views/headmin/forms/_blocks.html.erb +32 -0
  42. data/app/views/headmin/forms/_checkbox.html.erb +39 -0
  43. data/app/views/headmin/forms/_ckeditor.html.erb +42 -0
  44. data/app/views/headmin/forms/_date.html.erb +45 -0
  45. data/app/views/headmin/forms/_email.html.erb +39 -0
  46. data/app/views/headmin/forms/_file.html.erb +40 -0
  47. data/app/views/headmin/forms/_image.html.erb +55 -0
  48. data/app/views/headmin/forms/_label.html.erb +24 -0
  49. data/app/views/headmin/forms/_number.html.erb +50 -0
  50. data/app/views/headmin/forms/_password.html.erb +69 -0
  51. data/app/views/headmin/forms/_redactorx.html.erb +49 -0
  52. data/app/views/headmin/forms/_repeater.html.erb +133 -0
  53. data/app/views/headmin/forms/_select.html.erb +70 -0
  54. data/app/views/headmin/forms/_text.html.erb +62 -0
  55. data/app/views/headmin/forms/_textarea.html.erb +39 -0
  56. data/app/views/headmin/forms/_url.html.erb +39 -0
  57. data/app/views/headmin/forms/_validation.html.erb +21 -0
  58. data/app/views/headmin/forms/actions/_destroy.html.erb +13 -0
  59. data/app/views/headmin/forms/actions/_save.html.erb +12 -0
  60. data/app/views/headmin/forms/actions/_view.html.erb +15 -0
  61. data/app/views/headmin/forms/fields/_base.html.erb +25 -0
  62. data/app/views/headmin/forms/fields/_file.html.erb +15 -22
  63. data/app/views/headmin/forms/fields/_group.html.erb +38 -0
  64. data/app/views/headmin/forms/fields/_image.html.erb +15 -35
  65. data/app/views/headmin/forms/fields/_list.html.erb +26 -0
  66. data/app/views/headmin/forms/fields/_text.html.erb +15 -37
  67. data/app/views/headmin/forms/repeater/_row.html.erb +51 -0
  68. data/app/views/headmin/heading/_title.html.erb +1 -1
  69. data/app/views/headmin/layout/_body.html.erb +1 -1
  70. data/app/views/headmin/layout/_content.html.erb +1 -1
  71. data/app/views/headmin/layout/_footer.html.erb +1 -1
  72. data/app/views/headmin/layout/_header.html.erb +1 -1
  73. data/app/views/headmin/layout/_main.html.erb +2 -2
  74. data/app/views/headmin/layout/_sidebar.html.erb +1 -1
  75. data/app/views/headmin/layout/sidebar/{_menu.html.erb → _nav.html.erb} +1 -1
  76. data/app/views/headmin/nav/_item.html.erb +21 -0
  77. data/app/views/headmin/nav/item/_devise.html.erb +21 -0
  78. data/app/views/headmin/nav/item/_locale.html.erb +17 -0
  79. data/app/views/headmin/pagination/_per_page.html.erb +18 -0
  80. data/app/views/headmin/{kaminari → pagination/kaminari}/_first_page.html.erb +0 -0
  81. data/app/views/headmin/{kaminari → pagination/kaminari}/_gap.html.erb +0 -0
  82. data/app/views/headmin/{kaminari → pagination/kaminari}/_last_page.html.erb +0 -0
  83. data/app/views/headmin/{kaminari → pagination/kaminari}/_next_page.html.erb +0 -0
  84. data/app/views/headmin/{kaminari → pagination/kaminari}/_page.html.erb +1 -1
  85. data/app/views/headmin/{kaminari → pagination/kaminari}/_paginator.html.erb +0 -0
  86. data/app/views/headmin/{kaminari → pagination/kaminari}/_prev_page.html.erb +0 -0
  87. data/app/views/headmin/table/_actions.html.erb +24 -17
  88. data/app/views/headmin/table/_body.html.erb +12 -10
  89. data/app/views/headmin/table/_foot.html.erb +8 -4
  90. data/app/views/headmin/table/_footer.html.erb +3 -7
  91. data/app/views/headmin/table/_head.html.erb +3 -1
  92. data/app/views/headmin/table/_header.html.erb +3 -7
  93. data/app/views/headmin/table/actions/_action.html.erb +26 -9
  94. data/app/views/headmin/table/actions/_delete.html.erb +11 -7
  95. data/app/views/headmin/table/actions/_export.html.erb +11 -7
  96. data/app/views/headmin/table/body/_association.html.erb +1 -1
  97. data/app/views/headmin/table/body/_boolean.erb +1 -1
  98. data/app/views/headmin/table/body/_currency.html.erb +1 -1
  99. data/app/views/headmin/table/body/_date.html.erb +1 -1
  100. data/app/views/headmin/table/body/_id.html.erb +2 -2
  101. data/app/views/headmin/table/body/_row.html.erb +1 -1
  102. data/app/views/headmin/table/body/_string.html.erb +1 -1
  103. data/app/views/headmin/table/body/_text.html.erb +1 -1
  104. data/app/views/headmin/table/foot/_cell.html.erb +1 -1
  105. data/app/views/headmin/table/foot/_id.html.erb +2 -2
  106. data/app/views/headmin/table/head/_cell.html.erb +1 -1
  107. data/app/views/headmin/table/head/_id.html.erb +3 -3
  108. data/app/views/headmin/views/devise/confirmations/_new.html.erb +9 -0
  109. data/app/views/headmin/views/devise/passwords/_edit.html.erb +12 -0
  110. data/app/views/headmin/views/devise/passwords/_new.html.erb +9 -0
  111. data/app/views/{admin/users/registrations/edit.html.erb → headmin/views/devise/registrations/_edit.html.erb} +5 -5
  112. data/app/views/headmin/views/devise/registrations/_new.html.erb +11 -0
  113. data/app/views/headmin/views/devise/sessions/_new.html.erb +13 -0
  114. data/app/views/{admin/users → headmin/views/devise}/shared/_error_messages.html.erb +0 -0
  115. data/app/views/{admin/users → headmin/views/devise}/shared/_links.html.erb +0 -0
  116. data/app/views/headmin/views/devise/unlocks/_new.html.erb +10 -0
  117. data/config/initializers/customize_input_error.rb +9 -0
  118. data/config/locales/devise/en.yml +65 -0
  119. data/config/locales/en.yml +2 -134
  120. data/config/locales/headmin/filters/en.yml +13 -0
  121. data/config/locales/headmin/filters/nl.yml +13 -0
  122. data/config/locales/headmin/forms/en.yml +34 -0
  123. data/config/locales/headmin/forms/nl.yml +33 -0
  124. data/config/locales/headmin/heading/en.yml +5 -0
  125. data/config/locales/headmin/heading/nl.yml +5 -0
  126. data/config/locales/headmin/layout/en.yml +14 -0
  127. data/config/locales/headmin/layout/nl.yml +14 -0
  128. data/config/locales/headmin/pagination/en.yml +21 -0
  129. data/config/locales/headmin/pagination/nl.yml +21 -0
  130. data/config/locales/headmin/table/en.yml +23 -0
  131. data/config/locales/headmin/table/nl.yml +23 -0
  132. data/config/locales/headmin/views/en.yml +58 -0
  133. data/config/locales/headmin/views/nl.yml +58 -0
  134. data/config/locales/nl.yml +2 -135
  135. data/dist/css/headmin.css +3182 -743
  136. data/dist/js/headmin.js +729 -33
  137. data/docs/README.md +2 -1
  138. data/docs/blocks.md +70 -85
  139. data/docs/devise.md +1 -60
  140. data/docs/fields.md +57 -0
  141. data/headmin.gemspec +1 -0
  142. data/lib/generators/headmin/blocks_generator.rb +19 -0
  143. data/lib/generators/headmin/fields_generator.rb +19 -0
  144. data/lib/generators/templates/migrations/create_blocks.rb +10 -0
  145. data/lib/generators/templates/migrations/create_fields.rb +13 -0
  146. data/lib/generators/templates/models/block.rb +3 -0
  147. data/lib/generators/templates/models/field.rb +3 -0
  148. data/lib/headmin/version.rb +1 -1
  149. data/package.json +6 -6
  150. data/src/js/headmin/controllers/blocks_controller.js +103 -0
  151. data/src/js/headmin/controllers/filters_controller.js +23 -12
  152. data/src/js/headmin/controllers/popup_controller.js +68 -0
  153. data/src/js/headmin/controllers/repeater_controller.js +117 -11
  154. data/src/js/headmin/controllers/table_actions_controller.js +16 -5
  155. data/src/js/headmin/controllers/table_controller.js +84 -1
  156. data/src/js/headmin/headmin.js +29 -56
  157. data/src/scss/headmin/filters.scss +0 -14
  158. data/src/scss/headmin/form.scss +43 -3
  159. data/src/scss/headmin/popup.scss +17 -0
  160. data/src/scss/headmin/table.scss +9 -6
  161. data/src/scss/headmin.scss +7 -4
  162. data/src/scss/vendor/redactorx/override.css +3 -0
  163. data/src/scss/vendor/redactorx/redactorx.css +1460 -0
  164. data/yarn.lock +105 -2210
  165. metadata +108 -62
  166. data/app/controllers/admin/users/confirmations_controller.rb +0 -31
  167. data/app/controllers/admin/users/omniauth_callbacks_controller.rb +0 -31
  168. data/app/controllers/admin/users/passwords_controller.rb +0 -35
  169. data/app/controllers/admin/users/registrations_controller.rb +0 -63
  170. data/app/controllers/admin/users/sessions_controller.rb +0 -28
  171. data/app/controllers/admin/users/unlocks_controller.rb +0 -31
  172. data/app/views/admin/users/confirmations/new.html.erb +0 -9
  173. data/app/views/admin/users/mailer/confirmation_instructions.html.erb +0 -5
  174. data/app/views/admin/users/mailer/email_changed.html.erb +0 -7
  175. data/app/views/admin/users/mailer/password_change.html.erb +0 -3
  176. data/app/views/admin/users/mailer/reset_password_instructions.html.erb +0 -8
  177. data/app/views/admin/users/mailer/unlock_instructions.html.erb +0 -7
  178. data/app/views/admin/users/passwords/edit.html.erb +0 -12
  179. data/app/views/admin/users/passwords/new.html.erb +0 -9
  180. data/app/views/admin/users/registrations/new.html.erb +0 -11
  181. data/app/views/admin/users/sessions/new.html.erb +0 -13
  182. data/app/views/admin/users/unlocks/new.html.erb +0 -10
  183. data/app/views/headmin/forms/_group.html.erb +0 -36
  184. data/app/views/headmin/forms/fields/_checkbox.html.erb +0 -23
  185. data/app/views/headmin/forms/fields/_ckeditor.html.erb +0 -28
  186. data/app/views/headmin/forms/fields/_currency.html.erb +0 -24
  187. data/app/views/headmin/forms/fields/_date.html.erb +0 -36
  188. data/app/views/headmin/forms/fields/_email.html.erb +0 -39
  189. data/app/views/headmin/forms/fields/_label.html.erb +0 -9
  190. data/app/views/headmin/forms/fields/_multiple_select.html.erb +0 -37
  191. data/app/views/headmin/forms/fields/_password.html.erb +0 -39
  192. data/app/views/headmin/forms/fields/_repeater.html.erb +0 -48
  193. data/app/views/headmin/forms/fields/_select.html.erb +0 -36
  194. data/app/views/headmin/forms/fields/_select_tags.html.erb +0 -32
  195. data/app/views/headmin/forms/fields/_textarea.html.erb +0 -29
  196. data/app/views/headmin/forms/fields/_url.html.erb +0 -38
  197. data/app/views/headmin/forms/fields/_validation.html.erb +0 -12
  198. data/app/views/headmin/forms/fields/repeater/_row.html.erb +0 -16
  199. data/app/views/headmin/layout/dropdown/_item.html.erb +0 -17
  200. data/app/views/headmin/layout/header/_account.html.erb +0 -25
  201. data/app/views/headmin/layout/header/_locale.html.erb +0 -19
  202. data/app/views/headmin/layout/sidebar/menu/_account.html.erb +0 -25
  203. data/app/views/headmin/layout/sidebar/menu/_item.html.erb +0 -16
  204. data/app/views/headmin/layout/sidebar/menu/_locale.html.erb +0 -18
  205. data/src/js/headmin/controllers/index_controller.js +0 -79
  206. data/src/js/headmin/controllers/repeater_row_controller.js +0 -54
  207. data/src/scss/vendor/choices/cross-inverse.svg +0 -6
  208. data/src/scss/vendor/choices/cross.svg +0 -6
  209. data/src/scss/vendor/choices/custom.scss +0 -28
  210. data/src/scss/vendor/choices/variables.scss +0 -16
data/docs/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  # Features
2
2
 
3
- - [Blocks and fields link](blocks.md)
3
+ - [Working with Blocks](blocks.md)
4
+ - [Working with Fields](fields.md)
4
5
  - [Authentication with Devise](devise.md)
data/docs/blocks.md CHANGED
@@ -1,116 +1,101 @@
1
- # Blocks & Fields
1
+ # Blocks
2
2
 
3
- ## Database
4
- blocks
3
+ ## Installation
4
+ Run the following generators to generate the migration files.
5
5
  ```
6
- id = 1 resource_type="Page" resource_id="1" name = 'people', position = 1
6
+ rails generate headmin:blocks
7
+ rails db:migrate
7
8
  ```
8
9
 
9
- fields
10
- ```
11
- id = 1 block_id="1" name = 'people_title' type = 'string' value = 'test'
12
- id = 2 block_id="1" name = 'people_people' type = 'list'
13
- id = 3 block_id="1" name = 'people_people_1_person' type = 'group' parent_id = 2
14
- id = 4 block_id="1" name = 'people_people_1_person_name' type = 'string' parent_id = 3 value = 'Jef'
15
- id = 5 block_id="1" name = 'people_people_2_person' type = 'group' parent_id = 2
16
- id = 6 block_id="1" name = 'people_people_2_person_name' type = 'string' parent_id = 6 value = 'Gert-Jan'
17
- ```
18
-
19
- ## Models
20
-
10
+ ## Getting Started
21
11
 
22
- ```rb
23
- # models/page.rb
12
+ ### Setup model
24
13
 
25
- class Page < ActiveRecord::Model
26
- has_many :blocks, as: :blockable
27
- end
28
- ```
29
-
30
- ```rb
31
- # models/block.rb
32
-
33
- class Block < ActiveRecord::Model
34
- belongs_to :blockable, polymorphic: true
35
- has_many :fields
36
-
37
- # db to hash
38
- def fields_hash
39
- {
40
- title: 'test',
41
- people: [
42
- {name: 'Jef'},
43
- {name: 'Gert-Jan'}
44
- ]
45
- }
46
- end
14
+ ```ruby
15
+ class Page < ApplicationRecord
16
+ include Headmin::Blockable
47
17
  end
48
18
  ```
49
19
 
50
- ```rb
51
- # models/block/fields.rb
20
+ ### Setup forms
52
21
 
53
- class Block::Field < ActiveRecord::Model
54
- self.table_name = 'block_fields'
22
+ The view "headmin/forms/blocks" will render all the form views for the blocks in the database.
23
+ A hidden template form will be rendered for all types defined in `allow:`
55
24
 
56
- belongs_to :block
57
- end
25
+ ```erb
26
+ # app/views/admin/pages/_form.html.erb
27
+ <%= render 'headmin/form', model: [:admin, @page] do |form| %>
28
+ <%= render "headmin/forms/blocks", form: form %>
29
+ <% end %>
58
30
  ```
59
31
 
60
- ## Controllers
61
- ```ruby
62
- # website/pages_controller.rb
32
+ For each type of block you want to include, create a template in `views/admin/blocks`.
33
+ Make sure to include a hidden field to store the name of the block.
63
34
 
64
- class PagesController < ApplicationController
65
- def show
66
- @page = Page.friendly.find(params[:slug])
67
- end
68
- end
35
+ ```erb
36
+ # app/views/admin/blocks/_contact.html.erb
37
+ <%= form.hidden_field :name, value: :contact %>
38
+ ...
69
39
  ```
70
40
 
71
- ## Views
41
+ ### Render blocks in frontend
42
+
72
43
  ```erb
73
- # admin/pages/_form.html.erb
44
+ # app/views/website/pages/show.html.erb
45
+ <%= render 'headmin/blocks', blockable: @page %>
46
+ ```
74
47
 
75
- <input type="text" name="title" value="test">
48
+ This will render all the blocks associated with the blockable model (e.g. Page).
76
49
 
77
- <%= render 'blocks/people', required: true, moveable: false %>
78
- <%= render 'blocks/menu', required: true, moveable: false %>
79
- <%= render 'blocks/contact_form', required: true, moveable: false %>
80
- <%= render 'blocks/map', required: true, moveable: false %>
50
+ For each block in the admin, you'll need to create a corresponding template in your frontend.
81
51
 
82
- <%= render 'blocks/heading', required: false, moveable: true %>
83
- <%= render 'blocks/text', required: false, moveable: true %>
84
- <%= render 'blocks/image', required: false, moveable: true %>
85
- <%= render 'blocks/video', required: false, moveable: true %>
52
+ ```erb
53
+ # app/views/website/blocks/_contact.html.erb
54
+ <%= block.name %>
86
55
  ```
87
56
 
88
- ```erb
89
- # admin/blocks/_people.html.erb
90
-
91
- <input type="text" name="people_title" value="title" value="test">
92
- <div class="repeater" data-min="2" data-max="3">
93
- <div class="group">
94
- <input type="text" name="people_people_1_person_name" value="name" value="Jef">
95
- <input type="text" name="people_people_2_person_name" value="name" value="Gert-Jan">
96
- </div>
97
- </div>
57
+ ## Blocks + fields = Magic
58
+
59
+ ### Add fields to blocks
60
+
61
+ Setup a block model by including `Headmin::Fieldable`
62
+
63
+ ```ruby
64
+ class Block < ApplicationRecord
65
+ include Headmin::Block
66
+ include Headmin::Fieldable
67
+ end
98
68
  ```
99
69
 
70
+ ### Use fields in admin blocks
71
+
100
72
  ```erb
101
- # website/pages/show.html.erb
73
+ # app/views/admin/blocks/_contact.html.erb
74
+ <%= form.hidden_field :name, value: :contact %>
102
75
 
103
- <% @page.blocks.each do |block| %>
104
- <%= render "block_#{block.name}", block: block %>
76
+ <!-- Title -->
77
+ <%= render 'headmin/forms/fields/text', form: form, name: :title do |field, attribute, label| %>
78
+ <%= render 'headmin/forms/text', form: field, attribute: attribute, label: label %>
79
+ <% end %>
80
+
81
+ <!-- People list -->
82
+ <%= render 'headmin/forms/fields/list', form: form, name: :people do |item| %>
83
+ <%= render 'headmin/forms/fields/text', form: item, name: :name do |field, attribute, label| %>
84
+ <%= render 'headmin/forms/text', form: field, attribute: attribute, label: label %>
85
+ <% end %>
105
86
  <% end %>
106
87
  ```
107
88
 
108
- ```erb
109
- # website/blocks/_people.html.erb
89
+ ### Render blocks in your frontend
110
90
 
91
+ ```erb
92
+ # app/views/website/blocks/_contact.html.erb
111
93
  <% fields = block.fields_hash %>
112
- <%= fields[:title] %>
113
- <% fields[:people].each do |person| %>
114
- <%= person[:name] %>
115
- <% end %>
116
- ```
94
+
95
+ <h1><%= fields["title"] %></h1>
96
+ <ul>
97
+ <% fields["people"].each do |person| %>
98
+ <li><%= person["name"] %></h2></li>
99
+ <% end %>
100
+ </ul>
101
+ ```
data/docs/devise.md CHANGED
@@ -1,62 +1,3 @@
1
1
  # Authentication
2
2
 
3
- We use the gem Devise for authentication. In this example, two different user types will be set up:
4
- Admin and normal User. Only admins have access to the headmin back office. It is optional if normal users
5
- are allowed to login/register from the website.
6
-
7
- ##Installation
8
-
9
- #### Setup devise
10
- Add the gem devise and bundle install. Create a user model:
11
-
12
- ```
13
- rails generate devise User
14
- ```
15
-
16
- Add the field "type" to the user model
17
- ```
18
- rails g migration AddTypeToUsers type:string
19
- rails db:migrate
20
- ```
21
-
22
- In models/ add the file admin.rb
23
- ```
24
- class Admin < User
25
- end
26
- ```
27
-
28
- #### Setup routes
29
- In routes.rb
30
- ```
31
- namespace(:admin) do
32
- devise_for :admins, path: 'users', singular: 'admin', controllers: {
33
- sessions: 'admin/users/sessions',
34
- registrations: 'admin/users/registrations',
35
- passwords: 'admin/users/passwords',
36
- unlocks: 'admin/users/unlocks',
37
- confirmations: 'admin/users/confirmations',
38
- }
39
- end
40
- ```
41
-
42
- In case, no normal users are necessary, you can discard the next step:
43
- ```
44
- scope module: 'website' do
45
- devise_for :users, controllers: {
46
- sessions: 'website/users/sessions',
47
- registrations: 'website/users/registrations',
48
- passwords: 'website/users/passwords',
49
- unlocks: 'website/users/unlocks',
50
- confirmations: 'website/users/confirmations',
51
- }
52
- end
53
- ```
54
-
55
- #### Setup protected routes and configuration for headmin
56
- In controllers/admin_controller.rb
57
- ```
58
- class AdminController < ApplicationController
59
- alias_method :devise_current_user, :current_user
60
- include Headmin::Authentication
61
- end
62
- ```
3
+ TODO: rewrite
data/docs/fields.md ADDED
@@ -0,0 +1,57 @@
1
+ # Fields
2
+
3
+ ## Installation
4
+ Run the following generators to generate the migration files.
5
+ ```
6
+ rails generate headmin:fields
7
+ rails db:migrate
8
+ ```
9
+
10
+ ## Getting Started
11
+
12
+ ### Setup model
13
+
14
+ ```ruby
15
+ class Settings < ApplicationRecord
16
+ include Headmin::Fieldable
17
+ end
18
+ ```
19
+
20
+ ### Setup forms
21
+
22
+ ```erb
23
+ # app/views/admin/settings/_form.html.erb
24
+
25
+ <!-- Company name -->
26
+ <%= render 'headmin/forms/fields/text', form: form, name: :company_name do |field, attribute, label| %>
27
+ <%= render 'headmin/forms/text', form: field, attribute: attribute, label: label %>
28
+ <% end %>
29
+
30
+ <!-- People list -->
31
+ <%= render 'headmin/forms/fields/list', form: form, name: :people do |item| %>
32
+ <%= render 'headmin/forms/fields/text', form: item, name: :name do |field, attribute, label| %>
33
+ <%= render 'headmin/forms/text', form: field, attribute: attribute, label: label %>
34
+ <% end %>
35
+ <% end %>
36
+ ```
37
+
38
+ #### Type of fields
39
+ - Text: `headmin/forms/fields/text`
40
+ - File: `headmin/forms/fields/file`
41
+ - Image: `headmin/forms/fields/image`
42
+ - List: `headmin/forms/fields/list`
43
+ - Group: `headmin/forms/fields/group`
44
+
45
+ ### Render fields in frontend
46
+
47
+ ```erb
48
+ # app/views/website/settings/show.html.erb
49
+ <% fields = @setting.fields_hash %>
50
+
51
+ <h1><%= fields["company_name"] %></h1>
52
+ <ul>
53
+ <% fields["people"].each do |person| %>
54
+ <li><%= person["name"] %></h2></li>
55
+ <% end %>
56
+ </ul>
57
+ ```
data/headmin.gemspec CHANGED
@@ -29,6 +29,7 @@ Gem::Specification.new do |spec|
29
29
 
30
30
  # Uncomment to register a new dependency of your gem
31
31
  # spec.add_dependency "example-gem", "~> 1.0"
32
+ spec.add_runtime_dependency 'closure_tree', '~> 7.3'
32
33
 
33
34
  # For more information and examples about making a new gem, checkout our
34
35
  # guide at: https://bundler.io/guides/creating_gem.html
@@ -0,0 +1,19 @@
1
+ module Headmin
2
+ class BlocksGenerator < Rails::Generators::Base
3
+ include Rails::Generators::Migration
4
+
5
+ source_root File.expand_path('../../templates', __FILE__)
6
+
7
+ def blocks
8
+ template 'models/block.rb', 'app/models/block.rb'
9
+ migration_template 'migrations/create_blocks.rb', 'db/migrate/create_blocks.rb'
10
+ end
11
+
12
+ private
13
+
14
+ def self.next_migration_number(dirname)
15
+ next_migration_number = current_migration_number(dirname) + 1
16
+ ActiveRecord::Migration.next_migration_number(next_migration_number)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ module Headmin
2
+ class FieldsGenerator < Rails::Generators::Base
3
+ include Rails::Generators::Migration
4
+
5
+ source_root File.expand_path('../../templates', __FILE__)
6
+
7
+ def blocks
8
+ template 'models/field.rb', 'app/models/field.rb'
9
+ migration_template 'migrations/create_fields.rb', 'db/migrate/create_fields.rb'
10
+ end
11
+
12
+ private
13
+
14
+ def self.next_migration_number(dirname)
15
+ next_migration_number = current_migration_number(dirname) + 1
16
+ ActiveRecord::Migration.next_migration_number(next_migration_number)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,10 @@
1
+ class CreateBlocks < ActiveRecord::Migration[6.1]
2
+ def change
3
+ create_table :blocks do |t|
4
+ t.references :blockable, polymorphic: true
5
+ t.string :name
6
+ t.integer :position
7
+ t.timestamps
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,13 @@
1
+ class CreateFields < ActiveRecord::Migration[6.1]
2
+ def change
3
+ create_table :fields do |t|
4
+ t.references :fieldable, polymorphic: true
5
+ t.string :name
6
+ t.string :field_type
7
+ t.integer :parent_id
8
+ t.integer :position
9
+ t.text :value
10
+ t.timestamps
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ class Block < ApplicationRecord
2
+ include Headmin::Block
3
+ end
@@ -0,0 +1,3 @@
1
+ class Field < ApplicationRecord
2
+ include Headmin::Field
3
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Headmin
4
- VERSION = "0.1.1"
4
+ VERSION = "0.2.2"
5
5
  end
data/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "headmin",
3
- "version": "0.1.1",
4
- "description": "A complete library of commonly used components to build an admin interface in your Ruby on Rails project.",
3
+ "version": "0.2.0",
4
+ "description": "Admin component library",
5
5
  "main": "src/js/headmin.js",
6
6
  "sass": "src/scss/headmin.scss",
7
7
  "style": "src/css/headmin.css",
@@ -20,14 +20,14 @@
20
20
  },
21
21
  "homepage": "https://github.com/insiting/headmin#readme",
22
22
  "dependencies": {
23
+ "@popperjs/core": "^2.9.3",
23
24
  "@rails/ujs": "^6.0.0",
24
- "bootstrap": "^5.0.0-beta2",
25
- "choices.js": "^9.0.1",
25
+ "bootstrap": "^5.1.1",
26
26
  "ckeditor5-build-classic-simple-upload-adapter-image-resize": "^1.0.4",
27
27
  "flatpickr": "^4.6.9",
28
- "popper": "^1.0.1",
29
28
  "sortablejs": "^1.13.0",
30
- "stimulus": "^2.0.0"
29
+ "stimulus": "^2.0.0",
30
+ "tom-select": "^2.0.0-rc.4"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@babel/core": "^7.12.10",
@@ -0,0 +1,103 @@
1
+ import {Controller} from "stimulus"
2
+ import Sortable from "sortablejs";
3
+ import { createPopper } from '@popperjs/core';
4
+
5
+ export default class extends Controller {
6
+ static get targets() {
7
+ return ["templateBlock", "block", "blocks", "templateEmpty", "button", "buttons"]
8
+ }
9
+
10
+ connect() {
11
+ new Sortable(this.blocksTarget, {
12
+ onEnd: () => {
13
+ this.reorderPositions()
14
+ }
15
+ })
16
+
17
+ this.toggleEmpty()
18
+ }
19
+
20
+ toggleEmpty() {
21
+ if(this.blockCount() > 0) {
22
+ const empty = this.blocksTarget.querySelector('#blocks-empty')
23
+ if(empty) {
24
+ empty.remove()
25
+ }
26
+ } else {
27
+ const empty = this.templateEmptyTarget.innerHTML
28
+ this.blocksTarget.insertAdjacentHTML('beforeend', empty)
29
+ }
30
+ }
31
+
32
+ add(event) {
33
+ event.preventDefault()
34
+ const blockType = event.target.dataset.type
35
+ let html = this.templateBlockTargets.filter(blockTarget => blockTarget.id === blockType)[0].innerHTML
36
+
37
+ html = this.setPosition(html)
38
+
39
+ const element = this.blocksTarget.querySelector(`li[data-position='${event.target.dataset.position}']`)
40
+
41
+ if (element) {
42
+ element.insertAdjacentHTML('afterend', html)
43
+ }
44
+ else {
45
+ this.blocksTarget.insertAdjacentHTML('afterbegin', html)
46
+ }
47
+
48
+ // Dispatch an event
49
+ this.blocksTarget.dispatchEvent(new CustomEvent('headmin:reinit', {bubbles: true}))
50
+
51
+ this.reorderPositions()
52
+ this.toggleEmpty()
53
+ }
54
+
55
+ remove(event) {
56
+ event.preventDefault()
57
+
58
+ const block = event.target.closest(".list-group-item")
59
+ const destroyInput = block.querySelector("input[name*='_destroy']")
60
+
61
+ if (destroyInput) {
62
+ destroyInput.value = 1
63
+ block.style.display = 'none'
64
+ } else {
65
+ block.remove()
66
+ }
67
+
68
+ this.reorderPositions()
69
+ this.toggleEmpty()
70
+ }
71
+
72
+ setPosition(html) {
73
+ const position = this.retrieveLastPosition() + 1
74
+
75
+ const regex = new RegExp('99999', "g");
76
+ return html.replace(regex, position)
77
+ }
78
+
79
+ retrieveLastPosition() {
80
+ const blocks = Array.from(this.blockTargets)
81
+ if (blocks.length < 1) {
82
+ return 0
83
+ }
84
+
85
+ const lastBlock = blocks.slice(-1)[0]
86
+ return parseInt(lastBlock.querySelector("[name*='position']").value)
87
+ }
88
+
89
+ reorderPositions() {
90
+ for (let [index, block] of this.blockTargets.entries()) {
91
+ this.changePositionInfo(block, index)
92
+ }
93
+ }
94
+
95
+ changePositionInfo(block, index) {
96
+ block.setAttribute("data-position", index)
97
+ block.querySelector("input[name*='position']").value = index
98
+ }
99
+
100
+ blockCount() {
101
+ return this.blockTargets.length;
102
+ }
103
+ }
@@ -1,9 +1,8 @@
1
1
  import {Controller} from "stimulus"
2
- import {Headmin} from "../headmin";
3
2
 
4
3
  export default class extends Controller {
5
4
  static get targets() {
6
- return ["form", "list", "input", "template", "button"]
5
+ return ["form", "list", "input", "template", "button", "menuItem"]
7
6
  }
8
7
 
9
8
  add(event) {
@@ -13,13 +12,19 @@ export default class extends Controller {
13
12
  if (button) {
14
13
  this.openFilter(button)
15
14
  } else {
16
- const template = this.getTemplateByName(name)
17
- this.listTarget.insertAdjacentHTML('beforeend', template.innerHTML)
18
- const button = this.getButtonByName(name)
19
- Headmin.initPlugins()
15
+ this.createFilter(name)
20
16
  }
21
17
  }
22
18
 
19
+ createFilter(name) {
20
+ let html = this.getTemplateHTML(name)
21
+ html = this.replaceIdsWithTimestamps(html)
22
+ this.listTarget.insertAdjacentHTML('beforeend', html)
23
+
24
+ // Dispatch an event
25
+ this.menuItemTarget.dispatchEvent(new CustomEvent('headmin:reinit', {bubbles: true}))
26
+ }
27
+
23
28
  remove(event) {
24
29
  const filter = event.currentTarget.closest('.h-filter')
25
30
  filter.remove()
@@ -35,12 +40,6 @@ export default class extends Controller {
35
40
  this.formTarget.submit()
36
41
  }
37
42
 
38
- getTemplateByName(name) {
39
- return this.templateTargets.find(function (element) {
40
- return element.dataset.filterName === name
41
- })
42
- }
43
-
44
43
  getButtonByName(name) {
45
44
  return this.buttonTargets.find(function (element) {
46
45
  return element.dataset.filterName === name
@@ -50,4 +49,16 @@ export default class extends Controller {
50
49
  openFilter(button) {
51
50
  button.controller.open()
52
51
  }
52
+
53
+ getTemplateHTML(name) {
54
+ const template = this.templateTargets.filter((element) => {
55
+ return element.dataset.filterName === name
56
+ })[0]
57
+ return template.innerHTML
58
+ }
59
+
60
+ replaceIdsWithTimestamps(html) {
61
+ const regex = new RegExp('template_id', "g");
62
+ return html.replace(regex, new Date().getTime())
63
+ }
53
64
  }
@@ -0,0 +1,68 @@
1
+ import {Controller} from "stimulus"
2
+ import Sortable from "sortablejs";
3
+ import {createPopper} from '@popperjs/core';
4
+
5
+ export default class extends Controller {
6
+ static get targets() {
7
+ return ["popup", "button"]
8
+ }
9
+
10
+ static get values() {
11
+ return {id: String}
12
+ }
13
+
14
+ connect() {
15
+ // Clicked outside popup
16
+ document.addEventListener('click', (event) => {
17
+ this.handleOutsideClick(event)
18
+ })
19
+ }
20
+
21
+ handleOutsideClick(event) {
22
+ const inPopup = event.target.closest('[data-popup-target="popup"]') !== null
23
+ const inButton = event.target.closest('[data-popup-target="button"]') !== null
24
+ const openPopup = document.querySelector('[data-popup-target="popup"]:not(.closed)')
25
+
26
+ if(!inButton && !inPopup && openPopup) {
27
+ this.closePopup(openPopup)
28
+ }
29
+ }
30
+
31
+ open(event) {
32
+ const button = event.target.closest('[data-popup-target="button"]')
33
+ const popup = this.popupById(button.dataset['popupId'])
34
+ const passThru = button.dataset['popupPassThru']
35
+
36
+ if (passThru) {
37
+ // Pass click event to an element inside the popup
38
+ const passThruElement = popup.querySelector(passThru)
39
+ passThruElement.click()
40
+
41
+ } else {
42
+ // Open the popup
43
+ createPopper(button, popup)
44
+ this.openPopup(popup)
45
+ }
46
+ }
47
+
48
+ close(event) {
49
+ const button = event.target.closest('[data-popup-target="button"]')
50
+ const popup = this.popupById(button.dataset['popupId'])
51
+
52
+ this.closePopup(popup)
53
+ }
54
+
55
+ popupById(id) {
56
+ return this.popupTargets.find((popup) => {
57
+ return popup.dataset['popupId'] === id
58
+ })
59
+ }
60
+
61
+ openPopup(popup) {
62
+ popup.classList.remove('closed')
63
+ }
64
+
65
+ closePopup(popup) {
66
+ popup.classList.add('closed')
67
+ }
68
+ }