Feb 5, 2022
0
Level 17
Flatpickr Vuejs Componet
I created Flatpickr Vuejs Component and it works fine but when I use static to true as option it will wrap all template to a div with class: flatpickr-wrapper. it actually should wrap input with this div.
That's why I changes this.$el to this.$el.children[1], so now it works as expected.
I just wanted to know, is it normal to edit the this.$el? Is there any better solution?
<template>
<div :class="'col-md-' + column">
<label :for="name" :class="[required ? 'required' : null,'fw-bold fs-6 mb-3']">{{ label }}</label>
<input :name="name" class="form-control form-control-solid mb-3 mb-lg-0" :id="name" :value="value" :required="required">
<div class="fv-plugins-message-container invalid-feedback" v-if="error">{{ error }}</div>
</div>
</template>
<script>
// Events to emit, copied from flatpickr source
const includedEvents = [
'onChange',
'onClose',
'onDestroy',
'onMonthChange',
'onOpen',
'onYearChange',
];
// Let's not emit these events by default
const excludedEvents = [
'onValueUpdate',
'onDayCreate',
'onParseConfig',
'onReady',
'onPreCalendarPosition',
'onKeyDown',
];
const camelToKebab = (string) => {
return string.replace(/([a-z])([A-Z])/g, '-').toLowerCase();
};
const arrayify = (obj) => {
return obj instanceof Array ? obj : [obj];
};
const nullify = (value) => {
return (value && value.length) ? value : null;
}
const cloneObject = (obj) => {
return Object.assign({}, obj);
};
// Keep a copy of all events for later use
const allEvents = includedEvents.concat(excludedEvents);
// Passing these properties in `set()` method will cause flatpickr to trigger some callbacks
const configCallbacks = ['locale', 'showMonths'];
export default {
name: 'flat-pickr',
render(el) {
return el('input', {
attrs: {
type: 'text',
'data-input': true,
},
props: {
disabled: this.disabled
},
on: {
input: this.onInput
}
})
},
props: {
value: {
default: null,
required: true,
validator(value) {
return (
value === null ||
value instanceof Date ||
typeof value === "string" ||
value instanceof String ||
value instanceof Array ||
typeof value === "number"
);
}
},
column: {
type: String,
default: '4'
},
required: {
type: Boolean,
default: false
},
label: {
type: String,
default: null
},
name: {
type: String,
default: null
},
// https://chmln.github.io/flatpickr/options/
config: {
type: Object,
default: () => ({
wrap: false,
//defaultDate: 'today',
static: true,
dateFormat: "Y-m-d"
})
},
events: {
type: Array,
default: () => includedEvents
},
disabled: {
type: Boolean,
default: false
}
},
data() {
return {
/**
* The flatpickr instance
*/
fp: null,
error: null
};
},
mounted() {
// Return early if flatpickr is already loaded
/* istanbul ignore if */
if (this.fp) return;
// Don't mutate original object on parent component
let safeConfig = cloneObject(this.config);
this.events.forEach((hook) => {
// Respect global callbacks registered via setDefault() method
let globalCallbacks = flatpickr.defaultConfig[hook] || [];
// Inject our own method along with user callback
let localCallback = (...args) => {
this.$emit(camelToKebab(hook), ...args)
};
// Overwrite with merged array
safeConfig[hook] = arrayify(safeConfig[hook] || []).concat(globalCallbacks, localCallback);
});
// Set initial date without emitting any event
safeConfig.defaultDate = this.value || safeConfig.defaultDate;
// Init flatpickr
this.fp = new flatpickr(this.getElem(), safeConfig);
// Attach blur event
this.fpInput().addEventListener('blur', this.onBlur);
this.$on('on-close', this.onClose);
this.$on('on-open', this.onOpen);
// Immediate watch will fail before fp is set,
// so need to start watching after mount
this.$watch('disabled', this.watchDisabled, {immediate: true})
if (typeof errors[this.name] !== 'undefined') {
this.error = errors[this.name][0];
}
},
methods: {
/**
* Get the HTML node where flatpickr to be attached
* Bind on parent element if wrap is true
*/
getElem() {
return this.config.wrap ? this.$el.parentNode : this.$el.children[1]
},
/**
* Watch for value changed by date-picker itself and notify parent component
*
* @param event
*/
onInput(event) {
const input = event.target;
// Lets wait for DOM to be updated
this.$nextTick(() => {
this.$emit('input', nullify(input.value));
});
},
/**
* @return HTMLElement
*/
fpInput() {
return this.fp.altInput || this.fp.input;
},
/**
* Blur event is required by many validation libraries
*
* @param event
*/
onBlur(event) {
this.$emit('blur', nullify(event.target.value));
},
onOpen(event) {
if (!this.value) {
this.fp.clear();
}
},
/**
* Flatpickr does not emit input event in some cases
*/
onClose(selectedDates, dateStr) {
this.$emit('input', nullify(dateStr))
},
/**
* Watch for the disabled property and sets the value to the real input.
*
* @param newState
*/
watchDisabled(newState) {
if (newState) {
this.fpInput().setAttribute('disabled', newState);
} else {
this.fpInput().removeAttribute('disabled');
}
}
},
watch: {
/**
* Watch for any config changes and redraw date-picker
*
* @param newConfig Object
*/
config: {
deep: true,
handler(newConfig) {
let safeConfig = cloneObject(newConfig);
// Workaround: Don't pass hooks to configs again otherwise
// previously registered hooks will stop working
// Notice: we are looping through all events
// This also means that new callbacks can not passed once component has been initialized
allEvents.forEach((hook) => {
delete safeConfig[hook];
});
this.fp.set(safeConfig);
// Workaround: Allow to change locale dynamically
configCallbacks.forEach((name) => {
if (typeof safeConfig[name] !== 'undefined') {
this.fp.set(name, safeConfig[name])
}
});
}
},
/**
* Watch for changes from parent component and update DOM
*
* @param newValue
*/
value(newValue) {
// Prevent updates if v-model value is same as input's current value
if (newValue === nullify(this.$el.value)) return;
// Make sure we have a flatpickr instance
this.fp &&
// Notify flatpickr instance that there is a change in value
this.fp.setDate(newValue, true);
},
},
/**
* Free up memory
*/
beforeDestroy() {
/* istanbul ignore else */
if (this.fp) {
this.fpInput().removeEventListener('blur', this.onBlur);
this.fp.destroy();
this.fp = null;
}
},
};
</script>
Please or to participate in this conversation.