Populated select boxes for date

Not so long ago JavaScript terseness was a must. These days I like readability better.

You’re right, it works. I just got a little side-tracked with the difference between proto and prototype. Thanks for catching this. :slight_smile:

OK, I’ve added a delegated click event for the fieldset. It will check all clicks for the child elements, and it will replace the days list when year or month change.

Basically, a dayResetter goes into the “class” prototype, meaning the MyDate instances, for the onClick event, they will have a reference to this method, rather then every instance holding the logic for the event. This makes the prototype inheritance so cool.

Beside the bindUIActions method, which is the entry point for click events and other possible events, I’ve separate again the code logic, in three getXList methods. After AMDing this, I’m thinking about adding LoDash functional features to the project for these methods. Seems like a natural fit.

date.html and demo.js remain the same.

date.js


function MyDate(id) {
    this.targetElement = document.getElementById(id);
    this.init();
};

MyDate.prototype = {

    MONTHS: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sept','Oct','Nov','Dec'],

    YEAR_OFFSET: 21,

    HELPER_ELEMENTS_HTML_FRAGMENT: '<fieldset class="mydate"><select class="day">{{day}}</select><select class="month">{{month}}</select><select class="year">{{year}}</select></fieldset>',

    init: function () {
        this.toggle();
        this.addHelperElements();
        this.bindUIActions();
    },

    toggle: function () {
        var elemStyle = this.targetElement.style;

        if (elemStyle.display === "none") {
            elemStyle.display = "";
        } else {
            elemStyle.display = "none";
        };
    },

    addHelperElements: function () {
        var today = this.getToday(),
            lists = this.getHelperLists(today),
            fragment = '';

        fragment = this.HELPER_ELEMENTS_HTML_FRAGMENT
            .split('{{year}}').join(lists.years)
            .split('{{month}}').join(lists.months)
            .split('{{day}}').join(lists.days);

        this.targetElement.insertAdjacentHTML('afterend', fragment);
    },

    getToday: function () {
        var today = new Date();

        return {
            day: today.getUTCDate(),
            month: today.getUTCMonth(),
            year: today.getUTCFullYear()
        };
    },

    getHelperLists: function (dayObj) {
        return {
            years: this.getYearsList(dayObj),
            months: this.getMonthsList(dayObj),
            days: this.getDaysList(dayObj)
        };
    },

    getYearsList: function (dayObj) {
        var years = '';

        for(var i = 0; i < this.YEAR_OFFSET; i++){
            var y = dayObj.year + i;
            years = years + '<option value="'
                          + y
                          + '"'
                          + ( (i === dayObj.year) ? "selected" : "" )
                          + '>'
                          +  y
                          + '</option>';
        };

        return years;
    },

    getMonthsList: function (dayObj) {
        var months = '';

        for(var i = 0; i < this.MONTHS.length; i++){
            var m = this.MONTHS[i];
            months = months + '<option value="'
                            + m
                            + '"'
                            + ( (dayObj.month === i) ? "selected" : "" )
                            + '>'
                            +  m
                            + '</option>';
        };

        return months;
    },

    getDaysList: function (dayObj) {
        var days = '';

        for(var i = 0, n = this.getDaysInMonth(dayObj.year, dayObj.month); i < n; i++){
            var d = i + 1;
            days = days + '<option value="'
                        + d
                        + '"'
                        + ( (dayObj.day === i + 1) ? "selected" : "" )
                        + '>'
                        +  d
                        + '</option>';
        };

        return days;
    },

    getDaysInMonth: function (year, month) {
        return new Date(year, month, 0).getDate();
    },

    bindUIActions: function () {
        var dateWidgets = document.getElementsByClassName('mydate');

        for (var i = 0; i < dateWidgets.length; i++) {
            dateWidgets[i].onchange = this.dayResetter;
        };
    },

    dayResetter: function (event) {
        var allSelects,
            yearSelect, monthSelect, daySelect,
            year = '', month = '',
            fragment = '';

        event.preventDefault();

        if (event.target.className !== 'day') {
            allSelects = this.getElementsByTagName('select');

            yearSelect = allSelects[2];
            monthSelect = allSelects[1];
            daySelect = allSelects[0];

            year = yearSelect.options[yearSelect.selectedIndex].text;
            month = monthSelect.selectedIndex+1;

            fragment = '<select class="day">' + MyDate.prototype.getDaysList( {year: year, month: month} ) + '</select>';

            daySelect.innerHTML = '';
            daySelect.insertAdjacentHTML('afterBegin', fragment);
        }
    }
};


Great job!

Do you mean that in my version, each “month select” and each “year select”, in each instance of the date widget had its own copy of the click handler, whereas in your version they all reference the same prototype method?

I like that. It makes it much easier to read.

Also, +1 for no jQuery.

Yes, I’m only putting references in the onevent properties for the elements, you are filling them with actual function code.

$(“.month”) creates a HTMLCollection containing element references, and so does the $(“.year”). It’s a display of functional programming available with jQuery, where it loops over a collection, a list, and automatically performs actions for each item in the collection.

The jQuery .on() method attaches event handlers to the elements. Mine attaches references, yours attaches actual code, duplicated code for every one of them.

You could improve your original code in tow ways, like I did: 1) use a delegation event for the div class=“dateDropdown” parent element to check for select changes and 2) by creating a prototype method. When I call the method, it’s not found directly on the instance, but it gets looked up and it gets found up the prototype chain.

EXAMPLE
The prototype chain mechanism is how you can call [].slice.
[] is an Array instance, but the slice() method is in Array.prototype.
Prototype mechanism goes and looks for the method up the prototype chain for [] and finds it in Array.prototype for every array you create, instead of every array you create having the same duplicated slice() method.

Great example.
That makes a lot of sense.
Thank you.

You’re welcome. :slight_smile:

Here’s the github repo for the AMDed MyDate widget.
https://github.com/itmitica/myDate

For now, it’s still monolithic. I simply added the require library to the mix. This should give you an idea on how to start converting old code to AMD.

In the following steps I’m going to tear the code apart.

Hi myty,

I cloned the repo and had a poke around it.
Interesting stuff!

I haven’t had the occasion to do much with require.js or AMD.
I’ll have a read of the docs in the next couple of days to get up to speed.

Hey Pullo.

I’ve committed another change. I’ve separated the constructor and the prototype from the myDate class file.
https://github.com/itmitica/myDate/commit/6d8e0c158091f33fd1143edc7ffbe64890526284

Next step: breaking up the prototype.

Another commit: https://github.com/itmitica/myDate/commit/f6f6c62e7e402af6a6e6aaef23b6d2aa2bcf68aa

This time, myDate gets its own folder, and inside it, prototype gets its own folder for all its parts.

Next step: lodash integration.

For anyone following this thread:

Unfortunately, myty has become a “non-member”.

Further updates at the GIT repo

how can i use this widget for my site <snip />

Just download the code and include it in your page :slight_smile: