alchemy-form 0.1.5 → 0.1.8

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 (48) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/assets/stylesheets/form/alchemy_field_array.scss +4 -0
  3. package/assets/stylesheets/form/alchemy_select.scss +1 -3
  4. package/assets/stylesheets/form/alchemy_toggle.scss +2 -0
  5. package/assets/stylesheets/form/query_builder.scss +185 -0
  6. package/config/routes.js +8 -0
  7. package/controller/form_api_controller.js +43 -4
  8. package/element/20_query_builder_base.js +82 -0
  9. package/element/25_query_builder_data.js +139 -0
  10. package/element/alchemy_field.js +31 -7
  11. package/element/alchemy_select.js +172 -39
  12. package/element/alchemy_select_item.js +9 -0
  13. package/element/alchemy_table.js +172 -26
  14. package/element/query_builder.js +90 -0
  15. package/element/query_builder_entry.js +388 -0
  16. package/element/query_builder_group.js +248 -0
  17. package/element/query_builder_value.js +316 -0
  18. package/element/query_builder_variable.js +103 -0
  19. package/helper/form_actions/00_form_action.js +328 -0
  20. package/helper/form_actions/url_action.js +69 -0
  21. package/helper/query_builder_variable_definition/00_variable_definition.js +371 -0
  22. package/helper/query_builder_variable_definition/boolean_variable_definition.js +24 -0
  23. package/helper/query_builder_variable_definition/list_variable_definition.js +38 -0
  24. package/helper/query_builder_variable_definition/number_variable_definition.js +106 -0
  25. package/helper/query_builder_variable_definition/string_variable_definition.js +46 -0
  26. package/helper_field/query_builder_assignment.js +11 -0
  27. package/helper_field/query_builder_field.js +91 -0
  28. package/helper_field/query_builder_value.js +56 -0
  29. package/helper_field/query_builder_variable.js +56 -0
  30. package/package.json +2 -2
  31. package/view/form/elements/alchemy_field_array.hwk +3 -1
  32. package/view/form/elements/alchemy_field_array_entry.hwk +3 -1
  33. package/view/form/elements/alchemy_select_item.hwk +6 -1
  34. package/view/form/elements/query_builder.hwk +1 -0
  35. package/view/form/elements/query_builder_entry.hwk +33 -0
  36. package/view/form/elements/query_builder_group.hwk +64 -0
  37. package/view/form/elements/query_builder_value.hwk +10 -0
  38. package/view/form/elements/query_builder_variable.hwk +6 -0
  39. package/view/form/inputs/edit/belongs_to.hwk +1 -1
  40. package/view/form/inputs/edit/html.hwk +5 -0
  41. package/view/form/inputs/edit/query_builder.hwk +5 -0
  42. package/view/form/inputs/edit/query_builder_assignment.hwk +6 -0
  43. package/view/form/inputs/edit/query_builder_value.hwk +11 -0
  44. package/view/form/inputs/edit/query_builder_variable.hwk +10 -0
  45. package/view/form/inputs/view/string.hwk +1 -0
  46. package/view/form/inputs/view_inline/string.hwk +1 -0
  47. package/view/form/select/qb_item.hwk +7 -0
  48. package/view/form/wrappers/view_inline/default.hwk +1 -0
@@ -5,9 +5,7 @@
5
5
  * @since 0.1.0
6
6
  * @version 0.1.0
7
7
  */
8
- var AlchemySelect = Function.inherits('Alchemy.Element.Form.Base', function Select() {
9
- Select.super.call(this);
10
- });
8
+ const AlchemySelect = Function.inherits('Alchemy.Element.Form.Base', 'Select');
11
9
 
12
10
  /**
13
11
  * The remote url attribute
@@ -36,6 +34,26 @@ AlchemySelect.setProperty('_clear_count', 0);
36
34
  */
37
35
  AlchemySelect.setAttribute('total-item-count', {number: true});
38
36
 
