Tag Archives: javacript

Facebook-Style Expanding Textboxes With jQuery

Characters I was recently asked to create a textbox that would vertically expand depending on how much was written in it. This style of text box can be seen in the Facebook news feed for writing comments under peoples feed items. When I receive this type of request, I usually begin by researching what existing systems and plug-ins are out there, investigate the different approaches people take and then write my own based on which way I think best.

Firstly, a textbox (input type=text) is the wrong element to be using. It cannot be any larger than a single line, so a text area element needs to be used. As a text area element will not automatically expand by itself, the general approach to this problem is to use a hidden ‘staging’ element placed way off the viewable screen. The text entered into the text area is copied across to the staging element, which expands as needed and the resulting height used to resize the text area. It is not a difficult thing to achieve with jQuery and the basic structure of the plug-in can be made in relatively few lines of code.

There are a few things to watch out for however. Firstly, because we are taking the contents of a text area and placing it in a div, we need to watch out for special characters that need encoding before they will render correctly. Primarily the newline character but we also have to watch out for the other special characters that can cause issues. Nothing that cannot be fixed with regular expressions. The other thing that we need to be careful of is the CSS styles that have been applied to the text area. We require that the hidden element perfectly mimic the text area and that the text wraps at the same point. If it does not, we will incorrectly predict when the text area needs to grow. The font size and family, line height and padding need to be copied.

This would normally be the point that I post the code, but after I started writing the plug-in, I stumbled across an existing one that I could just not improve upon and decided to use it in it’s entirety. Its by a guy called Jason Frame and his plug-in can be found on github. For my particular implementation, I added the facility to have watermark text in it by utilising the great plugin over on digital bush and I also added some text parsing requirements that were required for the particular project.

Now for a word of warning. There are some problems with this approach, which I have not managed to solve (and incidentally, nor have Facebook). The text area element renders it’s content slightly differently in every browser. In the hidden element, the text will sit up directly against the edge of the div but in a text area it may not. I suspect this gap also varies from operating system to operating system but as far as I can tell, you cannot eliminate it with styles. The consequence of this is that predicting precisely when the text area needs resizing becomes very difficult. The only thing to do is to accommodate this margin of error. This can be done by making the text area display slightly taller than a single line and to always have an extra line available. That way, if things go slightly off, it is still usable. You can see this problem if you put a significant amount of dummy text into the text area and watch when it expands as you type. Instead of expanding when you reach a new line it happens at a different place. If any one manages to figure out how to completely eliminate this problem I am eager to hear how.

The Importance of a Good jQuery Selector

As a technical lead and working in a company that has had rapid expansion in the last six months or so, I have had the job of being a mentor to some of the new employees. I don’t think any of them had used jQuery before and so for every person that arrived, I needed to give a crash course on the subject. The jQuery selector is always one of the first things I describe and it is usually met with nods of understanding, as it is not a difficult concept to grasp when most developers are familiar with css and the DOM.

There have been two incidents recently however, that have made me realise that my crash course on jQuery selectors has been missing something vital. The first was when I found some time to explore the options of profiling some of our javascript code. When I first started this, the only option I could see was John Resig’s deep profiling script that injected the statistics at the bottom of the page. This had some limitations however, and I was very happy to open my news feeds soon after and find that he had added a couple of extra methods to FireUnit that enabled you to profile javascript function calls.

What I noticed straight away was that there were a lot of seemingly simple calls in our code that took a long time to execute. The reason for this was that the selectors were vague:

    $('.myclass').somefunction();

In order to find all the matches, even if there is only one, jQuery needs to traverse the entire DOM looking for any element that might have the matching class, and that takes time. By narrowing down the selector using, for instance, the type, jQuery can very quickly eliminate all other types. This speeds things up a lot.

The second incident was due to another ‘benefit’ of being a tech lead. I tend to get passed the most peculiar and hard to diagnose bugs. The bug in question was on a page that contained two autocomplete text boxes, one of which was part of a compound control. The bug manifested itself by not being able to select an item from the second autocomplete text box properly. I was handed the bug with the information that it seemed to happen only when you selected from the first autocomplete control first. Needless to say, there was a lot of poking around in the code of both the autocomplete and the compound control, which is particularly complex given the nature of the control.

Thanks to firebug’s great console logging, I discovered that the variable that contained all the autocomplete list items seemed to be a combination of both controls. It was not too long before i discovered the code that caused the problem:

    listitems = $('.auto').children('li');

There was nothing mysterious going on at all, jQuery was asked to select all of list items in both controls, so it did.

What had happened in both of these incidents is that I had failed to mention the importance of an accurate selector in jQuery during my crash course. The new developers were blissfully unaware of the consequences of not specifying exactly which elements you want jQuery to select. What I had also failed to emphasise was another way of narrowing down and speeding up the jQuery selection – by use of the elements that you already have. In the case of the autocomplete, we already had a reference to the list itself, so it was simple:

   listitems = thelist.children('li');

This not only eliminated the cross-contamination of the two controls but seeded things up. So the rules to keep things fast and to save me headaches are simple:

  • Use a selector that very accurately matches the elements you require
  • Utilise references to elements you already have

