glib-web 4.16.1 → 4.18.0

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.
@@ -1,8 +1,13 @@
1
+ import { executeGlibEvent } from "../../store";
1
2
  import { ifObject } from "../../utils/type";
2
3
 
3
4
  export default class {
4
5
  execute(properties, component) {
5
6
 
7
+ if (!executeGlibEvent('onbeforewindowsclose')) {
8
+ return;
9
+ }
10
+
6
11
  Utils.history.destroy();
7
12
 
8
13
  ifObject(properties["onClose"], it => {
@@ -1,7 +1,12 @@
1
1
  import Action from "../../action";
2
+ import { executeGlibEvent } from "../../store";
2
3
 
3
4
  export default class {
4
5
  execute(properties, component) {
6
+ if (!executeGlibEvent('onbeforewindowsclose')) {
7
+ return;
8
+ }
9
+
5
10
  Utils.history.destroy();
6
11
 
7
12
  // Don't use nextTick so that there is no visible flicker.
@@ -1,6 +1,12 @@
1
+ import { executeGlibEvent } from "../../store";
2
+
1
3
  export default class {
2
4
  execute(properties, component) {
3
- Utils.history.backWithoutRender()
5
+ if (!executeGlibEvent('onbeforewindowsclose')) {
6
+ return;
7
+ }
8
+
9
+ Utils.history.backWithoutRender();
4
10
 
5
11
  // Don't use nextTick so that there is no visible flicker.
6
12
  Utils.http.load(properties, component, true, true);
@@ -1,7 +1,12 @@
1
+ import { executeGlibEvent } from "../../store";
1
2
  import Hash from "../../utils/hash";
2
3
 
3
4
  export default class {
4
5
  execute(properties, component) {
6
+ if (!executeGlibEvent('onbeforewindowsclose')) {
7
+ return;
8
+ }
9
+
5
10
  const fallbackUrl = new Hash(properties).remove("fallbackUrl");
6
11
  const data = Object.assign({}, properties);
7
12
  if (!Utils.history.back()) {
@@ -11,7 +16,7 @@ export default class {
11
16
 
12
17
  // Allow time for history.back() to complete, which is important for actions that need
13
18
  // to use window.location.href such as `windows/reload`
14
- setTimeout(function() {
19
+ setTimeout(function () {
15
20
  Utils.http.reload(data, component);
16
21
  }, 100);
17
22
  }
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <v-icon class="aligner" :style="$styles()" :class="cssClasses" :size="icon.size" :color="color">{{
3
3
  icon.name
4
- }}</v-icon>
4
+ }}</v-icon>
5
5
  </template>
6
6
 
7
7
  <script>
@@ -36,6 +36,7 @@ export default {
36
36
  return this.$classes();
37
37
  },
38
38
  value() {
39
+ // TODO: is this still relevant?
39
40
  return this.isBusy ? "autorenew" : this.name;
40
41
  },
41
42
  icon() {
@@ -1,10 +1,9 @@
1
1
  <template>
2
2
  <!-- Use `click.prevent` to prevent bubbling when clicking a dropdown button that is
3
3
  located in a list row that has href. -->
4
- <v-btn :type="type" :disabled="isBusy || spec.disabled" :style="styles()" :class="$classes()" :href="$href()"
5
- :rel="$rel()" :variant="variant" :rounded="$classes().includes('rounded') || null" :density="density" :size="size"
6
- :color="color" :active="$classesInclude('active')" :icon="$classes().includes('icon') ? $vuetify : null"
7
- @click.prevent="
4
+ <v-btn :type="type" :disabled="spec.disabled" :style="styles()" :class="$classes()" :href="$href()" :rel="$rel()"
5
+ :variant="variant" :rounded="$classes().includes('rounded') || null" :density="density" :size="size" :color="color"
6
+ :active="$classesInclude('active')" :icon="$classes().includes('icon') ? $vuetify : null" @click.prevent="
8
7
  type == 'submit' ? $dispatchEvent('forms/submit') : $onClick()
9
8
  ">
10
9
  <!-- <span v-if="spec.icon"><common-icon :spec="spec.icon || {}" /></span> -->
@@ -16,9 +15,9 @@
16
15
  </template>
17
16
 
18
17
  <script>
19
- import { computed, nextTick } from "vue";
18
+ import { nextTick } from "vue";
20
19
  import { determineColor, determineDensity, determineVariant, determineSize } from '../utils/constant';
21
- import { vueApp } from "../store";
20
+
22
21
 
23
22
  export default {
24
23
  props: {
@@ -27,10 +26,6 @@ export default {
27
26
  hideTextOnXs: { type: Boolean },
28
27
  eventHandlers: { type: Object, default: null }
29
28
  },
30
- setup() {
31
- const isBusy = computed(() => vueApp.isBusy);
32
- return { isBusy };
33
- },
34
29
  computed: {
35
30
  color() {
36
31
  // return this.spec.color
@@ -41,6 +41,7 @@ const getFormData = (el, ignoredFields = new Set()) => {
41
41
  function useGlibForm({ formRef }) {
42
42
  const initFormData = ref({});
43
43
  const currentFormData = ref({});
44
+ const changed = ref(false);
44
45
  const el = ref(null);
45
46
  const registeredInputs = ref([]);
46
47
  const ignoredDirtyCheckFields = ref(new Set());
@@ -56,7 +57,12 @@ function useGlibForm({ formRef }) {
56
57
  el.value = htmlElement(formRef.value);
57
58
  });
58
59
 
59
- watch(registeredInputs, (value) => {
60
+ const inputRegistrationWatcher = watch(registeredInputs, (value) => {
61
+ if (changed.value) {
62
+ inputRegistrationWatcher(); // remove watcher
63
+ return;
64
+ }
65
+
60
66
  initFormData.value = getFormData(el.value, ignoredDirtyCheckFields.value);
61
67
  currentFormData.value = initFormData.value;
62
68
 
@@ -69,6 +75,7 @@ function useGlibForm({ formRef }) {
69
75
 
70
76
  const updateDirtyState = (event) => {
71
77
  currentFormData.value = getFormData(el.value, ignoredDirtyCheckFields.value);
78
+ changed.value = true;
72
79
  dispatchEv();
73
80
  };
74
81
 
@@ -4,12 +4,12 @@ import Uploader from "../../utils/glibDirectUpload";
4
4
  import { vueApp } from "../../store";
5
5
  import { useFilesState, useFileUtils, validateFile } from "./file";
6
6
 
7
- function submitOnAllUploaded({ url, formData, files }) {
7
+ function submitOnAllUploaded({ url, formData, files, actionName }) {
8
8
  const { uploaded } = useFilesState(files);
9
9
  return watch(uploaded, (val) => {
10
10
  if (val) {
11
11
  GLib.action.execute({
12
- action: 'http/post',
12
+ action: actionName || 'http/post',
13
13
  url: url.value,
14
14
  formData: formData.value
15
15
  }, {});
@@ -19,7 +19,6 @@
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";
23
22
 
24
23
  export default {
25
24
  components: {
@@ -29,9 +28,6 @@ export default {
29
28
  props: {
30
29
  spec: { type: Object, required: true }
31
30
  },
32
- setup(props) {
33
- useGlibInput({ props });
34
- },
35
31
  inject: ['radioGroupCtx'],
36
32
  computed: {
37
33
  isActive() {
@@ -14,6 +14,8 @@
14
14
  </template>
15
15
 
16
16
  <script>
17
+ import { useGlibInput } from '../composable/form';
18
+
17
19
  export default {
18
20
  props: {
19
21
  spec: { type: Object, required: true },
@@ -23,11 +25,14 @@ export default {
23
25
  radioGroupCtx: this
24
26
  };
25
27
  },
28
+ setup(props) {
29
+ useGlibInput({ props });
30
+ },
26
31
  computed: {
27
32
  childrenSpec() {
28
33
  return {
29
34
  childViews: this.spec.childViews
30
- }
35
+ };
31
36
  }
32
37
  },
33
38
  methods: {
@@ -303,7 +303,6 @@ export default {
303
303
  }
304
304
 
305
305
  this.$executeOnChange(this.producedValue);
306
- if (this.$refs.container) triggerOnInput(this.$refs.container);
307
306
 
308
307
  }),
309
308
  onRawTextEditorChanged: eventFiltering.debounce(function () {
@@ -314,7 +313,6 @@ export default {
314
313
  );
315
314
 
316
315
  this.$executeOnChange(this.producedValue);
317
- if (this.$refs.container) triggerOnInput(this.$refs.container);
318
316
  }),
319
317
  insertImage: function (file, Editor, cursorLocation, blob) {
320
318
  let vm = this;
@@ -92,6 +92,7 @@ import Action from "../../action";
92
92
  import Hash from "../../utils/hash";
93
93
  import Papa from "papaparse";
94
94
  import launch from "../../utils/launch";
95
+ import { isPresent } from "../../utils/type";
95
96
 
96
97
  class Row {
97
98
  constructor({ id, columns, selected, index, status }) {
@@ -128,14 +129,22 @@ class Row {
128
129
  }
129
130
 
130
131
  class Cell {
131
- constructor({ viewHeaders, cellIndex, rowId, view, value }) {
132
- this.viewHeaders = viewHeaders;
132
+ constructor({ cellIndex, rowId, dataRow, viewCell }) {
133
133
  this.cellIndex = cellIndex;
134
134
  this.rowId = rowId;
135
- this.view = view;
135
+ this.cellId = props.spec.viewHeaders[this.cellIndex].id;
136
+
137
+ let value = dataRow.columns[cellIndex].value;
138
+
139
+ // clean value
140
+ if (!value) value = undefined;
141
+ if (value && value.includes(',')) value = value.split(',');
136
142
  this.value = value;
137
143
 
138
- this.cellId = this.viewHeaders[this.cellIndex].id;
144
+ const name = viewCell.multiple ? `${this.cellId}[]` : this.cellId;
145
+
146
+ this.view = Object.assign({}, viewCell, dataRow.columns[cellIndex], { value: value, name: name });
147
+
139
148
  }
140
149
  }
141
150
 
@@ -246,17 +255,8 @@ function makeRows(dataRows, selected = false) {
246
255
  const rows = dataRows.map((dataRow, index) => {
247
256
  // const id = dataRow.rowId || Math.random().toString(36).slice(2, 7);
248
257
  const id = dataRow.rowId || `row_${index}`;
249
- const columns = props.spec.viewCells.map((viewCells, i) => {
250
- let value = dataRow.columns[i].value;
251
-
252
- // clean the data
253
- if (!value) value = undefined;
254
- if (value && value.includes(',')) value = value.split(',');
255
-
256
- const view = Object.assign({}, viewCells, dataRow.columns[i], { value: value });
257
- const cellIndex = i;
258
- const viewHeaders = props.spec.viewHeaders;
259
- return new Cell({ rowId: id, view, cellIndex, viewHeaders, value });
258
+ const columns = props.spec.viewCells.map((viewCell, i) => {
259
+ return new Cell({ rowId: id, cellIndex: i, dataRow, viewCell });
260
260
  });
261
261
  return new Row({ selected, columns, id, index });
262
262
  });
@@ -275,7 +275,8 @@ function handleCellChange(e, cell) {
275
275
  if (e.target instanceof HTMLInputElement) {
276
276
  value = e.target.value;
277
277
  } else {
278
- value = e.target.querySelector('input').value;
278
+ value = Array.from(e.target.querySelectorAll(`input[name="${cell.view.name}"]`)).map((el) => el.value);
279
+ value = cell.view.multiple ? value : value[0];
279
280
  }
280
281
 
281
282
  cell.value = value;
@@ -361,8 +362,11 @@ function submitRows(rows) {
361
362
  updateRow({ rowId: row.id, status: 'pending' });
362
363
 
363
364
  const formData = row.columns.reduce((prev, curr) => {
365
+ let value = curr.value;
366
+ if (curr.view.multiple && isPresent(curr.value)) value = [value].flat();
367
+ if (curr.view.multiple && !isPresent(curr.value)) value = [''];
364
368
  return Object.assign(prev, {
365
- [curr.cellId]: curr.value,
369
+ [curr.cellId]: value || '',
366
370
  [props.spec.paramNameForRowId || 'rowId']: row.id
367
371
  });
368
372
  }, {});
@@ -1,7 +1,7 @@
1
1
  <template>
2
- <v-progress-linear :class="$classes()" :height="spec.height || 25" :color="spec.color"
2
+ <v-progress-linear :class="$classes()" :height="spec.height" :color="spec.color"
3
3
  :background-color="spec.backgroundColor" :disabled="true" :model-value="value_in_percentage"
4
- :striped="$classes().includes('striped')" :reverse="spec.reversed || false" rounded>
4
+ :striped="$classes().includes('striped')" :reverse="spec.reversed || false" :rounded="spec.rounded">
5
5
  <strong v-if="!$classes().includes('no-text')">{{ Math.ceil(value_in_percentage) }}%</strong>
6
6
  </v-progress-linear>
7
7
  </template>
@@ -0,0 +1,75 @@
1
+ import { testPageUrl } from "../../helper"
2
+ const url = testPageUrl('dirty_state')
3
+
4
+ describe('dirtyState', () => {
5
+ it('can be disabled', () => {
6
+ cy.visit(url)
7
+
8
+ cy.get('input[name="user[dirty_check_disabled]"]')
9
+ .type('TEST TEST TEST')
10
+ .blur()
11
+
12
+ cy.reload()
13
+
14
+ cy.get('input[name="user[dirty_check_disabled]"]').should('be.empty')
15
+ })
16
+
17
+ it('dirty if not equal to init value', () => {
18
+ cy.visit(url)
19
+
20
+ cy.contains('Male').click();
21
+
22
+ cy.on('window:confirm', (str) => {
23
+ cy.then(() => expect(str).to.equal('Changes you made have not been saved. Are you sure?'))
24
+ return false;
25
+ })
26
+
27
+ cy.contains('multiupload').click()
28
+ cy.contains('Female').click()
29
+ cy.contains('multiupload').click() // Try to navigate to another page
30
+ cy.get('h2').should('contain.text', 'MultiUpload')
31
+ })
32
+
33
+ it('have different context between window and dialog', () => {
34
+ cy.visit(url)
35
+
36
+ cy.contains('Dialog Form').click()
37
+
38
+ cy.get('input[name="user[name]"]').click().type('John Doe')
39
+
40
+ cy.contains('cancel').click()
41
+
42
+ let text = '';
43
+ cy.on('window:confirm', (str) => {
44
+ text = str;
45
+ return true;
46
+ })
47
+
48
+ cy.then(() => expect(text).to.equal('Changes you made have not been saved. Are you sure?'))
49
+
50
+ cy.contains('multiupload').click() // Try to navigate to another page
51
+ cy.get('h2').should('contain.text', 'MultiUpload')
52
+ })
53
+
54
+ it('pop on history back', () => {
55
+ cy.visit(testPageUrl('multiupload'))
56
+ cy.contains('dirty_state').click()
57
+
58
+ cy.contains('choice2').click()
59
+
60
+ cy.go('back')
61
+
62
+ let text = ''
63
+ cy.on('window:confirm', (str) => {
64
+ text = str;
65
+ return false;
66
+ })
67
+
68
+ cy.then(() => expect(text).to.equal('Changes you made have not been saved. Are you sure?'))
69
+
70
+ // dirty state is removed after second try
71
+ cy.go('back')
72
+
73
+ cy.get('h2').should('contain.text', 'MultiUpload')
74
+ })
75
+ })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glib-web",
3
- "version": "4.16.1",
3
+ "version": "4.18.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/store.js CHANGED
@@ -34,7 +34,7 @@ export const closeAllDialog = () => {
34
34
  };
35
35
 
36
36
  export const closeAllPopover = () => {
37
- popovers.value.forEach((popover) => popover.toggle = false);
37
+ popovers.value.forEach((popover) => popover.close());
38
38
  popovers.value = [];
39
39
  };
40
40
 
package/utils/http.js CHANGED
@@ -289,11 +289,13 @@ export default class {
289
289
  static startIndicator(component) {
290
290
  if (component) realComponent(component)._disabled = true;
291
291
  this._showIndicator();
292
+ vueApp.isBusy = true;
292
293
  }
293
294
 
294
295
  static stopIndicator(component) {
295
296
  if (component) realComponent(component)._disabled = false;
296
297
  this._hideIndicator();
298
+ vueApp.isBusy = false;
297
299
  }
298
300
 
299
301
  static _showIndicator() {
@@ -1,52 +0,0 @@
1
- import { testPageUrl } from "../../helper"
2
- const url = testPageUrl('form_dynamic')
3
-
4
- describe('form', () => {
5
- it('dynamic group', () => {
6
- cy.visit(url)
7
-
8
- cy.get('.text-success').click()
9
-
10
- cy.contains('submit (if form valid)').should('have.class', 'v-btn--disabled')
11
-
12
- cy.get('#input-17').type('Rate your breakfast')
13
- cy.get('#input-19').click()
14
- cy.get('.v-overlay-container').contains('Rating').click()
15
-
16
- cy.contains('submit (if form valid)').should('not.have.class', 'v-btn--disabled')
17
-
18
- cy.get(':nth-child(3) > :nth-child(1) > [style="display: block;"] > .group-wrapper > .text-error').click()
19
-
20
- cy.contains('Confirm').click()
21
-
22
- cy.contains('submit').click()
23
-
24
-
25
- const result = `Method: POST
26
- Form Data:
27
- {
28
- "evaluation": {
29
- "0": {
30
- "question": "Punctuality",
31
- "type": "rating"
32
- },
33
- "1": {
34
- "question": "Quality of work",
35
- "type": "rating"
36
- },
37
- "2": {
38
- "_destroy": "1",
39
- "question": "Satisfied?",
40
- "type": "yes_no"
41
- },
42
- "3": {
43
- "question": "Rate your breakfast",
44
- "type": "rating"
45
- }
46
- }
47
- }`
48
-
49
- cy.get('.unformatted').should('contain.text', result)
50
-
51
- })
52
- })