@ramathibodi/nuxt-commons 0.1.60 → 0.1.61

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/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": "^3.0.0"
6
6
  },
7
- "version": "0.1.60",
7
+ "version": "0.1.61",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "0.8.4",
10
10
  "unbuild": "2.0.0"
@@ -103,6 +103,7 @@ defineExpose({setOriginalData,operation,formPad: formPadRef})
103
103
  <form-pad
104
104
  ref="formPadRef"
105
105
  v-model="formData"
106
+ :originalData="formDataOriginalValue"
106
107
  :readonly="readonly"
107
108
  isolated
108
109
  v-bind="$attrs"
@@ -125,6 +125,7 @@ watch(() => isShowing.value, (newValue) => {
125
125
  <form-pad
126
126
  ref="formPadRef"
127
127
  v-model="formData"
128
+ :originalData="formDataOriginalValue"
128
129
  :readonly="readonly"
129
130
  isolated
130
131
  >
@@ -104,6 +104,7 @@ defineExpose({operation,formPad:formPadRef})
104
104
  <form-pad
105
105
  ref="formPadRef"
106
106
  v-model="formData"
107
+ :originalData="formDataOriginalValue"
107
108
  :readonly="readonly"
108
109
  isolated
109
110
  v-bind="$attrs"
@@ -16,7 +16,7 @@ import {isObject} from 'lodash-es'
16
16
  import {watchDebounced} from '@vueuse/core'
17
17
  import {useRules} from '../../composables/utils/validation'
18
18
  import {useDocumentTemplate} from '../../composables/document/template'
19
- import { isArray, isString, isPlainObject } from 'lodash-es'
19
+ import { isArray, isString, isPlainObject, isEqual } from 'lodash-es'
20
20
  import FormPad from './Pad.vue'
21
21
 
22
22
  defineOptions({
@@ -25,6 +25,7 @@ defineOptions({
25
25
 
26
26
  interface Props {
27
27
  modelValue?: object
28
+ originalData?: object
28
29
  template?: any
29
30
  templateScript?: string
30
31
  disabled?: boolean
@@ -32,6 +33,8 @@ interface Props {
32
33
  isolated?: boolean
33
34
  decoration?: object
34
35
  parentTemplates?: string|string[]
36
+ dirtyClass?: string
37
+ dirtyOnCreate?: boolean
35
38
  }
36
39
 
37
40
  const props = withDefaults(defineProps<Props>(), {
@@ -40,6 +43,8 @@ const props = withDefaults(defineProps<Props>(), {
40
43
  isolated: false,
41
44
  decoration: () => { return {} },
42
45
  parentTemplates: (): string[] => [],
46
+ dirtyClass: "form-data-dirty",
47
+ dirtyOnCreate: false
43
48
  })
44
49
 
45
50
  const emit = defineEmits(['update:modelValue'])
@@ -83,36 +88,35 @@ function isBlankString(v: unknown): v is string {
83
88
  return isString(v) && v.length === 0
84
89
  }
85
90
 
86
- function sanitizeBlankStrings(val: any): boolean {
87
- let changed = false
91
+ function sanitizeBlankStrings(val: any, original?: any): void {
92
+ if (!original && props.originalData) {
93
+ sanitizeBlankStrings(val, props.originalData)
94
+ return
95
+ }
88
96
 
89
97
  if (isArray(val)) {
90
98
  for (let i = val.length - 1; i >= 0; i--) {
91
99
  const item = val[i]
92
100
  if (isBlankString(item)) {
93
- val.splice(i, 1) // remove array element
94
- changed = true
101
+ val.splice(i, 1)
95
102
  } else if (isPlainObject(item) || isArray(item)) {
96
- if (sanitizeBlankStrings(item)) changed = true
103
+ sanitizeBlankStrings(item,{})
97
104
  }
98
105
  }
99
- return changed
106
+ return
100
107
  }
101
108
 
102
109
  if (isPlainObject(val)) {
103
110
  for (const key of Object.keys(val)) {
104
111
  const v = val[key]
105
112
  if (isBlankString(v)) {
106
- delete val[key] // remove property directly
107
- changed = true
113
+ if (original && !Object.keys(original).includes(key)) delete val[key]
108
114
  } else if (isPlainObject(v) || isArray(v)) {
109
- if (sanitizeBlankStrings(v)) changed = true
115
+ let originalChild = (original && (isPlainObject(original[key]) || isArray(original[key]))) ? original[key] : {}
116
+ sanitizeBlankStrings(v, originalChild)
110
117
  }
111
118
  }
112
- return changed
113
119
  }
114
-
115
- return false
116
120
  }
117
121
 
118
122
  watch(formData, (newValue) => {
@@ -124,6 +128,23 @@ watch(() => props.modelValue, (newValue) => {
124
128
  formData.value = isObject(newValue) ? newValue : {}
125
129
  }, { deep: true, immediate: true })
126
130
 
131
+ const injectedClass = computed<Record<string, string>>(() => {
132
+ const data = (formData.value ?? {}) as Record<string, any>
133
+ const result: Record<string, string> = {}
134
+
135
+ if (!props.dirtyOnCreate && !props.originalData) return result
136
+
137
+ const original = (props.originalData ?? {}) as Record<string, any>
138
+
139
+ for (const key of Object.keys(data)) {
140
+ if (!isEqual(data[key], original[key])) {
141
+ if (!data[key] && !original[key]) continue
142
+ result[key] = props.dirtyClass || 'form-data-dirty'
143
+ }
144
+ }
145
+ return result
146
+ })
147
+
127
148
  const formComponent = shallowRef()
128
149
 
129
150
  function buildFormComponent() {
@@ -131,12 +152,13 @@ function buildFormComponent() {
131
152
  const originalConsoleError = console.warn
132
153
  console.warn = (error: any) => { throw new Error(error) } // eslint-disable-line
133
154
  try {
134
- const componentTemplate = '<form-pad ref="formPadTemplate" v-model="formComponentData" :disabled="disabled" :readonly="readonly" :decoration="decoration" :isolated="isolated"><template v-slot="{ data,isDisabled,isReadonly,rules,formProvided,decoration }">' + trimmedTemplate.value + '</template></form-pad>'
155
+ const componentTemplate = '<form-pad ref="formPadTemplate" v-model="formComponentData" :originalData="originalData" :disabled="disabled" :readonly="readonly" :decoration="decoration" :isolated="isolated"><template v-slot="{ data,isDisabled,isReadonly,rules,formProvided,decoration,injectedClass }">' + trimmedTemplate.value + '</template></form-pad>'
135
156
  compile(componentTemplate)
136
157
  formComponent.value = defineComponent({
137
158
  components: { FormPad },
138
159
  props: {
139
160
  modelValue: { type: Object, default: undefined },
161
+ originalData: { type: Object, default: undefined },
140
162
  disabled: { type: Boolean, default: false },
141
163
  readonly: { type: Boolean, default: false },
142
164
  decoration: { type: Object, default: () => { return {} } },
@@ -231,6 +253,7 @@ defineExpose({
231
253
  :is-readonly="readonly"
232
254
  :rules="rules"
233
255
  :decoration="decoration"
256
+ :injectedClass="injectedClass"
234
257
  />
235
258
  </template>
236
259
  </v-form>
@@ -242,6 +265,7 @@ defineExpose({
242
265
  :is-readonly="readonly"
243
266
  :rules="rules"
244
267
  :decoration="decoration"
268
+ :injectedClass="injectedClass"
245
269
  />
246
270
  </template>
247
271
  <component
@@ -249,6 +273,7 @@ defineExpose({
249
273
  v-if="trimmedTemplate"
250
274
  ref="formPad"
251
275
  v-model="formData"
276
+ :originalData="originalData"
252
277
  :disabled="disabled"
253
278
  :readonly="readonly"
254
279
  :decoration="decoration"
@@ -256,3 +281,6 @@ defineExpose({
256
281
  :class="$attrs.class"
257
282
  />
258
283
  </template>
284
+ <style>
285
+ .form-data-dirty,.form-data-dirty *{color:color-mix(in srgb,currentColor 70%,rgb(var(--v-theme-primary)))!important;text-shadow:0 0 .02em currentColor}
286
+ </style>
@@ -1,7 +1,9 @@
1
1
  <script lang="ts" setup>
2
2
  import { computed, useTemplateRef, defineExpose } from 'vue'
3
3
  import { type GraphqlModelItemProps, useGraphqlModelItem } from '../../composables/graphqlModelItem'
4
+ import type {FormDialogCallback} from '../../types/formDialog'
4
5
  import EditPad from '../form/EditPad.vue'
6
+ import {useDialog} from "../../composables/dialog";
5
7
 
6
8
  defineOptions({
7
9
  inheritAttrs: false,
@@ -14,6 +16,8 @@ const props = withDefaults(defineProps<Props & GraphqlModelItemProps>(), {
14
16
  fields: () => ['*'],
15
17
  })
16
18
 
19
+ const emit = defineEmits(['create', 'update'])
20
+
17
21
  const { item,
18
22
  canCreate, canUpdate,
19
23
  createItem, updateItem,
@@ -27,11 +31,25 @@ const operation : any = computed(()=>{
27
31
  return Object.assign({},editPad.value?.operation,{canSave,reload,item})
28
32
  })
29
33
 
34
+ const onCreateItem = (item: Record<string, any>,callback?: FormDialogCallback)=>{
35
+ createItem(item,callback).then(()=>{
36
+ useDialog().notify({message: "Data created successfully",type: 'success',title:"Success"})
37
+ emit("create",item,callback)
38
+ })
39
+ }
40
+
41
+ const onUpdateItem = (item: Record<string, any>,callback?: FormDialogCallback)=>{
42
+ updateItem(item,callback).then(()=>{
43
+ useDialog().notify({message: "Data updated successfully",type: 'success',title:"Success"})
44
+ emit("update",item,callback)
45
+ })
46
+ }
47
+
30
48
  defineExpose({ operation, formPad: editPad.value?.formPad })
31
49
  </script>
32
50
 
33
51
  <template>
34
- <FormEditPad v-bind="$attrs" :form-data="item" ref="editPadRef" @create="createItem" @update="updateItem">
52
+ <FormEditPad v-bind="$attrs" :form-data="item" ref="editPadRef" @create="onCreateItem" @update="onUpdateItem">
35
53
  <template #titleToolbar>
36
54
  <slot name="titleToolbar" :operation="operation">
37
55
  <VToolbarTitle>
@@ -50,7 +50,7 @@ const props = withDefaults(defineProps<Props & GraphqlModelProps>(), {
50
50
  onlyOwnerEdit: false,
51
51
  })
52
52
 
53
- const emit = defineEmits(['open:dialog','close:dialog'])
53
+ const emit = defineEmits(['open:dialog','close:dialog','create','update','delete'])
54
54
  const attrs = useAttrs()
55
55
  const plainAttrs = computed(() => {
56
56
  const returnAttrs = clone(attrs)
@@ -95,7 +95,25 @@ function openDialogReadonly(item?: object) {
95
95
 
96
96
  async function confirmDeleteItem(item: Record<string, any>, callback?: FormDialogCallback) {
97
97
  let confirm = await useDialog().confirm({message: "Do you want to delete record?"})
98
- if (confirm) deleteItem(item,callback)
98
+ if (confirm) onDeleteItem(item,callback)
99
+ }
100
+
101
+ const onCreateItem = (item: Record<string, any>,callback?: FormDialogCallback)=>{
102
+ createItem(item,callback).then(()=>{
103
+ emit("create",item,callback)
104
+ })
105
+ }
106
+
107
+ const onUpdateItem = (item: Record<string, any>,callback?: FormDialogCallback)=>{
108
+ updateItem(item,callback).then(()=>{
109
+ emit("update",item,callback)
110
+ })
111
+ }
112
+
113
+ const onDeleteItem = (item: Record<string, any>,callback?: FormDialogCallback)=>{
114
+ deleteItem(item,callback).then(()=>{
115
+ emit("delete",item,callback)
116
+ })
99
117
  }
100
118
 
101
119
  const canEditRow = function (item: Record<string, any>) {
@@ -301,8 +319,8 @@ defineExpose({ reload,operation })
301
319
  :fullscreen="dialogFullscreen"
302
320
  :initial-data="computedInitialData"
303
321
  :form-data="currentItem"
304
- @create="createItem"
305
- @update="updateItem"
322
+ @create="onCreateItem"
323
+ @update="onUpdateItem"
306
324
  @afterLeave="emit('close:dialog')"
307
325
  :saveAndStay="saveAndStay"
308
326
  :readonly="isDialogReadonly"
@@ -89,7 +89,7 @@ export function templateItemToString(inputItem, parentTemplates, dataVariable =
89
89
  }
90
90
  function columnWrapTemplateItemString(item, parentTemplates, dataVariable = "data") {
91
91
  let templateString = templateItemToString(item, parentTemplates, dataVariable);
92
- if (!["Separator", "FormHidden"].includes(item.inputType)) templateString = `<v-col${item.width ? ` cols="${item.width}"` : ""}${item.columnAttributes ? " " + item.columnAttributes : ""}${item.customClass ? ` class="${item.customClass}"` : ""}${item.customStyle ? ` class="${item.customStyle}"` : ""}>${templateString}</v-col>`;
92
+ if (!["Separator", "FormHidden"].includes(item.inputType)) templateString = `<v-col${item.width ? ` cols="${item.width}"` : ""}${item.columnAttributes ? " " + item.columnAttributes : ""} :class="(injectedClass) ? injectedClass['${item.variableName || ""}'] : undefined"${item.customClass ? ` class="${item.customClass}"` : ""}${item.customStyle ? ` class="${item.customStyle}"` : ""}>${templateString}</v-col>`;
93
93
  if (["Header", "DocumentForm"].includes(item.inputType)) templateString = `</v-row><v-row dense${item.customClass ? ` class="${item.customClass}"` : ""}${item.customStyle ? ` class="${item.customStyle}"` : ""}>${templateString}</v-row><v-row dense>`;
94
94
  return templateString || "";
95
95
  }
@@ -11,6 +11,11 @@ export declare function useRules(): {
11
11
  lengthGreater: (length: number, customError?: string) => (value: any) => string | true;
12
12
  lengthLess: (length: number, customError?: string) => (value: any) => string | true;
13
13
  telephone: (customError?: string) => (value: any) => string | true;
14
+ telephoneSingle: (customError?: string) => (value: any) => string | true;
15
+ internationalTelephone: (customError?: string) => (value: any) => string | true;
16
+ internationalTelephoneSingle: (customError?: string) => (value: any) => string | true;
17
+ mobilePhone: (customError?: string) => (value: any) => string | true;
18
+ mobilePhoneSingle: (customError?: string) => (value: any) => string | true;
14
19
  email: (customError?: string) => (value: any) => string | true;
15
20
  regex: (regex: RegExp | string, customError?: string) => (value: any) => string | true;
16
21
  idcard: (customError?: string) => (value: any) => string | true;
@@ -34,6 +39,11 @@ export declare function useRules(): {
34
39
  lengthGreater: (length: number, customError?: string) => (value: any) => string | true;
35
40
  lengthLess: (length: number, customError?: string) => (value: any) => string | true;
36
41
  telephone: (customError?: string) => (value: any) => string | true;
42
+ telephoneSingle: (customError?: string) => (value: any) => string | true;
43
+ internationalTelephone: (customError?: string) => (value: any) => string | true;
44
+ internationalTelephoneSingle: (customError?: string) => (value: any) => string | true;
45
+ mobilePhone: (customError?: string) => (value: any) => string | true;
46
+ mobilePhoneSingle: (customError?: string) => (value: any) => string | true;
37
47
  email: (customError?: string) => (value: any) => string | true;
38
48
  regex: (regex: RegExp | string, customError?: string) => (value: any) => string | true;
39
49
  idcard: (customError?: string) => (value: any) => string | true;
@@ -57,6 +67,11 @@ export declare function useRules(): {
57
67
  lengthGreater: (length: number, customError?: string) => (value: any) => string | true;
58
68
  lengthLess: (length: number, customError?: string) => (value: any) => string | true;
59
69
  telephone: (customError?: string) => (value: any) => string | true;
70
+ telephoneSingle: (customError?: string) => (value: any) => string | true;
71
+ internationalTelephone: (customError?: string) => (value: any) => string | true;
72
+ internationalTelephoneSingle: (customError?: string) => (value: any) => string | true;
73
+ mobilePhone: (customError?: string) => (value: any) => string | true;
74
+ mobilePhoneSingle: (customError?: string) => (value: any) => string | true;
60
75
  email: (customError?: string) => (value: any) => string | true;
61
76
  regex: (regex: RegExp | string, customError?: string) => (value: any) => string | true;
62
77
  idcard: (customError?: string) => (value: any) => string | true;
@@ -80,6 +95,11 @@ export declare function useRules(): {
80
95
  lengthGreater: (length: number, customError?: string) => (value: any) => string | true;
81
96
  lengthLess: (length: number, customError?: string) => (value: any) => string | true;
82
97
  telephone: (customError?: string) => (value: any) => string | true;
98
+ telephoneSingle: (customError?: string) => (value: any) => string | true;
99
+ internationalTelephone: (customError?: string) => (value: any) => string | true;
100
+ internationalTelephoneSingle: (customError?: string) => (value: any) => string | true;
101
+ mobilePhone: (customError?: string) => (value: any) => string | true;
102
+ mobilePhoneSingle: (customError?: string) => (value: any) => string | true;
83
103
  email: (customError?: string) => (value: any) => string | true;
84
104
  regex: (regex: RegExp | string, customError?: string) => (value: any) => string | true;
85
105
  idcard: (customError?: string) => (value: any) => string | true;
@@ -14,7 +14,12 @@ export function useRules() {
14
14
  const length = (length2, customError = `Length must be ${length2}`) => (value) => condition(!value || value.length == length2, customError);
15
15
  const lengthGreater = (length2, customError = `Length must be greater than ${length2}`) => (value) => condition(!value || value.length >= length2, customError);
16
16
  const lengthLess = (length2, customError = `Length must be less than ${length2}`) => (value) => condition(!value || value.length <= length2, customError);
17
- const telephone = (customError = "Invalid telephone number") => (value) => condition(!value || /^(?:\s*(?:0[2-57]\d{7}|0[689]\d{8})(?:#\d{1,5})?\s*(?:\([^()]+\)\s*)?(?:,(?=.+))?)*$/.test(value), customError);
17
+ const telephone = (customError = "Invalid telephone number") => (value) => condition(!value || /^(?:\s*(?:\+66|0)\s?(?:[2-57](?:[\s-]?\d){7}|[689](?:[\s-]?\d){8})(?:#\d{1,5})?\s*(?:\([^()]+\)\s*)?(?:,(?=\s*\+?6?0)|,|$))*$/.test(value), customError);
18
+ const telephoneSingle = (customError = "Invalid telephone number") => (value) => condition(!value || /^\s*(?:\+66|0)\s?(?:[2-57](?:[\s-]?\d){7}|[689](?:[\s-]?\d){8})(?:#\d{1,5})?\s*(?:\([^()]+\)\s*)?$/.test(value), customError);
19
+ const internationalTelephone = (customError = "Invalid telephone number") => (value) => condition(!value || /^(?:\s*(?:\+?\d{1,3}[-\s]?)?(?:0?\d{1,4}[-\s]?)?\d{3,4}[-\s]?\d{3,4}(?:#\d{1,5})?\s*(?:\([^()]+\)\s*)?(?:,(?=\s*\+?\d)|,|$))*$/.test(value), customError);
20
+ const internationalTelephoneSingle = (customError = "Invalid telephone number") => (value) => condition(!value || /^\s*(?:\+?\d{1,3}[-\s]?)?(?:0?\d{1,4}[-\s]?)?\d{3,4}[-\s]?\d{3,4}(?:#\d{1,5})?\s*(?:\([^()]+\)\s*)?$/.test(value), customError);
21
+ const mobilePhone = (customError = "Invalid telephone number") => (value) => condition(!value || /^(?:\s*(?:\+66|0)\s?(?:6\d{8}|8\d{8}|9\d{8})\s*(?:,(?=\s*\+?6?0)|,|$))*$/.test(value), customError);
22
+ const mobilePhoneSingle = (customError = "Invalid telephone number") => (value) => condition(!value || /^\s*(?:\+66|0)\s?(?:6\d{8}|8\d{8}|9\d{8})\s*$/.test(value), customError);
18
23
  const email = (customError = "Invalid email address") => (value) => condition(!value || /^([\w\-.]+)@([\w\-.]+)\.([a-z]{2,5})$/i.test(value), customError);
19
24
  const regex = (regex2, customError = "Invalid format") => (value) => condition(!value || new RegExp(regex2).test(value), customError);
20
25
  const idcard = (customError = "Invalid ID Card format") => (value) => condition(!value || /^\d{13}$/.test(value) && (11 - [...value].slice(0, 12).reduce((sum, n, i) => sum + +n * (13 - i), 0) % 11) % 10 === +value[12], customError);
@@ -38,6 +43,11 @@ export function useRules() {
38
43
  lengthGreater,
39
44
  lengthLess,
40
45
  telephone,
46
+ telephoneSingle,
47
+ internationalTelephone,
48
+ internationalTelephoneSingle,
49
+ mobilePhone,
50
+ mobilePhoneSingle,
41
51
  email,
42
52
  regex,
43
53
  idcard,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ramathibodi/nuxt-commons",
3
- "version": "0.1.60",
3
+ "version": "0.1.61",
4
4
  "description": "Ramathibodi Nuxt modules for common components",
5
5
  "repository": {
6
6
  "type": "git",