Captcha Audio with ASP.NET

The subject of captcha images has been well covered. There are plenty of available resources for creating those warped letters. I created such a control that is a combination of quite a few different ideas and was quite proud of it until not thirty seconds after demonstrating it, someone asked whether it had a ‘play audio’ button for blind people. All of a sudden my fancy captcha control was not so fancy.

I tried to suggest using reCaptcha as a solution that had all the bells and whistles, but the customisation of my control trumped the somewhat fixed design of reCaptcha. So the problem of the audio captcha brewed a little in the back of my mind and a month or two later I turned back to it to see if I could find a solution.

A speech synthesis engine was not on the cards, so I figured that because the captcha image was a random collection of letters and numbers, the only way I could generate the appropriate audio was to have audio files of all the letters and numbers. I would then need to join them together, on demand, and play them from the web page.

Creating all the letters and numbers is straight forward enough (cue microphone and best-est speaking voice) and playing audio files from a web page has also become pretty easy thanks to the great SoundManager 2 javascript plugin.

The trickiest part was definitely joining mp3 files that SoundManager requires. MP3 audio files are particularly tricky as they can contain ID1 or ID3 tag information, so just joining them back to back would not create a correct MP3 file. What I needed to do was determine if a particular file had the tag information in it and strip it out if necessary. This took a lot of Googling and studying of the MP3 specification but I eventually managed to ensure the files had no ID2/3 tags in them before joining the files together:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
Imports System.IO
 
