mymth / vanillajs-datepicker

A vanilla JavaScript remake of bootstrap-datepicker for Bulma and other CSS frameworks
MIT License
742 stars 153 forks source link

changeDate event source #9

Open Gruski opened 4 years ago

Gruski commented 4 years ago

For my unique implementation I need to know who triggered the changeDate event. Did it come from the user clicking on a date in the calendar or did it come from a setDate() API call. Right now there is no way to tell. The event target is always "input.datepicker-input", which is untrue for both situations.

Could you please add the source of the event in the callback?

anwariqbal73 commented 3 years ago

@Gruski Can you share code of working changeDate event! I am struggling with changeDate event its not working for me!

Gruski commented 3 years ago

@anwariqbal73 I ended up not using that event because it was useless form me without knowing the action that triggered the event. See below how I solved it in my Vue v2.6.x component that wraps this datepicker. You can see where I commented out my original use of "changeDate" handler. Look at all the places where my own getDate() handler replacement is called to see how I substituted.

Template:

<template id="vt-date-picker">  
    <span ref="button" @click="show" :style="buttonStyle">
        <slot></slot>
        <template v-if="!$slots.default">
            <img src="/ArgoWeb/Images/icon-calendar.svg" class="aw-icon" style="height:19px;vertical-align:-5px;" />
        </template>
    </span>
</template>

Script:

Vue.component('v-date-picker', {
    template: '#vt-date-picker',
    props: {
        value: String,
        disabled: {
            default: false
        },
        clearBtn: {
            default: true
        }
    },
    mounted() {  
        this.dpElem = $('body > .v-datepicker')[0];
        if (!this.dpElem) {
            // create single global datepicker for all components of this type to share
            this.dpElem = $('<div class="v-datepicker"></div>')[0];           
            $('body').append(this.dpElem);
            Datepicker.locales.en.titleFormat = 'M y';  // changes long month name to short in calendar header
            window.datepicker = new Datepicker(this.dpElem, {
                clearBtn: this.clearBtn,
                todayBtn: true,
                todayBtnMode: 1,
                nextArrow: '>',
                prevArrow: '<',
                disableTouchKeyboard: true
            });                         
        }
    },
    computed: {
        buttonStyle() {
            return this.disabled ? {} : { cursor: 'pointer' };
        }        
    },
    watch: {
        value: 'setDate'
    },
    methods: {
        show() {      
            if (this.disabled) return;

            this.shown = true;
            this.setDate(this.value);

            // events are bound/unbound everytime the picker is shown/hidden because its handlers need to be specific to each individual instance of this Vue component
            addEventListener('keydown', this.keydown);
            addEventListener('resize', this.hide);
            addEventListener('scroll', this.hide, true);  // 'true' for capture phase, otherwise it doesn't capture all elements that support scrolling
            addEventListener('mouseup', this.hide);
            if (top != self) top.addEventListener('mouseup', this.hide);  //so clicking on parent of frame can also hide the menu
            $(this.dpElem)
                .on('mouseup', e => e.stopPropagation())  // when click originates from picker do not bubble up event
                .on('mouseup', '.datepicker-picker .datepicker-cell.day, .datepicker-picker .today-btn, .datepicker-picker .clear-btn', this.hide); // delegation still easier with jquery
                //.on('changeDate', this.getDate);  // didn't use the picker's event because we can use the event delegation above used for hidding to also get the value

            $(this.dpElem).show();
            $.place(this.dpElem, this.$refs.button, {  // this is a custom absolute placement lib I wrote; original was not flexible enough and failed on edge cases; allows no tie to textbox input; datepicker can be launched from any element acting as button
                gap: { x: 0, y: 6 },
                center: { x: true, y: false },
                outerFlip: { x: false, y: true },
                fitViewport: { x: true, y: true }
            });          
        },
        hide(event) {
            this.shown = false;
            removeEventListener('keydown', this.keydown);
            removeEventListener('resize', this.hide);
            removeEventListener('scroll', this.hide, true);
            removeEventListener('mouseup', this.hide);
            if (top != self) top.removeEventListener('mouseup', this.hide);
            $(this.dpElem)
                .off('mouseup', e => e.stopPropagation())
                .off('mouseup', '.datepicker-picker .datepicker-cell.day, .datepicker-picker .today-btn, .datepicker-picker .clear-btn', this.hide);

            $(this.dpElem).hide();

            if (event.delegateTarget && event.delegateTarget === this.dpElem) {   // when hidding event comes from picker through {date, today, clear} selection
                setTimeout(this.getDate, 0);  // picker sets value after this event is raised so we have to let scripts finish before getting the date                                     
            }  
        },
        keydown(event) {
            if (event.key === 'Escape') this.hide(event);
        },
        setDate(val) {
            if (!this.shown || this.fromGetDate) return;          

            // datepicker.setDate() is very libral in how it parses nonsensical values into all kinds of crazy dates (i.e. 345 is parsed to a valid date)
            // so instead we use the more conservative Date.parse() to only set the calendar when dates look valid otherwise we leave the date clear
            val = Date.parse(val);                     
            if (val)
                // only date types are passed into setDate() because the datepicker component can not parse 2 digit year date strings
                // thus it will take 2020 as year 20 and show that in the header and year picker  
                window.datepicker.setDate(val);
            else
                window.datepicker.setDate([], { clear: true });

            // this forces the selected date to be in view (today if no selection) and cleans up month/year selection mode if left from before
            window.datepicker.refresh();
        },
        getDate() {
            this.fromGetDate = true;
            this.$emit('input', window.datepicker.getDate('mm/dd/yy') || '');
            Vue.nextTick(() => this.fromGetDate = false);              
        }
    }
});

Use in page:

<v-date-picker v-model="date"></v-date-picker>
Brian-OCarroll commented 2 years ago

This would be a valuable change for my implementation as well

mymth commented 2 years ago

@Brian-OCarroll, What are you actually trying to do with it?

I think most people don’t need it. I confirmed both bootstrap-datepicker and flatpickr don’t support it and I believe other date picker libraries don’t, too. (Seriously, if you know any libraries—not just date pickers—that support this feature, please let me know.) Also, to me, requesting this feature looks not so different from asking Bootstrap’s devs to add the reference to the element that triggered modal's closing to the hide event of the modal component so that you can know which of the close button or the backdrop the user clicked to close the modal.

You need to convince me by explaining why and how it will be helpful for not only you but also many other people.