PageSourceSearch

https://sbisyd.com.au/js/sbi-date-dropdown.js?v=1788426372

js sbisyd.com.au collected 2026-09-24 13:32:57 UTC 22,698 bytes, 649 lines download raw bytes

1/**
2 * SBI Equifax - Custom Date Dropdowns UI Component
3 * First-party UI component for rendering three-part date selection fields.
4 * Maintained internally by the development team.
5 * 
6 * Note: Maintained as a core structural component to preserve DOM integrity
7 * for existing validation and form submission workflows.
8 */
9;(function ($, window, document, undefined) {
10
11    'use strict';
12
13    // Create the defaults once
14    var pluginName = 'dateDropdowns',
15        pluginDefaults = {
16            defaultDate: null,
17            defaultDateFormat: 'yyyy-mm-dd',
18            displayFormat: 'dmy',
19            submitFormat: 'yyyy-mm-dd',
20            minAge: 0,
21            maxAge: 120,
22            minYear: null,
23            maxYear: null,
24            submitFieldName: 'date',
25            wrapperClass: 'date-dropdowns',
26            dropdownClass: null,
27            daySuffixes: true,
28            monthSuffixes: true,
29            monthFormat: 'long',
30            required: false,
31            dayLabel: 'Day',
32            monthLabel: 'Month',
33            yearLabel: 'Year',
34            monthLongValues: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
35            monthShortValues: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
36            initialDayMonthYearValues: ['Day', 'Month', 'Year'],
37            daySuffixValues: ['st', 'nd', 'rd', 'th'],
38            yearDescending: true
39        };
40
41    // The actual plugin constructor
42    function Plugin(element, options) {
43        this.element = element;                                 // Element handle
44        this.$element = $(element);                             // jQuery element handle
45        this.config = $.extend({}, pluginDefaults, options);    // Plugin options
46        this.internals = {                                      // Internal variables
47            objectRefs: {}
48        };
49        this.init();
50
51        return this;
52    }
53
54    // Avoid Plugin.prototype conflicts
55    $.extend(Plugin.prototype, {
56
57        /**
58         * Initialise the plugin
59         */
60        init: function () {
61            this.checkForDuplicateElement();
62            this.setInternalVariables();
63            this.setupMarkup();
64            this.buildDropdowns();
65            this.attachDropdowns();
66            this.bindChangeEvent();
67
68            if (this.config.defaultDate) {
69                this.populateDefaultDate();
70            }
71        },
72
73        /**
74         * Check whether an element exists with the same name attribute. If so, throw an error
75         */
76        checkForDuplicateElement: function () {
77            if ($('input[name="' + this.config.submitFieldName + '"]').length) {
78                $.error('Duplicate element found');
79                return false;
80            }
81
82            return true;
83        },
84
85        /**
86         * Set the plugin's internal variables
87         */
88        setInternalVariables: function () {
89            var date = new Date();
90            this.internals.currentDay = date.getDate();
91            this.internals.currentMonth = date.getMonth() + 1;
92            this.internals.currentYear = date.getFullYear();
93        },
94
95        /**
96         * Set the container which will hold the date dropdowns.
97         *
98         * - If the element on which the plugin was called is an input of type text or hidden, set it to
99         *   be hidden and wrap it in a div. This outer div will become the container.
100         * - If the element was an HTML element (e.g. <div/>), create a hidden text field within it, and use
101         *   the div as the container.
102         */
103        setupMarkup: function () {
104            var wrapper, hiddenField;
105
106            if (this.element.tagName.toLowerCase() === 'input') {
107                if (!this.config.defaultDate) {
108                    this.config.defaultDate = this.element.value;
109                }
110
111                // Configure the input element and wrap
112                hiddenField = this.$element.attr('type', 'hidden')
113                    .wrap('<div class="' + this.config.wrapperClass + '"></div>');
114
115                var customFieldNameProvided = (this.config.submitFieldName !== pluginDefaults.submitFieldName),
116                    fieldHasName = this.element.hasAttribute('name');
117
118                // Set the name attribute of the submit input
119                if (!fieldHasName && !customFieldNameProvided) {
120                    this.$element.attr('name', pluginDefaults.submitFieldName);
121                } else if (customFieldNameProvided) {
122                    this.$element.attr('name', this.config.submitFieldName);
123                }
124
125                wrapper = this.$element.parent();
126
127            } else {
128
129                // Build a hidden input and set this.$element as the wrapper
130                hiddenField = $('<input/>', {
131                    type: 'hidden',
132                    name: this.config.submitFieldName
133                });
134
135                this.$element.append(hiddenField).addClass(this.config.wrapperClass);
136
137                wrapper = this.$element;
138            }
139
140            // Store a reference to the wrapper and hidden field elements for later use
141            this.internals.objectRefs.pluginWrapper = wrapper;
142            this.internals.objectRefs.hiddenField = hiddenField;
143
144            return true;
145        },
146
147        /**
148         * Generate the Day, Month and Year dropdowns
149         */
150        buildDropdowns: function () {
151            var $dayDropdown, $monthDropdown, $yearDropdown;
152
153            Plugin.message = {
154                day: this.config.initialDayMonthYearValues[0],
155                month: this.config.initialDayMonthYearValues[1],
156                year: this.config.initialDayMonthYearValues[2]
157            };
158
159            // Build the day dropdown element
160            $dayDropdown = this.buildDayDropdown();
161            this.internals.objectRefs.dayDropdown = $dayDropdown;
162
163            $monthDropdown = this.buildMonthDropdown();
164            this.internals.objectRefs.monthDropdown = $monthDropdown;
165
166            $yearDropdown = this.buildYearDropdown();
167            this.internals.objectRefs.yearDropdown = $yearDropdown;
168
169            return true;
170        },
171
172        /**
173         * Attach the generated dropdowns to the container
174         */
175        attachDropdowns: function () {
176            var $element = this.internals.objectRefs.pluginWrapper,
177                $daySelect = this.internals.objectRefs.dayDropdown,
178                $monthSelect = this.internals.objectRefs.monthDropdown,
179                $yearSelect = this.internals.objectRefs.yearDropdown;
180
181            switch (this.config.displayFormat) {
182                case 'mdy':
183                    $element.append($monthSelect, $daySelect, $yearSelect);
184                    break;
185                case 'ymd':
186                    $element.append($yearSelect, $monthSelect, $daySelect);
187                    break;
188                case 'dmy':
189                default:
190                    $element.append($daySelect, $monthSelect, $yearSelect);
191                    break;
192            }
193
194            return true;
195        },
196
197        /**
198         * Bind the change event to the generated dropdowns
199         */
200        bindChangeEvent: function () {
201            var $daySelect = this.internals.objectRefs.dayDropdown,
202                $monthSelect = this.internals.objectRefs.monthDropdown,
203                $yearSelect = this.internals.objectRefs.yearDropdown,
204                pluginHandle = this,
205                objectRefs = this.internals.objectRefs;
206
207            objectRefs.pluginWrapper.on('change', 'select', function () {
208                var day = $daySelect.val(),
209                    month = $monthSelect.val(),
210                    year = $yearSelect.val(),
211                    invalidDate,
212                    newDate;
213
214                // Find out whether the change has made the date invalid (e.g. 31st Feb)
215                invalidDate = pluginHandle.checkDate(day, month, year);
216
217                // If invalid - add an error class to the day dropdown and return
218                if (invalidDate) {
219                    objectRefs.dayDropdown.addClass('invalid');
220                    return false;
221                }
222
223                if ('00' !== objectRefs.dayDropdown.val()) {
224                    objectRefs.dayDropdown.removeClass('invalid');
225                }
226
227                // Empty the hidden field after each change
228                objectRefs.hiddenField.val('');
229
230                // Only format the submit date if a full date has been selected
231                if (!invalidDate && (day * month * year !== 0)) {
232                    newDate = pluginHandle.formatSubmitDate(day, month, year);
233
234                    objectRefs.hiddenField.val(newDate);
235                }
236
237                objectRefs.hiddenField.change();
238            });
239        },
240
241        /**
242         * Take a provided default date and populate both the hidden field and the
243         * dropdown elements with the relevant formatted values
244         *
245         * @returns {boolean}
246         */
247        populateDefaultDate: function () {
248            var date = this.config.defaultDate,
249                parts = [],
250                day = '',
251                month = '',
252                year = '';
253
254            switch (this.config.defaultDateFormat) {
255                case 'yyyy-mm-dd':
256                default:
257                    parts = date.split('-');
258                    day = parts[2];
259                    month = parts[1];
260                    year = parts[0];
261                    break;
262
263                case 'dd-mm-yyyy':
264                    parts = date.split('-');
265                    day = parts[0];
266                    month = parts[1];
267                    year = parts[2];
268                    break;
269
270                case 'dd/mm/yyyy':
271                    parts = date.split('/');
272                    day = parts[0];
273                    month = parts[1];
274                    year = parts[2];
275                    break;
276
277                case 'mm/dd/yyyy':
278                    parts = date.split('/');
279                    day = parts[1];
280                    month = parts[0];
281                    year = parts[2];
282                    break;
283
284                case 'unix':
285                    parts = new Date();
286                    parts.setTime(date * 1000);
287                    day = parts.getDate() + '';
288                    month = (parts.getMonth() + 1) + '';
289                    year = parts.getFullYear();
290
291                    if (day.length < 2) {
292                        day = '0' + day;
293                    }
294                    if (month.length < 2) {
295                        month = '0' + month;
296                    }
297                    break;
298            }
299
300            // Set the values on the dropdowns
301            this.internals.objectRefs.dayDropdown.val(day);
302            this.internals.objectRefs.monthDropdown.val(month);
303            this.internals.objectRefs.yearDropdown.val(year);
304            this.internals.objectRefs.hiddenField.val(date);
305
306            if (true === this.checkDate(day, month, year)) {
307                this.internals.objectRefs.dayDropdown.addClass('invalid');
308            }
309
310            return true;
311        },
312
313        /**
314         * Build a generic dropdown element
315         *
316         * @param type
317         * @returns {*|HTMLElement}
318         */
319        buildBaseDropdown: function (type) {
320            var classString = type;
321
322            if (this.config.dropdownClass) {
323                classString += ' ' + this.config.dropdownClass;
324            }
325
326            return $('<select></select>', {
327                class: classString,
328                name: this.config.submitFieldName + '_[' + type + ']',
329                required: this.config.required
330            });
331        },
332
333        /**
334         * Build the day dropdown element
335         *
336         * @returns {*|HTMLElement}
337         */
338        buildDayDropdown: function () {
339            var day,
340                dropdown = this.buildBaseDropdown('day'),
341                option = document.createElement('option');
342
343            option.setAttribute('value', '');
344            option.appendChild(document.createTextNode(this.config.dayLabel));
345            dropdown.append(option);
346
347            // Days 1-9
348            for (var i = 1; i < 10; i++) {
349                if (this.config.daySuffixes) {
350                    day = i + this.getSuffix(i);
351                } else {
352                    day = '0' + i;
353                }
354                option = document.createElement('option');
355                option.setAttribute('value', '0' + i);
356                option.appendChild(document.createTextNode(day));
357                dropdown.append(option);
358            }
359
360            // Days 10-31
361            for (var j = 10; j <= 31; j++) {
362                day = j;
363
364                if (this.config.daySuffixes) {
365                    day = j + this.getSuffix(j);
366                }
367                option = document.createElement('option');
368                option.setAttribute('value', j);
369                option.appendChild(document.createTextNode(day));
370                dropdown.append(option);
371            }
372
373            return dropdown;
374        },
375
376        /**
377         * Build the month dropdown element
378         *
379         * @returns {*|HTMLElement}
380         */
381        buildMonthDropdown: function () {
382            var dropdown = this.buildBaseDropdown('month'),
383                option = document.createElement('option');
384
385            option.setAttribute('value', '');
386            option.appendChild(document.createTextNode(this.config.monthLabel));
387            dropdown.append(option);
388
389            // Populate the month values
390            for (var monthNo = 1; monthNo <= 12; monthNo++) {
391
392                var month;
393
394                switch (this.config.monthFormat) {
395                    case 'short':
396                        month = this.config.monthShortValues[monthNo - 1];
397                        break;
398                    case 'long':
399                        month = this.config.monthLongValues[monthNo - 1];
400                        break;
401                    case 'numeric':
402                        month = monthNo;
403
404                        if (this.config.monthSuffixes) {
405                            month += this.getSuffix(monthNo);
406                        }
407                        break;
408                }
409
410                if (monthNo < 10) {
411                    monthNo = '0' + monthNo;
412                }
413
414                option = document.createElement('option');
415                option.setAttribute('value', monthNo);
416                option.appendChild(document.createTextNode(month));
417                dropdown.append(option);
418            }
419
420            return dropdown;
421        },
422
423        /**
424         * Build the year dropdown element.
425         *
426         * By default minYear and maxYear are null, however if provided they take precedence over
427         * the minAge and maxAge values.
428         *
429         * @returns {*|HTMLElement}
430         */
431        buildYearDropdown: function () {
432            var minYear = this.config.minYear,
433                maxYear = this.config.maxYear,
434                dropdown = this.buildBaseDropdown('year'),
435                option = document.createElement('option');
436
437            option.setAttribute('value', '');
438            option.appendChild(document.createTextNode(this.config.yearLabel));
439            dropdown.append(option);
440
441            if (!minYear) {
442                minYear = this.internals.currentYear - (this.config.maxAge + 1);
443            }
444
445            if (!maxYear) {
446                maxYear = this.internals.currentYear - this.config.minAge;
447            }
448
449            if(this.config.yearDescending) {
450                for (var i = maxYear; i >= minYear; i--) {
451                    option = document.createElement('option');
452                    option.setAttribute('value', i);
453                    option.appendChild(document.createTextNode(i));
454                    dropdown.append(option);
455                }
456            } else {
457                for (var i = minYear; i <= maxYear; i++) {
458                    option = document.createElement('option');
459                    option.setAttribute('value', i);
460                    option.appendChild(document.createTextNode(i));
461                    dropdown.append(option);
462                }
463            }
464
465
466            return dropdown;
467        },
468
469        /**
470         * Get the relevant suffix for a day/month number
471         *
472         * @param number
473         * @returns {string}
474         */
475        getSuffix: function (number) {
476            var suffix = '';
477            var st = this.config.daySuffixValues[0];
478            var nd = this.config.daySuffixValues[1];
479            var rd = this.config.daySuffixValues[2];
480            var th = this.config.daySuffixValues[3];
481
482            switch (number % 10) {
483                case 1:
484                    suffix = (number % 100 === 11) ? th : st;
485                    break;
486                case 2:
487                    suffix = (number % 100 === 12) ? th : nd;
488                    break;
489                case 3:
490                    suffix = (number % 100 === 13) ? th : rd;
491                    break;
492                default:
493                    suffix = 'th';
494                    break;
495            }
496
497            return suffix;
498        },
499
500        /**
501         * Check whether the date entered is invalid, e.g. 31st Feb
502         *
503         * @param day
504         * @param month
505         * @param year
506         * @returns {boolean}
507         */
508        checkDate: function (day, month, year) {
509            var invalidDate;
510
511            if (month !== '00') {
512                var numDaysInMonth = (new Date(year, month, 0).getDate()),
513                    selectedDayInt = parseInt(day, 10);
514
515                invalidDate = this.updateDayOptions(numDaysInMonth, selectedDayInt);
516
517                // If the date is invalid, empty the hidden field to prevent invalid submissions
518                if (invalidDate) {
519                    this.internals.objectRefs.hiddenField.val('');
520                }
521            }
522
523            return invalidDate;
524        },
525
526        /**
527         * Update the day options available based on the month now selected
528         *
529         * @param numDaysInMonth
530         * @param selectedDayInt
531         * @returns {boolean}
532         */
533        updateDayOptions: function (numDaysInMonth, selectedDayInt) {
534            var lastDayOption = parseInt(this.internals.objectRefs.dayDropdown.children(':last').val(), 10),
535                newDayOption = '',
536                newDayText = '',
537                invalidDay = false;
538
539            // If the selected month has less days than the Day dropdown currently contains - remove the extra days
540            if (lastDayOption > numDaysInMonth) {
541
542                while (lastDayOption > numDaysInMonth) {
543                    this.internals.objectRefs.dayDropdown.children(':last').remove();
544                    lastDayOption--;
545                }
546
547                // If the user-selected day is removed, indicate this so a message can be shown to the user
548                if (selectedDayInt > numDaysInMonth) {
549                    invalidDay = true;
550                }
551
552                // If the month contains more days than the Day dropdown contains - add the missing options
553            } else if (lastDayOption < numDaysInMonth) {
554
555                while (lastDayOption < numDaysInMonth) {
556
557                    newDayOption = ++lastDayOption;
558                    newDayText = newDayOption;
559
560                    // Add the suffix if required
561                    if (this.config.daySuffixes) {
562                        newDayText += this.getSuffix(lastDayOption);
563                    }
564
565                    // Build the option and append to the dropdown
566                    var option = document.createElement('option');
567                    option.setAttribute('value', newDayOption);
568                    option.appendChild(document.createTextNode(newDayText));
569                    this.internals.objectRefs.dayDropdown.append(option);
570                }
571            }
572
573            return invalidDay;
574        },
575
576        /**
577         * Format the selected date based on the submitFormat config value provided
578         *
579         * @param day
580         * @param month
581         * @param year
582         * @returns {*}
583         */
584        formatSubmitDate: function (day, month, year) {
585            var formattedDate,
586                _date;
587
588            switch (this.config.submitFormat) {
589                case 'unix':
590                    _date = new Date();
591                    _date.setDate(day);
592                    _date.setMonth(month - 1);
593                    _date.setYear(year);
594                    formattedDate = Math.round(_date.getTime() / 1000);
595                    break;
596
597                default:
598                    formattedDate = this.config.submitFormat
599                        .replace('dd', day)
600                        .replace('mm', month)
601                        .replace('yyyy', year);
602                    break;
603            }
604
605            return formattedDate;
606        },
607
608        /**
609         * Revert the changes applied by the plugin on the specified element
610         */
611        destroy: function () {
612            var wrapperClass = this.config.wrapperClass;
613
614            if (this.$element.hasClass(wrapperClass)) {
615                this.$element.empty();
616            } else {
617                var $parent = this.$element.parent(),
618                    $select = $parent.find('select');
619
620                this.$element.unwrap();
621                $select.remove();
622            }
623        }
624    });
625
626    // A really lightweight plugin wrapper around the constructor,
627    // preventing against multiple instantiations
628    $.fn[pluginName] = function (options) {
629        this.each(function () {
630            if (typeof options === 'string') {
631                var args = Array.prototype.slice.call(arguments, 1);
632                var plugin = $.data(this, 'plugin_' + pluginName);
633
634                if (typeof plugin === 'undefined') {
635                    $.error('Please initialize the plugin before calling this method.');
636                    return false;
637                }
638                plugin[options].apply(plugin, args);
639            } else {
640                if (!$.data(this, 'plugin_' + pluginName)) {
641                    $.data(this, 'plugin_' + pluginName, new Plugin(this, options));
642                }
643            }
644        });
645        // chain jQuery functions
646        return this;
647    };
648
649})(jQuery, window, document);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.