Public Class MP3Concatenator
 
    Public Shared Function Join(ByVal MP3sToJoin As Generic.List(Of String)) As MemoryStream
        Dim ms As New MemoryStream()
        Dim bw As New BinaryWriter(ms)
 
        'loop around each file and remove the tags and then concatenate the files
        For Each mp3File As String In MP3sToJoin
            Dim bytes() As Byte
            Dim fs As New FileStream(mp3File, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
            Dim br As New BinaryReader(fs)
            Dim audioStart As Integer = 0
 
            'Check for ID3 Tags
            fs.Position = 0
            If (System.Text.Encoding.ASCII.GetString(br.ReadBytes(3)).ToUpper = "ID3") Then
                'position of the header size bytes
                fs.Position = 6
                'MSB of size is Set to 0 and ignored so we need to convert the value
                audioStart = BitsToLow(br.ReadBytes(9))
                'add the header end position to this
                audioStart += 9
            End If
 
            'Check ID1 Tag
            fs.Seek(-128, SeekOrigin.End)
            If (System.Text.Encoding.ASCII.GetString(br.ReadBytes(3)).ToUpper = "TAG") Then
                'there is a ID3v1 tag on the end which needs removing
                fs.Position = audioStart
                bytes = br.ReadBytes(CInt(fs.Length - 128))
            Else
                fs.Position = audioStart
                bytes = br.ReadBytes(CInt(fs.Length))
            End If
 
            bw.Write(bytes)
            br.Close()
            fs.Close()
        Next
 
        ms.Position = 0
        Return ms
    End Function
 
    Private Shared Function BitsToLow(ByVal Size() As Byte) As Integer
        Dim Ret As Integer
        Ret = Size(3)
        If Size(2) <> 0 Then
            If CBool(Size(2) And 1) Then Ret += 128
            If CBool(Size(2) And 2) Then Ret += 256
            If CBool(Size(2) And 4) Then Ret += 512
            If CBool(Size(2) And 8) Then Ret += 1024
            If CBool(Size(2) And 16) Then Ret += 2048
            If CBool(Size(2) And 32) Then Ret += 4096
            If CBool(Size(2) And 64) Then Ret += 8192
        End If
        If Size(1) <> 0 Then
            If CBool(Size(1) And 1) Then Ret += 16384
            If CBool(Size(1) And 2) Then Ret += 32768
            If CBool(Size(1) And 4) Then Ret += 65536
            If CBool(Size(1) And 8) Then Ret += 131072
            If CBool(Size(1) And 16) Then Ret += 262144
            If CBool(Size(1) And 32) Then Ret += 524288
            If CBool(Size(1) And 64) Then Ret += 1048576
        End If
        If Size(0) <> 0 Then
            If CBool(Size(0) And 1) Then Ret += 2097152
            If CBool(Size(0) And 2) Then Ret += 4194304
            If CBool(Size(0) And 4) Then Ret += 8388608
            If CBool(Size(0) And 8) Then Ret += 16777216
            If CBool(Size(0) And 16) Then Ret += 33554432
            If CBool(Size(0) And 32) Then Ret += 67108864
            If CBool(Size(0) And 64) Then Ret += 134217728
        End If
        BitsToLow = Ret
    End Function
End Class

The trickiest part was handling the ID3 tag, as the specification states:

The ID3v2 tag size is encoded with four bytes where the most significant bit (bit 7) is set to zero in every byte, making a total of 28 bits. The zeroed bits are ignored..

Which is why I have the BitsToLow function in the code above.

The resulting concatenation is returned as a memory stream because I knew I could output this directly to the Response without writing the concatenated file to disk:

1
2
3
4
5
6
7
8
9
            ....
            Dim ms As IO.MemoryStream = Nothing
            ms = MP3Concatenator.Join(MP3FileList)
            Response.ContentType = "audio/mpeg"
            Response.ExpiresAbsolute = Date.MinValue
            If ms IsNot Nothing Then Response.OutputStream.Write(ms.GetBuffer, 0, CInt(ms.Length))
            ms.Close()
            Response.End()
            ....

By wiring up the CaptchaAudio.aspx page that returned the concatenated audio to the SoundManager plugin, I could create a link next to the captcha image, that played the letters in the image. Now my captcha control really was fancy.

jQuery Plugin Callbacks and Events

I have written a fair few plugins for jQuery for both work and side projects. As any developer should, I am always trying to improve my techniques for creating them so that they are as efficient and maintainable as possible.
Up until recently, my usual technique for callbacks and events would be to include the function in the options that you pass in. For instance, you would setup you plugin as follows:

1
2
3
4
5
6
7
8
9
10
11
(function($) {
    $.fn.myPlugin= function(options) {
        var settings = {
            setting1: 0,
            setting2: ''
        };
        //overload default settings
        if (options) { jQuery.extend(settings, options); }
 
        return this.each(function() {
        .....

This would allow you to pass in any number of settings, including callbacks to utilise at runtime:

1
2
3
4
5
  $('#myselector').myPlugin({ 
        setting1:1234, 
        setting2:'somesetting',
        callback1: function() {}
  });

Yesterday it occurred to me that jQuery had a neat feature that is much better than doing this – custom event binding. jQuery not only allows you to bind the DOM events to elements such as click, focus, keydown etc but because of the way it stores all the bindings, you can bind any number of custom events as well. This means that you can set your events for the plugin as follows:

1
2
3
4
$('#myselector').myPlugin({ 
        setting1:1234, 
        setting2:'somesetting'
}).bind("mycallbackevent", function() {...});

Within your plugin you would need to trigger that event like so:

$(this).trigger("mycallbackevent", [somedata]);

This technique also provides a neater alternative to public functions on your plugin. Say you wanted to initialise the plugin when a link is clicked. I would have previously setup a public function in the plugin to do this:

this.init = function() { .. }

and then run it as follows:

$('a').click(function() { $('pluginselector').get(0).init(); }

With custom events you would setup the event inside the plugin as follows:

$(this).bind('init',function() { .. });

and run as follows:

$('a').click(function() { $('pluginselector').trigger('init'); }

Perhaps I have just been writing plugins incorrectly all this time but that seems much better to me.

jQuery Inline Confirm

I have been using Delicious social bookmarking service ever since Blinklist degraded and finally lost the plot with their new design. When I first started using Delicious, a feature of their interface really leapt out at me and I could not wait to include it into a project.
The feature I am talking about is their inline confirmation. That is, when you, for instance, delete a bookmark by clicking on the delete link, the link is replaced with an ‘Are You Sure? Yes/Cancel’ message.

Inline Confirm - Before

Inline Confirm - After

I really liked this as there was no page refresh for the confirmation message and it was not a modal popup, which is a little obtrusive at best. It also occurred to me that something like that would not require a huge amount of code either. This is what I came up with:

1
2
3
4
5
6
7
8
9
$.inlineconfirm = function(el, callback, parentSel) {
        var hideEl = parentSel ? $(el).parents(parentSel) : $(el);
        hideEl.hide()
        $('<div class="confirm">Are you sure? <a class="confirmyes" href="#">Yes</a> <span>|</span> <a class="confirmcancel" href="#">Cancel</a></div>')
            .find('a.confirmyes').click(function() { callback(); hideEl.show();  $(this).parents('div.confirm:first').remove(); return false; }).end()
            .find('a.confirmcancel').click(function() { hideEl.show(); $(this).parents('div.confirm:first').remove(); return false; }).end()
            .insertAfter(hideEl);
        return false;
    }

I decided on using just a function rather than a plugin, as I wanted asp.net control compatibility (see later). The function just swaps out the element(s) for the confirmation message and puts them back if cancel is pressed, otherwise it runs the callback. It utilises three parameters:

  • el – The element which initiates the inline confirm. i.e. The link
  • callback – A function to run if the answer is Yes
  • parentSel – (optional) This is a jquery selector that identifies a parent element of el that will be replaced with the prompt. This is so that you could for instance hide an entire section of html with the ‘are you sure?’ prompt. Particularly useful to keep layouts consistent.

This is a nice easy function to use in most web development language, however, if you are using asp.net, it becomes difficult to use it with the asp controls like <asp:Button /> or <asp:LinkButton />. This is because ASP.NET likes to take control of the click events of these so that it can create its event model at the server. There is the onClientClick property which allows us to have some control over the click event but we need to pass a callback function. After a little investigating, I discovered the GetPostBackEventReference function:

mylinkbutton.OnClientClick = "$.inlineconfirm(this,function() {" & ClientScript.GetPostBackEventReference(mylinkbutton, "") & ";});return false;"

The GetPostBackEventReference returns a string of the function that will be used to invoke the postback. Just what we needed.