Archive for the ‘jQuery’ Category

I liked the hack that I used in the bililiteRange utilities demo to highlight the ranges in a textarea: creating a "shadow" preelement and using that to set the background of the desired text. It's based on this trick (explained in a ddforum post) by someone with a screen name of Trinithis.

I put it into a jQuery plugin, $.fn.texthighlight(bounds, id, color) that lets you quickly highlight a section of text in a textarea that will scroll with the text (having the highlighting stay with the text as it's edited is a different story; see the source for the bililiteRange utilities demo for that). It is a hack, and remains flaky (especially for Internet Explorer, for which I cannot get white-space: pre-wrap to work consistently).

Download the code.

See the demo.

Parameters

bounds
{Array [start, end]} | "remove" | "delete". Required. The starting and ending character positions to highlight. "remove" removes the given highlighter, and "delete" removes all of them and cleans up the wrapping element and data.
id
{String}. Optional; if falsy, uses "default". An arbitrary string to identify the highighter for future changing or removing.
color
{String}. Optional; if falsy, uses "#f0f" (a nice, bright magenta:    ). The CSS color to use for the highighting (at 0.25 opacity).

Events

After highlighting, triggers a "texthighlight" event, with parameters [span, bounds] where span is a jQuery collection with one item, the highlighter elemeent, and bounds is the array originally passed into the plugin. So $('textarea').on('texthighlight', function (event, span, bounds) {span[0].scrollIntoView()}) will scroll the highlighted area to the top of the window whenever the highlighter is set.

I've been using Ilya Lebedev's JavaScript VirtualKeyboard for a while, and even created a small plugin to integrate it with jQuery. But I wanted to make it more jQuery-ish, so I made a new version of jsvk, the plugin to wrap Ilya's code.

See a demo that allows dynamic control of keyboard layout and skin (CSS), and a demo that shows multiple inputs, including contenteditable and jQuery UI's draggable.

Download the code.

Usage

$(element).jsvk(opts) to initialize a text-inputing element (either <input>, <textarea> or <div contenteditable>), $(element).jsvk('off') to detach the keyboard, and $(element).jsvk() reattach the keyboard leaving the options the same. Note that the VirtualKeyboard itself is a Singleton, so there can only be one open at a time (attaching to one element removes it from another) and that setting opts changes the appearance for every attached keyboard. You can do $(element).jsvk(opts) on an existing attached keyboard to change the options.

It requires bililiteRange to set the text and selections (the original code does not handle <div contenteditable> and does not trigger input events).

For <div contenteditable>, the enter key just inserts a newline, which is treated as white space and thus does not go to a new line. You could try to listen to the input event and insert a <br>, or do what I do in the demo and set the style to white-space: pre-wrap. This makes all white space significant, which is generally what I want anyway.

Options

dir
{String} URL of the directory (without a trailing slash) where the VirtualKeyboard is found. It expects a fixed structure of the directory, so just install it as is from the download. This is used exactly once per page; until it is set in some call of jsvk all other calls are held and once it's set any future uses of this option is ignored.
kbdiv
{jQuery object | Element | String } The container that the keyboard will be placed into (actually $(kbdiv)[0]). If not set, creates a new element with $('<div>').insertAfter(this).
skin
{String} Name of the CSS directory for styling. The choices are the names of the folders in the css directory in opts.dir; they are listed in the subversion directory and the right of the online demo.
layout
{String} Name of language/layout, like 'IL Hebrew' (see the bottom of the online demo for choices).
langfilter
{Array of String} list of language codes (the two-letter codes; must be uppercase) to allow in the drop-down menu for language choices (see the right of the online demo for choices). Note that this overrides opts.layout; if the selected layout is not in the filter list the first layout that is will be chosen.
nokeyevents
{Boolean} The Virtual Keyboard normally captures keystroke events to translate them into the virtual keys. Set this to true to detach the event capturing (the on-screen keyboard still works with clicking the buttons). This uses a very hacky way of grabbing the event handler, so it may be buggy, though it seems to work for me.

Examples

  • $('#input').jsvk({ dir: '/inc/VirtualKeyboard', skin: 'air_mid' }). Open a VirtualKeyboard with a specific skin but the default layout.
  • $('#input').jsvk('off'). Hide and detach the VirtualKeyboard.
  • $('#input').jsvk(). Show the VirtualKeyboard, with skin and layout unchanged.
  • $('#input').jsvk({layout: 'US US'}). Show the VirtualKeyboard, with skin unchanged but set the language and layout.
  • $('#input').jsvk({langfilter: ['GR', 'BA']}). Show the VirtualKeyboard, but only allow Greek and Bosnian layouts.
  • The Bililite Hebrew Keyboard uses this plugin, including the nokeyevents option.

tl;dr: use $.screenfade(callback) to gently fade the screen to white, have the callback make changes, then fade the screen back in.

Download the code.

See the demo.

I like WordPress's full screen editor mode for distraction-free editing, and I especially liked how gradually it fades the full screen in and out. Making a text box fill the screen width is straightforward as long as you remember to set the box-sizing as follows: textarea { width: 100%; box-sizing: border-box; -moz-box-sizing: border-box;}. There's no way to make the box fill the available screen height, though, with CSS alone and we have to resort to Javascript:

	var defaultHeight = $('#editor').height();
$(window).on('resize', function(){
	var restHeight = $('body').outerHeight(true)-$('#editor').height();
	$('#editor').height(Math.max($(window).height()-restHeight, editorHeight));
});

And then use CSS to remove the distractions:

body.fullscreen head, body.fullscreen footer, body.fullscreen nav {display: none}
body.fullscreen article {width: 100%; margin: 0; background: white}

And use $('body').toggleClass('fullscreen'); $(window).trigger('resize') to switch between full screen and full distractions modes.

But the other thing WordPress does is make the transition gradual, fading out the screen then fading the new mode back in, which I find much more pleasant. I initially used CSS3 transitions, which work wonderfully for the CSS changes but we need to trigger the Javascript as well. There is a transitionend event that we could bind to, but that would mean having the height adjust after the other transitions. I didn't like that.

Then I tried fadeOut of the entire body then fading back in, but jQuery adds a display: none at the end of fading out via opacity, so that ended up being too jerky. $('body').animate{opacity: 0} was smooth enough, but left the body background color in place, which then changed abruptly to the new color. $('html').animate{opacity: 0} worked beautifully, almost.

There's a weird Chrome bug (seems to be http://code.google.com/p/chromium/issues/detail?id=131588 but they claim to have fixed it) where the scroll bars lose their alpha-channel when the opacity is less than 1, so they turn black rather than transparent. Ugly (demo page).

So I decided instead of fading out the real elements, I'd fade in a covering element, make my changes, then fade the cover out.

The plugin is called as $.screenfade(callback, [options]) where callback is a function to be called after the fadeout and before the fadein, and options is a bit of a hack: it is passed both as the CSS of the covering element, and as the options object for the animation. Since the two do not have overlapping parameters, it should work. Thus $.screenfade(callback, {background: black, duration: 2000 }) will fade the screen to black over 2 seconds, execute the callback, then fade back to normal over two seconds.

Turns out jQuery UI version 1.9 (which is listed on their site as the stable one, but is not served as the latest by the Google library API or listed as such on jquery.com; I assume things are still in a bit of flux) incorporates many of my ideas for subclassing widgets. So you could replace the entire section headed "Subclassing Widgets" with the following:

Note that this post throws an error since it loads UI twice, once as the 1.8 (which is what Google serves as the latest stable version) and again to use 1.9. That doesn't affect the code below.

Subclassing Widgets

When you use an existing widget as the base for a new one, it becomes a subclass of the original, with the original methods available from within the new code with the _super method


$.widget('ui.superbox',{
	_init: function(){
		var self = this;
		this.element.click(function(){
			self.move();
		});
	},
	move: function(){
		this.element.css (this._newPoint());
	},
	_newPoint: function(){
		return {top: this._distance(), left: this._distance()};
	},	
	_distance: function(){
		return Math.round (Math.random()*this.options.distance);
	},
	options: {
		distance: 200
	}
});

$.widget ('ui.supererbox', $.ui.superbox, {
	// overriding and new methods
	move: function(){
		this.element.animate(this._newPoint(), this.options.speed);
	},
	home: function(){
		this.element.animate({top:0, left:0}, this.options.speed);
	},
	options: {
		speed: 'normal'
	}
});

We now have a new widget called supererbox that is just like superbox but moves smoothly.

Experiment 2 (Click Me)

Calling Superclass Methods

If we want to use the superclass methods in our method, we use this._super:


$.widget('ui.superboxwithtext', $.ui.supererbox, {
	move: function(){
		this.options.count = this.options.count || 0;
		++this.options.count;
		this.element.text('Move number '+this.options.count);
		this._super(); 
	}
});
Experiment 3 (Click Me)

Note that the builtin subclassing does not include the aspect-oriented code, and does not include the code to automatically call the superclass _init and _create methods. It also adds the _super wrapper to every method, not just those that use _super. So I still like my version better. But this means you don't have to use my $.ui.widget; you can use vanilla jQuery UI to get much of the same benefit.

The bilirubin graph uses jQuery UI and used to use the official theme switching widget, which I just discovered has been broken since September 2012. That's the problem with graceful degradation; when something breaks the page still works so smoothly I don't notice unless I'm paying close attention. Anyway, David Hoff has written a replacement, Super Theme Switcher which works well, but requires an explicit option to tell the plugin where to find the sample images:

$('#themeswitcher').themeswitcher({
	imgpath: '/inc/themeswitcher/images/'
});

The other thing I needed was a way to detect the change in theme. themeswitcher has an onSelect callback, but that is called when the new stylesheet is added to the page, not when it is actually loaded. I previously used a hack with fake images. But that was a terrible hack. Chrome still doesn't trigger load events for stylesheets, so that won't work. But all the modern browsers support CSS transitions, including the transitionend event (webkit requires a prefix).

So to detect a change in stylesheet that affects, say, elements with a class of ui-state-default, do the following:

var div = $('<div class=ui-state-default>').css({
  transition: 'color 0.01s',
  webkitTransition: 'color 0.01s',
  visibility: 'hidden' // make sure the element is invisible
}).appendTo('body').on('transitionend webkitTransitionEnd', function(){
  alert $(this).css('color'); // or whatever
});

Internet Explorer, of course, doesn't support CSS transitions (we'll see about IE 10), but luckily they are the only ones to correctly support stylesheet load events and we can use $('link:last').one('load', function(){ whatever });. So we can detect stylesheet loading in either case, and create a new version of themeswitcher that calls the onSelect callback when the stylesheet is loaded, not when it is added:

