FAHNZ

MODE

Aug 15 2010

JavaScript Binary Search Function

Coffee beansI was looking into binary search functions lately and decided to comment this JavaScript binary search function I found (original is at http://www.dweebd.com/javascript/binary-search-an-array-in-javascript/) to help ensure that I had a good grasp on everything that was going on in it. Thought it might be useful for others, so I expanded my comments (read: commented the holy hell out of it) for posting here. Enjoy!

<script type="text/javascript">
 // Custom sort comparator
 function sortNumericAscending(a, b) {
 return a - b;
 }

 // Based off of http://www.dweebd.com/javascript/binary-search-an-array-in-javascript/

 // Extend the array object to include a new function to perform a binary search.
 // Since a binary search requires the array to be sorted, we can optionally
 // pass in the name of the function to do the comparison as well as the value to find.
 Array.prototype.binarySearch = function binarySearch(valueToFind, comparator) {
 var
 // The lower bound of the array, initally set to the true lower bound.
 lowerBound = 0,

 // The upper bound of the array, initally set to the true upper bound.
 upperBound = this.length - 1,

 // Stores the median index of the array.
 medianIndex,

 // Stores the result after we compare the value to find
 // with the value at the current median index of the array.
 comparisonResult;

 // As the loop progresses, the lower and upper bounds
 // will halve upon each iteration and eventually meet.
 while (lowerBound <= upperBound) {

 // Determine and store the median index.
 medianIndex = parseInt((lowerBound + upperBound) / 2, 10);

 // Check to see if a comparator was passed in.
 if (comparator == null) {

 // If there was no comparator to use, then use the default comparator.
 if (this[medianIndex] == valueToFind) {
 // If the comparison shows that the value that's at the current median index of the array
 // is equal to the value to find then set the comparison result to zero.
 comparisonResult = 0;
 }
 else {
 // If the comparison shows that the value that's at the current median index of the array
 // is less than the value to find then set the comparison result to one. If the comparison
 // shows that the median array value is greater than the value to find, then set the
 // comparison result to negative one.
 comparisonResult = (this[medianIndex] < valueToFind) ? 1 : -1;
 }
 }
 else {
 // If a comparator was supplied, then use it to determine
 // if the value is greater, lesser, or equal to the value to find.
 // It is presumed that this comparator is the same that was used to sort the array
 comparisonResult = comparator(this[medianIndex], valueToFind);
 }

 // If the comparison shows that the value that's at the current median index of the array
 // is less than the value to find then we know we'll need to look at the top half of this array.
 if (comparisonResult < 0) {

 // Set the new lower bound to just above the current median index
 // so that the next pass will only look at the top half of this array.
 lowerBound = medianIndex + 1;

 // Since the comparison result did not show that the values were equal
 // and we've set up the new bounds of the array to search on next,
 // then just disregard the rest of this iteration of the loop and contine with the next iteration.
 continue;
 }

 // If the comparison shows that the value at the current median index of the array
 // is greater than the value to find then we know we'll need to look at the bottom half of this array.
 if (comparisonResult > 0) {

 // Set the new lower bound to just below the current median index
 // so that the next pass will only look at the bottom half of this array.
 upperBound = medianIndex - 1;

 // Since the comparison result did not show that the values were equal
 // and we've set up the new bounds of the array to search on next,
 // then just disregard the rest of this iteration of the loop and contine with the next iteration.
 continue;
 }

 // We can only get to this point if the comparison result was equal
 // so we can return the value found at the last median index of the array.
 return this[medianIndex];
 }

 // If we searched the entire array and didn't locate
 // the value to find then just return null.
 return null;
 };

 // Testing the implementation.
 var newArray = [0, 3, 10, 4, 1, 2];

 // Default sort comparator.
 newArray.sort(); // default sort is alphanumeric
 document.write("newArray.binarySearch(3)=" + newArray.binarySearch(3));

 // Custom sort comparator.
 newArray.sort(sortNumericAscending);
 document.write("newArray.binarySearch(3, sortNumericAscending)=" + newArray.binarySearch(3, sortNumericAscending));
 </script>

I still think there’s a good chance I missed something so if you find anything amiss, please let me know so I can fix.


Jul 9 2010

Automatically Add/Remove Self-Labeling for Textboxes

This is simple and I’m sure that many have done this (or something similar) already, but I recently had to go through a website and retro-fit all textboxes with jQuery to automatically remove the “self label” value when the user clicks in the box. Instead of doing all of these individually, I came up with this brief jQuery script to add and remove the labels according to the title attribute (which should be there in lieu of a real label for accessibility). Maybe this will be useful to someone else, but at a minimum, I’ll know where to go to grab this again if I need it. =)

