Archive for December, 2010

Update: Connie Wiolowan of allanguages.info has made some comments and I've updated the post to reflect them. Thanks!

Connie also created a jQuery plugin for the virtual keyboard; see the comment for details.

I like Ilya Lebedev's JavaScript VirtualKeyboard a lot. It's clever, got an elegant UI, allows you to remap the physical keyboard to any language you want, and has an active and responsive support forum. But it's got a few quirks that make it hard to use with jQuery.

  • Virtually no documentation. You have to read the source to get anywhere. But here's a brief introduction (assuming you use jQuery):
    1. Download the package from SourceForge, unzip the tarball and put the whole folder (don't mess up the organization!) somewhere accessible.
    2. Include the script with <script type="text/javascript" src="/path/to/files/vk_loader.js" ></script>.
    3. Have a blank <div> in your HTML that will house the keyboard. I'll use <div id="jsvk" /> in these instructions.
    4. Have one or more <input type="text" >s or <textarea>s that will accept the keyboard input.
    5. In your $(document).load function, include the statement VirtualKeyboard.show($('input')[0], $('#jsvk')[0]).
    6. Other useful functions include VirtualKeyboard.hide() that hides the keyboard and removes it from the DOM, VirtualKeyboard.attachInput($('input')[0]) that leaves the keyboard visible where it was but associates a new input element with it, and VirtualKeyboard.detachInput() that leaves the keyboard visible but removes the association with an input element.
    7. VirtualKeyboard.open is a synonym for VirtualKeyboard.show and VirtualKeyboard.close is a synonym for VirtualKeyboard.hide.
  • The VirtualKeyboard is a Singleton. This means that you can only have one keyboard shown on a page, and the "skin" (the CSS of the keyboard) is fixed at initialization even if you move it around and associate it with different input elements. It also means that if you use the keyboard for different input elements, changing the language for one changes it for all of them. You can get around this by putting the keyboard into its own document and using iframes or popup windows, and the code includes samples for both of those.
  • The package is huge. The "compacted" version is 4 MB. Most of that is the development files, so it's not like every page downloads megabyte files (the largest downloaded file is the keyboard layout package, about 390K), but it's still huge.
  • Part of the reason that it's so big is that it does everything. Lebedev has put an entire Javascript library into this package, including its own event manager that overrides jQuery's. So you can't do $('input').click(function(evt){}) on elements that are attached to the keyboard. You have to use his EM.addEventListener(self.element[0],'click', function(evt){}).
  • Options are too clever by half: he parses the <script src="/path/to/vk_loader.js?query-string> query string to get them. So to set the initial layout and the skin, I used <script type="text/javascript" src="/path/to/vk_loader.js?vk_layout=IL%20Hebrew&vk_skin=flat_gray" ></script>. Options are:
    • vk_skin: name of folder in the /css folder in the package to use for the CSS
    • vk_layout: name of the keyboard layout. The available names are listed on the right hand side of the home page or the project developement page

But these are really minor issues for such a great package, and adapting it to use jQuery isn't that hard (I'm using my textpopup plugin as a base):


<div id="example">
	Example: <br/><input style="float:left"/><input style="float:right"/>
	<br style="clear:both"/>
</div>

var keyboard = false; // VirtualKeyboard is a singleton, so we have to make sure there's just one container
$.ui.textpopup.subclass("ui.jsvk", {
	_createBox: function(){
		if (keyboard) return keyboard;
		var self=this;
		self._super();
		keyboard = self.theBox;
		VirtualKeyboard.open(self.element[0], self.theBox[0]);
		EM.addEventListener(self.element[0],'keypress', function(e){
			if (e.getKeyCode()==27) self.hide();
		});
		return self.theBox;
	},
	show: function(){
		VirtualKeyboard.attachInput(this.element[0]);
		this._super();
	},
	hide: function(){
		VirtualKeyboard.detachInput();
		this._super();
	}
});

$('#example input:eq(0)').jsvk({position: 'bl'});
$('#example input:eq(1)').jsvk({position: 'br'});

Sitemeter works by inserting an image and iframe into your page, so that when the browser retrieves it, sitemeter can look at the referer and know what page was visited. But you can't include image links in an XML or GIF file, so I can't tell which of the bililite webservices are being visited. But behind the scenes, all the files are PHP files, so I can use cURL to get the image file as long as I can spoof the parameters so sitemeter thinks it knows where the request came from. A little experimentation came up with this:

function getSitemeterImage(){
	// the sitemeter image parameters; obtained by inspection
	$opts = array(
		'site' => 's48bililite',
		'refer' => urlencode ($_SERVER['HTTP_REFERER']),
		'pg' => urlencode('http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']),
		'rnd' => rand()/getrandmax()
	);
	$ch = curl_init();
	curl_setopt($ch, CURLOPT_URL, 'http://s48.sitemeter.com/meter.asp?'.http_build_query($opts));
	curl_setopt($ch, CURLOPT_REFERER, $_SERVER['HTTP_REFERER']);
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
	// be nice to the server (http://inanimatt.com/php-curl.php)
	curl_setopt($ch, CURLOPT_USERAGENT, 'Bililite Webservices (http://bililite.com/webservices)');
	curl_exec($ch); // get the image to tell sitemeter that we were here
}

