Archive for May, 2009

I added keyboard accessibility to flexcal, based on the AOL style guide. Tabbing into the trigger for a textpopup makes it pop. The focus remains on the input element and requires another tab to put the focus on the calendar (a bit awkward to require two tabs but I didn't want to have the input element lose focus every time it gets it), and the escape key (or tabbing out) hides it.

Unfortunately, control-page up/down if used by Firefox to change its tabs and I can't override that, so I use alt-page up/down to skip years. Alt-left/right arrow changes calendars.

Unfortunately, the tabbing in and out doesn't work in Chrome; after tabbing out when the focus returns to the input element it won't leave. I'm not sure what the problem is. And it uses tabindex=0 which isn't supported by Safari at all. I think I will leave it as is and wait for the browsers to catch up to me.

Just checking. No reason it shouldn't work with the jQuery UI themeswitcher. The slot machine animation has a white background, which looks off with some themes. The text color for the dates unfortunately is (by the CSS specificity rules) overridden by the blog's text color, so dark themes are near unreadable.

Slot Machine Transition

Cycle Transitions

Modified 2009-05-19 to use a simpler technique to make it inline.

OK, one more flexcal transition, inspired by Stefan Petre's jQuery slot machine. I should have been balancing my checkbook, but my kids thought this was cooler.


$('#slots').flexcal({
	transition: function(o){ 
		var pane = o.elements.eq(1-o.currSlide), origTable = pane.find('table');
		o.elements.eq(o.currSlide).css({zIndex: 1}).animate({top: -o.$cont.height()}, 'normal', 'easeInBack');
		pane.css({top: o.$cont.height(), zIndex: 2}).show().animate({top: 0}, 'normal', 'easeInBack', function(){
			// make two identical tables without headers and position them correctly (lined up with the bottom)
			var spinners = tableSpinners (origTable, o.gif);
			var tables = origTable.clone().add(origTable.clone()).appendTo(pane).
				find('caption').remove().end().
				css({bottom: 0 , marginBottom: 0, zIndex: 101}).hide();
			function dropOne(){
				if (spinners.length == 0){
					tables.remove();
					return;
				}
				var spinner = spinners.eq(Math.floor(Math.random()*spinners.length));
				var h = spinner.height();
				var left = spinner.offset().left - origTable.offset().left;
				var right = left + spinner.width();
				spinners = spinners.not(spinner.remove());
				origTable.animate({foo: 0}, { // the {foo:0} seems necessary because we need to animate some property, even if it isn't real
					duration: o.duration,
					easing: 'easeOutElastic',
					step: function(now, fx){
						if (fx.state == 0){
							tables.show();
							fx.start = now = 0; 
							fx.end = 3*h; 
						}
						tables.eq(0).css(offsetColumn(right, left, h, now));
						tables.eq(1).css(offsetColumn(right, left, h, now-h));
					},
					complete: dropOne
				});
			}
			setTimeout (dropOne, o.duration);
		})
	}, 
	position: {at: 'top', my: 'top'},
	speed: 0,
	hideOnOutsideClick: false,
	transitionOptions: {
		duration: 1500,
		gif: '/images/ani-bw.gif'
	},
	// make room for the inline calendar
	shown: function() { $('#slots').height($('#slots').flexcal('box').height()); }
});
$('#slots').flexcal('around', 'commit', function(d){
	this.d = d;
	this.box().find('td a').removeClass('ui-state-active');
	this.box().find('td a[rel="'+$.ui.flexcal.date2string(d)+'"]').addClass('ui-state-active');
	this.element.val(this.format(d));
	this._trigger('commit', 0, d);
});
$('#slots').flexcal('show');

function tableSpinners(t, gif){
	var topRow = t.find('tr:first td, tr:first th'), botRow = t.find('tr:last td'), ret = [];
	for(var i = 0; i < topRow.length; ++i){
		var topCell = topRow.eq(i), botCell = botRow.eq(i);
		ret.push($('<img>').appendTo('body').css(topCell.offset()).
			css({
				paddingTop: '2px',
				paddingBottom: '2px',
				position: 'absolute', 
				width: botCell.outerWidth(true), 
				// hack to avoid Firefox table height bug
				height: t.find('tbody').height()+t.find('thead').height(),
				zIndex: 101
			}).
			attr('src', gif)[0]);
	}
	return $(ret);
}

// create a css object with bottom = b and clip set to rect(0 r h l) pixels, with
// the top and bottom of that rect offset by b so the clip rect is fixed on the screen
// and offsetting b if it is out of the range -h..h
function offsetColumn (r, l, h, b){
	b = b%(2*h)-h; // move b into range
	return {bottom: b, clip: ['rect(',b,'px ',r,'px ',b+h,'px ',l,'px)'].join('')};
}

The code also shows how to make the datepicker "inline".

Updated for jQuery UI 1.8 and cycle 2.81, but it doesn't work so well anymore

The alert reader will notice that the parameters for the transition animation for my flexcal widget are the same as that used by Mike Alsup's cycle plugin. My hope was that I could use { transition: $.fn.cycle.next } and be able to use all those cool effects. It turned out to be not so simple; I couldn't jump into the middle of his code and have things work. But I could create a function that set things up correctly and called his transition code:

Continue reading ‘flexcal and cycle’ »