37
+ /**
38
+ * The optional template to use for value items
39
+ * (The chosen value)
40
+ *
41
+ * @author Jelle De Loecker <jelle@develry.be>
42
+ * @since 0.1.6
43
+ * @version 0.1.6
44
+ */
45
+ AlchemySelect.setAttribute('value-item-template');
46
+
47
+ /**
48
+ * The optional template to use for option items
49
+ * (The items visible in the dropdown)
50
+ *
51
+ * @author Jelle De Loecker <jelle@develry.be>
52
+ * @since 0.1.6
53
+ * @version 0.1.6
54
+ */
55
+ AlchemySelect.setAttribute('option-item-template');
56
+
39
57
  /**
40
58
  * The hawkejs template to use
41
59
  *
@@ -369,14 +387,56 @@ AlchemySelect.setProperty(function loading_dropdown() {
369
387
  *
370
388
  * @author Jelle De Loecker <jelle@develry.be>
371
389
  * @since 0.1.0
372
- * @version 0.1.0
390
+ * @version 0.1.8
373
391
  */
374
392
  AlchemySelect.setProperty(function search_value() {
375
- var type_area = this.type_area;
393
+
394
+ let type_area = this.type_area,
395
+ result = '';
376
396
 
377
397
  if (type_area) {
378
- return type_area.value;
398
+ result = type_area.value;
399
+ }
400
+
401
+ if (this.previous_search_value == null) {
402
+ this.previous_search_value = '';
403
+ }
404
+
405
+ // If the search value changed, nullify the last page result
406
+ if (this.previous_search_value != result) {
407
+ this.setLastSuccessfulLoadedPage(null);
408
+ }
409
+
410
+ this.previous_search_value = result;
411
+
412
+ return result;
413
+ });
414
+
415
+ /**
416
+ * Make sure the pagination watcher exists
417
+ *
418
+ * @author Jelle De Loecker <jelle@elevenways.be>
419
+ * @since 0.1.0
420
+ * @version 0.1.8
421
+ */
422
+ AlchemySelect.setStatic(function ensurePaginationChecker() {
423
+
424
+ if (AlchemySelect.has_paginator_viewer) {
425
+ return;
379
426
  }
427
+
428
+ // Only ever add 1 viewer per page
429
+ AlchemySelect.has_paginator_viewer = true;
430
+
431
+ hawkejs.scene.appears('js-he-ais-pager', {live: true, padding: 10, throttle: 200}, function onInfinity(el) {
432
+
433
+ if (!el.parentElement || !el.parentElement.parentElement) {
434
+ return;
435
+ }
436
+
437
+ const alchemy_select_instance = el.parentElement.parentElement;
438
+ alchemy_select_instance.loadOptions(++alchemy_select_instance.loaded_page);
439
+ });
380
440
  });
381
441
 
382
442
  /**
@@ -400,7 +460,7 @@ AlchemySelect.setMethod(function rendered() {
400
460
  *
401
461
  * @author Jelle De Loecker <jelle@develry.be>
402
462
  * @since 0.1.0
403
- * @version 0.1.0
463
+ * @version 0.1.8
404
464
  */