We don't actually use the image; all we want is to let sitemeter know that we want it.

I just started using Sitemeter for tracking page visits (everyone seems to use Google Analytics, but I've used Sitemeter before and am satisfied with what I've got). Sort of depressing seeing a "1" for the visit count, but it's new as of today. Creating a widget to put in the sidebar was straightforward, since I've already created one widget:

/*
Plugin Name: Sitemeter Widget
Plugin URI: http://bililite.nfshost.com/blog/2010/12/26/simple-sitemeter-widget/
Description: Display the sitemeter.com badge in the sidebar
Author: Daniel Wachsstock
Version: 1.0
Author URI: http://bililite.nfshost.com/blog
*/ 

// WordPress 2.8 Widget code based on http://jessealtman.com/2009/06/08/tutorial-wordpress-28-widget-api/
// and http://justcoded.com/article/wordpress-28-multi-widgets/

class Sitemeter_Widget extends WP_Widget{
  function Sitemeter_Widget(){
    $this->WP_Widget ('sitemeter', __('Sitemeter'), array(
     'description' => 'Display the Sitemeter Badge'
    ));
  } // constructor
  function widget ($args, $instance){
    extract ($args, EXTR_SKIP);
    extract ($instance, EXTR_SKIP);
    echo $before_widget.$before_title.$title.$after_title;
		echo "<!-- Site Meter -->
<script type=\"text/javascript\" src=\"http://$server.sitemeter.com/js/counter.js?site=$account\">
</script>
<noscript>
<a href=\"http://$server.sitemeter.com/stats.asp?site=$account\" target=\"_top\">
<img src=\"http://$server.sitemeter.com/meter.asp?site=$account\" alt=\"Site Meter\" border=\"0\"/></a>
</noscript>
<!-- Copyright (c)2009 Site Meter -->";
    echo $after_widget;
  } // widget display code
  function update ($new, $old){
    return array_merge ($old, $new);
  } // options update
  function form ($instance){
    extract ($instance, EXTR_SKIP);
    $this->textElement ('title', 'Title', $title);
    $this->textElement ('server', 'Server', $server);
    $this->textElement ('account', 'Account', $account);
  } // control form
  function textElement ($index, $text, $value){
    $id = $this->get_field_id($index);
    $name = $this->get_field_name ($index);
    $text = __($text); // localize
    echo "<p><label for=\"$id\">$text <input name=\"$name\" class=\"widefat\" value=\"$value\" id=\"$id\" /></label></p>";
  } // textElement
} // class Sitemeter_Widget

add_action ('widgets_init', create_function('', 'return register_widget("Sitemeter_Widget");'));

Short and sweet

I like my virtual keyboard, but I just found this one, which is so much more elegant and general-purpose. But it's huge, and isn't really pop-up (it opens a new window). And I like the idea of learning new techniques. So I'll have to dissect it and steal all the cool parts and make it part of my keyboard. In my copious free time.

My ever-unreliable AT&T 8525 finally had a permanent white screen of death and, rather than be a slave to AT&T forever, decided to get a separate PDA and phone. I bought an iPod Touch and it is a world of difference. Sync'ing it to Microsoft Outlook was automatic (which never quite worked right with Microsoft Windows Mobile!), the software is smooth and slick (everyone knows that), and it even gets daylight saving time right! ePocrates works, and there's lots of medical software out there. The only thing missing is Riley Kidometer, which I was running under a Palm emulator. It gives age-specific parameters for things like height and weight and a ton of other pediatric-relevant information. I'm getting around it with growth charts from statcoder. The only other things I need from that program are peak flows, which I am serving with the bililite webservices, and the EKG parameters. I'll have to look those up and add them to the webservices.

And having an MP3 player is cool, too.

The best part has been Apple customer service. 2 weeks after I bought the thing, I dropped my iPod on the workshop floor. The glass shattered (I know, I should use a case and not keep it in my shirt pocket when leaning over. פראקטיק שולע איז די בעסטע שולע אבער די פרייז איז זייער טייער—Experience is the best teacher but the tuition is astronomical). Made an appointment at the Apple Store, got the lecture that dropping the iPod is not covered by the warranty, and the Apple rep gave me a new iPod anyway. Unbelievable! They definitely have a customer for life now.

And restoring from backup should have been a no-brainer, but the new iPod had out-of-date system software. After plugging the new iPod in, I was offered the choice of restoring from backup. But when I selected it, I got an error message that the iPod software had to be updated first, which meant setting it up as a new iPod (one click), naming it (a few keystrokes), updating (one click), "restoring to factory settings" (one click to let the iPod know I would want to restore from backup), then restore from backup (one more click). Everything, even the placement of the app icons and the web pages I was looking at, was restored. Annoying, but not difficult.