active_element 0.0.12 → 0.0.14

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 (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