// create the test div
var div = $('<div class=ui-state-default>').css({
	transition: 'color 0.01s',
	webkitTransition: 'color 0.01s',
	visibility: 'hidden'
}).appendTo('body');

 // decide if we can handle CSS transitions
$.support.transition =
 document.body.style.transition !== undefined ||
 document.body.style.webkitTransition !== undefined;

$.fn.themeswitcher2 = function(opts){
	if (opts.onSelect && $.support.transition){
		div.on('transitionend webkitTransitionEnd', opts.onSelect);
		delete opts.onSelect;
	}else if (opts.onSelect){
		// this is really browser sniffing; it turns out that the only major browser that doesn't support transitions is IE, and they support
		// the stylesheet onload event
		// We know that themeswitcher adds the last stylesheet, so tie into that load event
		var onSelect = opts.onSelect;
		opts.onSelect = function(){
			$('link:last').one('load', onSelect);
		};
	}
	return this.themeswitcher(opts);
};

And this works in Chrome, Firefox, Opera and IE. See it in action on the bilirubin graph.

I was playing with a minor project and wanted to simulate a command line interface, like the old April Fools xkcd, and realized that their code was far more complicated than what you need if you use jQuery and contenteditable.

My quick jQuery plugin to turn any <div> into a terminal emulator is:

	$.fn.cli = function(handler, prompt, effect){
		if (!prompt) prompt = '&gt;&nbsp;';
		if (!effect) effect = $.fn.text;
		return this.each(function(){
			var self = $(this);
			function newline(){
				self.
				 append('<p class=input><span class=prompt>'+prompt+'</span><span  style=outline:none contenteditable></span>').
				 find('[contenteditable]')[0].focus(); // focus only works on the element, not the jQuery object
			}
			newline();
			self.on('keydown', '[contenteditable]', function(evt){
				if (evt.keyCode == 13){
					this.removeAttribute('contenteditable'); // the old input line should not be editable
					// standards use textContent, IE uses innerText
					effect.call($('<p class=response>').appendTo(self),handler(this.textContent || this.innerText));
					newline();
					return false;
				}
			});
		});
	};

