mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-07-23 18:02:18 +00:00
Form Components
This commit is contained in:
@@ -0,0 +1,137 @@
|
|||||||
|
import FhcFragment from "../Fragment.js";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
FhcFragment
|
||||||
|
},
|
||||||
|
provide() {
|
||||||
|
return {
|
||||||
|
$registerToForm: component => {
|
||||||
|
if (this.inputs.indexOf(component) < 0)
|
||||||
|
this.inputs.push(component);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
tag: {
|
||||||
|
type: String,
|
||||||
|
default: 'form'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
inputs: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
sortedInputs() {
|
||||||
|
return this.inputs.reduce((a,c) => {
|
||||||
|
let name = c.name || '_default';
|
||||||
|
if (!a[name])
|
||||||
|
a[name] = [];
|
||||||
|
a[name].push(c);
|
||||||
|
|
||||||
|
if (c.lcType == 'checkbox' && name.substr(-1) == ']' && name.indexOf('[')) {
|
||||||
|
name = name.substr(0, name.lastIndexOf('['));
|
||||||
|
if (!a[name])
|
||||||
|
a[name] = [];
|
||||||
|
a[name].push(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
return a;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
_sendFeedbackToInput(inputs, feedback, valid) {
|
||||||
|
if (inputs.length) {
|
||||||
|
inputs.forEach(input => input.setFeedback(valid, feedback));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.$fhcAlert) {
|
||||||
|
this.$fhcAlert[valid ? 'alertSuccess' : 'alertError'](feedback);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
setFeedback(valid, feedback) {
|
||||||
|
if (Array.isArray(feedback)) {
|
||||||
|
let remaining = feedback.filter(fb =>
|
||||||
|
this._sendFeedbackToInput(
|
||||||
|
this.sortedInputs['_default'] || [],
|
||||||
|
fb,
|
||||||
|
valid
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return remaining.length ? remaining : null;
|
||||||
|
}
|
||||||
|
if (typeof feedback === 'object') {
|
||||||
|
let remaining = Object.entries(feedback).filter(([name, fb]) =>
|
||||||
|
this._sendFeedbackToInput(
|
||||||
|
this.sortedInputs[name.split('.')[0] + name.split('.').slice(1).map(p => `[${p}]`).join("")] || this.sortedInputs['_default'] || [],
|
||||||
|
fb,
|
||||||
|
valid
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return remaining.length ? Object.fromEntries(remaining) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let remaining = this._sendFeedbackToInput(
|
||||||
|
this.sortedInputs['_default'] || [],
|
||||||
|
feedback,
|
||||||
|
valid
|
||||||
|
);
|
||||||
|
return remaining ? feedback : null;
|
||||||
|
},
|
||||||
|
clearValidation() {
|
||||||
|
this.inputs.forEach(input => input.clearValidation());
|
||||||
|
},
|
||||||
|
send(promise) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
promise.then(result => {
|
||||||
|
if (result?.status == 200 && result.data) {
|
||||||
|
if (typeof result.data !== 'object' || !result.data.hasOwnProperty('retval'))
|
||||||
|
// TODO(chris): IMPLEMENT! Error in API
|
||||||
|
return reject(result);
|
||||||
|
if (result.data.error)
|
||||||
|
// TODO(chris): IMPLEMENT! Error in API
|
||||||
|
return reject(result);
|
||||||
|
const data = result.data.retval;
|
||||||
|
// TODO(chris): check for something better/add new standardized return value
|
||||||
|
if (result.data.code == 1)
|
||||||
|
this.setFeedback(true, data);
|
||||||
|
return resolve(data);
|
||||||
|
}
|
||||||
|
// TODO(chris): IMPLEMENT! Wrong result object
|
||||||
|
reject(result);
|
||||||
|
}).catch(result => {
|
||||||
|
if (result?.response?.status == 400 && result.response.data) {
|
||||||
|
if (typeof result.response.data !== 'object' || !result.response.data.hasOwnProperty('retval'))
|
||||||
|
// TODO(chris): IMPLEMENT! Error in API
|
||||||
|
return reject(result);
|
||||||
|
this.clearValidation();
|
||||||
|
const remaining = this.setFeedback(
|
||||||
|
false,
|
||||||
|
result.response.data.retval
|
||||||
|
);
|
||||||
|
if (remaining) {
|
||||||
|
result.response.data.retval = remaining;
|
||||||
|
return reject(result);
|
||||||
|
}
|
||||||
|
} else if (result?.response?.status == 500) {
|
||||||
|
if (this.$fhcAlert)
|
||||||
|
this.$fhcAlert.handleSystemError(result);
|
||||||
|
else
|
||||||
|
return reject(result);
|
||||||
|
} else {
|
||||||
|
return reject(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<component :is="tag || 'FhcFragment'" v-bind="$attrs">
|
||||||
|
<slot></slot>
|
||||||
|
</component>`
|
||||||
|
}
|
||||||
@@ -0,0 +1,278 @@
|
|||||||
|
import FhcFragment from "../Fragment.js";
|
||||||
|
|
||||||
|
let _uuid = {};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
inheritAttrs: false,
|
||||||
|
components: {
|
||||||
|
FhcFragment
|
||||||
|
},
|
||||||
|
inject: [
|
||||||
|
'$registerToForm'
|
||||||
|
],
|
||||||
|
props: {
|
||||||
|
bsFeedback: Boolean,
|
||||||
|
noAutoClass: Boolean,
|
||||||
|
noFeedback: Boolean,
|
||||||
|
inputGroup: Boolean,
|
||||||
|
type: String,
|
||||||
|
name: String,
|
||||||
|
containerClass: [String, Array, Object]
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
valid: undefined,
|
||||||
|
feedback: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
hasContainer() {
|
||||||
|
if (!this.bsFeedback)
|
||||||
|
return true;
|
||||||
|
if (this.containerClass)
|
||||||
|
return true;
|
||||||
|
if (this.autoContainerClass)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
acc() {
|
||||||
|
if (!this.containerClass)
|
||||||
|
return {};
|
||||||
|
if (typeof this.containerClass === 'string' || this.containerClass instanceof String)
|
||||||
|
return this.containerClass.split(' ').reduce((a,c) => {a[c] = true; return a}, {});
|
||||||
|
if (Array.isArray(this.containerClass))
|
||||||
|
return this.containerClass.reduce((a,c) => {a[c] = true; return a}, {});
|
||||||
|
return this.containerClass;
|
||||||
|
},
|
||||||
|
autoContainerClass() {
|
||||||
|
if (this.noAutoClass)
|
||||||
|
return this.acc;
|
||||||
|
|
||||||
|
const acc = {...this.acc};
|
||||||
|
|
||||||
|
if (this.inputGroup)
|
||||||
|
acc['input-group-item'] = true;
|
||||||
|
|
||||||
|
if (this.lcType == 'radio' || this.lcType == 'checkbox')
|
||||||
|
acc['form-check'] = true;
|
||||||
|
|
||||||
|
if (this.inputGroup && acc['form-check']) {
|
||||||
|
acc['input-group-item'] = false;
|
||||||
|
acc['form-check'] = false;
|
||||||
|
acc['input-group-text'] = true;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
lcType() {
|
||||||
|
if (!this.type)
|
||||||
|
return 'text';
|
||||||
|
return this.type.toLowerCase();
|
||||||
|
},
|
||||||
|
tag() {
|
||||||
|
switch (this.lcType) {
|
||||||
|
case 'textarea':
|
||||||
|
case 'select':
|
||||||
|
return this.lcType;
|
||||||
|
case 'datepicker':
|
||||||
|
return 'VueDatePicker';
|
||||||
|
case 'autocomplete':
|
||||||
|
return 'PvAutocomplete';
|
||||||
|
case 'uploadimage':
|
||||||
|
return 'UploadImage';
|
||||||
|
case 'uploadfile':
|
||||||
|
case 'uploaddms':
|
||||||
|
return 'UploadDms';
|
||||||
|
default:
|
||||||
|
return 'input';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
validationClass() {
|
||||||
|
const classes = [];
|
||||||
|
if (this.valid)
|
||||||
|
classes.push('is-valid');
|
||||||
|
else if (this.valid === false)
|
||||||
|
classes.push('is-invalid');
|
||||||
|
|
||||||
|
if (!this.noAutoClass) {
|
||||||
|
let c = this.$attrs.class ? this.$attrs.class.split(' ') : [];
|
||||||
|
switch (this.lcType) {
|
||||||
|
// TODO(chris): complete list!
|
||||||
|
case 'select':
|
||||||
|
if (!c.includes('form-select'))
|
||||||
|
classes.push('form-select');
|
||||||
|
break;
|
||||||
|
case 'range':
|
||||||
|
if (!c.includes('form-range'))
|
||||||
|
classes.push('form-range');
|
||||||
|
break;
|
||||||
|
case 'radio':
|
||||||
|
case 'checkbox':
|
||||||
|
// TODO(chris): maybe different handling?
|
||||||
|
if (!c.includes('form-check-input') && !c.includes('btn-check'))
|
||||||
|
classes.push('form-check-input');
|
||||||
|
break;
|
||||||
|
case 'autocomplete':
|
||||||
|
case 'datepicker':
|
||||||
|
classes.push('p-0');
|
||||||
|
classes.push('border-0');
|
||||||
|
case 'color':
|
||||||
|
if (!c.includes('form-control-color'))
|
||||||
|
classes.push('form-control-color');
|
||||||
|
case 'text':
|
||||||
|
case 'number':
|
||||||
|
case 'password':
|
||||||
|
case 'textarea':
|
||||||
|
if (!c.includes('form-control'))
|
||||||
|
classes.push('form-control');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return classes;
|
||||||
|
},
|
||||||
|
feedbackClass() {
|
||||||
|
if (!this.feedback || this.feedback === true)
|
||||||
|
return '';
|
||||||
|
if (!this.bsFeedback)
|
||||||
|
return {
|
||||||
|
'valid-tooltip': this.valid === true,
|
||||||
|
'invalid-tooltip': this.valid === false
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
'valid-feedback': this.valid === true,
|
||||||
|
'invalid-feedback': this.valid === false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
modelValueCmp: {
|
||||||
|
get() {
|
||||||
|
return this.$attrs.modelValue;
|
||||||
|
},
|
||||||
|
set(v) {
|
||||||
|
this.$emit('update:modelValue', v);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
idCmp() {
|
||||||
|
let uuid = this.$attrs.id;
|
||||||
|
if (this.lcType == 'datepicker')
|
||||||
|
uuid = this.$attrs.uid;
|
||||||
|
if (!uuid && this.$attrs.label)
|
||||||
|
uuid = 'fhc-form-input';
|
||||||
|
if (!uuid)
|
||||||
|
return undefined;
|
||||||
|
if (this.lcType == 'datepicker')
|
||||||
|
uuid = 'dp-input-' + uuid;
|
||||||
|
if (_uuid[uuid] === undefined)
|
||||||
|
_uuid[uuid] = 0;
|
||||||
|
return uuid + '-' + (_uuid[uuid]++);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
clearValidation() {
|
||||||
|
this.valid = undefined;
|
||||||
|
this.feedback = [];
|
||||||
|
},
|
||||||
|
setFeedback(valid, feedback) {
|
||||||
|
if (!feedback)
|
||||||
|
feedback = [];
|
||||||
|
if (!Array.isArray(feedback))
|
||||||
|
feedback = [feedback];
|
||||||
|
this.valid = valid;
|
||||||
|
this.feedback = feedback;
|
||||||
|
},
|
||||||
|
_loadComponents() {
|
||||||
|
if (this.tag == 'VueDatePicker' && !this._.components.VueDatePicker) {
|
||||||
|
this._.components.VueDatePicker = Vue.defineAsyncComponent(() => import("../vueDatepicker.js.php"));
|
||||||
|
} else if (this.tag == 'PvAutocomplete' && !this._.components.PvAutocomplete) {
|
||||||
|
this._.components.PvAutocomplete = Vue.defineAsyncComponent(() => import(FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + "/public/js/components/primevue/autocomplete/autocomplete.esm.min.js"));
|
||||||
|
} else if (this.tag == 'UploadImage' && !this._.components.UploadImage) {
|
||||||
|
this._.components.UploadImage = Vue.defineAsyncComponent(() => import("./Upload/Image.js"));
|
||||||
|
} else if (this.tag == 'UploadDms' && !this._.components.UploadDms) {
|
||||||
|
this._.components.UploadDms = Vue.defineAsyncComponent(() => import("./Upload/Dms.js"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeMount() {
|
||||||
|
this._loadComponents();
|
||||||
|
},
|
||||||
|
beforeUpdate() {
|
||||||
|
this._loadComponents();
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
if (this.$registerToForm)
|
||||||
|
this.$registerToForm(this);
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<component :is="!hasContainer ? 'FhcFragment' : 'div'" class="position-relative" :class="autoContainerClass">
|
||||||
|
<label v-if="$attrs.label && lcType != 'radio' && lcType != 'checkbox'" :for="idCmp">{{$attrs.label}}</label>
|
||||||
|
<input v-if="tag == 'input'" :type="lcType" v-model="modelValueCmp" v-bind="$attrs" :id="idCmp" :name="name" :class="validationClass" :modelValue="undefined" @input="clearValidation(); $emit('input', $event)">
|
||||||
|
<textarea v-else-if="tag == 'textarea'" v-model="modelValueCmp" v-bind="$attrs" :id="idCmp" :name="name" :class="validationClass" :modelValue="undefined" @input="clearValidation(); $emit('input', $event)"></textarea>
|
||||||
|
<select v-else-if="tag == 'select'" v-model="modelValueCmp" v-bind="$attrs" :id="idCmp" :name="name" :class="validationClass" :modelValue="undefined" @input="clearValidation(); $emit('input', $event)">
|
||||||
|
<slot></slot>
|
||||||
|
</select>
|
||||||
|
<component
|
||||||
|
v-else-if="tag == 'VueDatePicker'"
|
||||||
|
:is="tag"
|
||||||
|
:type="type"
|
||||||
|
v-model="modelValueCmp"
|
||||||
|
v-bind="$attrs"
|
||||||
|
:uid="idCmp ? idCmp.substr(9) : idCmp"
|
||||||
|
:name="name"
|
||||||
|
:class="validationClass"
|
||||||
|
:input-class-name=
|
||||||
|
"[...Object.entries({'form-control': !noAutoClass, 'is-valid': valid === true, 'is-invalid': valid === false}).reduce((a,[k,v]) => {if(v) a.push(k);return a}, []), ...($attrs['input-class-name'] ? $attrs['input-class-name'].split(' ') : [])].join(' ')"
|
||||||
|
@update:model-value="clearValidation"
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</component>
|
||||||
|
<component
|
||||||
|
v-else-if="tag == 'PvAutocomplete'"
|
||||||
|
:is="tag"
|
||||||
|
:type="type"
|
||||||
|
v-model="modelValueCmp"
|
||||||
|
v-bind="$attrs"
|
||||||
|
:id="idCmp"
|
||||||
|
:input-props="{name}"
|
||||||
|
:class="validationClass"
|
||||||
|
:input-class="[...Object.entries({'form-control': !noAutoClass, 'is-valid': valid === true, 'is-invalid': valid === false}).reduce((a,[k,v]) => {if(v) a.push(k);return a}, []), ...($attrs['input-class'] ? $attrs['input-class'].split(' ') : [])].join(' ')"
|
||||||
|
@update:model-value="clearValidation"
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</component>
|
||||||
|
<component
|
||||||
|
v-else-if="tag == 'UploadDms'"
|
||||||
|
:is="tag"
|
||||||
|
:type="type"
|
||||||
|
v-model="modelValueCmp"
|
||||||
|
v-bind="$attrs"
|
||||||
|
:id="idCmp"
|
||||||
|
:name="name"
|
||||||
|
:class="validationClass"
|
||||||
|
:input-class="validationClass"
|
||||||
|
:no-list="inputGroup"
|
||||||
|
@update:model-value="clearValidation"
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</component>
|
||||||
|
<component
|
||||||
|
v-else
|
||||||
|
:is="tag"
|
||||||
|
:type="type"
|
||||||
|
v-model="modelValueCmp"
|
||||||
|
v-bind="$attrs"
|
||||||
|
:id="idCmp"
|
||||||
|
:name="name"
|
||||||
|
:class="validationClass"
|
||||||
|
@update:model-value="clearValidation"
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</component>
|
||||||
|
<label v-if="$attrs.label && (lcType == 'radio' || lcType == 'checkbox')" :for="idCmp" :class="!noAutoClass && 'form-check-label'">{{$attrs.label}}</label>
|
||||||
|
<div v-if="valid !== undefined && feedback.length && !noFeedback" :class="feedbackClass">
|
||||||
|
<template v-for="(msg, i) in feedback" :key="i">
|
||||||
|
<hr v-if="i" class="m-0">
|
||||||
|
{{msg}}
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</component>
|
||||||
|
`
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
export default {
|
||||||
|
emits: [
|
||||||
|
'update:modelValue'
|
||||||
|
],
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: [ FileList, Array ],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
multiple: Boolean,
|
||||||
|
id: String,
|
||||||
|
name: String,
|
||||||
|
inputClass: [String, Array, Object],
|
||||||
|
noList: Boolean
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
stringifyFile(file) {
|
||||||
|
return JSON.stringify({
|
||||||
|
lastModified: file.lastModified,
|
||||||
|
lastModifiedDate: file.lastModifiedDate,
|
||||||
|
name: file.name,
|
||||||
|
size: file.size,
|
||||||
|
type: file.type
|
||||||
|
});
|
||||||
|
},
|
||||||
|
addFiles(event) {
|
||||||
|
if (!this.multiple)
|
||||||
|
return this.$emit('update:modelValue', event.target.files);
|
||||||
|
|
||||||
|
const dt = new DataTransfer();
|
||||||
|
const doubles = [];
|
||||||
|
for (var file of this.modelValue) {
|
||||||
|
dt.items.add(file);
|
||||||
|
doubles.push(this.stringifyFile(file));
|
||||||
|
}
|
||||||
|
for (var file of event.target.files) {
|
||||||
|
// NOTE(chris): deep check (with FileReader) would require an async function so we only check the basic attributes
|
||||||
|
if (doubles.indexOf(this.stringifyFile(file)) < 0)
|
||||||
|
dt.items.add(file);
|
||||||
|
}
|
||||||
|
this.$emit('update:modelValue', dt.files);
|
||||||
|
},
|
||||||
|
removeFile(id) {
|
||||||
|
const fileToRemove = Array.from(this.modelValue)[id];
|
||||||
|
|
||||||
|
const dt = new DataTransfer();
|
||||||
|
for (var file of this.modelValue) {
|
||||||
|
if (file !== fileToRemove)
|
||||||
|
dt.items.add(file);
|
||||||
|
}
|
||||||
|
this.$emit('update:modelValue', dt.files);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
modelValue(n) {
|
||||||
|
if (n instanceof FileList)
|
||||||
|
return this.$refs.upload.files = n;
|
||||||
|
|
||||||
|
const dt = new DataTransfer();
|
||||||
|
const dms = [];
|
||||||
|
for (var file of n) {
|
||||||
|
if (file instanceof File) {
|
||||||
|
dt.items.add(file);
|
||||||
|
} else {
|
||||||
|
const dmsFile = new File([JSON.stringify(file)], file.name, {
|
||||||
|
type: 'application/x.fhc-dms+json'
|
||||||
|
});
|
||||||
|
dt.items.add(dmsFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.$emit('update:modelValue', dt.files);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<div class="form-upload-dms">
|
||||||
|
<input ref="upload" class="form-control" :class="inputClass" :id="id" :name="name" :multiple="multiple" type="file" @change="addFiles">
|
||||||
|
<ul v-if="modelValue.length && multiple && !noList" class="list-unstyled m-0">
|
||||||
|
<li v-for="(file, index) in modelValue" :key="index" class="d-flex mx-1 mt-1">
|
||||||
|
<span class="col-auto"><i class="fa fa-file me-1"></i></span>
|
||||||
|
<span class="col">{{ file.name }}</span>
|
||||||
|
<button class="col-auto btn btn-outline-secondary btn-p-0" @click="removeFile(index)">
|
||||||
|
<i class="fa fa-close"></i>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>`
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
export default {
|
||||||
|
emits: [
|
||||||
|
'update:modelValue'
|
||||||
|
],
|
||||||
|
props: {
|
||||||
|
modelValue: String
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
valueAsBase64DataString() {
|
||||||
|
if (!this.modelValue || this.modelValue.substring(0, 10) == 'data:image')
|
||||||
|
return this.modelValue;
|
||||||
|
return 'data:image/jpeg;charset=utf-8;base64,' + this.modelValue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
openUploadDialog() {
|
||||||
|
this.$refs.fileInput.click();
|
||||||
|
},
|
||||||
|
pickFile() {
|
||||||
|
let file = this.$refs.fileInput.files;
|
||||||
|
if (file && file[0]) {
|
||||||
|
let reader = new FileReader();
|
||||||
|
reader.onload = e => {
|
||||||
|
this.$emit('update:modelValue', e.target.result);
|
||||||
|
}
|
||||||
|
reader.readAsDataURL(file[0]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deleteImage() {
|
||||||
|
this.$emit('update:modelValue', '');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<div class="form-upload-image">
|
||||||
|
<template v-if="modelValue">
|
||||||
|
<img class="img-thumbnail" :src="valueAsBase64DataString" />
|
||||||
|
<div class="fotobutton">
|
||||||
|
<div class="d-grid gap-2 d-md-flex">
|
||||||
|
<button type="button" class="btn btn-outline-dark btn-sm" @click="deleteImage">
|
||||||
|
<i class="fa fa-close"></i>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-outline-dark btn-sm" @click="openUploadDialog">
|
||||||
|
<i class="fa fa-pen"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<slot>
|
||||||
|
<svg class="bd-placeholder-img img-thumbnail" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="A generic square placeholder image with a white border around it, making it resemble a photograph taken with an old instant camera: 200x200" preserveAspectRatio="xMidYMid slice" focusable="false"><title>A generic square placeholder image with a white border around it, making it resemble a photograph taken with an old instant camera</title><rect width="100%" height="100%" fill="#868e96"></rect><text x="50%" y="50%" fill="#dee2e6" dy=".3em"></text></svg>
|
||||||
|
</slot>
|
||||||
|
<div class="fotobutton-visible">
|
||||||
|
<div class="d-grid gap-2 d-md-flex">
|
||||||
|
<button type="button" class="btn btn-outline-dark btn-sm" @click="openUploadDialog">
|
||||||
|
<i class="fa fa-pen"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<input :id="$attrs.id" class="d-none" type="file" ref="fileInput" @input="pickFile" accept="image/*">
|
||||||
|
</div>`
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user