glib-web 4.1.0 → 4.1.2
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.
- package/.eslintrc.js +4 -1
- package/README.md +1 -0
- package/action.js +1 -1
- package/actions/components/replace.js +1 -1
- package/actions/components/set.js +4 -4
- package/actions/dialogs/open.js +1 -1
- package/actions/dialogs/show.js +1 -1
- package/components/fields/_patternText.vue +1 -1
- package/components/fields/_select.vue +2 -2
- package/components/fields/check.vue +1 -1
- package/components/fields/checkGroup.vue +3 -2
- package/components/fields/chipGroup.vue +5 -3
- package/components/fields/creditCard.vue +1 -1
- package/components/fields/dynamicGroup.vue +1 -1
- package/components/fields/dynamicSelect.vue +1 -1
- package/components/fields/file.vue +1 -1
- package/components/fields/googlePlace.vue +1 -1
- package/components/fields/hidden.vue +1 -1
- package/components/fields/location.vue +1 -1
- package/components/fields/multiUpload.vue +1 -1
- package/components/fields/otpField.vue +1 -1
- package/components/fields/phone.vue +1 -1
- package/components/fields/radio.vue +111 -4
- package/components/fields/radioGroup.vue +1 -1
- package/components/fields/rating.vue +1 -1
- package/components/fields/richText.vue +1 -1
- package/components/fields/sign.vue +1 -1
- package/components/fields/stripe/stripeFields.vue +1 -1
- package/components/fields/stripe/stripeIndividualFields.vue +1 -1
- package/components/fields/stripeExternalAccount.vue +1 -1
- package/components/fields/stripeToken.vue +1 -1
- package/components/fields/text.vue +1 -1
- package/components/fields/textarea.vue +1 -1
- package/components/fields/timer.vue +1 -1
- package/components/mixins/generic.js +9 -3
- package/components/mixins/styles.js +31 -7
- package/cypress/e2e/glib-web/dialog.cy.ts +13 -0
- package/cypress/e2e/glib-web/display.cy.ts +12 -0
- package/cypress/e2e/glib-web/reactivity.cy.ts +48 -0
- package/cypress/helper.ts +7 -0
- package/nav/dialog.vue +7 -8
- package/package.json +1 -1
- package/store.js +18 -5
- package/utils/http.js +1 -1
- package/utils/launch.js +3 -3
- package/cypress/e2e/glib-web/components_set.cy.ts +0 -8
- package/cypress/e2e/glib-web/picker.cy.ts +0 -9
- package/cypress/e2e/glib-web/select.cy.ts +0 -57
package/.eslintrc.js
CHANGED
package/README.md
CHANGED
package/action.js
CHANGED
|
@@ -228,7 +228,7 @@ export default class Action {
|
|
|
228
228
|
|
|
229
229
|
if (response.header || response.body || response.footer) {
|
|
230
230
|
Utils.http.forceComponentUpdate(() => {
|
|
231
|
-
const dialog = dialogs.last();
|
|
231
|
+
const dialog = dialogs.value.last();
|
|
232
232
|
const updateDialog = windowMode ? false : Utils.type.isObject(dialog);
|
|
233
233
|
if (updateDialog) {
|
|
234
234
|
dialog.updateContent(response);
|
|
@@ -5,7 +5,7 @@ export default class {
|
|
|
5
5
|
execute(spec, component) {
|
|
6
6
|
const target = GLib.component.findById(spec.targetId) || component;
|
|
7
7
|
if (target) {
|
|
8
|
-
|
|
8
|
+
target.action_merge(spec.newView);
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
Action.execute(spec.onReplace, target);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
const setFileModel = (component, value) => {
|
|
2
|
-
|
|
3
|
-
};
|
|
1
|
+
// const setFileModel = (component, value) => {
|
|
2
|
+
// component.fieldModel = component.$sanitizeValue(component.$externalizeValue(value));
|
|
3
|
+
// };
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
export default class {
|
|
@@ -13,7 +13,7 @@ export default class {
|
|
|
13
13
|
|
|
14
14
|
Utils.type.ifObject(spec.data, (data) => {
|
|
15
15
|
targetComponent.action_merge(data);
|
|
16
|
-
if (data.value) setFileModel(targetComponent, data.value);
|
|
16
|
+
// if (data.value) setFileModel(targetComponent, data.value);
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
GLib.action.execute(spec.onSet, targetComponent);
|
package/actions/dialogs/open.js
CHANGED
package/actions/dialogs/show.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div :style="$styles()" :class="classes()">
|
|
2
|
+
<div :style="$styles()" :class="classes()" v-if="!isInputIgnored">
|
|
3
3
|
<!-- See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date for why we need to use `pattern` -->
|
|
4
4
|
<v-text-field :color="gcolor" v-model="fieldModel" :name="fieldName" :label="spec.label" :hint="spec.hint"
|
|
5
5
|
:type="type" :readonly="spec.readOnly" :disabled="inputDisabled" :min="$sanitizeValue(spec.min)"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div ref="container" :style="$styles()" :class="classes()">
|
|
2
|
+
<div ref="container" :style="$styles()" :class="classes()" v-if="!isInputIgnored">
|
|
3
3
|
<v-autocomplete :color="gcolor" v-model="fieldModel" :label="label" :items="normalizedOptions"
|
|
4
4
|
:chips="spec.multiple" :multiple="spec.multiple" :readonly="spec.readOnly" :clearable="!spec.readOnly"
|
|
5
5
|
:placeholder="spec.placeholder" :rules="rules" persistent-hint :append-icon="append.icon" validate-on="blur"
|
|
@@ -49,7 +49,7 @@ export default {
|
|
|
49
49
|
return {
|
|
50
50
|
options: null,
|
|
51
51
|
append: {},
|
|
52
|
-
rules:
|
|
52
|
+
rules: this.$validation(),
|
|
53
53
|
focused: false
|
|
54
54
|
};
|
|
55
55
|
},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div :class="$classes()" :style="$styles()">
|
|
2
|
+
<div :class="$classes()" :style="$styles()" v-if="!isInputIgnored">
|
|
3
3
|
<!-- This hidden field should always be there to make sure the submitted param is not empty,
|
|
4
4
|
which could cause "Not accessible" error on the server. -->
|
|
5
5
|
<input v-model="uncheckValue" type="hidden" :name="fieldName" />
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div :style="$styles()" :class="$classes()">
|
|
2
|
+
<div :style="$styles()" :class="$classes()" v-if="!isInputIgnored">
|
|
3
3
|
<fields-check v-for="(childView, index) in childViews" :spec="childView" :key="index"
|
|
4
4
|
@oncheck="updateFieldModel"></fields-check>
|
|
5
5
|
<v-input ref="validator" :model-value="fieldModel" :rules="$validation()" :name="fieldName" validate-on="input"
|
|
@@ -31,7 +31,8 @@ export default defineComponent({
|
|
|
31
31
|
return this.spec.childViews.map((childView) => {
|
|
32
32
|
const { readOnly, disabled } = this.spec;
|
|
33
33
|
const parentName = this.spec.name;
|
|
34
|
-
|
|
34
|
+
const value = [this.spec.value].flat().includes(childView.checkValue) ? childView.checkValue : null;
|
|
35
|
+
return Object.assign({}, { readOnly, disabled, parentName, value }, childView);
|
|
35
36
|
});
|
|
36
37
|
}
|
|
37
38
|
},
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div ref="container" :style="$styles()" :class="$classes()">
|
|
2
|
+
<div ref="container" :style="$styles()" :class="$classes()" v-if="!isInputIgnored">
|
|
3
3
|
<v-chip-group :color="gcolor" v-model="fieldModel" @update:modelValue="onChange()" :multiple="spec.multiple" column>
|
|
4
4
|
<v-chip v-for="(option, index) in spec.options" :key="index" filter :variant="variant">{{
|
|
5
5
|
option.text }}</v-chip>
|
|
6
6
|
</v-chip-group>
|
|
7
|
+
<v-input ref="validator" :model-value="fieldModel" :rules="$validation()" :name="fieldName" validate-on="input"
|
|
8
|
+
:disabled="inputDisabled"></v-input>
|
|
7
9
|
<input v-for="(item, index) in indexToValue(fieldModel)" :key="index" type="hidden" :disabled="inputDisabled"
|
|
8
10
|
:name="fieldName" :value="item" />
|
|
9
11
|
</div>
|
|
@@ -12,8 +14,7 @@
|
|
|
12
14
|
<script>
|
|
13
15
|
import { defineComponent } from "vue";
|
|
14
16
|
import inputVariant from "../mixins/inputVariant";
|
|
15
|
-
import { triggerOnInput } from "../composable/form";
|
|
16
|
-
import { fieldModels } from "../composable/conditional";
|
|
17
|
+
import { triggerOnChange, triggerOnInput } from "../composable/form";
|
|
17
18
|
|
|
18
19
|
export default defineComponent({
|
|
19
20
|
mixins: [inputVariant],
|
|
@@ -26,6 +27,7 @@ export default defineComponent({
|
|
|
26
27
|
const containerEl = this.$refs.container;
|
|
27
28
|
if (containerEl) {
|
|
28
29
|
triggerOnInput(containerEl);
|
|
30
|
+
triggerOnChange(containerEl);
|
|
29
31
|
}
|
|
30
32
|
},
|
|
31
33
|
indexToValue(value) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div :style="$styles()" :class="$classes()">
|
|
2
|
+
<div :style="$styles()" :class="$classes()" v-if="!isInputIgnored">
|
|
3
3
|
<div class="stretcher">
|
|
4
4
|
<StripeIndividualFields v-if="$classes().includes('individual')" :spec="spec" ref="delegate"
|
|
5
5
|
:on-complete="_retrieveToken" :on-error="_displayError" />
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div :style="$styles()" :class="$classes()">
|
|
2
|
+
<div :style="$styles()" :class="$classes()" v-if="!isInputIgnored">
|
|
3
3
|
<div v-for="(group, groupIndex) in groupSpecs" :key="`group${groupIndex}`">
|
|
4
4
|
<input v-if="isDeleted(groupIndex)" type="hidden" :name="`${spec.name}[${groupIndex}][_destroy]`" value="1" />
|
|
5
5
|
<div :style="{ display: isDeleted(groupIndex) ? 'none' : 'block' }">
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div :style="$styles()">
|
|
2
|
+
<div :style="$styles()" v-if="!isInputIgnored">
|
|
3
3
|
<v-autocomplete ref="autocomplete" v-model="model" :items="allItems" :loading="$isBusy"
|
|
4
4
|
:v-model:search-input="search" :label="spec.label" hide-no-data hide-selected no-filter return-object
|
|
5
5
|
:chips="true" :deletable-chips="true" :multiple="spec.multiple">
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div ref="fileUploadContainer" :style="$styles()" :class="$classes()">
|
|
2
|
+
<div ref="fileUploadContainer" :style="$styles()" :class="$classes()" v-if="!isInputIgnored">
|
|
3
3
|
<label v-if="spec.label" class="v-label v-label--active">{{ spec.label }}</label>
|
|
4
4
|
<div class="preview-container" :class="[fileTitle ? 'uploaded' : '']">
|
|
5
5
|
<v-avatar v-if="placeholder.type == 'avatar'" :style="$styles(placeholder)">
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<!-- TODO: This probably can be merged with latLong-v1 or map-v1, i.e. add infoWindow support to latLong or map -->
|
|
2
2
|
|
|
3
3
|
<template>
|
|
4
|
-
<v-container fluid class="pa-0">
|
|
4
|
+
<v-container fluid class="pa-0" v-if="!isInputIgnored">
|
|
5
5
|
<div class="v-input v-text-field theme--light">
|
|
6
6
|
<div class="v-input__control">
|
|
7
7
|
<div class="v-input__slot">
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div :style="$styles()" :class="$classes()">
|
|
2
|
+
<div :style="$styles()" :class="$classes()" v-if="!isInputIgnored">
|
|
3
3
|
<div ref="container" @click="handleClick" @drop="handleDrop" @dragover="handleDragOver" @dragleave="handleDragLeave"
|
|
4
4
|
class="gdrop-file border-[2px]">
|
|
5
5
|
<input ref="fileSelect" type="file" multiple style="display: none">
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div ref="otp" :style="$styles()" :class="$classes()">
|
|
2
|
+
<div ref="otp" :style="$styles()" :class="$classes()" v-if="!isInputIgnored">
|
|
3
3
|
<v-otp-input ref="field" v-model="fieldModel" :name="fieldName" :disabled="inputDisabled" :rounded="4"
|
|
4
4
|
:max-width="maxWidth" type="spec.type || 'number'" :length="length" :variant="variant"
|
|
5
5
|
@change="$executeOnChange()" />
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div :style="$styles()" :class="$classes()">
|
|
2
|
+
<div :style="$styles()" :class="$classes()" v-if="!isInputIgnored">
|
|
3
3
|
<v-phone-input :guessCountry="guessCountry" displayFormat="e164" :defaultCountry="defaultCountry" :color="gcolor"
|
|
4
4
|
v-model="fieldModel" :label="spec.label" :name="fieldName" :hint="spec.hint" :placeholder="spec.placeholder"
|
|
5
5
|
:maxlength="spec.maxLength || 255" :readonly="spec.readOnly" :rules="$validation()"
|
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div :class="$classes()" :style="$styles()">
|
|
3
|
-
<v-radio :label="spec.label" :value="spec.value.presence() || vuetifyEmptyString" :readonly="spec.readOnly"
|
|
4
|
-
:on-icon="spec.onIcon" :off-icon="spec.offIcon" @click="$onClick()" :color="gcolor"
|
|
2
|
+
<div :class="$classes()" :style="$styles()" v-if="!isInputIgnored">
|
|
3
|
+
<v-radio :label="spec.label" :value="(spec.value || '').presence() || vuetifyEmptyString" :readonly="spec.readOnly"
|
|
4
|
+
:on-icon="spec.onIcon" :off-icon="spec.offIcon" @click="$onClick()" :color="gcolor"
|
|
5
|
+
:class="spec.imageUrl || spec.icon ? 'custom-radio' : ''">
|
|
6
|
+
<template v-slot:label v-if="spec.imageUrl || spec.icon">
|
|
7
|
+
<div class="custom-radio-content">
|
|
8
|
+
<img v-if="spec.imageUrl" :src="spec.imageUrl" alt="icon" class="custom-radio-icon" />
|
|
9
|
+
<v-icon v-if="spec.icon" class="aligner" :color="spec.iconColor" :size="spec.iconSize">
|
|
10
|
+
{{ spec.icon }}
|
|
11
|
+
</v-icon>
|
|
12
|
+
<div class="custom-radio-label">{{ spec.label }}</div>
|
|
13
|
+
</div>
|
|
14
|
+
</template>
|
|
15
|
+
</v-radio>
|
|
5
16
|
<div v-if="spec.childViews" class="radio-childviews">
|
|
6
|
-
<div v-for="(item, i) in spec.childViews" :key="i">
|
|
17
|
+
<div v-for="( item, i ) in spec.childViews " :key="i">
|
|
7
18
|
<glib-component :spec="item" />
|
|
8
19
|
</div>
|
|
9
20
|
</div>
|
|
@@ -18,3 +29,99 @@ export default {
|
|
|
18
29
|
}
|
|
19
30
|
};
|
|
20
31
|
</script>
|
|
32
|
+
|
|
33
|
+
<style scoped>
|
|
34
|
+
.custom-radio {
|
|
35
|
+
/* styles for custom-radio class */
|
|
36
|
+
display: flex;
|
|
37
|
+
align-items: center;
|
|
38
|
+
justify-content: center;
|
|
39
|
+
width: 240px;
|
|
40
|
+
height: 254px;
|
|
41
|
+
transition: border-color 0.3s, box-shadow 0.3s, color 0.3s;
|
|
42
|
+
text-align: center;
|
|
43
|
+
cursor: pointer;
|
|
44
|
+
padding: 16px;
|
|
45
|
+
position: relative;
|
|
46
|
+
border: 1px solid #E6E6E6;
|
|
47
|
+
border-radius: 24px;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.custom-radio:hover {
|
|
51
|
+
border: 2px solid;
|
|
52
|
+
border-radius: 24px;
|
|
53
|
+
border-color: #0A2A9E;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.custom-radio:hover .custom-radio-label {
|
|
57
|
+
font-size: 22px;
|
|
58
|
+
color: #0A2A9E;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.custom-radio .custom-radio-content {
|
|
62
|
+
display: flex;
|
|
63
|
+
flex-direction: column;
|
|
64
|
+
align-items: center;
|
|
65
|
+
justify-content: center;
|
|
66
|
+
width: 100%;
|
|
67
|
+
height: 100%;
|
|
68
|
+
margin-right: 30px;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.custom-radio .custom-radio-icon {
|
|
72
|
+
width: 80px;
|
|
73
|
+
height: 80px;
|
|
74
|
+
margin-bottom: 8px;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.custom-radio .custom-radio-label {
|
|
78
|
+
font-size: 22px;
|
|
79
|
+
color: inherit;
|
|
80
|
+
margin-top: 16px;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.custom-radio ::v-deep .v-selection-control__input {
|
|
84
|
+
width: 240px;
|
|
85
|
+
height: 254px;
|
|
86
|
+
background-color: transparent;
|
|
87
|
+
left: 50%;
|
|
88
|
+
|
|
89
|
+
>.v-icon {
|
|
90
|
+
opacity: 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.custom-radio ::v-deep .v-selection-control__input::before {
|
|
96
|
+
background-color: transparent
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.custom-radio ::v-deep .v-ripple__container {
|
|
100
|
+
display: none;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
.custom-radio .v-ripple__container {
|
|
105
|
+
display: none;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.radio--active .custom-radio {
|
|
109
|
+
border: 2px solid;
|
|
110
|
+
border-radius: 24px;
|
|
111
|
+
border-color: #0A2A9E;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.radio--active .custom-radio-label {
|
|
115
|
+
color: #0A2A9E;
|
|
116
|
+
font-weight: 700;
|
|
117
|
+
font-size: 22px;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.radio--active .v-icon {
|
|
121
|
+
color: #0A2A9E;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.radio-childviews {
|
|
125
|
+
margin-top: 16px;
|
|
126
|
+
}
|
|
127
|
+
</style>
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
-->
|
|
6
6
|
<v-radio-group v-model="fieldModel" :name="fieldName" :readonly="spec.readOnly" :disabled="inputDisabled"
|
|
7
7
|
:rules="$validation()" :inline="spec.row" validate-on="blur" @change="$executeOnChange()" :class="$classes()"
|
|
8
|
-
:style="$styles()">
|
|
8
|
+
:style="$styles()" v-if="!isInputIgnored">
|
|
9
9
|
<div v-for="(childView, index) in spec.childViews" :key="index" style="width: 100%;">
|
|
10
10
|
<glib-component :spec="childSpec(childView)" />
|
|
11
11
|
</div>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div>
|
|
2
|
+
<div v-if="!isInputIgnored">
|
|
3
3
|
<v-rating v-model="fieldModel" :name="fieldName" empty-icon="star_outline" full-icon="star" half-icon="star_half"
|
|
4
4
|
:half-increments="spec.halfIncrements" hover length="5" :readonly="spec.readOnly" :disabled="inputDisabled"
|
|
5
5
|
:color="spec.color" :bg-color="spec.color" :size="spec.size"></v-rating>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div ref="container" :style="$styles()" :class="$classes()">
|
|
2
|
+
<div ref="container" :style="$styles()" :class="$classes()" v-if="!isInputIgnored">
|
|
3
3
|
<v-tabs v-model="mode" fixed-tabs>
|
|
4
4
|
<v-tab @click="onRichTextClicked">Editor</v-tab>
|
|
5
5
|
<v-tab @click="onRawTextClicked">Code</v-tab>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div :class="$classes()" :style="$styles()">
|
|
2
|
+
<div :class="$classes()" :style="$styles()" v-if="!isInputIgnored">
|
|
3
3
|
<div class="glib-canvas-container">
|
|
4
4
|
<canvas style="width: 100%; height: 100%" @mousedown="handleDrawStart" @mousemove="handleDrawing"
|
|
5
5
|
@mouseup="handleDrawEnd" @touchstart="handleDrawStart" @touchmove="handleDrawing" @touchend="handleDrawEnd"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div :style="$styles()" :class="$classes()">
|
|
2
|
+
<div :style="$styles()" :class="$classes()" v-if="!isInputIgnored">
|
|
3
3
|
<v-text-field v-model="accountNumber" label="Account Number" placeholder="000123456" :variant="variant"
|
|
4
4
|
:rules="accountNumberRules" :error-messages="errorMessages" @keyup="$onTyping({ duration: 200 })" />
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div :style="$styles()" :class="$classes()">
|
|
2
|
+
<div :style="$styles()" :class="$classes()" v-if="!isInputIgnored">
|
|
3
3
|
<StripeIndividualFields v-if="$classes().includes('individual')" :spec="spec" :on-complete="_retrieveToken"
|
|
4
4
|
:on-error="_displayError" />
|
|
5
5
|
<StripeFields v-else :spec="spec" :on-complete="_retrieveToken" :on-error="_displayError" />
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div :style="$styles()" :class="$classes()">
|
|
2
|
+
<div :style="$styles()" :class="$classes()" v-if="!isInputIgnored">
|
|
3
3
|
<v-text-field :color="gcolor" ref="field" v-model="fieldModel" :label="spec.label" :name="fieldName"
|
|
4
4
|
:placeholder="spec.placeholder" :density="density" :hint="spec.hint" :maxlength="spec.maxLength || 255"
|
|
5
5
|
:readonly="spec.readOnly" :disabled="inputDisabled" :type="config.type" :rules="rules"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div :style="styles()" :class="classes()">
|
|
2
|
+
<div :style="styles()" :class="classes()" v-if="!isInputIgnored">
|
|
3
3
|
<v-textarea :color="gcolor" v-model="fieldModel" :label="spec.label" :name="fieldName" :hint="spec.hint"
|
|
4
4
|
:placeholder="spec.placeholder" :maxlength="spec.maxLength || 255" :readonly="spec.readOnly" :height="height"
|
|
5
5
|
:rules="$validation()" counter :outlined="$classes().includes('outlined')" :disabled="inputDisabled"
|
|
@@ -25,9 +25,15 @@ export default {
|
|
|
25
25
|
var augmentedRules = rules || [];
|
|
26
26
|
Utils.type.ifObject(this.spec.validation, val => {
|
|
27
27
|
Utils.type.ifObject(val.required, spec => {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
const validationFunc = (v) => {
|
|
29
|
+
if (!Utils.type.isNotNull(v)) return spec.message;
|
|
30
|
+
if (Array.isArray(v) && v.length <= 0) return spec.message;
|
|
31
|
+
if (Utils.type.isString(v) && v.length <= 0) return spec.message;
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
return true;
|
|
35
|
+
};
|
|
36
|
+
augmentedRules = augmentedRules.concat([validationFunc]);
|
|
31
37
|
});
|
|
32
38
|
Utils.type.ifObject(val.format, spec => {
|
|
33
39
|
augmentedRules = augmentedRules.concat([
|
|
@@ -3,6 +3,7 @@ import { fieldModels, watchFieldModels } from "../composable/conditional";
|
|
|
3
3
|
import { dirtySpecs } from "../composable/dirtyState";
|
|
4
4
|
import { determineColor } from "../../utils/constant";
|
|
5
5
|
import Action from "../../action";
|
|
6
|
+
import { vueApp } from "../../store";
|
|
6
7
|
|
|
7
8
|
const NUMBER_PRECISION = 2;
|
|
8
9
|
const isNeedToBeFixed = (val, component) => {
|
|
@@ -18,6 +19,7 @@ export default {
|
|
|
18
19
|
_submitWhenNotDisplayed: !this.spec || !Utils.type.isNotNull(this.spec.submitWhenNotDisplayed) ? false : !!this.spec.submitWhenNotDisplayed,
|
|
19
20
|
_watchers: [],
|
|
20
21
|
_isValueChanged: false,
|
|
22
|
+
ignoredInputs: new Set(),
|
|
21
23
|
// Some components do not support null or empty string value, so we need to use an intermediary value.
|
|
22
24
|
// See https://github.com/vuetifyjs/vuetify/issues/8876
|
|
23
25
|
vuetifyEmptyString: "<EMPTY_STRING>"
|
|
@@ -39,9 +41,25 @@ export default {
|
|
|
39
41
|
gcolor() {
|
|
40
42
|
if (!this.spec) return '';
|
|
41
43
|
return determineColor(this.spec.styleClasses, this.spec.color || 'primary');
|
|
44
|
+
},
|
|
45
|
+
isInputIgnored() {
|
|
46
|
+
return vueApp.ignoredInputs.has(this.fieldName) && !this._submitWhenNotDisplayed;
|
|
42
47
|
}
|
|
43
48
|
},
|
|
44
49
|
watch: {
|
|
50
|
+
_show: {
|
|
51
|
+
handler(val) {
|
|
52
|
+
this.ignoredInputs.forEach((input) => vueApp.ignoredInputs.delete(input));
|
|
53
|
+
this.ignoredInputs.clear();
|
|
54
|
+
if (!val && this.$el && this.$el instanceof HTMLElement && !this._submitWhenNotDisplayed) {
|
|
55
|
+
Array.from(this.$el.querySelectorAll('input,select,textarea')).forEach((el) => {
|
|
56
|
+
this.ignoredInputs.add(el.name);
|
|
57
|
+
vueApp.ignoredInputs.add(el.name);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
// immediate: true
|
|
62
|
+
},
|
|
45
63
|
fieldModel: function (val) {
|
|
46
64
|
if (val === this.vuetifyEmptyString) {
|
|
47
65
|
val = "";
|
|
@@ -114,7 +132,7 @@ export default {
|
|
|
114
132
|
// this._watchers.push(watcher1);
|
|
115
133
|
// }
|
|
116
134
|
|
|
117
|
-
if (this.spec && (this.spec.showIf || this.spec.loadIf)
|
|
135
|
+
if (this.spec && (this.spec.showIf || this.spec.loadIf)) {
|
|
118
136
|
watcher2 = watchFieldModels(this.spec.showIf || this.spec.loadIf, (value) => {
|
|
119
137
|
const oldValue = this._show;
|
|
120
138
|
|
|
@@ -124,6 +142,10 @@ export default {
|
|
|
124
142
|
this._show = false;
|
|
125
143
|
}
|
|
126
144
|
|
|
145
|
+
// if (this.spec.showIf) {
|
|
146
|
+
// this._submitWhenNotDisplayed = true;
|
|
147
|
+
// }
|
|
148
|
+
|
|
127
149
|
if (oldValue != value) {
|
|
128
150
|
this.$nextTick(() => {
|
|
129
151
|
// Call either onIfTrue or onIfFalse.
|
|
@@ -266,12 +288,14 @@ export default {
|
|
|
266
288
|
if (Utils.type.isNotNull(newSpec.displayed)) vm._show = newSpec.displayed;
|
|
267
289
|
vm._submitWhenNotDisplayed = newSpec.submitWhenNotDisplayed;
|
|
268
290
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
291
|
+
// if (vm.$el) {
|
|
292
|
+
// const els = vm.$el.querySelectorAll('input,textarea');
|
|
293
|
+
// if (!vm.submitWhenNotDisplayed) {
|
|
294
|
+
// els.forEach((el) => el.disabled = true);
|
|
295
|
+
// } else {
|
|
296
|
+
// els.forEach((el) => el.disabled = false);
|
|
297
|
+
// }
|
|
298
|
+
// }
|
|
275
299
|
|
|
276
300
|
Object.assign(this.spec, newSpec);
|
|
277
301
|
},
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { testPageUrl } from "../../helper"
|
|
2
|
+
|
|
3
|
+
describe('dialog', () => {
|
|
4
|
+
it('updateExisting', () => {
|
|
5
|
+
cy.visit(testPageUrl())
|
|
6
|
+
cy.contains('Dialog updateExisting').click()
|
|
7
|
+
cy.get('.v-dialog h1').should('contain.text', 'Hello world')
|
|
8
|
+
cy.get('.v-dialog .v-icon').should('exist')
|
|
9
|
+
cy.get('.v-dialog').contains('change dialog content').click()
|
|
10
|
+
cy.get('.v-dialog h1').should('contain.text', 'Hello world (updated)')
|
|
11
|
+
cy.get('.v-dialog .v-icon').should('not.exist')
|
|
12
|
+
})
|
|
13
|
+
})
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { testPageUrl } from "../../helper"
|
|
2
|
+
|
|
3
|
+
describe('display', () => {
|
|
4
|
+
it('form validity', () => {
|
|
5
|
+
cy.visit(testPageUrl())
|
|
6
|
+
|
|
7
|
+
cy.get('.fields-select .v-field__clearable').first().click()
|
|
8
|
+
cy.contains('submit (if form valid)').should('have.class', 'v-btn--disabled')
|
|
9
|
+
cy.contains('hide select').click()
|
|
10
|
+
cy.contains('submit (if form valid)').should('not.have.class', 'v-btn--disabled')
|
|
11
|
+
})
|
|
12
|
+
})
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { testPageUrl } from "../../helper"
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
describe('reactivity', () => {
|
|
5
|
+
it('logics/set, components/set, components/replace', () => {
|
|
6
|
+
cy.visit(testPageUrl())
|
|
7
|
+
|
|
8
|
+
// submit button should be clickabl e
|
|
9
|
+
cy.contains('submit (if form valid)').should('not.have.class', 'v-btn--disabled')
|
|
10
|
+
|
|
11
|
+
cy.contains('components/set').click()
|
|
12
|
+
cy.contains('logics/set').click()
|
|
13
|
+
cy.contains('components/replace').click()
|
|
14
|
+
|
|
15
|
+
// submit button should be disabled
|
|
16
|
+
cy.contains('submit (if form valid)').should('have.class', 'v-btn--disabled')
|
|
17
|
+
|
|
18
|
+
cy.get('.fields-check input[type="checkbox"]').first().click()
|
|
19
|
+
|
|
20
|
+
cy.contains('submit (if form valid)').should('have.class', 'v-btn--disabled')
|
|
21
|
+
|
|
22
|
+
cy.get('.fields-radio input[type="radio"]').first().click()
|
|
23
|
+
|
|
24
|
+
cy.contains('submit (if form valid)').should('have.class', 'v-btn--disabled')
|
|
25
|
+
|
|
26
|
+
cy.get('.fields-chipGroup .v-chip').first().click()
|
|
27
|
+
|
|
28
|
+
cy.contains('submit (if form valid)').should('not.have.class', 'v-btn--disabled')
|
|
29
|
+
|
|
30
|
+
cy.get('button').contains('submit').first().click()
|
|
31
|
+
|
|
32
|
+
const result = `Method: POST
|
|
33
|
+
Form Data:
|
|
34
|
+
{
|
|
35
|
+
"date": "2024-07-27",
|
|
36
|
+
"select": [
|
|
37
|
+
"option99"
|
|
38
|
+
],
|
|
39
|
+
"chip_group": "option99",
|
|
40
|
+
"text": "Doe John",
|
|
41
|
+
"textarea": "The quick brown fox jumps over the lazy dog"
|
|
42
|
+
}`
|
|
43
|
+
|
|
44
|
+
cy.get('.unformatted').should('contain.text', result)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
})
|
package/nav/dialog.vue
CHANGED
|
@@ -49,7 +49,7 @@ import { ref } from "vue";
|
|
|
49
49
|
import { usePasteable } from "../components/composable/pasteable";
|
|
50
50
|
|
|
51
51
|
export default {
|
|
52
|
-
expose: ['isFormDirty', 'model'],
|
|
52
|
+
expose: ['isFormDirty', 'model', 'close'],
|
|
53
53
|
setup(props) {
|
|
54
54
|
const filePaster = ref(undefined);
|
|
55
55
|
usePasteable(filePaster);
|
|
@@ -74,7 +74,6 @@ export default {
|
|
|
74
74
|
model: null,
|
|
75
75
|
url: null,
|
|
76
76
|
urlLoaded: false,
|
|
77
|
-
disableCloseButton: false,
|
|
78
77
|
isMobile: false,
|
|
79
78
|
formSpec: null,
|
|
80
79
|
isFormDirty: false
|
|
@@ -101,10 +100,13 @@ export default {
|
|
|
101
100
|
return `height: ${this.mainHeight}px;`;
|
|
102
101
|
}
|
|
103
102
|
return null;
|
|
103
|
+
},
|
|
104
|
+
disableCloseButton() {
|
|
105
|
+
return this.spec.disableCloseButton || Utils.type.isString(this.url);
|
|
104
106
|
}
|
|
105
107
|
},
|
|
106
108
|
mounted() {
|
|
107
|
-
dialogs.push(this);
|
|
109
|
+
dialogs.value.push(this);
|
|
108
110
|
},
|
|
109
111
|
created() {
|
|
110
112
|
this.onResize();
|
|
@@ -135,7 +137,7 @@ export default {
|
|
|
135
137
|
close() {
|
|
136
138
|
if (!this.isDirty()) {
|
|
137
139
|
this.filePaster = null;
|
|
138
|
-
dialogs.remove(this);
|
|
140
|
+
dialogs.value.remove(this);
|
|
139
141
|
this.model = false;
|
|
140
142
|
}
|
|
141
143
|
},
|
|
@@ -160,7 +162,6 @@ export default {
|
|
|
160
162
|
show(reload) {
|
|
161
163
|
const spec = this.spec;
|
|
162
164
|
this.url ||= spec.url;
|
|
163
|
-
this.disableCloseButton = this.spec.disableCloseButton || Utils.type.isString(this.url);
|
|
164
165
|
|
|
165
166
|
if (Utils.type.isString(this.url)) {
|
|
166
167
|
if (!this.urlLoaded) {
|
|
@@ -178,12 +179,10 @@ export default {
|
|
|
178
179
|
this.urlLoaded = true;
|
|
179
180
|
this.message = "";
|
|
180
181
|
this.formSpec = response.fullPageForm;
|
|
181
|
-
this.disableCloseButton = this.spec.disableCloseButton || false;
|
|
182
182
|
this.updateContent(response);
|
|
183
183
|
});
|
|
184
184
|
},
|
|
185
185
|
(error, response) => {
|
|
186
|
-
this.disableCloseButton = false;
|
|
187
186
|
Utils.launch.snackbar.error(error.toString(), this);
|
|
188
187
|
}
|
|
189
188
|
);
|
|
@@ -193,7 +192,6 @@ export default {
|
|
|
193
192
|
// this.body = this.spec.body;
|
|
194
193
|
|
|
195
194
|
this.message = this.spec.message;
|
|
196
|
-
this.disableCloseButton = this.spec.disableCloseButton || false;
|
|
197
195
|
|
|
198
196
|
this.updateContent(this.spec);
|
|
199
197
|
}
|
|
@@ -205,6 +203,7 @@ export default {
|
|
|
205
203
|
this.header = response.header;
|
|
206
204
|
this.body = response.body;
|
|
207
205
|
this.footer = response.footer;
|
|
206
|
+
this.spec.disableCloseButton = response.disableCloseButton;
|
|
208
207
|
|
|
209
208
|
// Make sure action only executes after the content has finished populating.
|
|
210
209
|
this.$nextTick(() => {
|
package/package.json
CHANGED
package/store.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { reactive } from 'vue';
|
|
1
|
+
import { reactive, ref } from 'vue';
|
|
2
2
|
import { useDirtyState } from './components/composable/dirtyState';
|
|
3
3
|
import Action from "./action";
|
|
4
4
|
|
|
@@ -18,6 +18,7 @@ export const vueApp = reactive({
|
|
|
18
18
|
richTextValues: {},
|
|
19
19
|
draggedComponent: null,
|
|
20
20
|
lastNavigationCount: null,
|
|
21
|
+
ignoredInputs: new Set(),
|
|
21
22
|
tooltipSpec: {},
|
|
22
23
|
bottomBanners: {},
|
|
23
24
|
uploader: {},
|
|
@@ -26,11 +27,14 @@ export const vueApp = reactive({
|
|
|
26
27
|
|
|
27
28
|
export const jsonView = reactive({ page: window.__page });
|
|
28
29
|
|
|
29
|
-
export const dialogs =
|
|
30
|
+
export const dialogs = ref([]);
|
|
30
31
|
|
|
31
|
-
export const closeAllDialog = () =>
|
|
32
|
+
export const closeAllDialog = () => {
|
|
33
|
+
dialogs.value.forEach((dialog) => dialog.model = false);
|
|
34
|
+
dialogs.value = [];
|
|
35
|
+
};
|
|
32
36
|
|
|
33
|
-
export const ctx = () => dialogs.slice(-1)[0] || vueApp;
|
|
37
|
+
export const ctx = () => dialogs.value.slice(-1)[0] || vueApp;
|
|
34
38
|
|
|
35
39
|
export const glibevent = reactive({
|
|
36
40
|
onbeforewindowsopen: null,
|
|
@@ -39,12 +43,21 @@ export const glibevent = reactive({
|
|
|
39
43
|
});
|
|
40
44
|
|
|
41
45
|
function glibEventHandler(e) {
|
|
46
|
+
vueApp.ignoredInputs = new Set();
|
|
47
|
+
|
|
42
48
|
const { onUnload } = jsonView.page;
|
|
43
49
|
if (onUnload) {
|
|
44
50
|
Action.execute(onUnload, {});
|
|
45
51
|
}
|
|
46
52
|
|
|
47
|
-
|
|
53
|
+
if (e) {
|
|
54
|
+
if (ctx().isFormDirty && !ctx().isFormSubmitted) {
|
|
55
|
+
e.preventDefault();
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
return !isDirty();
|
|
59
|
+
}
|
|
60
|
+
|
|
48
61
|
}
|
|
49
62
|
export const watchGlibEvent = () => {
|
|
50
63
|
window.onbeforeunload = glibEventHandler;
|
package/utils/http.js
CHANGED
|
@@ -81,7 +81,7 @@ export default class {
|
|
|
81
81
|
const htmlUrl = Utils.url.htmlUrl(properties["url"]);
|
|
82
82
|
|
|
83
83
|
Utils.http.execute(properties, "GET", component, (data, response) => {
|
|
84
|
-
const pushHistory = windowMode ? true : !Utils.type.isObject(dialogs.last());
|
|
84
|
+
const pushHistory = windowMode ? true : !Utils.type.isObject(dialogs.value.last());
|
|
85
85
|
// TODO: Check if it is okay to remove this `if` statement so we always push even if it's the same URL.
|
|
86
86
|
if (forcePushHistory || (htmlUrl !== currentUrl && pushHistory)) {
|
|
87
87
|
const redirectUrl = Utils.url.htmlUrl(response.url);
|
package/utils/launch.js
CHANGED
|
@@ -93,7 +93,7 @@ class LaunchDialog {
|
|
|
93
93
|
|
|
94
94
|
|
|
95
95
|
static topDialog() {
|
|
96
|
-
return dialogs.last();
|
|
96
|
+
return dialogs.value.last();
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
static open(properties, component) {
|
|
@@ -122,13 +122,13 @@ class LaunchDialog {
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
static reload(properties, component) {
|
|
125
|
-
Utils.type.ifObject(dialogs.last(), dialog => {
|
|
125
|
+
Utils.type.ifObject(dialogs.value.last(), dialog => {
|
|
126
126
|
dialog.reload(properties);
|
|
127
127
|
});
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
static close(properties, component) {
|
|
131
|
-
Utils.type.ifObject(dialogs.last(), dialog => {
|
|
131
|
+
Utils.type.ifObject(dialogs.value.last(), dialog => {
|
|
132
132
|
dialog.close();
|
|
133
133
|
});
|
|
134
134
|
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
describe('components set', () => {
|
|
2
|
-
it('populate select options', () => {
|
|
3
|
-
cy.visit('http://localhost:3000/glib/json_ui_garage?path=views%2Fcomponents_set')
|
|
4
|
-
cy.get('.pages-body .v-btn').eq(0).click()
|
|
5
|
-
cy.get('.fields-select').eq(0).click()
|
|
6
|
-
cy.get('.v-overlay__content > .v-list').should('contain.text', 'Option1')
|
|
7
|
-
})
|
|
8
|
-
})
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
describe('pick country and region', () => {
|
|
2
|
-
it('autopopulate region', () => {
|
|
3
|
-
cy.visit('http://localhost:3000/glib/json_ui_garage?path=forms%2Fpickers')
|
|
4
|
-
cy.get('.fields-select').eq(0).click()
|
|
5
|
-
cy.get('.v-overlay__content > .v-list').contains('Australia').click()
|
|
6
|
-
cy.get('.fields-select').eq(1).click()
|
|
7
|
-
cy.get('.v-overlay__content > .v-list').should('contain.text', 'ACT')
|
|
8
|
-
})
|
|
9
|
-
})
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
describe('input select', () => {
|
|
2
|
-
it('produce value', () => {
|
|
3
|
-
const result = `Method: POST
|
|
4
|
-
Form Data:
|
|
5
|
-
{
|
|
6
|
-
"city": "canberra",
|
|
7
|
-
"cities": [
|
|
8
|
-
"melbourne",
|
|
9
|
-
"sydney"
|
|
10
|
-
],
|
|
11
|
-
"language": "nl",
|
|
12
|
-
"languages": [
|
|
13
|
-
"ja",
|
|
14
|
-
"de"
|
|
15
|
-
],
|
|
16
|
-
"empty_default": "",
|
|
17
|
-
"nil_default": "",
|
|
18
|
-
"mixed_default": [
|
|
19
|
-
"",
|
|
20
|
-
"specified",
|
|
21
|
-
""
|
|
22
|
-
],
|
|
23
|
-
"time_zone": "Asia/Jakarta"
|
|
24
|
-
}`
|
|
25
|
-
|
|
26
|
-
cy.visit('http://localhost:3000/glib/json_ui_garage?path=forms%2Fselects')
|
|
27
|
-
cy.contains('Submit').click()
|
|
28
|
-
cy.get('.unformatted').should('contain.text', result)
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it('produce value (empty)', () => {
|
|
32
|
-
const result = `Method: POST
|
|
33
|
-
Form Data:
|
|
34
|
-
{
|
|
35
|
-
"city": "",
|
|
36
|
-
"cities": [
|
|
37
|
-
""
|
|
38
|
-
],
|
|
39
|
-
"language": "",
|
|
40
|
-
"languages": [
|
|
41
|
-
""
|
|
42
|
-
],
|
|
43
|
-
"empty_default": "",
|
|
44
|
-
"nil_default": "",
|
|
45
|
-
"mixed_default": [
|
|
46
|
-
""
|
|
47
|
-
],
|
|
48
|
-
"time_zone": ""
|
|
49
|
-
}`
|
|
50
|
-
|
|
51
|
-
cy.visit('http://localhost:3000/glib/json_ui_garage?path=forms%2Fselects')
|
|
52
|
-
cy.get('.v-field__clearable').click({ multiple: true })
|
|
53
|
-
cy.contains('Submit').click()
|
|
54
|
-
|
|
55
|
-
cy.get('.unformatted').should('contain.text', result)
|
|
56
|
-
})
|
|
57
|
-
})
|