active_element 0.0.12 → 0.0.14

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/Gemfile.lock +17 -17
  4. data/app/assets/javascripts/active_element/highlight.js +311 -0
  5. data/app/assets/javascripts/active_element/json_field.js +51 -20
  6. data/app/assets/javascripts/active_element/popover.js +6 -4
  7. data/app/assets/stylesheets/active_element/_dark.scss +1 -1
  8. data/app/assets/stylesheets/active_element/application.scss +39 -1
  9. data/app/controllers/concerns/active_element/default_controller_actions.rb +7 -7
  10. data/app/views/active_element/_title.html.erb +1 -0
  11. data/app/views/active_element/components/fields/_json.html.erb +3 -2
  12. data/app/views/active_element/components/form/_field.html.erb +2 -1
  13. data/app/views/active_element/components/form/_json.html.erb +2 -0
  14. data/app/views/active_element/components/form/_label.html.erb +7 -0
  15. data/app/views/active_element/components/form.html.erb +2 -2
  16. data/app/views/layouts/active_element.html.erb +29 -6
  17. data/example_app/Gemfile.lock +1 -1
  18. data/lib/active_element/components/form.rb +1 -8
  19. data/lib/active_element/components/util/display_value_mapping.rb +0 -2
  20. data/lib/active_element/components/util/form_field_mapping.rb +2 -1
  21. data/lib/active_element/components/util.rb +7 -0
  22. data/lib/active_element/controller_interface.rb +2 -1
  23. data/lib/active_element/controller_state.rb +1 -1
  24. data/lib/active_element/default_controller/actions.rb +3 -0
  25. data/lib/active_element/default_controller/controller.rb +145 -0
  26. data/lib/active_element/default_controller/json_params.rb +48 -0
  27. data/lib/active_element/default_controller/params.rb +97 -0
  28. data/lib/active_element/default_controller/search.rb +112 -0
  29. data/lib/active_element/default_controller.rb +10 -132
  30. data/lib/active_element/version.rb +1 -1
  31. data/lib/active_element.rb +0 -2
  32. data/rspec-documentation/_head.html.erb +2 -0
  33. data/rspec-documentation/pages/000-Introduction.md +8 -5
  34. data/rspec-documentation/pages/005-Setup.md +21 -28
  35. data/rspec-documentation/pages/010-Components/Form Fields.md +35 -0
  36. data/rspec-documentation/pages/015-Custom Controllers.md +32 -0
  37. data/rspec-documentation/pages/016-Default Controller.md +132 -0
  38. data/rspec-documentation/pages/Themes.md +3 -0
  39. metadata +12 -4
  40. data/lib/active_element/default_record_params.rb +0 -62
  41. data/lib/active_element/default_search.rb +0 -110
