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’ »

Right now UI widgets need to explicitly declare "getter" functions (methods that return information about the widget rather than manipulate it) with $.ui.widget.getter = 'gettermethod1 gettermethod2'. The reason is that most jQuery plugins are chainable: $(selector).widget('method').css('color','blue') executes method on each element matched, then css('color','blue') on each element. Some plugins and some uses of plugins instead return information about the first element selected, so they cannot be chained, like $(selector).css('color'). For normal methods, the widget code creates a plugin that automatically executes the method named on each element matched, then returns the jQuery object to allow chaining. It uses the getter list to determine which methods should pass their return value from the plugin instead, and only operate on the first element matched.

This is inelegant, and makes my subclassing code less useful, since the getter list cannot be extended easily, only copied or replaced.

This is due to be improved in jQuery 1.7.2 (or at least it's in the latest nightlies): any method that returns this is a "regular" method that, when used as a plugin, should be chainable. Any method that returns anything else is a "getter" that returns that value when used as a plugin.

Thus, in the widget tutorial, we no longer need the line $.ui.green4.getter = "getLevel"; and getLevel remains unchanged, but setLevel needs to be changed to:

    setLevel: function (x) {
        var greenlevels = this._getData('greenlevels'); 
        var level = Math.floor(Math.min(greenlevels.length-1, Math.max(0,x))); 
        this._setData('level', level); 
        this.element.css({background: greenlevels[level]}); 
        return this; // note new return value
    }, 

I think this will be an improvement, even if it means rewriting all my widgets. I would rather have used a return value of "undefined" as the flag for a chainable method, since that would not require rewriting regular methods, and getters that intentionally return undefined should be few and far between. But no one asked me.

Updated 2015-02-25 to version 3.0, with the new subclassing code.

Updated 2015-02-16 to document changes in default URL and need for separate CSS file.

Updated 2015-02-13 to document additional parameters.

Updated 2015-01-28 to link to github.

Just what the world needs—another date picker

Download the code from github. You need jquery.ui.subclass.js, jquery.textpopup.js, /inc/jquery.flexcal.js and flexcal.css.

Continue reading ‘New jQuery Widget: flexcal’ »

The Firefox caption bug is still present in version 37, in 2015.

<table> elements are among the most frustrating to use if you are trying to style them with Javascript, because their sizes are so unpredictable and poorly controlled with CSS. I was trying to set the size of a table (a calendar, if you must know) by setting the size of each <td> and hoped the surrounding <div> would expand to fit (the way a div containing any other, fixed size, element would) but the table was shrunk to the smallest possible size. Compare:


<div style="width: 150px; overflow: visible; background:#fedcba;border: 5px solid purple">This is 150px wide</div>
<div style="width: 150px; overflow: visible; border: 5px solid purple">
	<table border="1">
		<caption>A simple table</caption>
		<tbody>
			<tr><td style="width: 200px">1</td><td style="width: 200px">2</td></tr>
		</tbody>
	</table>
</div>
<div style="width: 150px; overflow: visible; border: 5px solid purple">
	<div style="width: 400px; background: #fedcba">a simple div 400px wide</div>
</div>

Notice how the lower <div> has width: 400px and actually is 400px wide; the surrounding <div> is 150px wide but has overflow: visible so you can see the bigger <div> extending past it. The <table>, on the other hand, even though it has two <td>s that are supposed to be 200px wide, is shrunk to fit the enclosing <div>. Not good.

The only solution I found was to calculate the width that the <table> should have had, and set the width of the enclosing <div> to that value. But how to find the "natural" width of the table? We have to set the size of all the enclosing elements to something huge so the table will not be shrunk, get the width of the <table>, then set everything back. And, to make things harder, the table has to be visible in order to get its width; all the parameters like offsetWidth and clientWidth are set to zero if the table is not visible. And that includes the case where any of the parent elements have display: none, so jQuery's innerWidth hack (set the CSS of the element itself to {display: 'block'; visible: 'hidden'}, get the offsetWidth, then switch the CSS back) won't work; it only affects the visibility of the element itself.

Making the <table> itself have position: absolute temporarily solves the shrinking problem but not the visibility problem.

The key was to use jQuery's undocumented $.swap function. It is what jQuery itself uses to calculate sizes of hidden elements. It takes an element, a set of CSS properties and a callback, and temporarily sets the element to have those CSS properies, calls the callback, then restores the CSS. Now all I have to do is use that recursively:


function truewidth (e, parent){
	if (arguments.length == 1) parent = e.parentNode;
	var ret = 0;
	// we assume the table is in the document, so we eventually reach <body>, and we assume the body is visible
	if (!parent || parent.nodeName.toLowerCase() == 'body') return e.offsetWidth;
	$.swap (
		parent,
		{ position: 'absolute', width: '9999px' , display: 'block' },
		function() { ret = truewidth(e, parent.parentNode) }
	);
	return ret;
}

And I can use it:


<div style="width: 150px; overflow: visible; background:#fedcba;border: 5px solid purple">This is 150px wide</div>
<div id="widthexample" style="width: 150px; overflow: visible; border: 5px solid purple">
	<table border="1">
		<caption>A simple table</caption>
		<tbody>
			<tr><td style="width: 200px">1</td><td style="width: 200px">2</td></tr>
		</tbody>
	</table>
</div>
<div style="width: 150px; overflow: visible; border: 5px solid purple">
	<div style="width: 400px; background: #fedcba">a simple div 400px wide</div>
</div>

$('#widthexample').width(truewidth($('#widthexample table')[0]));

Table height

One more problem with tables: I wanted to use a <caption> for the calendar header (the month/year part) and wanted it to be animated when it changed, so I needed to know the height of the table so I could set the size of the containing element. Easy, no? Just $('table').height(). Unfortunately, I discovered a heretofore undocumented bug in Firefox: it does not include the caption in the height. IE, Opera, Safari and Chrome all do.

The easiest way I've found to correct for this without browser sniffing is to remove the caption, calculate the height, then put the caption back:


function trueheight(e){
	for (var child = e.firstChild; child; child = child.nextSibling){
		if (child.nodeName.toLowerCase() == 'caption'){
			e.removeChild(child);
			var h = e.offsetHeight;
			e.insertBefore(child, e.firstChild);
			return h + child.offsetHeight;
		}
	}
	return e.offsetHeight;
}

Note that if the table is hidden, you may need to use the $.swap technique above. Note also that it uses offsetHeight, which does not include the margin. I can't offhand imagine using margins on a caption, but if you do, you will have to add it back (doing $(child).outerHeight(true) is reasonable).

Demonstrating:


<div style="height: 5em">
	<div class="heightexample" style="border: 1px solid purple">
		<table border="1" style="position: absolute">
			<caption>A simple table</caption>
			<tbody>
				<tr>
					<td>the surrounding div does not</td>
					<td>correct for caption size</td>
				</tr>
			</tbody>
		</table>
	</div>
</div>
<div style="height: 5em">
	<div class="heightexample" style="border: 1px solid purple">
		<table border="1" style="position: absolute">
			<caption>A simple table</caption>
			<tbody>
				<tr>
					<td>the surrounding div corrects</td>
					<td>for caption size</td>
				</tr>
			</tbody>
		</table>
	</div>
</div>

$('.heightexample:eq(0)').height($('.heightexample:eq(0) table').height());
$('.heightexample:eq(1)').height(trueheight($('.heightexample:eq(1) table')[0]));

Updated 2011-02-23 to allow for destroying the widget correctly and the new Google Custom Search API

I made a jQuery UI widget (a subclass of textpopup) that hijaxes a Google search form to show the results in a popup box rather than on a new page. Google AJAX search returns only the top four results, so the popup box includes a link to the full search page.

Download the code.

Download the textpopup code and the widget subclassing code.

Continue reading ‘New Widgets: googleSearch and ajaxpopup’ »

I was looking at some code that included converting arabic to roman numerals, and it was all things like this:

Continue reading ‘Roman Numerals in Javascript’ »
Finally, jQuery UI 1.6 final is out, renamed 1.7, and it's on google ajax libraries, so it's minimized (45K for the whole thing; .27 sec to download for me, which is nothing, especially if you're loading at the end of your code so the user is busy reading the content of your site). The contributors list doesn't include me ($.widget uses $.metadata); so much for my fame and fortune. This release still has the droppable bug; looks like that's to be fixed in 1.7.1.

I've got a cheap website that doesn't let me use cron to schedule tasks (like database backups), so I had to do it myself. I found pseudo-cron, which looks cool but has some bugs and was more complicated than I wanted, so I wrote a simple PHP script to do what I wanted.

Continue reading ‘Scheduling tasks with PHP’ »

I use an AT&T 8525 smartphone (it's the HTC Hermes TyTN) that a friend recommended (some friend!). I've been pulling my hair out with its daylight savings bug—events have the correct time in Outlook on the computer, but are an hour off or worse on the phone. Outlook stores events in UTC and displays them in the local time zone. Events that take place during daylight savings time occur one hour earlier UTC, but Outlook knows that. Windows Mobile 6 doesn't. Why would a Microsoft programmer creating a calendar program care about daylight savings time? It only affects half the year! So all appointments during DST are listed on the phone as one hour earlier. Worse, all-day events are stored as midnight-midnight, with an extra flag marking them as all day long. The bug shifts the event to 11pm the day before, still marked as an all day event, so Easter Sunday falls on Saturday on my phone!

Solution: Follow the instructions at Microsoft's website: www.microsoft.com/windowsmobile/en-us/downloads/microsoft/daylight-savings-update.mspx. The phone resets itself without warning; that's OK. The times are still messed up. Open Outlook and make sure your contacts and calendar are correct. Now open Active Sync and select Delete Mobile Device from the File menu. This means that everything on the phone that syncs (Calendar, Contacts, etc.) will be erased and recopied from the computer (Microsoft neglected to provide a way to just sync with "computer overwrites handheld" for just the calendar, the way Palm does). Connect the phone. A "Connected" message appears in Active Sync, then the whole program disappears. Disconnect the phone and reconnect. A "Connected" message appears in Active Sync, then the setup wizard starts, and you can sync as usual. Pray that all your critical appointments will be copied correctly (this took 2 hours with my data). The phone calendar now has the correct days for all-day events, but the times are still off. Changing the date to a day in DST (under Settings/System tab/Clock and Alarms), however, corrects the times, both during DST and regular time. Changing the date to the correct date does not lose the correction. Restarting the phone makes it forget the correction, so you have to do the change date hack again.

Liberally mutter or curse while performing this arcane art.

Bottom Line solution:

  1. Don't buy Microsoft products
  2. If you must buy Microsoft products, don't depend on them
  3. If you must depend on them, keep multiple backups and count the days until you can justify buying an iPhone