Archive for the ‘Javascript’ Category

I like Andrea Ercolino's chili syntax highlighter. I like it because it works well with jQuery, it allows me to use the HTML5 <code class="language-whatever"> notation for determining language, and I can highlight inline elements, not just pre elements. And Andrea has been very responsive to my questions.

But it has an annoying habit of being too clever about cutting and pasting: selecting text in marked-up pre elements evidently used to cause problems in Firefox, so chili detects the mousedown when you try to select and creates a pop-up box with plain text instead. Unfortunately, it just gets in the way and the text is no longer formatted correctly. And the problem only exists for code with line numbers, which I never use.

So I modified the code in file jquery.chili.js: I turned the function fixTextSelection (line 1097 in my copy) into a no-op by adding a return; at the beginning. Now select and copy/paste work correctly in Firefox. It may be broken in Internet Explorer, but that doesn't bother me: my whole blog theme breaks in Internet Explorer. That's a feature, not a bug :) .

There still are some problems; it loads styles and scripts dynamically and if there's a lot happening on a page the syntax highlighter tends to lose race conditions and nothing happens. I may look at Benjamin Lupton's jQuery syntaxhighlighter or JUSH or make my own (in my copious free time).

As Douglas Crockford has said, javascript is Lisp is C's clothing. That is part of what make javascript fun to program, but I never realized how literally true that statement is until I found this fascinating article by Thomas Lord about GNU and Scheme, that claims:

I've read, since then, that up to around that point Brendan Eich had been working on a Scheme-based extension language for Netscape Navigator. Such was the power of the hegemony of the high level folks at Sun that the word came down on Mr. Eich: "Get it done. And make it look like Java." Staying true to his sense of Self, he quickly knocked out the first implementation of Mocha, later renamed Javascript.

Explains a lot. And shows what an under-appreciated genius Brendan Eich is.

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'});

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.

I had a terrible time getting the positioning for my textpopup to come out right and my solution was hacky and didn't work consistently. It seems that everyone had that problem, and the jQuery UI team solved it (yay!) for version 1.8. Enter the enhanced position. It does all the work for me and allows for much greater flexibility. One downside is that it won't work on invisible (display: none) elements, so I have to hack it with x.css({display: 'block', visibility: 'hidden'}).position(positionOptions).css({display: 'none', visibility: 'visible'}).

The other downside is that the display animation isn't as elegant: my hack involved appending the popup directly after the input box and setting the css so that the correct edge was fixed to the input box. This meant that changing the size of the popup automatically moved it into position, and the animation always appeared to grow out of the input box. Now the position is fixed absolutely, and the standard show always grows from the upper left corner. You have to manually set the animation or reposition the box. A small sacrifice for "just works."

Now, this is cool:


<p contentEditable="true">This is a test (click me)</p>

I was looking for a in-line editor (on the order of jEditable) but it turns out all the modern browsers have it built-in.

However, my sendkeys plugin doesn't work and the position of textpopupup is messed up:


<p id="hebrew" contentEditable="true">This is a test (click me)</p>

$('#hebrew').hebrewKeyboard();

More stuff to work on. Sigh

Update 2010-11-21: new textpopup now is positioned correctly. Now to update the documentation and fix sendkeys.

See the updated version.

I needed a date/time picker and spend a couple of hours making a simple one:

Obviously, I didn't spend a couple of hours putting this together. I wrote it quickly, then spend the hours getting it to work even remotely correctly in Internet Explorer.

Continue reading ‘Don’t Reinvent the Wheel’ »

Updated 2012-12-26 with a much better algorithm for detecting stylesheet loading.

So let's say I want to create elements that match a jQuery UI theme (in my case, I was using a <canvas> element and wanted to copy colors). I could try to parse the CSS file directly, but that runs afoul of the same-origin security problem (I use Google's CDN to get the jQuery UI stylesheets, as in http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.0/themes/smoothness/jquery-ui.css). I could go through the document stylesheets to find the relevant styles, but there's an easier way; just create an element with the desired classes and query that:

var background = $('<div class="ui-state-default">').css('background-color');
Continue reading ‘Copying jQuery UI styles and Detecting CSS Loading’ »

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.

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]));