glib-web 4.11.2 → 4.11.4

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.
@@ -3,6 +3,7 @@ import merge from 'lodash.merge';
3
3
  import { nextTick } from "vue";
4
4
  import { sanitize } from "../../components/composable/date";
5
5
  import { isPresent } from "../../utils/type";
6
+ import { getFormData as _getFormData } from "../../components/composable/form";
6
7
 
7
8
  const sumDate = function (a, b) {
8
9
  const date = new Date(a);
@@ -76,21 +77,7 @@ jsonLogic.add_operation("isPresent", isPresentOperator);
76
77
 
77
78
  function getFormData() {
78
79
  return Array.from(document.querySelectorAll('form')).reduce((prev, curr) => {
79
- const formData = new FormData(curr);
80
- const obj = {};
81
-
82
- formData.forEach((value, key) => {
83
- // Reflect.has in favor of: object.hasOwnProperty(key)
84
- if (!Reflect.has(obj, key)) {
85
- obj[key] = value;
86
- return;
87
- }
88
- if (!Array.isArray(obj[key])) {
89
- obj[key] = [obj[key]];
90
- }
91
- obj[key].push(value);
92
- });
93
-
80
+ const obj = _getFormData(curr);
94
81
  return Object.assign({}, prev, obj);
95
82
  }, {});
96
83
  }
package/app.vue CHANGED
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <v-app :class="page.styleClasses">
2
+ <v-app :class="page.styleClasses" ref="appRef">
3
3
  <component :is="containerComponent" :spec="formSpec" :style="'height: 100%;'">
4
4
  <nav-layout ref="navBar" @mounted="updateMainHeight()" :page="page" />
5
5
 
@@ -44,7 +44,7 @@ import FormPanel from "./components/panels/form.vue";
44
44
  import { vueApp } from "./store";
45
45
  import { useSocket } from "./components/composable/socket";
46
46
  import { usePasteable } from "./components/composable/pasteable";
47
- import { computed, getCurrentInstance, onMounted } from "vue";
47
+ import { computed, onMounted, ref } from "vue";
48
48
  import { TOOLTIP_ID } from "./constant";
49
49
  import { watchGlibEvent } from "./store";
50
50
  import { htmlElement } from "./components/helper";
@@ -56,16 +56,17 @@ export default {
56
56
  const filePaster = computed(() => props.page.filePaster);
57
57
  usePasteable(filePaster);
58
58
  const tooltipId = TOOLTIP_ID;
59
+ const appRef = ref(null);
59
60
 
60
61
  onMounted(() => {
61
- const el = htmlElement(getCurrentInstance().ctx);
62
+ const el = htmlElement(appRef.value);
62
63
  el.addEventListener('dirtyupdate', (event) => {
63
64
  const { dirty } = event.detail;
64
65
  vueApp.isFormDirty = dirty;
65
66
  });
66
67
  });
67
68
 
68
- return { vueApp, tooltipId };
69
+ return { vueApp, tooltipId, appRef };
69
70
  },
70
71
  components: {
71
72
  "nav-layout": NavLayout,
@@ -73,7 +73,7 @@ const RichTextField = defineAsyncComponent(() => import("./fields/richText.vue")
73
73
  const FileField = defineAsyncComponent(() => import("./fields/file.vue"));
74
74
  const MultiUploadField = defineAsyncComponent(() => import("./fields/multiUpload.vue"));
75
75
  import SignField from "./fields/sign.vue";
76
- import SelectField from "./fields/select.vue";
76
+ import SelectField from "./fields/_select.vue";
77
77
  import TimeZoneField from "./fields/timeZone.vue";
78
78
  import DynamicSelectField from "./fields/dynamicSelect.vue";
79
79
  import RadioGroupField from "./fields/radioGroup.vue";
@@ -1,4 +1,4 @@
1
- import { getCurrentInstance, nextTick, onBeforeUnmount, onMounted, ref } from "vue";
1
+ import { inject, nextTick, onBeforeUnmount, onMounted, provide, ref, watch } from "vue";
2
2
  import { htmlElement } from "../helper";
3
3
 
4
4
  const setBusy = (htmlElement, value) => {
@@ -14,6 +14,7 @@ const triggerOnChange = (htmlElement) => {
14
14
  const triggerOnInput = (htmlElement) => nextTick(() => htmlElement.dispatchEvent(new Event('input', { bubbles: true })));
15
15
 
16
16
  const getFormData = (el, ignoredFields = new Set()) => {
17
+ if (!el) return {};
17
18
  if (!(el instanceof HTMLFormElement) && el instanceof HTMLElement) {
18
19
  el = el.querySelector('form');
19
20
  }
@@ -37,37 +38,36 @@ const getFormData = (el, ignoredFields = new Set()) => {
37
38
  return obj;
38
39
  };
39
40
 
40
- const ignoredDirtyCheckFields = ref(new Set());
41
-
42
- function useDirtyState() {
43
-
41
+ function useGlibForm({ formRef }) {
44
42
  const initFormData = ref({});
45
43
  const currentFormData = ref({});
46
44
  const el = ref(null);
47
- const instance = getCurrentInstance();
45
+ const registeredInputs = ref([]);
46
+ const ignoredDirtyCheckFields = ref(new Set());
48
47
 
49
48
  const dispatchEv = (dirty = isFormDirty()) => {
49
+ if (!el.value) return;
50
+
50
51
  const event = new CustomEvent('dirtyupdate', { bubbles: true, detail: { dirty: dirty } });
51
52
  el.value.dispatchEvent(event);
52
53
  };
53
54
 
54
-
55
55
  onMounted(() => {
56
- setTimeout(() => {
57
- el.value = htmlElement(instance.ctx);
58
- initFormData.value = getFormData(el.value, ignoredDirtyCheckFields.value);
59
- currentFormData.value = initFormData.value;
56
+ el.value = htmlElement(formRef.value);
57
+ });
60
58
 
61
- dispatchEv();
62
- }, 500);
59
+ watch(registeredInputs, (value) => {
60
+ initFormData.value = getFormData(el.value, ignoredDirtyCheckFields.value);
61
+ currentFormData.value = initFormData.value;
63
62
 
63
+ dispatchEv();
64
64
  });
65
65
 
66
66
  onBeforeUnmount(() => {
67
67
  dispatchEv(false);
68
68
  });
69
69
 
70
- const update = (event) => {
70
+ const updateDirtyState = (event) => {
71
71
  currentFormData.value = getFormData(el.value, ignoredDirtyCheckFields.value);
72
72
  dispatchEv();
73
73
  };
@@ -76,7 +76,22 @@ function useDirtyState() {
76
76
  return JSON.stringify(initFormData.value) !== JSON.stringify(currentFormData.value);
77
77
  };
78
78
 
79
- return { update };
79
+ provide('registeredInputs', registeredInputs);
80
+ provide('ignoredDirtyCheckFields', ignoredDirtyCheckFields);
81
+
82
+ return { updateDirtyState };
83
+ }
84
+
85
+ function useGlibInput({ props }) {
86
+ const registeredInputs = inject('registeredInputs', null);
87
+ const ignoredDirtyCheckFields = inject('ignoredDirtyCheckFields', null);
88
+
89
+ if (!registeredInputs || !ignoredDirtyCheckFields) return;
90
+
91
+ onMounted(() => {
92
+ if (props.spec.disableDirtyCheck) ignoredDirtyCheckFields.value.add(props.spec.name);
93
+ registeredInputs.value = Array.from(new Set(registeredInputs.value).add(props.spec.name));
94
+ });
80
95
  }
81
96
 
82
- export { setBusy, triggerOnChange, triggerOnInput, useDirtyState, getFormData, ignoredDirtyCheckFields };
97
+ export { setBusy, triggerOnChange, triggerOnInput, useGlibForm, useGlibInput, getFormData };
@@ -1,4 +1,6 @@
1
- export function parseCsv(csvString) {
1
+ export function parseCsv(csvString, options = { withHeader: true }) {
2
+ const { withHeader } = options;
3
+
2
4
  // https://stackoverflow.com/a/41563966/9970813
3
5
  let prevLetter = "",
4
6
  row = [""],
@@ -19,7 +21,8 @@ export function parseCsv(csvString) {
19
21
  letter = row[++columnIndex] = "";
20
22
  } else if ("\n" === letter && canSplit) {
21
23
  if ("\r" === prevLetter) {
22
- row[columnIndex] = row[columnIndex].slice(0, -1);
24
+ const str = row[columnIndex].slice(0, -1);
25
+ row[columnIndex] = str.includes(',') ? parseCsv(str, { withHeader: false })[0] : str;
23
26
  }
24
27
  row = result[++rowIndex] = [(letter = "")];
25
28
  columnIndex = 0;
@@ -37,10 +40,14 @@ export function parseCsv(csvString) {
37
40
  // return r[0] !== "undefined"
38
41
  // });
39
42
 
40
- const columns = result.shift();
41
- return result.map(row => {
42
- return columns.reduce((prev, curr, index) => {
43
- return Object.assign({}, prev, { [curr]: row[index] });
44
- }, {});
45
- });
43
+ if (withHeader) {
44
+ const columns = result.shift();
45
+ return result.map(row => {
46
+ return columns.reduce((prev, curr, index) => {
47
+ return Object.assign({}, prev, { [curr]: row[index] });
48
+ }, {});
49
+ });
50
+ }
51
+
52
+ return result;
46
53
  }
@@ -11,6 +11,7 @@
11
11
 
12
12
  <script>
13
13
  import { sanitize } from "../composable/date";
14
+ import { useGlibInput } from "../composable/form";
14
15
  import inputVariant from '../mixins/inputVariant';
15
16
 
16
17
  export default {
@@ -20,6 +21,9 @@ export default {
20
21
  type: { type: String, required: true },
21
22
  pattern: { type: String, required: true }
22
23
  },
24
+ setup(props) {
25
+ useGlibInput({ props });
26
+ },
23
27
  methods: {
24
28
  _linkFieldModels(valueChanged) {
25
29
  if (valueChanged) this.fieldModel = this.sanitizeValue(this.spec.value);
@@ -38,7 +38,7 @@
38
38
  <script>
39
39
  import inputVariant from '../mixins/inputVariant';
40
40
  import { determineDensity } from "../../utils/constant";
41
- import { triggerOnChange, triggerOnInput } from "../composable/form";
41
+ import { triggerOnChange, triggerOnInput, useGlibInput } from "../composable/form";
42
42
 
43
43
  export default {
44
44
  mixins: [inputVariant],
@@ -46,6 +46,9 @@ export default {
46
46
  spec: { type: Object, required: true },
47
47
  defaultValue: { type: String, default: null }
48
48
  },
49
+ setup(props) {
50
+ useGlibInput({ props });
51
+ },
49
52
  data() {
50
53
  return {
51
54
  options: null,
@@ -96,6 +99,13 @@ export default {
96
99
  }
97
100
  },
98
101
  methods: {
102
+ // _linkFieldModels(valueChanged) {
103
+ // if (this.spec.csvMode && valueChanged) {
104
+ // this.fieldModel = (this.spec.value || '').replace(/,\s+/g, ",").split(",").filter((v) => v != '');
105
+ // return;
106
+ // }
107
+ // this.fieldModel = this.spec.value;
108
+ // },
99
109
  onChange() {
100
110
  this.$executeOnChange();
101
111
  const containerEl = this.$refs.container;
@@ -121,13 +131,6 @@ export default {
121
131
  this.fieldModel = this.defaultValue;
122
132
  }
123
133
  },
124
- // TODO
125
- // sanitizeValue(value) {
126
- // if (this.spec.csvMode) {
127
- // return value.replace(/,\s+/g,",").split(",");
128
- // }
129
- // return value;
130
- // },
131
134
  $registryEnabled() {
132
135
  return false;
133
136
  }
@@ -20,6 +20,7 @@
20
20
  <script>
21
21
  import ThumbnailCheck from "../fields/check/_thumbnail.vue";
22
22
  import FeaturedCheck from "../fields/check/_featured.vue";
23
+ import { useGlibInput } from "../composable/form";
23
24
  export default {
24
25
  components: {
25
26
  "thumbnail-check": ThumbnailCheck,
@@ -35,6 +36,9 @@ export default {
35
36
  props: {
36
37
  spec: { type: Object, required: true }
37
38
  },
39
+ setup(props) {
40
+ useGlibInput({ props });
41
+ },
38
42
  data() {
39
43
  return {
40
44
  uncheckValue: this.spec.uncheckValue
@@ -14,6 +14,7 @@
14
14
  <script>
15
15
  import { defineComponent, ref, provide, reactive } from "vue";
16
16
  import FieldCheck from './check.vue';
17
+ import { useGlibInput } from "../composable/form";
17
18
 
18
19
  export default defineComponent({
19
20
  components: {
@@ -22,6 +23,9 @@ export default defineComponent({
22
23
  props: {
23
24
  spec: { type: Object, required: true }
24
25
  },
26
+ setup(props) {
27
+ useGlibInput({ props });
28
+ },
25
29
  watch: {
26
30
  childModels: function (val) {
27
31
  // Ensure submitted params are in the same order as the checkboxes in display`
@@ -14,13 +14,16 @@
14
14
  <script>
15
15
  import { defineComponent } from "vue";
16
16
  import inputVariant from "../mixins/inputVariant";
17
- import { triggerOnChange, triggerOnInput } from "../composable/form";
17
+ import { triggerOnChange, triggerOnInput, useGlibInput } from "../composable/form";
18
18
 
19
19
  export default defineComponent({
20
20
  mixins: [inputVariant],
21
21
  props: {
22
22
  spec: Object
23
23
  },
24
+ setup(props) {
25
+ useGlibInput({ props });
26
+ },
24
27
  computed: {
25
28
  values() {
26
29
  if (this.fieldModel == null || this.fieldModel == undefined || this.fieldModel.length <= 0) return [null];
@@ -4,6 +4,7 @@
4
4
  </template>
5
5
 
6
6
  <script>
7
+ import { useGlibInput } from "../composable/form";
7
8
  import PatternTextField from "./_patternText.vue";
8
9
 
9
10
  export default {
@@ -13,7 +14,10 @@ export default {
13
14
  },
14
15
  props: {
15
16
  spec: { type: Object, required: true },
16
- }
17
+ },
18
+ setup(props) {
19
+ useGlibInput({ props });
20
+ },
17
21
  };
18
22
  </script>
19
23
 
@@ -18,6 +18,7 @@
18
18
  </template>
19
19
 
20
20
  <script>
21
+ import { useGlibInput } from "../composable/form";
21
22
  import PatternTextField from "./_patternText.vue";
22
23
 
23
24
  export default {
@@ -27,7 +28,10 @@ export default {
27
28
  },
28
29
  props: {
29
30
  spec: { type: Object, required: true },
30
- }
31
+ },
32
+ setup(props) {
33
+ useGlibInput({ props });
34
+ },
31
35
  };
32
36
  </script>
33
37
 
@@ -36,11 +36,15 @@
36
36
  import Uploader from "../../utils/glibDirectUpload";
37
37
  import { validateFile } from "../composable/file";
38
38
  import { determineVariant } from "../../utils/constant";
39
+ import { useGlibInput } from "../composable/form";
39
40
 
40
41
  export default {
41
42
  props: {
42
43
  spec: { type: Object, required: true },
43
44
  },
45
+ setup(props) {
46
+ useGlibInput({ props });
47
+ },
44
48
  data() {
45
49
  return {
46
50
  uploaded: false,
@@ -3,10 +3,15 @@
3
3
  </template>
4
4
 
5
5
  <script>
6
+ import { useGlibInput } from "../composable/form";
7
+
6
8
  export default {
7
9
  props: {
8
10
  spec: { type: Object, required: true },
9
11
  },
12
+ setup(props) {
13
+ useGlibInput({ props });
14
+ },
10
15
  methods: {
11
16
  $ready() {
12
17
  // this.fieldModel = this.spec.value;
@@ -58,7 +58,7 @@ import { ref, computed, defineComponent, watch, getCurrentInstance } from 'vue';
58
58
  import { VIcon } from 'vuetify/components';
59
59
  import * as dropUploader from "../composable/upload";
60
60
  import * as delegateUploader from "../composable/upload_delegator";
61
- import { triggerOnChange } from "../composable/form";
61
+ import { triggerOnChange, useGlibInput } from "../composable/form";
62
62
  import { nextTick } from "vue";
63
63
  import { useFilesState, useFileUtils } from "../composable/file";
64
64
  import doc from "./selectAsset/doc-1.png";
@@ -187,6 +187,8 @@ export default defineComponent({
187
187
  fileSelect.value.click();
188
188
  }
189
189
 
190
+ useGlibInput({ props });
191
+
190
192
  return {
191
193
  files,
192
194
  fileSelect,
@@ -8,7 +8,7 @@
8
8
  </template>
9
9
 
10
10
  <script>
11
- import { triggerOnChange } from "../composable/form";
11
+ import { triggerOnChange, useGlibInput } from "../composable/form";
12
12
  import inputVariant from "../mixins/inputVariant";
13
13
 
14
14
  export default {
@@ -16,6 +16,9 @@ export default {
16
16
  props: {
17
17
  spec: { type: Object, required: true },
18
18
  },
19
+ setup(props) {
20
+ useGlibInput({ props });
21
+ },
19
22
  watch: {
20
23
  // there is a bug empty otp doesn't trigger form onchange
21
24
  fieldModel(val, oldVal) {
@@ -19,6 +19,7 @@
19
19
 
20
20
  import ThumbnailRadio from "../fields/radio/_thumbnail.vue";
21
21
  import FeaturedRadio from "../fields/radio/_featured.vue";
22
+ import { useGlibInput } from "../composable/form";
22
23
 
23
24
  export default {
24
25
  components: {
@@ -28,6 +29,9 @@ export default {
28
29
  props: {
29
30
  spec: { type: Object, required: true }
30
31
  },
32
+ setup(props) {
33
+ useGlibInput({ props });
34
+ },
31
35
  inject: ['radioGroupCtx'],
32
36
  computed: {
33
37
  isActive() {
@@ -8,7 +8,12 @@
8
8
  </template>
9
9
 
10
10
  <script>
11
+ import { useGlibInput } from "../composable/form";
12
+
11
13
  export default {
12
- props: { spec: { type: Object, required: true } }
14
+ props: { spec: { type: Object, required: true } },
15
+ setup(props) {
16
+ useGlibInput({ props });
17
+ },
13
18
  };
14
19
  </script>
@@ -39,7 +39,7 @@ import QuillImageDropAndPaste from "quill-image-drop-and-paste";
39
39
  import bus from "../../utils/eventBus";
40
40
 
41
41
  import { vueApp } from "../../store";
42
- import { triggerOnInput } from "../composable/form";
42
+ import { triggerOnInput, useGlibInput } from "../composable/form";
43
43
  import { validateFile } from "../composable/file";
44
44
 
45
45
  var ImageBlot = Quill.import("formats/image");
@@ -177,6 +177,9 @@ export default {
177
177
  props: {
178
178
  spec: { type: Object, required: true },
179
179
  },
180
+ setup(props) {
181
+ useGlibInput({ props });
182
+ },
180
183
  data: function () {
181
184
  return {
182
185
  customToolbar: [
@@ -47,6 +47,9 @@
47
47
  import { ref, computed } from "vue";
48
48
  import * as uploader from "../composable/upload";
49
49
  import { useFileUtils } from "../composable/file";
50
+ import { useGlibInput } from "../composable/form";
51
+
52
+ useGlibInput({ props });
50
53
 
51
54
  const { makeKey, Item } = useFileUtils();
52
55
  const props = defineProps(['spec']);
@@ -14,6 +14,7 @@
14
14
  import eventFiltering from "../../utils/eventFiltering";
15
15
  import inputVariant from "../mixins/inputVariant";
16
16
  import { determineDensity } from "../../utils/constant";
17
+ import { useGlibInput } from "../composable/form";
17
18
 
18
19
  export default {
19
20
  mixins: [inputVariant],
@@ -21,6 +22,9 @@ export default {
21
22
  spec: { type: Object, required: true },
22
23
  type: { type: String, required: true },
23
24
  },
25
+ setup(props) {
26
+ useGlibInput({ props });
27
+ },
24
28
  data() {
25
29
  return {
26
30
  // config: {},
@@ -12,12 +12,16 @@
12
12
  import eventFiltering from "../../utils/eventFiltering";
13
13
  import inputVariant from "../mixins/inputVariant";
14
14
  import { determineDensity } from "../../utils/constant";
15
+ import { useGlibInput } from "../composable/form";
15
16
 
16
17
  export default {
17
18
  mixins: [inputVariant],
18
19
  props: {
19
20
  spec: { type: Object, required: true },
20
21
  },
22
+ setup(props) {
23
+ useGlibInput({ props });
24
+ },
21
25
  data() {
22
26
  return {
23
27
  // styles: null,
@@ -2,7 +2,7 @@ import Hash from "../../utils/hash";
2
2
  import { fieldModels, watchFieldModels } from "../composable/conditional";
3
3
  import { determineColor } from "../../utils/constant";
4
4
  import Action from "../../action";
5
- import { ignoredDirtyCheckFields, triggerOnChange } from "../composable/form";
5
+ import { triggerOnChange } from "../composable/form";
6
6
  import { realComponent } from "../helper";
7
7
  import set from "lodash.set";
8
8
  const NUMBER_PRECISION = 2;
@@ -284,8 +284,6 @@ export default {
284
284
  if (valueChanged) {
285
285
  this.fieldModel = this.latestSpec.value;
286
286
  }
287
-
288
- if (this.spec.disableDirtyCheck) ignoredDirtyCheckFields.value.add(this.spec.name);
289
287
  }
290
288
  },
291
289
  action_resetValue() {
@@ -7,20 +7,21 @@
7
7
  <table :class="rowLoaded ? 'loaded' : 'nonLoaded'" width="100%">
8
8
  <thead v-if="props.spec.viewHeaders">
9
9
  <tr>
10
- <th class="cell-selection" v-if="rowLoaded"><v-checkbox v-model="checkbox" color="primary"
10
+ <th class="cell-selection" v-if="rowLoaded"><v-checkbox v-model="checkbox" density="compact" color="primary"
11
11
  @change="checkAll"></v-checkbox></th>
12
- <th :class="`cell-column${cellIndex}`" v-for="(cell, cellIndex) in props.spec.viewHeaders" :key="cell.id"
13
- :style="{ minWidth: `${cell.minWidth || 100}px` }">
14
- <span>{{ cell.text }}</span>
12
+ <th :class="`cell-column${cellIndex}`" v-for="(headerSpec, cellIndex) in tableHeaders" :key="cellIndex"
13
+ :style="{ minWidth: `${props.spec.viewHeaders[cellIndex].minWidth || 100}px` }">
14
+ <glib-component :spec="headerSpec" />
15
15
  </th>
16
16
  </tr>
17
17
  </thead>
18
18
 
19
19
  <tbody>
20
20
  <template v-if="rowLoaded">
21
- <tr v-for="(row, rowIndex) in currentPageRows" :key="`row_${rowIndex}_${row.status}`">
21
+ <tr v-for="(row, rowIndex) in currentPageRows" :key="`row_${rowIndex}`"
22
+ :class="row.selected ? 'selected' : 'unselected'">
22
23
  <td class="cell-selection">
23
- <v-checkbox v-model="row.selected" color="primary"></v-checkbox>
24
+ <v-checkbox v-model="row.selected" density="compact" color="primary"></v-checkbox>
24
25
  <glib-component :spec="row.icon"></glib-component>
25
26
  </td>
26
27
  <!-- <td class="cell-status"><v-icon :icon="row.icon.name" :color="row.icon.color"></v-icon></td> -->
@@ -52,8 +53,8 @@
52
53
  </table>
53
54
 
54
55
  </div>
55
- <v-pagination density="compact" v-if="paginationLength > 1" v-model="currPageIndex"
56
- :length="paginationLength"></v-pagination>
56
+ <v-pagination :disabled="paginationConfig.disabled" density="compact" v-if="paginationLength > 1"
57
+ v-model="currPageIndex" :length="paginationLength"></v-pagination>
57
58
  </div>
58
59
  </template>
59
60
 
@@ -75,10 +76,14 @@ table thead th {
75
76
  display: flex;
76
77
  align-items: center;
77
78
  }
79
+
80
+ tr.selected {
81
+ background-color: white;
82
+ }
78
83
  </style>
79
84
 
80
85
  <script setup>
81
- import { computed, getCurrentInstance, onMounted, ref, watch } from "vue";
86
+ import { computed, getCurrentInstance, onMounted, reactive, ref, watch } from "vue";
82
87
  import { parseCsv } from "../composable/parser";
83
88
  import Action from "../../action";
84
89
  import Hash from "../../utils/hash";
@@ -97,7 +102,7 @@ class Row {
97
102
  pending: { name: 'pending', color: 'info' },
98
103
  succeeded: { name: 'verified', color: 'success' },
99
104
  failed: { name: 'cancel', color: 'error' }
100
- }
105
+ };
101
106
 
102
107
  // if (!this.status) return {};
103
108
  const properties = new Hash(this.status ? obj[this.status] : { name: '' });
@@ -110,8 +115,9 @@ class Row {
110
115
  } else {
111
116
  return {
112
117
  view: 'label',
113
- text: this.index + 1
114
- }
118
+ styleClasses: ['status'],
119
+ text: `#${this.index + 1}`
120
+ };
115
121
  }
116
122
  }
117
123
  }
@@ -132,8 +138,20 @@ const props = defineProps(['spec']);
132
138
  const fileInput = ref(null);
133
139
  const checkbox = ref(false);
134
140
  const instance = getCurrentInstance();
141
+ const tableHeaders = props.spec.viewHeaders.map((header) => {
142
+ const spec = {
143
+ view: 'label',
144
+ text: header.text
145
+ };
146
+
147
+ if (header.tooltip) {
148
+ spec.tooltip = header.tooltip;
149
+ }
150
+
151
+ return spec;
152
+ });
135
153
  const currPageIndex = ref(1);
136
- const paginationConfig = { per: 25 };
154
+ const paginationConfig = reactive({ per: 25, disabled: false });
137
155
  // const paginationLength = computed(() => Math.ceil(unsavedRows.value.length / paginationConfig.per));
138
156
  const paginationLength = computed(() => getPageIndex(unsavedRows.value.length));
139
157
 
@@ -141,15 +159,15 @@ const rows = ref(makeRows(props.spec.dataRows));
141
159
  const indexedRows = ref({});
142
160
  let succeededRows = [];
143
161
  // const succeededRows = computed(() => rows.value.filter((row) => row.status === 'succeeded'))
144
- const unsavedRows = computed(() => rows.value.filter((row) => row.status !== 'succeeded'))
162
+ const unsavedRows = computed(() => rows.value.filter((row) => row.status !== 'succeeded'));
145
163
  const currentPageRows = computed(() => {
146
164
  // const firstItemIndex = currPageIndex.value <= 1 ? 0 : currPageIndex.value * paginationConfig.per;
147
165
 
148
166
  const firstItemIndex = (currPageIndex.value - 1) * paginationConfig.per;
149
167
  const lastItemIndex = firstItemIndex + paginationConfig.per;
150
168
 
151
- return unsavedRows.value.slice(firstItemIndex, lastItemIndex)
152
- })
169
+ return unsavedRows.value.slice(firstItemIndex, lastItemIndex);
170
+ });
153
171
  const selectedRows = computed(() => {
154
172
  const arr = [];
155
173
  unsavedRows.value.filter((row) => row.selected).forEach((row) => arr.push(row));
@@ -168,7 +186,8 @@ onMounted(() => {
168
186
  }
169
187
 
170
188
  const clonedRows = JSON.parse(JSON.stringify(selectedRows.value));
171
- Action.execute(props.spec.onSubmitStart, instance.ctx)
189
+ Action.execute(props.spec.onSubmitStart, instance.ctx);
190
+ paginationConfig.disabled = true;
172
191
 
173
192
  importCanceled = false;
174
193
  // Reset to first page before importing, so the progress is visible.
@@ -178,11 +197,11 @@ onMounted(() => {
178
197
  });
179
198
 
180
199
 
181
- watch(props, (value) => {
182
- if (value.spec.dataRows && value.spec.dataRows.length > 0) {
183
- rows.value = makeRows(value.spec.dataRows);
184
- }
185
- });
200
+ // watch(props, (value) => {
201
+ // if (value.spec.dataRows && value.spec.dataRows.length > 0) {
202
+ // rows.value = makeRows(value.spec.dataRows);
203
+ // }
204
+ // });
186
205
 
187
206
  watch(selectedRows, (value) => {
188
207
  const rows = value.map((row) => {
@@ -208,11 +227,11 @@ function handleDrop(e) {
208
227
  }
209
228
 
210
229
  function updateRows(newRows) {
211
- rows.value = newRows
230
+ rows.value = newRows;
212
231
 
213
232
  newRows.forEach((row) => {
214
- indexedRows.value[row.id] = row
215
- })
233
+ indexedRows.value[row.id] = row;
234
+ });
216
235
  }
217
236
 
218
237
  function makeRows(dataRows, selected = false) {
@@ -221,10 +240,10 @@ function makeRows(dataRows, selected = false) {
221
240
  // const id = dataRow.rowId || Math.random().toString(36).slice(2, 7);
222
241
  const id = dataRow.rowId || `row_${index}`;
223
242
  const columns = props.spec.viewCells.map((viewCells, i) => {
224
- const view = Object.assign({}, viewCells, dataRow.columns[i]);
243
+ const value = dataRow.columns[i].value;
244
+ const view = Object.assign({}, viewCells, dataRow.columns[i], { value: value });
225
245
  const cellIndex = i;
226
246
  const viewHeaders = props.spec.viewHeaders;
227
- const value = dataRow.columns[i].value;
228
247
  return new Cell({ rowId: id, view, cellIndex, viewHeaders, value });
229
248
  });
230
249
  return new Row({ selected, columns, id, index });
@@ -248,6 +267,7 @@ function handleCellChange(e, cell) {
248
267
  }
249
268
 
250
269
  cell.value = value;
270
+ cell.view.value = value;
251
271
 
252
272
  Action.executeWithFormData(props.spec.onCellChange, instance.ctx, { rowId, columnId, value });
253
273
  }
@@ -285,7 +305,7 @@ function loadFile(files) {
285
305
  }
286
306
 
287
307
  function getIndexAmongVisibleRows(row) {
288
- let indexAmongVisibleRows = 0
308
+ let indexAmongVisibleRows = 0;
289
309
  for (const currentRow of rows.value) {
290
310
  if (currentRow.status !== 'succeeded') {
291
311
  indexAmongVisibleRows++;
@@ -303,7 +323,7 @@ function getLatestIndexAmongVisibleRows() {
303
323
  }
304
324
 
305
325
  function getPageIndex(rowIndex) {
306
- return Math.ceil(rowIndex / paginationConfig.per)
326
+ return Math.ceil(rowIndex / paginationConfig.per);
307
327
  }
308
328
 
309
329
  function submitRows(rows) {
@@ -311,7 +331,8 @@ function submitRows(rows) {
311
331
  // Move back to the first page because the current page might not have any row anymore, e.g.
312
332
  // all the rows on the current page has been successfully submitted.
313
333
  currPageIndex.value = getPageIndex(getLatestIndexAmongVisibleRows());
314
- Action.execute(props.spec.onSubmitEnd, instance.ctx)
334
+ Action.execute(props.spec.onSubmitEnd, instance.ctx);
335
+ paginationConfig.disabled = false;
315
336
  return;
316
337
  }
317
338
 
@@ -321,14 +342,14 @@ function submitRows(rows) {
321
342
  currPageIndex.value = getPageIndex(getIndexAmongVisibleRows(row));
322
343
 
323
344
  // change row status to pending
324
- updateRow({ rowId: row.id, status: 'pending' })
345
+ updateRow({ rowId: row.id, status: 'pending' });
325
346
 
326
347
  const formData = row.columns.reduce((prev, curr) => {
327
348
  return Object.assign(prev, {
328
349
  [curr.cellId]: curr.value,
329
- [props.spec.paramNameForRowId || rowId]: row.id
330
- })
331
- }, {})
350
+ [props.spec.paramNameForRowId || 'rowId']: row.id
351
+ });
352
+ }, {});
332
353
  const { submitUrl, paramName } = props.spec.import;
333
354
 
334
355
  const data = {
@@ -341,14 +362,16 @@ function submitRows(rows) {
341
362
 
342
363
  if (importCanceled) {
343
364
  importCanceled = false;
344
- Action.execute(props.spec.onSubmitEnd, instance.ctx)
365
+ Action.execute(props.spec.onSubmitEnd, instance.ctx);
366
+ paginationConfig.disabled = false;
345
367
  } else {
346
368
  submitRows(rows);
347
369
  }
348
370
  }, (error, response) => {
349
371
  const message = error.toString();
350
372
  Utils.launch.snackbar.error(message, instance.ctx);
351
- Action.execute(props.spec.onSubmitEnd, instance.ctx)
373
+ Action.execute(props.spec.onSubmitEnd, instance.ctx);
374
+ paginationConfig.disabled = false;
352
375
  });
353
376
  }
354
377
 
@@ -67,7 +67,6 @@ export default {
67
67
  this.applyStyles(styles, this.lg);
68
68
  break;
69
69
  case "md":
70
- console.log(this.applyStyles(styles, this.xs));
71
70
  this.applyStyles(styles, this.xs);
72
71
  this.applyStyles(styles, this.sm);
73
72
  this.applyStyles(styles, this.md);
@@ -14,7 +14,7 @@
14
14
 
15
15
  <script>
16
16
  import { provide, ref } from "vue";
17
- import { useDirtyState } from "../composable/form";
17
+ import { useGlibForm } from "../composable/form";
18
18
 
19
19
  export default {
20
20
  props: {
@@ -23,11 +23,12 @@ export default {
23
23
  setup() {
24
24
  const formCtx = ref({ form: null });
25
25
 
26
- const dirtyState = useDirtyState();
26
+ const form = ref(null)
27
+ const glibForm = useGlibForm({ formRef: form });
27
28
 
28
29
  provide('formCtx', formCtx);
29
30
 
30
- return { formCtx, dirtyState };
31
+ return { formCtx, glibForm, form };
31
32
  },
32
33
  data: () => ({
33
34
  url: null,
@@ -109,7 +110,7 @@ export default {
109
110
  };
110
111
  this.formElement.onchange = () => onChangeHandler();
111
112
 
112
- this.formElement.oninput = (event) => this.dirtyState.update(event);
113
+ this.formElement.oninput = (event) => this.glibForm.updateDirtyState(event);
113
114
  }
114
115
 
115
116
  // this.parentStyles = this.genericStyles({ width: this.spec.width });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glib-web",
3
- "version": "4.11.2",
3
+ "version": "4.11.4",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/utils/http.js CHANGED
@@ -108,14 +108,14 @@ export default class {
108
108
  const windowOrDialog = windowMode ? true : !topOfDialog;
109
109
 
110
110
  this.execute(properties, "GET", component, (data, response) => {
111
+ const redirectUrl = Utils.url.htmlUrl(response.url);
111
112
  // TODO: Check if it is okay to remove this `if` statement so we always push even if it's the same URL.
112
113
  if (forcePushHistory || (windowOrDialog && !sameUrl && !properties.updateExisting)) {
113
- const redirectUrl = Utils.url.htmlUrl(response.url);
114
114
  Utils.history.pushPage(data, redirectUrl);
115
115
  }
116
116
 
117
117
  if (!forcePushHistory && windowMode && properties.updateExisting) {
118
- Utils.history.updatePage(data);
118
+ Utils.history.updatePage(data, redirectUrl);
119
119
  }
120
120
 
121
121
  this.forceComponentUpdate(() => {