And you use it like:

$('div').cli(function(text){
  return 'You wrote: '+text;
});

See the demo. The font there is Peter Hull's VT323.

handler is a function that is passed the text in the input line and should return the text to output. prompt is the prompt string. effect is the function that lets you do fancy things to the output. It is called with this set to a <p> element that is to contain the text, and is passed one parameter, the text to output. It defaults to jQuery.fn.text, just showing the text.

It's not fancy; any actual processing of the text is up to you. Catching special characters like the up-arrow for history is also up to you. It doesn't have the old-fashioned blinking block cursor, since the insertion point caret is not styleable, and faking it is not worth the effort.

If you want a mind-blowingly cool terminal emulator, see mass:werk's termlib. And check out their cool Space Invaders game on their 404 page.

One of the nice things about my sendkeys plugin is the fact that it's easy to add new "special keys." I have a macro to create a link in Notepad++: it takes the selection, wraps it in <a href=""></a> and places the insertion point between the quotes so I can start typing the link.

I added two things to sendkeys to make that possible: {selection}, which remembers the original selected text and inserts it, and {mark}, which records the location of the insertion point and sets the selection there after the string is inserted. Thus, '<a href="{mark}">{selection}</a>' accomplishes the same thing and I can use it in my home-grown text editors.

<input class=text /> <input type=button class=test value="Try It" />
$('input.test').click(function(){ $('input.text').sendkeys('<a href="{mark}">{selection}</a>'); });