@@ -4,11 +4,26 @@ ActiveElement.JsonField = (() => {
4
4
  const humanize = ({ string, singular = false }) => {
5
5
  if (!string) return '';
6
6
 
7
- const humanized = string.split('_').map(item => item.charAt(0).toUpperCase() + item.substring(1)).join(' ');
7
+ const humanized = string.split('_')
8
+ .map(item => item.charAt(0).toUpperCase() + item.substring(1)).join(' ')
9
+ .replace(/([a-z])([A-Z])/g, '$1 $2');
8
10
 
9
11
  if (!singular) return humanized;
10
12
 
11
- return humanized.replace(/s$/, ''); // FIXME: Expose translations from back-end to make this more useful.
13
+ // FIXME: Expose translations from back-end to make this more useful.
14
+ return humanized.replace(/[^u]s$/, '');
15
+ };
16
+
17
+ // Adapted from: https://stackoverflow.com/a/7557433
18
+ const isVisible = (element) => {
19
+ const boundingClientRect = element.getBoundingClientRect();
20
+
21
+ return (
22
+ boundingClientRect.top >= 0 &&
23
+ boundingClientRect.left >= 0 &&
24
+ boundingClientRect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
25
+ boundingClientRect.right <= (window.innerWidth || document.documentElement.clientWidth)
26
+ );
12
27
  };
13
28
 
14
29
  const isObject = (object) => object && typeof object === 'object';
@@ -44,7 +59,6 @@ ActiveElement.JsonField = (() => {
44
59
  };
45
60
 
46
61
  store.state = defaultState({ schema, path: [], defaultValue: data });
47
- console.log(store.state)
48
62
 
49
63
  const stateChangedCallbacks = [];
50
64
  const stateChanged = (callback, state) => stateChangedCallbacks.push([callback, state]);
@@ -211,10 +225,12 @@ ActiveElement.JsonField = (() => {
211
225
  };
212
226
 
213
227
  const Component = ({ store, stateChanged, connectState, schema, element, fieldName }) => {
214
- const ObjectField = ({ schema, state, path, omitLabel = false }) => {
228
+ const ObjectField = ({ schema, state, path, omitLabel = false, depth = 0 }) => {
215
229
  const getPath = () => schema.name ? path.concat(schema.name) : path;
216
230
  const currentPath = getPath();
217
231
 
232
+ depth = depth + 1;
233
+
218
234
  let element;
219
235
 
220
236
  switch (schema.type) {
@@ -235,7 +251,9 @@ ActiveElement.JsonField = (() => {
235
251
  case 'decimal':
236
252
  return DecimalField({ state, omitLabel, schema, path: currentPath });
237
253
  case 'object':
238
- element = cloneElement('form-group-floating');
254
+ element = cloneElement('form-group');
255
+ element.classList.add(`depth-${depth}`);
256
+ if (schema.name) element.append(Label({ schema }));
239
257
  (schema.shape.fields).forEach((field) => {
240
258
  element.append(
241
259
  ObjectField({
@@ -243,6 +261,7 @@ ActiveElement.JsonField = (() => {
243
261
  schema: field,
244
262
  state: state ? state[field.name] : null,
245
263
  path: currentPath,
264
+ depth,
246
265
  })
247
266
  );
248
267
  });
@@ -250,10 +269,10 @@ ActiveElement.JsonField = (() => {
250
269
  return element;
251
270
  case 'array':
252
271
  element = cloneElement('form-group');
253
- const list = ArrayField({ schema, state, path: currentPath });
272
+ const list = ArrayField({ schema, state, path: currentPath, depth });
254
273
  if (schema.shape?.type === 'object') list.classList.add('array-of-objects');
255
274
  element.append(AppendButton({ list, schema, state, path: currentPath }));
256
- element.append(Label({ title: schema.name }));
275
+ element.append(Label({ schema }));
257
276
  element.append(list);
258
277
  return element;
259
278
  }
@@ -270,12 +289,12 @@ ActiveElement.JsonField = (() => {
270
289
  const element = cloneElement('form-check');
271
290
 
272
291
  element.append(checkbox);
273
- element.append(Label({ title: schema.name, template: 'form-check-label' }));
292
+ element.append(Label({ schema, template: 'form-check-label', labelFor: checkbox }));
274
293
 
275
294
  return element;
276
295
  };
277
296
 
278
- const ArrayField = ({ schema, state, path: objectPath }) => {
297
+ const ArrayField = ({ schema, state, depth, path: objectPath }) => {
279
298
  const element = cloneElement('list-group');
280
299
 
281
300
  if (schema.focus) element.classList.add('focus');
@@ -284,21 +303,22 @@ ActiveElement.JsonField = (() => {
284
303
  if (state) {
285
304
  state.forEach((eachState, index) => {
286
305
  const path = objectPath.concat([index]);
287
- element.append(ArrayItem({ state: eachState, path, schema }));
306
+ element.append(ArrayItem({ state: eachState, path, schema, depth }));
288
307
  });
289
308
  }
290
309
 
291
310
  return element;
292
311
  };
293
312
 
294
- const ArrayItem = ({ state, path, schema, newItem = false }) => {
313
+ const ArrayItem = ({ state, path, schema, depth, newItem = false }) => {
295
314
  const element = cloneElement('list-item');
296
315
  const wrapper = document.createElement('div');
297
316
  const objectField = ObjectField({
298
317
  path,
299
318
  omitLabel: true,
300
319
  schema: { ...schema.shape },
301
- state: state
320
+ state: state,
321
+ depth,
302
322
  });
303
323
 
304
324
  // TODO: Use same template etc. for all delete buttons, use presentation layer to
@@ -366,6 +386,7 @@ ActiveElement.JsonField = (() => {
366
386
  valueElement.href = '#';
367
387
  modalBody.append(group);
368
388
  modalBody.classList.add('json-field');
389
+ group.classList.add('depth-1');
369
390
  titleElement.append(deleteObjectButton);
370
391
  modalHeader.append(deleteObjectButton);
371
392
  deleteObjectButton.addEventListener('click', () => bootstrapModal.hide());
@@ -382,10 +403,10 @@ ActiveElement.JsonField = (() => {
382
403
  return element;
383
404
  };
384
405
 
385
- const Label = ({ title, template, labelFor }) => {
406
+ const Label = ({ schema, template, labelFor }) => {
386
407
  const element = cloneElement(template || 'label');
387
408
 
388
- element.append(humanize({ string: title }));
409
+ element.append(schema.label || humanize({ string: schema.name }));
389
410
 
390
411
  if (labelFor) {
391
412
  element.htmlFor = labelFor.id;
@@ -443,7 +464,7 @@ ActiveElement.JsonField = (() => {
443
464
  const group = cloneElement('form-group-floating');
444
465
 
445
466
  group.append(element);
446
- group.append(Label({ title: schema.name, labelFor: element }));
467
+ group.append(Label({ schema, labelFor: element }));
447
468
 
448
469
  return group;
449
470
  };
@@ -464,9 +485,9 @@ ActiveElement.JsonField = (() => {
464
485
 
465
486
  if (floating) {
466
487
  group.append(element);
467
- group.append(Label({ title: schema.name, labelFor: element }));
488
+ group.append(Label({ schema, labelFor: element }));
468
489
  } else {
469
- group.append(Label({ title: schema.name, labelFor: element }));
490
+ group.append(Label({ schema, labelFor: element }));
470
491
  group.append(element);
471
492
  }
472
493
 
@@ -513,9 +534,8 @@ ActiveElement.JsonField = (() => {
513
534
 
514
535
  const AppendButton = ({ list, schema, state, path: objectPath }) => {
515
536
  const element = cloneElement('append-button');
516
- const humanName = humanize({ string: schema.name || fieldName, singular: true });
517
537
 
518
- element.append(`Add ${humanName}`);
538
+ element.append(`Add Item`);
519
539
  element.classList.add('append-button', 'float-end');
520
540
  element.onclick = (ev) => {
521
541
  ev.preventDefault();
@@ -524,7 +544,8 @@ ActiveElement.JsonField = (() => {
524
544
  const item = ArrayItem({ path, state: appendState, schema, newItem: true })
525
545
 
526
546
  list.append(item);
527
- item.scrollIntoView();
547
+
548
+ if (!isVisible(item)) item.scrollIntoView({ block: 'center' });
528
549
 
529
550
  return false;
530
551
  };
@@ -540,19 +561,29 @@ ActiveElement.JsonField = (() => {
540
561
  const formId = element.dataset.formId;
541
562
  const formFieldElement = document.querySelector(`#${element.dataset.fieldId}`);
542
563
  const schemaFieldElement = document.querySelector(`#${element.dataset.schemaFieldId}`);
564
+ const jsonViewModal = document.querySelector(`#${element.dataset.jsonViewModalId}`);
565
+ const jsonViewModalTrigger = document.querySelector(`#${element.dataset.jsonViewModalTriggerId}`);
543
566
  const fieldName = element.dataset.fieldName;
544
567
  const schema = getSchema(element);
545
568
  const { store, stateChanged, connectState } = createStore({ data, schema });
569
+ let currentState = null;
546
570
 
547
571
  schemaFieldElement.value = JSON.stringify(schema);
548
572
 
549
573
  stateChanged(({ getState }) => {
550
574
  const state = getState();
551
575
 
576
+ currentState = state;
552
577
  formFieldElement.value = JSON.stringify(state);
553
578
  ActiveElement.log.debug(state);
554
579
  });
555
580
 
581
+ jsonViewModalTrigger.addEventListener('click', () => {
582
+ const highlighted = hljs.highlight(JSON.stringify(currentState, null, 2), { language: 'json' }).value;
583
+ jsonViewModal.querySelector('[data-field-type="modal-body"]').innerHTML = `<pre>${highlighted}</pre>`;
584
+ return true;
585
+ });
586
+
556
587
  connectState({ element });
557
588
 
558
589
  const component = Component({ store, stateChanged, connectState, schema, element, fieldName });
@@ -1,6 +1,8 @@
1
1
  (() => {
2
- const popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]'))
3
- const popoverList = popoverTriggerList.map(function (element) {
4
- return new bootstrap.Popover(element)
5
- })
2
+ window.addEventListener('DOMContentLoaded', () => {
3
+ const popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]'))
4
+ const popoverList = popoverTriggerList.map(function (element) {
5
+ return new bootstrap.Popover(element)
6
+ })
7
+ });
6
8
  })();
@@ -25,7 +25,7 @@
25
25
 
26
26
  .json-field {
27
27
  .form-group {
28
- background-color: #58575755;
28
+ background-color: #5857571f;
29
29
  }
30
30
  }
31
31
 
@@ -2,12 +2,23 @@
2
2
  @import "bootstrap";
3
3
  @import "dark";
4
4
 
5
+
6
+ @keyframes fade-in {
7
+ from {
8
+ opacity: 0;
9
+ }
10
+
11
+ to {
12
+ opacity: 1;
13
+ }
14
+ }
15
+
5
16
  .application-menu {
6
17
  height: 5rem;
7
18
  padding-left: 2rem;
8
19
  top: 0;
9
20
  transition: height 0.5s ease-in-out, background-position 0.8s ease-in-out;
10
- background-color: #456060 !important;
21
+ background-color: #508ea1 !important;
11
22
  z-index: 2000;
12
23
  .dropdown-toggle::after {
13
24
  color: #{$blue};
@@ -77,6 +88,12 @@ form {
77
88
  margin-top: 5rem;
78
89
  }
79
90
 
91
+ .modal-content pre, .modal-content div.json-highlight {
92
+ font-size: 0.875rem;
93
+ line-height: 1.2rem;
94
+ font-family: monospace;
95
+ }
96
+
80
97
  .json-field {
81
98
  ol.json-array-field {
82
99
  margin-top: 1rem;
@@ -105,6 +122,11 @@ form {
105
122
  .form-group {
106
123
  padding: 1rem;
107
124
  background-color: #58575755;
125
+
126
+ &.depth-1 {
127
+ padding: 0;
128
+ background-color: transparent;
129
+ }
108
130
  }
109
131
 
110
132
  .form-check {
@@ -176,6 +198,16 @@ form {
176
198
  }
177
199
 
178
200
 
201
+ .json-array-field {
202
+ li, .json-delete-button {
203
+ opacity: 0;
204
+ animation: fade-in ease-in 1;
205
+ animation-fill-mode: forwards;
206
+ animation-duration: 0.5s;
207
+ animation-delay: 0;
208
+ }
209
+ }
210
+
179
211
  .json-array-field {
180
212
  li .json-text-field,
181
213
  li .json-select-field,
@@ -185,12 +217,18 @@ form {
185
217
  li .json-date-field,
186
218
  li .json-time-field,
187
219
  li .json-datetime-field {
220
+
188
221
  &.deletable {
189
222
  width: calc(100% - 3.2rem);
190
223
  }
191
224
  }
192
225
  }
193
226
 
227
+ .append-button {
228
+ position: relative;
229
+ z-index: 300;
230
+ }
231
+
194
232
  .form-control, .form-select {
195
233
  display: inline;
196
234
  .append-button {
@@ -8,31 +8,31 @@ module ActiveElement
8
8
  extend ActiveSupport::Concern
9
9
 
10
10
  def index
11
- ActiveElement::DefaultController.new(controller: self).index
11
+ ActiveElement::DefaultController::Controller.new(controller: self).index
12
12
  end
13
13
 
14
14
  def show
15
- ActiveElement::DefaultController.new(controller: self).show
15
+ ActiveElement::DefaultController::Controller.new(controller: self).show
16
16
  end
17
17
 
18
18
  def new
19
- ActiveElement::DefaultController.new(controller: self).new
19
+ ActiveElement::DefaultController::Controller.new(controller: self).new
20
20
  end
21
21
 
22
22
  def create
23
- ActiveElement::DefaultController.new(controller: self).create
23
+ ActiveElement::DefaultController::Controller.new(controller: self).create
24
24
  end
25
25
 
26
26
  def edit
27
- ActiveElement::DefaultController.new(controller: self).edit
27
+ ActiveElement::DefaultController::Controller.new(controller: self).edit
28
28
  end
29
29
 
30
30
  def update
31
- ActiveElement::DefaultController.new(controller: self).update
31
+ ActiveElement::DefaultController::Controller.new(controller: self).update
32
32
  end
33
33
 
34
34
  def destroy
35
- ActiveElement::DefaultController.new(controller: self).destroy
35
+ ActiveElement::DefaultController::Controller.new(controller: self).destroy
36
36
  end
37
37
  end
38
38
  end
@@ -0,0 +1 @@
1
+ <title><%= active_element.application_name.humanize %> - <%= controller_name.humanize %></title>
@@ -1,9 +1,10 @@
1
1
  <a data-modal-id="<%= "#json-modal-#{field_id}" %>"
2
+ id="<%= "json-view-modal-trigger-#{field_id}" %>"
2
3
  data-json-modal-link="true"
3
4
  data-bs-toggle="modal"
4
5
  data-bs-target="#json-modal-<%= field_id %>"
5
- class="text-decoration-none"
6
- href="#">Inspect JSON <i class="fa-solid fa-magnifying-glass"></i></a>
6
+ class="text-decoration-none text-nowrap"
7
+ href="#">JSON <i class="fa-solid fa-magnifying-glass"></i></a>
7
8
  <div id="json-modal-<%= field_id %>" class="modal fade"
8
9
  tabindex="-1"
9
10
  aria-hidden="true">
@@ -6,7 +6,8 @@
6
6
  locals: { form_id: id, form: form, field: field, options: options, component: component } %>
7
7
  <% elsif type == :json_field %>
8
8
  <%= render partial: 'active_element/components/form/json',
9
- locals: { form_id: id, form: form, field: field, field_id: ActiveElement.element_id, options: options, component: component } %>
9
+ locals: { form_id: id, form: form, field: field, field_id: "#{id}-json-field-#{field}",
10
+ options: options, component: component } %>
10
11
  <% elsif type == :text_search_field %>
11
12
  <%= render partial: 'active_element/components/form/text_search',
12
13
  locals: { form_id: id, form: form, field: field, options: options, component: component } %>
@@ -4,6 +4,8 @@
4
4
  data-form-id="<%= form_id %>"
5
5
  data-field-id="<%= field_id %>"
6
6
  data-schema-field-id="<%= field_id %>-schema"
7
+ data-json-view-modal-id="<%= "json-modal-#{form_id}-#{field}-json-view" %>"
8
+ data-json-view-modal-trigger-id="<%= "json-view-modal-trigger-#{form_id}-#{field}-json-view" %>"
7
9
  >
8
10
 
9
11
  </div>
@@ -26,3 +26,10 @@
26
26
  <%= render partial: 'active_element/components/form/option_groups_summary',
27
27
  locals: { option_groups: options[:option_groups] } %>
28
28
  <% end %>
29
+
30
+ <% if type == :json_field %>
31
+ <div>
32
+ <%= render partial: 'active_element/components/fields/json',
33
+ locals: { value: component.value_for(field), field_id: "#{id}-#{field}-json-view" } %>
34
+ </div>
35
+ <% end %>
@@ -1,5 +1,5 @@
1
1
  <% if destroy %>
2
- <div class="container w-100 text-end">
2
+ <div class="container me-0 w-100 text-end">
3
3
  <%= active_element.component.destroy_button(record) %>
4
4
  </div>
5
5
  <% end %>
@@ -51,7 +51,7 @@
51
51
  <% field_group.each do |field, type, options| %>
52
52
  <div class="col-sm-3">
53
53
  <%= render partial: 'active_element/components/form/label',
54
- locals: { type: type, form: form, field: field, options: options } %>
54
+ locals: { component: component, id: id, type: type, form: form, field: field, options: options } %>
55
55
  </div>
56
56
 
57
57
 
@@ -1,6 +1,15 @@
1
1
  <html>
2
2
  <head>
3
3
  <%= render_active_element_hook 'active_element/before_head' %>
4
+ <%= render_active_element_hook 'active_element/favicon' %>
5
+ <%= render_active_element_hook 'active_element/title' %>
6
+
7
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
8
+ <link rel="stylesheet"
9
+ href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/default.min.css">
10
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/js/all.min.js" integrity="sha512-fD9DI5bZwQxOi7MhYWnnNPlvXdp/2Pj3XSTRrFs5FQa4mizyGLnJcN6tuvUS6LbmgN1ut+XGSABKvjN0H6Aoow==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
11
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/2.9.2/umd/popper.min.js" integrity="sha512-2rNj2KJ+D8s1ceNasTIex6z4HWyOnEYLVC3FigGOmyQCZc2eBXKgOxQmo3oKLHyfcj53uz4QMsRCWNbLd32Q1g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
12
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
4
13
 
5
14
  <style>
6
15
  <%= Rouge::Theme.find('tulip').render(scope: '.json-highlight') %>
@@ -12,14 +21,28 @@
12
21
  color: #6b7399
13
22
  }
14
23
 
24
+ .json-highlight .kc, .json-highlight .c {
25
+ color: #695;
26
+ }
27
+
15
28
  .json-highlight {
16
29
  background-color: transparent;
17
30
  }
18
- </style>
19
31
 
20
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
21
- <script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/js/all.min.js" integrity="sha512-fD9DI5bZwQxOi7MhYWnnNPlvXdp/2Pj3XSTRrFs5FQa4mizyGLnJcN6tuvUS6LbmgN1ut+XGSABKvjN0H6Aoow==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
22
- <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/2.9.2/umd/popper.min.js" integrity="sha512-2rNj2KJ+D8s1ceNasTIex6z4HWyOnEYLVC3FigGOmyQCZc2eBXKgOxQmo3oKLHyfcj53uz4QMsRCWNbLd32Q1g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
32
+ .hljs-punctuation {
33
+ color: #7e4b6f;
34
+ }
35
+
36
+ .hljs-attr {
37
+ color: #9f93e6;
38
+ font-weight: bold;
39
+ }
40
+
41
+ .hljs-string {
42
+ color: #6b7399;
43
+ font-weight: bold;
44
+ }
45
+ </style>
23
46
 
24
47
  <script>
25
48
  window.ActiveElement = window.ActiveElement || {};
@@ -27,7 +50,7 @@
27
50
 
28
51
  <%= stylesheet_link_tag 'active_element/application', 'data-turbolinks-track': 'reload' %>
29
52
 
30
- <% if Rails.application.assets.find_asset('application.css').present? %>
53
+ <% if Rails.application.assets&.find_asset('application.css').present? %>
31
54
  <%= stylesheet_link_tag 'application', 'data-turbolinks-track': 'reload' %>
32
55
  <% end %>
33
56
 
@@ -75,7 +98,7 @@
75
98
  <%= render_active_element_hook 'active_element/after_content' %>
76
99
 
77
100
  <%= javascript_include_tag 'active_element/application', 'data-turbolinks-track': 'reload' %>
78
- <% if Rails.application.assets.find_asset('application.js').present? %>
101
+ <% if Rails.application.assets&.find_asset('application.js').present? %>
79
102
  <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
80
103
  <% end %>
81
104
  </body>
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- active_element (0.0.12)
4
+ active_element (0.0.13)
5
5
  bootstrap (~> 5.3.0alpha3)
6
6
  kaminari (~> 1.2)
7
7
  paintbrush (~> 0.1.2)
@@ -81,14 +81,7 @@ module ActiveElement
81
81
  end
82
82
 
83
83
  def schema_for(field, options)
84
- options.key?(:schema) ? options.fetch(:schema) : schema_from_yaml(field)
85
- end
86
-
87
- def schema_from_yaml(field)
88
- YAML.safe_load(
89
- Rails.root.join("config/forms/#{record.class.name.underscore}/#{field}.yml").read,
90
- symbolize_names: true
91
- )
84
+ options.key?(:schema) ? options.fetch(:schema) : Util.json_schema(model: record.class, field: field)
92
85
  end
93
86
 
94
87
  def display_value_for_select(field, options)
@@ -16,8 +16,6 @@ module ActiveElement
16
16
  end
17
17
 
18
18
  def json_value
19
- return ActiveElement.json_pretty_print(value_from_record) unless component.is_a?(CollectionTable)
20
-
21
19
  component.controller.render_to_string(
22
20
  partial: 'active_element/components/fields/json',
23
21
  locals: { value: value_from_record, field_id: ActiveElement.element_id }
@@ -136,8 +136,9 @@ module ActiveElement
136
136
  end
137
137
 
138
138
  def searchable_fields(field)
139
+ # FIXME: Use database column type to only include strings/numbers.
139
140
  (Util.relation_controller(model, controller, field)&.active_element&.state&.searchable_fields || [])
140
- .reject { |field| field.to_s.end_with?('_at') } # FIXME: Select strings/numbers only.
141
+ .reject { |searchable_field| searchable_field.to_s.end_with?('_at') }
141
142
  end
142
143
 
143
144
  def relation_primary_key(field)
@@ -41,6 +41,13 @@ module ActiveElement
41
41
  "#{namespace.classify}::#{base}".safe_constantize || base.safe_constantize
42
42
  end
43
43
 
44
+ def self.json_schema(model:, field:)
45
+ YAML.safe_load(
46
+ Rails.root.join("config/forms/#{model.name.underscore}/#{field}.yml").read,
47
+ symbolize_names: true
48
+ )
49
+ end
50
+
44
51
  def self.json_pretty_print(json)
45
52
  formatter = Rouge::Formatters::HTML.new
46
53
  lexer = Rouge::Lexers::JSON.new
@@ -25,7 +25,8 @@ module ActiveElement
25
25
  @authorize
26
26
  end
27
27
 
28
- def listable_fields(*args)
28
+ def listable_fields(*args, order: nil)
29
+ state.list_order = order
29
30
  state.listable_fields.concat(args.map(&:to_sym)).uniq!
30
31
  end
31
32
 
@@ -7,7 +7,7 @@ module ActiveElement
7
7
  class ControllerState
8
8
  attr_reader :permissions, :listable_fields, :viewable_fields, :editable_fields, :searchable_fields
9
9
  attr_accessor :sign_in_path, :sign_in, :sign_in_method, :sign_out_path, :sign_out_method,
10
- :deletable, :authorizor, :authenticator
10
+ :deletable, :authorizor, :authenticator, :list_order
11
11
 
12
12
  def initialize(controller:)
13
13
  @controller = controller
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+ # TODO: Move each default controller action into individual classes inside
3
+ # ActiveElement::DefaultController::Actions