$(document).ready(function () {
    InitSelfLabelTextboxes();
});

// Sets textboxes to automatically add (and remove) a 'label' text value based on the title attribute.
// Note that the value should still be present initially so people without JS still can see a label.
function InitSelfLabelTextboxes() {
    $('input:text').each(function () {
        var thisBox = $(this);
        var titleVal = thisBox.attr('title');

        if (titleVal.length > 0) {
            if (thisBox.val().length == 0) thisBox.val(titleVal);

            thisBox.focus(function () {
                var currentVal = thisBox.val();
                if (currentVal == titleVal) thisBox.val('');
            });

            thisBox.blur(function () {
                var currentVal = thisBox.val();
                if (currentVal.length == 0) thisBox.val(titleVal);
            });
        }
    });
}

Dec 7 2009

JavaScript (jQuery) Text Resizer

First off, let it be known that I think that JavaScript-based text resizers suck. I firmly believe that the text size should be controlled by the user via the web browser controls. I’ve found that most clients want a JS-based text resizer because they think that it will make their website more accessible when, ironically, these types of resizers are not accessible by nature (since they require JS to work).

So, having seen poor implementations of this functionality in the past, I had a few requirements that I wanted my version of the text resizer to meet:

  1. Must be accessible or not be available at all.
    In other words, if it’s going to be non-usable or broken, then don’t even display it. There’s nothing worse to me that having something on a page that requires JavaScript to work, but doesn’t do anything at all to account for when a user does not have JavaScript. Especially when there is an incredibly easy way of addressing this (see below).
  2. Must account for the initial font-size and allow the user to return to that size
    The design mock-up that I received for this project showed that the text size controls would be a series of three letter “A” links, each larger than the previous. I decided that it made sense to make the smallest “A” represent the initial font-size. The subsequent links would add the ability for “bigger” and “biggest”.
  3. Must persist across page views
    I have seen many, many implementations of this that don’t even persist from one page to the next. That has got to be annoying if you’re really trying to use this feature.
  4. Must work on only specified areas of the design (as opposed to the whole page)
    This requirement was from the client and – in my opinion – is the closest thing to a good reason for implementing this type of functionality.

So, the first item on my list was “Must be accessible or not available at all” and was nice ‘n easy to implement. Simple solution that I’ve used in the past is to render the JavaScript-requiring-component with JS. If the user has JS, then JS will make the component available. If not, then it will never even be rendered. Simple! Now, I realize that this could be made to work server-side using AJAX or postbacks, but that was out of the scope of this particular project (not to mention getting to the point of needing to argue the downfalls of doing it this way to the client).

The third and forth items on my list were also easy. To get this to work only on specified areas of a page, I would make it work on only those elements that have a specific CSS class applied to them. To get it to work from page to page, I’d just store the requested size in a cookie. Easy-peasy!

What I found to be the most problematic once I got into this was accounting for the initial font-size when the page is first requested. I have my default font-size set in the CSS as 0.76em (which is around 12px). Using jQuery, most browsers would return the correct value in pixel units (12 point something), but – ever the oddball – Internet Explorer 7 would return some bizarre value in the vicinity of 600-or-so pixels. Needless to say, that ended up being a bit too big when the text was resized. So, researching on the ‘net showed be that this is a common problem and I didn’t come across a good, simple solution. The primary solution I came across involved measuring the height of a hidden div to get the font height pixels so that everything could then be rendered with pixel font sizes. So there was a lot of discussion out there of how to find out what the current pixel size is of the font.