Download the code.

Demo.

Download the WP Audio Player Standalone.

So the rabbi asked me to add the ability to play the audio on the taamim page (basically, a long list of short MP3's) directly on the page, rather than click the link to open a new page. No problem, right? We're living in an HTML5 world, so I should be able to do:

$('a[href$=mp3]').each(function(){
  $('<audio>').attr({src: this.href, controls: 'controls'}).insertBefore(this);
});

And everything ought to work: browsers that can't handle <audio> elements get nothing, modern browsers get a modern audio player. Nice progressive enhancement.

But of course, it's not that easy. Webkit (Chrome, Safari) supports MP3 playing, but Firefox does not (and won't), and Internet Explorer only does for IE9 and up, and I have to support Windows XP and IE8 (source; consistent with my experimentation). I don't like the <embed>ed players, so I'll go with Flash. I like the player that Language Log uses, and viewing the source tells me that's WPAudioplayer, which has a standalone version that requires just two files, the 11-kb swf file and a 12-kb javascript file.

To use it, include the javascript with a <script> element and initialize the player with AudioPlayer.setup('/path/to/player.swf', {width: 100}); where 100 is the desired width of the player in pixels (it's constant for every player on the page and it's a mandatory option). Then, each player is implemented by replacing an existing element, identified by id: AudioPlayer.embed(id, {soundFile: '/path/to/soundfile.mp3'});.

Of course, iOS won't run Flash, so I still need to use the <audio> element there. So I need to detect if the audio element works, and if not, insert the Flash substitute. Browsers that can't handle either get a blank spot.

Putting it together into a plugin:

(function($) {

var uid = 0;
var init = function (swf, width){
	AudioPlayer.setup(swf, {width: width});
	init = $.noop;
}
$.fn.inline_mp3 = function(swf){
  return this.each(function(){
		var id = 'audioplayer_'+(uid++);
		var player = $('<audio>').attr({
			src: this.href,
			controls: 'controls',
			id: id
		}).insertBefore(this);
		// audio.canPlayType test from http://diveintohtml5.com/everything.html#audio-mp3
		if (!(player[0].canPlayType && player[0].canPlayType('audio/mpeg;').replace(/no/, ''))){
			init (swf, player.width());
			AudioPlayer.embed(id, {soundFile: this.href});
		}
	});
};
})(jQuery);

It uses a unique number to assign an id to each element, and lazy-initializes the Flash player. The player should be styled with a given width (since IE8 doesn't have a default <audio> size):

audio {
	width: 80px; 
	display: inline-block;
}

And use it:

$('a[href$=mp3]').inline_mp3('/path/to/player.swf');

And there are lots of other packages of html5/Flash fallback audio players but this is small and easy enough for me to understand.

Brad noted an incredibly dumb bug; evidently I used a single = instead of a double == in an if condition, and never tested for the false case. It is now corrected (and named version 1.4).

I put yesterday's unmask password plugin together with the code to automatically create a checkbox to control it, and removed the dependency on jQuery UI.

Download the code.

Example

<input type="password" id="password"/>
$('#password').showPassword('Show Characters as I Type');

Use

$('[type=password]').showPassword(string) creates a checkbox as above with string as the label. If the checkbox is checked then the password field is unmasked. $('type=password').showPassword(true) unmasks the field directly, and $('type=password').showPassword(false) restores masking.