dynamic_layouts 0.1.0 → 0.1.1
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.
- checksums.yaml +4 -4
- data/README.md +307 -16
- data/lib/dynamic_layouts/dynamic_layout_service.rb +3 -2
- data/lib/dynamic_layouts/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fa58eac2c16e862711740275aec6a38fe3a27124b511ddb9bbac54d31a9500da
|
|
4
|
+
data.tar.gz: 218b984c4b32fa690bf5ec1212cc0218aacffa34f071c104a95cce490823b996
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 021d57dc7a614d963784f9b5fa5332e04d79ab3df950f674cbdf1cc9ac0db5775c341489b96952e576f5106ff3985add018c90aa414512d503e118a2ba2284cc
|
|
7
|
+
data.tar.gz: ca58426ba01060c493aaffc9843ade0f21319f66856a72975a629b2238ccae377e0cb9b32ae77e50c4b818884669d575cfa83ee6859d6b489667bd9f22b3e8ac
|
data/README.md
CHANGED
|
@@ -1,39 +1,330 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Dynamic Layouts
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A Ruby on Rails gem for building flexible, database-driven UI layouts. Dynamic Layouts allows you to define and manage complex form layouts, tables, dashboards, and markdown content through a hierarchical structure stored in your database.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Grid Layouts**: Create responsive form layouts with various field types
|
|
8
|
+
- **Table Layouts**: Build dynamic tables with configurable columns
|
|
9
|
+
- **Dashboard Widgets**: Design dashboard interfaces with customizable widgets
|
|
10
|
+
- **Markdown Elements**: Add rich text content and documentation
|
|
11
|
+
- **Layout Overrides**: Programmatically customize layouts at runtime
|
|
12
|
+
- **Custom Transformers**: Implement business logic to modify layouts dynamically
|
|
13
|
+
- **Easy Setup**: Rails generator creates all necessary migrations and models
|
|
14
|
+
|
|
15
|
+
## Requirements
|
|
16
|
+
|
|
17
|
+
- Ruby >= 3.2.0
|
|
18
|
+
- Rails >= 6.1
|
|
6
19
|
|
|
7
20
|
## Installation
|
|
8
21
|
|
|
9
|
-
|
|
22
|
+
Add this line to your application's Gemfile:
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
gem 'dynamic_layouts'
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Then execute:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
bundle install
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Run the generator to create migrations and models:
|
|
10
35
|
|
|
11
|
-
|
|
36
|
+
```bash
|
|
37
|
+
rails generate dynamic_layouts:install
|
|
38
|
+
rails db:migrate
|
|
39
|
+
```
|
|
12
40
|
|
|
13
|
-
|
|
41
|
+
This will create the following database tables and models:
|
|
42
|
+
- `dynamic_layouts`
|
|
43
|
+
- `dynamic_layout_containers`
|
|
44
|
+
- `dynamic_layout_sections`
|
|
45
|
+
- `dynamic_layout_grid_fields`
|
|
46
|
+
- `dynamic_layout_table_configs`
|
|
47
|
+
- `dynamic_layout_table_columns`
|
|
48
|
+
- `dynamic_layout_dashboard_widgets`
|
|
49
|
+
- `dynamic_layout_markdown_elements`
|
|
14
50
|
|
|
15
|
-
|
|
51
|
+
## Architecture
|
|
16
52
|
|
|
17
|
-
|
|
53
|
+
Dynamic Layouts uses a hierarchical structure:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
Layout
|
|
57
|
+
└── Container
|
|
58
|
+
└── Section
|
|
59
|
+
└── Elements (Grid Fields, Table Columns, Dashboard Widgets, or Markdown)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
- **Layout**: The top-level container for a complete UI layout
|
|
63
|
+
- **Container**: Groups related sections together
|
|
64
|
+
- **Section**: Defines a specific layout type (grid, table, dashboard, or markdown)
|
|
65
|
+
- **Elements**: Individual components within a section
|
|
18
66
|
|
|
19
67
|
## Usage
|
|
20
68
|
|
|
21
|
-
|
|
69
|
+
### Basic Layout Building
|
|
70
|
+
|
|
71
|
+
Build and retrieve a layout:
|
|
72
|
+
|
|
73
|
+
```ruby
|
|
74
|
+
# Simple build
|
|
75
|
+
layout = DynamicLayouts::DynamicLayoutBuilder.build('user_form')
|
|
76
|
+
|
|
77
|
+
# Build with transformation
|
|
78
|
+
layout = DynamicLayouts::DynamicLayoutBuilder.build(
|
|
79
|
+
'user_form',
|
|
80
|
+
record: @user,
|
|
81
|
+
controller: self,
|
|
82
|
+
user: current_user
|
|
83
|
+
)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The builder will:
|
|
87
|
+
1. Find the layout by name
|
|
88
|
+
2. Eager load all associated sections and elements
|
|
89
|
+
3. Serialize the layout to a hash structure
|
|
90
|
+
4. Apply transformations if a transformer is provided
|
|
91
|
+
|
|
92
|
+
### Custom Transformers
|
|
93
|
+
|
|
94
|
+
Create custom transformers to modify layouts based on business logic:
|
|
95
|
+
|
|
96
|
+
```ruby
|
|
97
|
+
class UserLayoutTransformer < DynamicLayouts::DynamicLayoutTransformer
|
|
98
|
+
def transform
|
|
99
|
+
# Hide email field for non-admin users
|
|
100
|
+
if @current_user && !@current_user.admin?
|
|
101
|
+
hide_field('contact_info', :email)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Make fields read-only for archived records
|
|
105
|
+
if @record&.archived?
|
|
106
|
+
make_section_readonly('personal_details')
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
@dynamic_layout
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
def hide_field(section_slug, field_attr)
|
|
115
|
+
section = find_section(section_slug)
|
|
116
|
+
return unless section
|
|
117
|
+
|
|
118
|
+
field = section[:elements].find { |f| f[:attr] == field_attr.to_s }
|
|
119
|
+
field[:hidden] = true if field
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def make_section_readonly(section_slug)
|
|
123
|
+
section = find_section(section_slug)
|
|
124
|
+
return unless section
|
|
125
|
+
|
|
126
|
+
section[:writable] = false
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def find_section(slug)
|
|
130
|
+
@dynamic_layout[:containers].each do |container|
|
|
131
|
+
section = container[:sections].find { |s| s[:slug] == slug.to_s }
|
|
132
|
+
return section if section
|
|
133
|
+
end
|
|
134
|
+
nil
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The transformer will be automatically discovered if it follows the naming convention: `{Controller}LayoutTransformer`.
|
|
140
|
+
|
|
141
|
+
### Layout Overrides
|
|
142
|
+
|
|
143
|
+
Override layout configuration at runtime:
|
|
144
|
+
|
|
145
|
+
```ruby
|
|
146
|
+
layout = DynamicLayouts::DynamicLayoutBuilder.build('user_form')
|
|
147
|
+
|
|
148
|
+
overrides = {
|
|
149
|
+
contact_info: {
|
|
150
|
+
writable: false,
|
|
151
|
+
collapsed: true,
|
|
152
|
+
elements: {
|
|
153
|
+
email: {
|
|
154
|
+
required: true,
|
|
155
|
+
tooltip: 'A valid email address is required'
|
|
156
|
+
},
|
|
157
|
+
phone: {
|
|
158
|
+
hidden: true
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
personal_details: {
|
|
163
|
+
elements: {
|
|
164
|
+
first_name: {
|
|
165
|
+
label: 'First Name (Required)',
|
|
166
|
+
required: true
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
DynamicLayouts::DynamicLayoutService.override_layout_config(layout, overrides)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Grid Layout
|
|
176
|
+
|
|
177
|
+
Grid layouts are ideal for forms with various field types:
|
|
178
|
+
|
|
179
|
+
```ruby
|
|
180
|
+
# In your layout configuration
|
|
181
|
+
section = dynamic_layout.containers.first.sections.create!(
|
|
182
|
+
name: 'Contact Information',
|
|
183
|
+
slug: 'contact_info',
|
|
184
|
+
layout: 'grid',
|
|
185
|
+
position: 1,
|
|
186
|
+
writable: true
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
# Add fields
|
|
190
|
+
section.grid_fields.create!(
|
|
191
|
+
label: 'Email',
|
|
192
|
+
attr: 'email',
|
|
193
|
+
attr_type: 'email',
|
|
194
|
+
required: true,
|
|
195
|
+
position: 1
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
section.grid_fields.create!(
|
|
199
|
+
label: 'Phone',
|
|
200
|
+
attr: 'phone',
|
|
201
|
+
attr_type: 'tel',
|
|
202
|
+
position: 2
|
|
203
|
+
)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Table Layout
|
|
207
|
+
|
|
208
|
+
Table layouts display data in columns:
|
|
209
|
+
|
|
210
|
+
```ruby
|
|
211
|
+
section = dynamic_layout.containers.first.sections.create!(
|
|
212
|
+
name: 'Users List',
|
|
213
|
+
slug: 'users_list',
|
|
214
|
+
layout: 'table',
|
|
215
|
+
position: 1
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
table_config = section.table_config
|
|
219
|
+
table_config.table_columns.create!(
|
|
220
|
+
header: 'Name',
|
|
221
|
+
attr: 'name',
|
|
222
|
+
position: 1
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
table_config.table_columns.create!(
|
|
226
|
+
header: 'Email',
|
|
227
|
+
attr: 'email',
|
|
228
|
+
position: 2
|
|
229
|
+
)
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Dashboard Layout
|
|
233
|
+
|
|
234
|
+
Dashboard layouts create widget-based interfaces:
|
|
235
|
+
|
|
236
|
+
```ruby
|
|
237
|
+
section = dynamic_layout.containers.first.sections.create!(
|
|
238
|
+
name: 'Analytics Dashboard',
|
|
239
|
+
slug: 'analytics',
|
|
240
|
+
layout: 'dashboard',
|
|
241
|
+
position: 1
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
section.dashboard_widgets.create!(
|
|
245
|
+
label: 'Total Users',
|
|
246
|
+
slug: 'total_users',
|
|
247
|
+
widget_type: 'stat',
|
|
248
|
+
position: 1
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
section.dashboard_widgets.create!(
|
|
252
|
+
label: 'Revenue Chart',
|
|
253
|
+
slug: 'revenue_chart',
|
|
254
|
+
widget_type: 'chart',
|
|
255
|
+
position: 2
|
|
256
|
+
)
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Markdown Layout
|
|
260
|
+
|
|
261
|
+
Markdown layouts add documentation and rich text:
|
|
262
|
+
|
|
263
|
+
```ruby
|
|
264
|
+
section = dynamic_layout.containers.first.sections.create!(
|
|
265
|
+
name: 'Instructions',
|
|
266
|
+
slug: 'instructions',
|
|
267
|
+
layout: 'markdown',
|
|
268
|
+
position: 1
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
section.markdown_elements.create!(
|
|
272
|
+
content: '# Welcome
|
|
273
|
+
position: 1
|
|
274
|
+
)
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Field Options
|
|
278
|
+
|
|
279
|
+
#### Dropdown Fields
|
|
280
|
+
|
|
281
|
+
```ruby
|
|
282
|
+
grid_field.update!(
|
|
283
|
+
attr_type: 'dropdown',
|
|
284
|
+
options: 'mode:static;values:active=Active,inactive=Inactive,pending=Pending'
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
# Or with associations
|
|
288
|
+
grid_field.update!(
|
|
289
|
+
attr_type: 'dropdown',
|
|
290
|
+
options: 'mode:association;class:Department;scopes:active;label:name;value:id'
|
|
291
|
+
)
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
#### Autocomplete Fields
|
|
295
|
+
|
|
296
|
+
```ruby
|
|
297
|
+
grid_field.update!(
|
|
298
|
+
attr_type: 'autocomplete',
|
|
299
|
+
options: 'mode:static;values:New York,Los Angeles,Chicago,Houston'
|
|
300
|
+
)
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
#### Rich Text Fields
|
|
304
|
+
|
|
305
|
+
```ruby
|
|
306
|
+
grid_field.update!(
|
|
307
|
+
attr_type: 'rich-text',
|
|
308
|
+
options: 'placeholder_attrs:first_name,last_name,email'
|
|
309
|
+
)
|
|
310
|
+
```
|
|
22
311
|
|
|
23
312
|
## Development
|
|
24
313
|
|
|
25
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
|
|
314
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
|
|
26
315
|
|
|
27
|
-
|
|
316
|
+
## Testing
|
|
28
317
|
|
|
29
|
-
|
|
318
|
+
Run the test suite:
|
|
30
319
|
|
|
31
|
-
|
|
320
|
+
```bash
|
|
321
|
+
bundle exec rspec
|
|
322
|
+
```
|
|
32
323
|
|
|
33
324
|
## License
|
|
34
325
|
|
|
35
|
-
The gem is available as open source under the terms of the [MIT License](
|
|
326
|
+
The gem is available as open source under the terms of the [MIT License](LICENSE.txt).
|
|
36
327
|
|
|
37
|
-
##
|
|
328
|
+
## Contributing
|
|
38
329
|
|
|
39
|
-
|
|
330
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/CodeTectonics/dynamic-layouts.
|
|
@@ -174,8 +174,9 @@ module DynamicLayouts
|
|
|
174
174
|
res.send(scope)
|
|
175
175
|
end
|
|
176
176
|
|
|
177
|
-
|
|
178
|
-
|
|
177
|
+
column = Arel.sql(ActiveRecord::Base.connection.quote_column_name(options[:attribute]))
|
|
178
|
+
data_source.where("#{column} IS NOT NULL AND #{column} != ''")
|
|
179
|
+
.select("LOWER(TRIM(#{column})) AS formatted_value")
|
|
179
180
|
.order(:formatted_value).distinct
|
|
180
181
|
.map do |item|
|
|
181
182
|
{ label: item.formatted_value.capitalize, value: item.formatted_value.capitalize }
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dynamic_layouts
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mark Harbison
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-02 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|