From ec0d679e648d8ff949b348d511c91ce727e0ed2f Mon Sep 17 00:00:00 2001 From: Felix Nagel Date: Sat, 24 Jan 2015 02:23:24 +0100 Subject: [PATCH 1/4] Calendar: Remove selected property from $.date --- external/date.js | 9 --------- ui/calendar.js | 13 +++++++++---- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/external/date.js b/external/date.js index 8df3f63de92..fd473fbbf18 100644 --- a/external/date.js +++ b/external/date.js @@ -32,7 +32,6 @@ $.date = function( date, globalFormat ) { this.dateObject = this.dateObject || new Date(); this.globalFormat = globalFormat; - this.selected = null; }; $.date.prototype = { @@ -153,7 +152,6 @@ $.date.prototype = { lead: printDate.getMonth() != date.getMonth(), date: printDate.getDate(), timestamp: printDate.getTime(), - current: this.selected && this.selected.equal( printDate ), today: today.equal( printDate ) }; day.render = day.selectable = !day.lead; @@ -180,13 +178,6 @@ $.date.prototype = { result[ result.length - 1 ].last = true; return result; }, - select: function() { - this.selected = this.clone(); - return this; - }, - selectedDate: function() { - return this.selected.date(); - }, clone: function() { var date = this.dateObject; return new $.date( new Date( date.getFullYear(), date.getMonth(), diff --git a/ui/calendar.js b/ui/calendar.js index ced58bc528d..c1ce10b6bdb 100644 --- a/ui/calendar.js +++ b/ui/calendar.js @@ -61,8 +61,9 @@ return $.widget( "ui.calendar", { this.labels = Globalize.translate( "datepicker" ); this.buttonClickContext = this.element[ 0 ]; - this.date = $.date( this.options.value, this.options.dateFormat ).select(); + this.date = $.date( this.options.value, this.options.dateFormat ); this.date.eachDay = this.options.eachDay; + this.options.value = this.date.date(); this._on( this.element, { "click .ui-calendar-prev": function( event ) { @@ -332,7 +333,7 @@ return $.widget( "ui.calendar", { var content = "", attributes = [ "role='gridcell'", - "aria-selected='" + ( day.current ? true : false ) + "'" + "aria-selected='" + ( this._isCurrent( day ) ? true : false ) + "'" ], selectable = ( day.selectable && this._isValid( new Date( day.timestamp ) ) ); @@ -357,7 +358,7 @@ return $.widget( "ui.calendar", { if ( day === this.date && selectable ) { classes.push( "ui-state-focus" ); } - if ( day.current ) { + if ( this._isCurrent( day ) ) { classes.push( "ui-state-active" ); } if ( day.today ) { @@ -383,6 +384,10 @@ return $.widget( "ui.calendar", { return content; }, + _isCurrent: function( day ) { + return day.timestamp === this.options.value.getTime(); + }, + _createButtonPane: function() { this.buttonPane = $( "
" ) .addClass( "ui-calendar-buttonpane ui-widget-content ui-helper-clearfix" ); @@ -558,7 +563,7 @@ return $.widget( "ui.calendar", { _setOption: function( key, value ) { if ( key === "value" ) { if ( this._isValid( value ) ) { - this.date.setTime( value.getTime() ).select(); + this.date.setTime( value.getTime() ); this._super( key, value ); } return; From 4c2229fc51dbb70e1dcb96afdaef92d7c41a44eb Mon Sep 17 00:00:00 2001 From: Felix Nagel Date: Sat, 24 Jan 2015 02:23:24 +0100 Subject: [PATCH 2/4] Calendar: Fix month jumping WIP Fixes multiple-month demo and other-month demo. --- external/date.js | 2 ++ ui/calendar.js | 79 +++++++++++++++++++++++++++--------------------- 2 files changed, 46 insertions(+), 35 deletions(-) diff --git a/external/date.js b/external/date.js index fd473fbbf18..7ddf4c53a56 100644 --- a/external/date.js +++ b/external/date.js @@ -151,6 +151,8 @@ $.date.prototype = { var day = week.days[ week.days.length ] = { lead: printDate.getMonth() != date.getMonth(), date: printDate.getDate(), + month: printDate.getMonth(), + year: printDate.getFullYear(), timestamp: printDate.getTime(), today: today.equal( printDate ) }; diff --git a/ui/calendar.js b/ui/calendar.js index c1ce10b6bdb..05d4c229b80 100644 --- a/ui/calendar.js +++ b/ui/calendar.js @@ -62,26 +62,26 @@ return $.widget( "ui.calendar", { this.buttonClickContext = this.element[ 0 ]; this.date = $.date( this.options.value, this.options.dateFormat ); - this.date.eachDay = this.options.eachDay; - this.options.value = this.date.date(); + this.viewDate = this.date.clone(); + this.viewDate.eachDay = this.options.eachDay; this._on( this.element, { "click .ui-calendar-prev": function( event ) { event.preventDefault(); this.date.adjust( "M", -this.options.numberOfMonths ); - this.refresh(); + this._refresh(); }, "click .ui-calendar-next": function( event ) { event.preventDefault(); this.date.adjust( "M", this.options.numberOfMonths ); - this.refresh(); + this._refresh(); }, "mousedown .ui-calendar-calendar button": function( event ) { event.preventDefault(); - // TODO Exclude clicks on lead days or handle them correctly - // TODO Store/read more then just date, also required for multi month picker - this._select( event, $( event.currentTarget ).data( "timestamp" ) ); + this._setOption( "value", new Date( $( event.currentTarget ).data( "timestamp" ) ) ); + this.refresh(); + this._trigger( "select", event ); this.grid.focus(); }, "mouseenter .ui-calendar-header button": "_hover", @@ -99,10 +99,6 @@ return $.widget( "ui.calendar", { }, _handleKeydown: function( event ) { - var oldMonth = this.date.month(), - oldYear = this.date.year(); - - // TODO: Handle for pickers with multiple months switch ( event.keyCode ) { case $.ui.keyCode.ENTER: this.activeDescendant.mousedown(); @@ -136,16 +132,26 @@ return $.widget( "ui.calendar", { return; } - if ( this.date.month() !== oldMonth || this.date.year() !== oldYear ) { - this.refresh(); + if ( this._needsRefresh() ) { + this._refresh(); this.grid.focus(); } this._setActiveDescendant(); }, + _needsRefresh: function() { + if ( this.date.month() !== this.viewDate.month() || this.date.year() !== this.viewDate.year() ) { + return !this.grid.find( + this._sanitizeSelector( "#" + this._getDayId( this.date ) ) + ).length; + } + + return false; + }, + _setActiveDescendant: function() { - var id = this.id + "-" + this.date.day(); + var id = this._getDayId( this.date ); this.grid .attr( "aria-activedescendant", id ) @@ -184,14 +190,14 @@ return $.widget( "ui.calendar", { _buildMultiplePicker: function() { var headerClass, html = "", - currentDate = this.date, - months = this.date.months( this.options.numberOfMonths - 1 ), + currentDate = this.viewDate, + months = this.viewDate.months( this.options.numberOfMonths - 1 ), i = 0; for ( ; i < months.length; i++ ) { // TODO: Shouldn't we pass date as a parameter to build* fns instead of setting this.date? - this.date = months[ i ]; + this.viewDate = months[ i ]; headerClass = "ui-calendar-header ui-widget-header ui-helper-clearfix"; if ( months[ i ].first ) { headerClass += " ui-corner-left"; @@ -212,7 +218,7 @@ return $.widget( "ui.calendar", { html += "
"; - this.date = currentDate; + this.viewDate = currentDate; return html; }, @@ -260,17 +266,17 @@ return $.widget( "ui.calendar", { _buildTitle: function() { return "" + - this.date.monthName() + + this.viewDate.monthName() + " " + "" + - this.date.year() + + this.viewDate.year() + ""; }, _buildGrid: function() { return "" + + "aria-activedescendant='" + this._getDayId( this.date ) + "'>" + this._buildGridHeading() + this._buildGridBody() + "
"; @@ -279,7 +285,7 @@ return $.widget( "ui.calendar", { _buildGridHeading: function() { var cells = "", i = 0, - weekDayLength = this.date.weekdays().length; + weekDayLength = this.viewDate.weekdays().length; if ( this.options.showWeek ) { cells += "" + this._getTranslation( "weekHeader" ) + ""; @@ -304,7 +310,7 @@ return $.widget( "ui.calendar", { _buildGridBody: function() { // this.date.days() needs caching as it has O(n^2) complexity. - var days = this.date.days(), + var days = this.viewDate.days(), i = 0, rows = ""; @@ -338,7 +344,7 @@ return $.widget( "ui.calendar", { selectable = ( day.selectable && this._isValid( new Date( day.timestamp ) ) ); if ( day.render ) { - attributes.push( "id='" + this.id + "-" + day.date + "'" ); + attributes.push( "id='" + this.id + "-" + day.year + "-" + day.month + "-" + day.date + "'" ); if ( !selectable ) { attributes.push( "aria-disabled='true'" ); @@ -351,6 +357,10 @@ return $.widget( "ui.calendar", { return "" + content + ""; }, + _getDayId: function( date ) { + return this.id + "-" + date.year() + "-" + date.month() + "-" + date.day(); + }, + _buildDayElement: function( day, selectable ) { var attributes, content, classes = [ "ui-state-default" ]; @@ -364,7 +374,6 @@ return $.widget( "ui.calendar", { if ( day.today ) { classes.push( "ui-state-highlight" ); } - // TODO Explain and document this if ( day.extraClasses ) { classes.push( day.extraClasses.split( " " ) ); } @@ -385,7 +394,7 @@ return $.widget( "ui.calendar", { }, _isCurrent: function( day ) { - return day.timestamp === this.options.value.getTime(); + return this.options.value && day.timestamp === this.options.value.getTime(); }, _createButtonPane: function() { @@ -439,6 +448,11 @@ return $.widget( "ui.calendar", { this.buttonPane.appendTo( this.element ); }, + _refresh: function() { + this.viewDate.setTime( this.date.date().getTime() ); + this.refresh(); + }, + // Refreshing the entire calendar during interaction confuses screen readers, specifically // because the grid heading is marked up as a live region and will often not update if it's // destroyed and recreated instead of just having its text change. Additionally, interacting @@ -469,9 +483,9 @@ return $.widget( "ui.calendar", { for ( ; i < this.options.numberOfMonths; i++ ) { this.element.find( ".ui-calendar-title" ).eq( i ).html( this._buildTitle() ); this.element.find( ".ui-calendar-calendar" ).eq( i ).html( this._buildGrid() ); - this.date.adjust( "M", 1 ); + this.viewDate.adjust( "M", 1 ); } - this.date.adjust( "M", -this.options.numberOfMonths ); + this.viewDate.adjust( "M", -this.options.numberOfMonths ); // TODO: This assumes focus is on the first grid. For multi pickers, the widget needs // to maintain the currently focused grid and base queries like this off of it. @@ -493,11 +507,6 @@ return $.widget( "ui.calendar", { }); }, - _select: function( event, time ) { - this.valueAsDate( new Date( time ) ); - this._trigger( "select", event ); - }, - value: function( value ) { if ( arguments.length ) { this.valueAsDate( Globalize.parseDate( value, this.options.dateFormat ) ); @@ -556,7 +565,7 @@ return $.widget( "ui.calendar", { }); if ( refresh ) { - this.refresh(); + this._refresh(); } }, @@ -589,7 +598,7 @@ return $.widget( "ui.calendar", { } if ( key === "eachDay" ) { - this.date.eachDay = value; + this.viewDate.eachDay = value; } if ( key === "dateFormat" ) { From d9ee3a1ec4e03a46de86adf4db6ce8cd95d88247 Mon Sep 17 00:00:00 2001 From: Felix Nagel Date: Tue, 24 Mar 2015 20:06:32 +0100 Subject: [PATCH 3/4] Calendar tests: Fix dateFormat option test Do not use value option to pass in a string but value method. --- tests/unit/calendar/calendar_options.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/unit/calendar/calendar_options.js b/tests/unit/calendar/calendar_options.js index f1ec947550f..f8e942e037c 100644 --- a/tests/unit/calendar/calendar_options.js +++ b/tests/unit/calendar/calendar_options.js @@ -109,12 +109,11 @@ test( "buttons - advanced", function() { test( "dateFormat", function() { expect( 2 ); - var element = $( "#calendar" ).calendar({ - value: "1/1/14" - }), - firstDayLink = element.calendar( "widget" ).find( "td[id]:first button" ); + var element = $( "#calendar" ).calendar(); - firstDayLink.trigger( "mousedown" ); + element.calendar( "value", "1/1/14" ); + + element.calendar( "widget" ).find( "td[id]:first button" ).trigger( "mousedown" ); equal( element.calendar( "value" ), "1/1/14", "default formatting" ); element.calendar( "option", "dateFormat", { date: "full" } ); From a3df6dd8a8e363a276392acd7744f7db1e75eb61 Mon Sep 17 00:00:00 2001 From: Felix Nagel Date: Sat, 11 Apr 2015 00:04:33 +0200 Subject: [PATCH 4/4] Calendar: Add comment in _needsRefresh method --- ui/calendar.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ui/calendar.js b/ui/calendar.js index 05d4c229b80..40884c28315 100644 --- a/ui/calendar.js +++ b/ui/calendar.js @@ -142,6 +142,9 @@ return $.widget( "ui.calendar", { _needsRefresh: function() { if ( this.date.month() !== this.viewDate.month() || this.date.year() !== this.viewDate.year() ) { + + // Check if the needed day is already present in our grid due + // to eachDay option changes (eg. other-months demo) return !this.grid.find( this._sanitizeSelector( "#" + this._getDayId( this.date ) ) ).length;