405
465
  AlchemySelect.setMethod(function introduced() {
406
466
 
@@ -413,14 +473,7 @@ AlchemySelect.setMethod(function introduced() {
413
473
  this.makeSortable();
414
474
  }
415
475
 
416
- if (!AlchemySelect.has_paginator_viewer) {
417
- AlchemySelect.has_paginator_viewer = true;
418
-
419
- hawkejs.scene.appears('js-he-ais-pager', {live: true, padding: 10, throttle: 200}, function onInfinity(el) {
420
- var ais = el.parentElement.parentElement;
421
- ais.loadOptions(++ais.loaded_page);
422
- });
423
- }
476
+ AlchemySelect.ensurePaginationChecker();
424
477
 
425
478
  this.addEventListener('focus-without', function onFocusOut(e) {
426
479
  that.type_area.value = '';
@@ -442,8 +495,6 @@ AlchemySelect.setMethod(function introduced() {
442
495
 
443
496
  this.addEventListener('keydown', function onKeydown(e) {
444
497
 
445
- console.log('keydown:', e);
446
-
447
498
  // Only listen for keydowns on the alchemy-select itself
448
499
  if (e.target != that) {
449
500
  return;
@@ -809,11 +860,16 @@ AlchemySelect.setMethod(function _processPreloadedValues() {
809
860
  *
810
861
  * @author Jelle De Loecker <jelle@develry.be>
811
862
  * @since 0.1.0
812
- * @version 0.1.0
863
+ * @version 0.1.8
813
864
  *
814
865
  * @param {Object} response
866
+ * @param {Number} page The page, if it's via pagination
815
867
  */
816
- AlchemySelect.setMethod(function _processResponseData(response) {
868
+ AlchemySelect.setMethod(function _processResponseData(response, page) {
869
+
870
+ if (!response) {
871
+ response = {};
872
+ }
817
873
 
818
874
  if (response.available) {
819
875
  this.total_item_count = response.available;
@@ -823,14 +879,52 @@ AlchemySelect.setMethod(function _processResponseData(response) {
823
879
  record,
824
880
  item;
825
881
 
826
- for (record of records) {
882
+ if (!records) {
883
+ records = [];
884
+ }
885
+
886
+ this._processResponseList(records, page);
887
+
888
+ this.refreshResultAmount();
889
+ this._markSelectedItems();
890
+ });
891
+
892
+ /**
893
+ * Process the response list of a certain paige
894
+ *
895
+ * @author Jelle De Loecker <jelle@elevenways.be>
896
+ * @since 0.1.8
897
+ * @version 0.1.8
898
+ *
899
+ * @param {Array} list
900
+ * @param {Number} page
901
+ */
902
+ AlchemySelect.setMethod(function _processResponseList(list, page) {
903
+
904
+ let record,
905
+ item;
906
+
907
+ if (page == 1 && this.current_option && this.current_option.data) {
908
+ record = this.current_option.data;
909
+ item = this._makeOption(record._id || record.id, record);
910
+ this.addToDropdown(item);
911
+ }
912
+
913
+ for (record of list) {
827
914
  item = this._makeOption(record._id || record.id, record);
828
915
  this.addToDropdown(item);
829
916
  }
830
917
 
831
918
  this.loading_dropdown = false;
832
- this.refreshResultAmount();
833
- this._markSelectedItems();
919
+
920
+ // If there were values & they come from a page, record this as a succesful page
921
+ if (page != null && list.length) {
922
+ if (!this.last_page_result) {
923
+ this.setLastSuccessfulLoadedPage(page);
924
+ } else if (page > this.last_page_result) {
925
+ this.setLastSuccessfulLoadedPage(page);
926
+ }
927
+ }
834
928
  });
835
929
 
836
930
  /**
@@ -838,7 +932,7 @@ AlchemySelect.setMethod(function _processResponseData(response) {
838
932
  *
839
933
  * @author Jelle De Loecker <jelle@develry.be>
840
934
  * @since 0.1.0
841
- * @version 0.1.0
935
+ * @version 0.1.8
842
936
  *
843
937
  * @param {Number} page
844
938
  *
@@ -859,6 +953,10 @@ AlchemySelect.setMethod(function loadRemote(page) {
859
953
  return pledge;
860
954
  }
861
955
 
956
+ if (page == null) {
957
+ page = 1;
958
+ }
959
+
862
960
  const that = this;
863
961
 
864
962
  this.loading_dropdown = true;
@@ -878,11 +976,11 @@ AlchemySelect.setMethod(function loadRemote(page) {
878
976
  throw err;
879
977
  }
880
978
 
881
- if (page == null) {
979
+ if (page == null || page == 1) {
882
980
  Hawkejs.removeChildren(that.dropdown_content);
883
981
  }
884
982
 
885
- that._processResponseData(data);
983
+ that._processResponseData(data, page);
886
984
 
887
985
  pledge.resolve(true);
888
986
  };
@@ -973,7 +1071,19 @@ AlchemySelect.setMethod(function _markSelectedItems() {
973
1071
  }
974
1072
  }
975
1073
  }
1074
+ });
976
1075
 
1076
+ /**
1077
+ * Set the last successful loaded page
1078
+ *
1079
+ * @author Jelle De Loecker <jelle@develry.be>
1080
+ * @since 0.1.8
1081
+ * @version 0.1.8
1082
+ *
1083
+ * @param {Number} page
1084
+ */
1085
+ AlchemySelect.setMethod(function setLastSuccessfulLoadedPage(page) {
1086
+ this.last_page_result = page;
977
1087
  });
978
1088
 
979
1089
  /**
@@ -981,15 +1091,13 @@ AlchemySelect.setMethod(function _markSelectedItems() {
981
1091
  *
982
1092
  * @author Jelle De Loecker <jelle@develry.be>
983
1093
  * @since 0.1.0
984
- * @version 0.1.0
1094
+ * @version 0.1.8
985
1095
  *
986
1096
  * @param {Number} page
987
1097
  */
988
1098
  AlchemySelect.setMethod(function loadOptions(page) {
989
1099
 
990
- var that = this,
991
- model,
992
- field;
1100
+ const that = this;
993
1101
 
994
1102
  if (this.total_item_count >= this.loaded_item_count) {
995
1103
  if (this.dropdown_content.children.length == 0) {
@@ -1001,12 +1109,34 @@ AlchemySelect.setMethod(function loadOptions(page) {
1001
1109
  return;
1002
1110
  }
1003
1111
 
1112
+ // Prevent it from keeping on loading empty pages if the previous page was empty
1113
+ if (page && this.last_page_result) {
1114
+ let next_allowed_page = this.last_page_result + 1;
1115
+
1116
+ if (page > next_allowed_page) {
1117
+ return;
1118
+ }
1119
+ }
1120
+
1121
+ // Previous page will only be set to the last page that had items.
1122
+ // So if there are 0 results overall, it won't ever be set.
1123
+ // In that case: ignore any page load over 1.
1124
+ if (page > 1 && !this.last_page_result) {
1125
+ return;
1126
+ }
1127
+
1128
+ // If stuff has already been loaded and this is just re-opening the dropdown,
1129
+ // don't do anything yet
1130
+ if (page == null && this.last_page_result > 0) {
1131
+ return;
1132
+ }
1133
+
1004
1134
  // If an URL is provided, use that
1005
1135
  if (this.src || this.dataprovider) {
1006
1136
  return this.loadRemote(page);
1007
1137
  }
1008
1138
 
1009
- field = this.model_field;
1139
+ let field = this.model_field;
1010
1140
 
1011
1141
  if (!field) {
1012
1142
  return;
@@ -1014,7 +1144,7 @@ AlchemySelect.setMethod(function loadOptions(page) {
1014
1144
 
1015
1145
  this.loading_dropdown = true;
1016
1146
 
1017
- model = this.form.getModel(field.modelName);
1147
+ let model = this.form.getModel(field.modelName);
1018
1148
 
1019
1149
  model.find('all', {
1020
1150
  limit : 20,
@@ -1025,12 +1155,7 @@ AlchemySelect.setMethod(function loadOptions(page) {
1025
1155
  throw err;
1026
1156
  }
1027
1157
 
1028
- records.forEach(function eachRecord(record) {
1029
- var item = that._makeOption(record._id || record.id, record);
1030
- that.addToDropdown(item);
1031
- });
1032
-
1033
- that.loading_dropdown = false;
1158
+ that._processResponseList(records, page);
1034
1159
  });
1035
1160
  });
1036
1161
 
@@ -1166,7 +1291,7 @@ AlchemySelect.setMethod(function close(event) {
1166
1291
  *
1167
1292
  * @author Jelle De Loecker <jelle@develry.be>
1168
1293
  * @since 0.1.0
1169
- * @version 0.1.5
1294
+ * @version 0.1.6
1170
1295
  *
1171
1296
  * @param {String} type "value" or "option"
1172
1297
  * @param {Mixed} value The actual value of this item
@@ -1181,6 +1306,13 @@ AlchemySelect.setMethod(function _makeValueItem(type, value, data) {
1181
1306
  // Set the type ("value" or "option")
1182
1307
  item.type = type;
1183
1308
 
1309
+ // Set the custom template to use, if any
1310
+ let custom_template = this[type + '-item-template'];
1311
+
1312
+ if (custom_template) {
1313
+ item.custom_template = custom_template;
1314
+ }
1315
+
1184
1316
  // Assign the value
1185
1317
  item.value = value;
1186
1318
 
@@ -1664,9 +1796,10 @@ AlchemySelect.setMethod(function applyLocalFilter(query) {
1664
1796
  *
1665
1797
  * @author Jelle De Loecker <jelle@develry.be>
1666
1798
  * @since 0.1.0
1667
- * @version 0.1.0
1799
+ * @version 0.1.8
1668
1800
  */
1669
1801
  AlchemySelect.setMethod('refreshRemote', Fn.throttle(function refreshRemote() {
1802
+ this.loaded_page = 0;
1670
1803
  this.loadRemote();
1671
1804
  }, {
1672
1805
  minimum_wait : 350,
@@ -37,6 +37,15 @@ Item.setAttribute('type');
37
37
  */
38
38
  Item.setAttribute('selected', {boolean: true});
39
39
 
40
+ /**
41
+ * Should a custom template be used?
42
+ *
43
+ * @author Jelle De Loecker <jelle@elevenways.be>
44
+ * @since 0.1.6
45
+ * @version 0.1.6
46
+ */
47
+ Item.setAttribute('custom-template');
48
+
40
49
  /**
41
50
  * The value of this item
42
51
  *
@@ -513,7 +513,7 @@ Table.setMethod(function getCurrentStateUrl() {
513
513
  *
514
514
  * @author Jelle De Loecker <jelle@elevenways.be>
515
515
  * @since 0.1.0
516
- * @version 0.1.0
516
+ * @version 0.1.6
517
517
  *
518
518
  * @param {Array} records
519
519
  */
@@ -530,6 +530,7 @@ Table.setMethod(function setRecords(records) {
530
530
  }
531
531
 
532
532
  this.showPagination();
533
+ this.attachContextMenus();
533
534
  });
534
535
 
535
536
  /**
@@ -588,12 +589,49 @@ Table.setMethod(function showPagination() {
588
589
  pager.showPage(records.page, Math.ceil(records.available / records.page_size));
589
590
  });
590
591
 
592
+ /**
593
+ * Get a fieldset's preferred content view
594
+ *
595
+ * @author Jelle De Loecker <jelle@elevenways.be>
596
+ * @since 0.1.8
597
+ * @version 0.1.8
598
+ *
599
+ * @param {FieldConfig} field_config The config on how to display the field
600
+ * @param {Object} container The container where the field should be in
601
+ *
602
+ * @return
603
+ */
604
+ Table.setMethod(function getFieldConfigView(field_config, container) {
605
+
606
+ let value = field_config.getValueIn(container),
607
+ field = field_config.getFieldDefinition();
608
+
609
+ if (value == null && !field) {
610
+ return null;
611
+ }
612
+
613
+ // Fallback to simple text view
614
+ if (!field) {
615
+ let text = field_config.getDisplayValueIn(container);
616
+
617
+ return Hawkejs.createText(text);
618
+ }
619
+
620
+ let alchemy_field = this.createElement('alchemy-field');
621
+ alchemy_field.view_type = 'view_inline';
622
+ alchemy_field.field_name = field.name;
623
+ alchemy_field.config = field;
624
+ alchemy_field.original_value = value;
625
+
626
+ return alchemy_field;
627
+ });
628
+
591
629
  /**
592
630
  * Create a datarow
593
631
  *
594
632
  * @author Jelle De Loecker <jelle@elevenways.be>
595
633
  * @since 0.1.0
596
- * @version 0.1.1
634
+ * @version 0.1.8
597
635
  *
598
636
  * @param {Object} entry
599
637
  *
@@ -601,18 +639,21 @@ Table.setMethod(function showPagination() {
601
639
  */
602
640
  Table.setMethod(function createDataRow(entry) {
603
641
 
604
- let field,
642
+ let field_set_config,
605
643
  value,
606
644
  tr = this.createElement('tr'),
607
- td;
645
+ td,
646
+ id = entry.$pk || entry._id || entry.id;
647
+
648
+ tr.dataset.pk = id;
608
649
 
609
- for (field of this.fieldset) {
650
+ for (field_set_config of this.fieldset) {
610
651
  td = this.createElement('td');
611
652
 
612
- value = field.getDisplayValueIn(entry);
653
+ let element = this.getFieldConfigView(field_set_config, entry);
613
654
 
614
- if (value != null) {
615
- td.textContent = value;
655
+ if (element) {
656
+ td.append(element);
616
657
  }
617
658
 
618
659
  tr.append(td);
@@ -623,31 +664,22 @@ Table.setMethod(function createDataRow(entry) {
623
664
  td.classList.add('aft-actions');
624
665
  tr.append(td);
625
666
 
626
- let actions;
627
-
628
- if (entry.$hold && entry.$hold.actions) {
629
- actions = entry.$hold.actions;
630
- } else {
631
- actions = entry.$actions;
632
- }
667
+ let actions = this.getEntryActions(entry, 'row');
633
668
 
634
669
  if (actions && actions.length) {
670
+
635
671
  let action,
636
672
  anchor;
637
673
 
674
+ // Iterate over all the defined actions
638
675
  for (action of actions) {
639
- anchor = this.createElement('a');
640
- anchor.dataset.name = action.name;
641
-
642
- if (action.icon) {
643
- let alico = this.createElement('al-ico');
644
- alico.setAttribute('type', action.icon);
645
- anchor.append(alico);
646
- } else {
647
- anchor.textContent = action.title || action.name;
676
+
677
+ anchor = action.constructElement(this);
678
+
679
+ if (!anchor) {
680
+ continue;
648
681
  }
649
682
 
650
- anchor.setAttribute('href', action.url);
651
683
  td.append(anchor);
652
684
  }
653
685
  }
@@ -658,6 +690,118 @@ Table.setMethod(function createDataRow(entry) {
658
690
  return tr;
659
691
  });
660
692
 
693
+ /**
694
+ * Get a list of actions
695
+ *
696
+ * @author Jelle De Loecker <jelle@elevenways.be>
697
+ * @since 0.1.6
698
+ * @version 0.1.6
699
+ *
700
+ * @param {Object} record
701
+ * @param {Stirng} filter
702
+ *
703
+ * @return {Alchemy.Form.Action.Action[]}
704
+ */
705
+ Table.setMethod(function getEntryActions(record, filter) {
706
+
707
+ let result = [];
708
+
709
+ if (!record) {
710
+ return result;
711
+ }
712
+
713
+ let actions;
714
+
715
+ if (record.$hold && record.$hold.actions) {
716
+ actions = record.$hold.actions;
717
+ } else {
718
+ actions = record.$actions;
719
+ }
720
+
721
+ if(!actions || !actions.length) {
722
+ return result;
723
+ }
724
+
725
+ for (let action of actions) {
726
+
727
+ if (!action.type) {
728
+ action.type = 'url';
729
+ }
730
+
731
+ // Make sure it's an action instance
732
+ action = Classes.Alchemy.Form.Action.Action.cast(action);
733
+
734
+ if (!action || (filter && !action.isAllowedIn(filter))) {
735
+ continue;
736
+ }
737
+
738
+ result.push(action);
739
+ }
740
+
741
+ return result;
742
+ });
743
+
744
+ /**
745
+ * Add context menus to rows
746
+ *
747
+ * @author Jelle De Loecker <jelle@elevenways.be>
748
+ * @since 0.1.6
749
+ * @version 0.1.8
750
+ *
751
+ * @param {Object} entry
752
+ */
753
+ Table.setMethod(function attachContextMenus() {
754
+
755
+ if (!Blast.isBrowser) {
756
+ return;
757
+ }
758
+
759
+ if (!this.assigned_data.records || !this.assigned_data.records.length) {
760
+ return;
761
+ }
762
+
763
+ for (let record of this.assigned_data.records) {
764
+
765
+ let context_actions = this.getEntryActions(record, 'context');
766
+
767
+ if (!context_actions || !context_actions.length) {
768
+ continue;
769
+ }
770
+
771
+ let pk = record.$pk || record._id || record.id;
772
+
773
+ if (!pk) {
774
+ continue;
775
+ }
776
+
777
+ let tr = this.querySelector('[data-pk="' + pk + '"]');
778
+
779
+ if (!tr) {
780
+ continue;
781
+ }
782
+
783
+ tr.addEventListener('contextmenu', e => {
784
+
785
+ // Ignore right clicks on certain elements
786
+ if (e && e.target) {
787
+ if (e.target.closest('a')) {
788
+ return;
789
+ }
790
+ }
791
+
792
+ this.selectRow(tr);
793
+
794
+ let menu = this.createElement('he-context-menu');
795
+
796
+ for (let action of context_actions) {
797
+ action.addToContextMenu(menu);
798
+ }
799
+
800
+ menu.show(e);
801
+ });
802
+ }
803
+ });
804
+
661
805
  /**
662
806
  * On new fieldset
663
807
  *
@@ -931,12 +1075,14 @@ Table.setMethod(function sortData() {
931
1075
  *
932
1076
  * @author Jelle De Loecker <jelle@elevenways.be>
933
1077
  * @since 0.1.0
934
- * @version 0.1.0
1078
+ * @version 0.1.6
935
1079
  */
936
1080
  Table.setMethod(function introduced() {
937
1081
 
938
1082
  const that = this;
939
1083
 
1084
+ this.attachContextMenus();
1085
+
940
1086
  this.addEventListener('click', function onClick(e) {
941
1087
 
942
1088
  let target = e.target,