The pixel size of the font seemed dangerous and irrelevant to me. Potentially dangerous since pixel sizes don’t scale well in early versions of IE (I know, I know, I have ridiculous – but oftentimes necessary – browser compatibility requirements). Irrelevant since I really don’t care what specific font size is shown initially since the user will just be wanting to make the font size “bigger” or “biggest”. All I care about is that the font size is bigger relative to the initial font size. After pondering and experimenting, I came up with the following solution: upon the font resize function, add an inner HTML element to the area being resized, then add a percentage value to that element to resize the text.

I’m sure there’s room for improvement and added flexibility, but it seems to be working out pretty well so far. Code below:

HTML:

Text Size:
  <a href="javascript:ResizeText(1);">A</a>
  <a href="javascript:ResizeText(1.2);">A</a>
  <a href="javascript:ResizeText(1.6);">A</a>

<div>
  <p>This is text that will get resized.</p>
</div>

JavaScript (using jQuery):

// Global Variables
var fontSizeArea = ".body-outer"; // use 'html' for entire page
var textResizeCookie = "textResizeFactor";
var originalFontSize;

// Initialization Function
$(document).ready(function() {
  $(".text-sizer").show();

  originalFontSize = $(fontSizeArea).css("font-size"); // remember the original font size
  if (originalFontSize == null) { originalFontSize = $("html").css("font-size"); }

  var savedResizeFactor = $.cookie(textResizeCookie);
  var resizeFactor;

  if (savedResizeFactor == null) {
    resizeFactor = 1;
  }
  else {
    resizeFactor = parseFloat($.cookie(textResizeCookie));
  }

  if (resizeFactor != 1) {
    ResizeText(resizeFactor);
  }
});

// Resize Text Function
function ResizeText(resizeFactor) {
  var wrapperClassName = "g-fs-wrapper"; // global font-size wrapper
  var newFontSize = (100 * resizeFactor) + "%";

  if ($("."+wrapperClassName).length > 0) {
    $("." + wrapperClassName).css("font-size", newFontSize);
  }
  else {
    $(fontSizeArea).wrapInner('<div class="' + wrapperClassName + '" style="font-size:' + newFontSize + '"></div>');
  }

  $.cookie(textResizeCookie, resizeFactor); // remember the new size
}

Aug 24 2009

Rollover Images – CSS versus JavaScript

I seem to always wrestle with this no matter how many times it comes up. When a website design calls for image-based rollovers on the primary navigation, rollover which method to use? CSS or JavaScript. Accessibility for me in this context is that the navigation must be functional if one or any combination of the following is disabled: JavaScript, CSS, images. It also validate as XHTML Strict, CSS level 2WCAG priority 2, and it (obviously) must work with screen readers. Browser compatibility doesn’t factor in heavily here, but it must work in my standard browser list, which includes IE6. I also consider the actual rollover effect itself to be non-essential.

CSS Benefits

  • [Maintenance] Can use one “sprite” based image for all items (changing background position to show the different items)
  • [Accessibility] Works even if JavaScript is disabled
  • [Accessibility] Shows text if images are disabled (via Ryan Rollovers method)

CSS Cons

  • [Maintenance] Funky HTML/CSS needed for full accessibility (the empty span trick of Ryan Rollovers)
  • [Extras] Will probably look bad if printed (since background images don’t print by default)

JS Pros

  • [Accessibility] Can use forground images with alternate text
  • [Maintenance] Simple, clean inline script using “onmouseover” and “onmouseout”
  • [Accessibility] Shows alt text if images are disabled
  • [Extras] Will print foreground images by default so printout should look better

JS Cons

  • [Maintenance] Will have many images to deal with – 2 for each item. If one image changes width, you need to redo all images and rollover states.

Both methods have a potential issue of when images are disabled, then the foreground or alternate text spacing can be tricky. Result can be that even though the text is displayed, it will be difficult to read.

I think that looking at these items summed up shows the direction I should take. As far as I can tell, the simple JavaScript method provides the most benefits with the least amount of potential problems. So it shall be!