FAHNZ

MODE

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
}

Nov 17 2009

Bubbling UserControl Events

This scenario has bitten me before. User control events fire after page events. This can be a problem if an update within the user control needs to be reflected in the outer page container. The solution is to “bubble” the event from the user control into the page so it can react accordingly. So, as much for my reference as anyone else’s, here’s some quick ‘n dirty code to bubble events from an ASP.NET user control to the page that contains it:

User Control Code:

public partial class SamplePage : System.Web.UI.Page
{
  protected void Page_Load(object sender, EventArgs e)
  {
    // "SampleUserControl1" is the ID of the control that has already been placed in the HTML of the page
    SampleUserControl1.BubbleEventHappened += new EventHandler(BubbleEventHappened);
  }

  private void BubbleEventHappened(object sender, EventArgs e)
  {
    // code to react to bubbled event goes here
  }
}

Page (that contains the User Control) Code:

public partial class SamplePage : System.Web.UI.Page
{
  protected void Page_Load(object sender, EventArgs e)
  {
    // "SampleUserControl1" is the ID of the control that has already been placed in the HTML of the page
    SampleUserControl1.BubbleEventHappened += new EventHandler(BubbleEventHappened);
  }

  private void BubbleEventHappened(object sender, EventArgs e)
  {
    // code to react to bubbled event goes here
  }
}

There. That wasn’t so bad!


Oct 26 2009

My Test ASP.NET Email Page

I need to test out this kind of functionality all the time. I’ve found it helpful to post this file to the web server that I need to be able to send email from and then tell the person who has control over the configuration of the email server to use this page to test with.

What follows is a self-contained ASP.NET page that allows the user to input all the information needed to configure how the email parameters are handled in the code in order to send a test message from the web server. This was recently (and quickly) converted from an ancient version in VB.NET and could use some serious tweaking; I’ll update the code here if I get around to it.  ;-)

<%@ Page Language="c#" Strict="false" %>
<%@ Import Namespace="System.Net.Mail" %>
<script language="c#" runat="server">
private string _mailServer;
private int _mailServerPort = 0;
private string _username;
private string _password;

private string _toAddress;
private string _fromAddress;
private string _subject;
private string _body;

private void btnSubmit_Click(Object sender, EventArgs e)
{
if (Page.IsValid)
{
_mailServer = txtMailServer.Text;
_username = txtUsername.Text;
_password = txtPassword.Text;

if (!string.IsNullOrEmpty(txtMailServerPort.Text)) { _mailServerPort = int.Parse(txtMailServerPort.Text); }

_toAddress = txtMailTo.Text;
_fromAddress = txtMailFrom.Text;
_subject = txtSubject.Text;
_body = txtBody.Text;

MailMessage message = new MailMessage(_fromAddress, _toAddress);

message.Headers.Add("Reply-To", _fromAddress);
message.Subject = _subject;
message.Body = GetResponse(_body);

SmtpClient mailer = new SmtpClient(_mailServer);
if (_mailServerPort > 0) { mailer.Port = _mailServerPort; }
if (!string.IsNullOrEmpty(_username)) { mailer.Credentials = new System.Net.NetworkCredential(_username, _password); }
mailer.EnableSsl = chkEnableSsl.Checked;

try
{
mailer.Send(message);
lblResponse.Text = CleanHtml(GetResponse("Email message appeared to send successfully."));
}

catch (Exception ex)
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();
sb.Append("There was an error sending the email message:");
sb.Append("\r\n");
sb.Append("\r\n");

sb.Append(ex.Message);
sb.Append("\r\n");
sb.Append("\r\n");

sb.Append(ex.StackTrace);

lblResponse.Text = CleanHtml(GetResponse(sb.ToString()));
}
}
}

private string GetResponse(string msg)
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();
sb.Append(msg);
sb.Append("\r\n");
sb.Append("\r\n");

sb.Append("Mail Server: ");
sb.Append(_mailServer);
if (_mailServerPort > 0) { sb.Append(":" + _mailServerPort.ToString()); }
sb.Append("\r\n");

if (!string.IsNullOrEmpty(_username))
{
sb.Append("Logon Username: ");
sb.Append(_username);
sb.Append("\r\n");
}

sb.Append("Enable SSL: ");
sb.Append(chkEnableSsl.Checked.ToString());
sb.Append("\r\n");
sb.Append("\r\n");

sb.Append("To Address: ");
sb.Append(_toAddress);
sb.Append("\r\n");

sb.Append("From Address: ");
sb.Append(_fromAddress);
sb.Append("\r\n");

sb.Append("Subject: ");
sb.Append(_subject);
sb.Append("\r\n");

sb.Append("Body: ");
sb.Append(_body);
sb.Append("\r\n");

return sb.ToString();
}

public enum ConvertHtml
{
GtLt = 1,
SpecialSingleCharacters = 2,
CrLfToBr = 4,
BrToCrLf = 8,

ForHtmlEmail = 6,
ForWeb = 7
}

public static string CleanHtml(string original)
{
return CleanHtml(original, ConvertHtml.ForWeb);
}

public static string CleanHtml(string original, ConvertHtml convertFormat)
{
System.Text.StringBuilder cleaned = new System.Text.StringBuilder();
cleaned.Append(original);

if ((convertFormat & ConvertHtml.GtLt) == ConvertHtml.GtLt)
{
cleaned = cleaned.Replace("<", "&lt;");
cleaned = cleaned.Replace(">", "&gt;");
}

if ((convertFormat & ConvertHtml.SpecialSingleCharacters) == ConvertHtml.SpecialSingleCharacters)
{
cleaned = cleaned.Replace(" & ", " &amp; ");
cleaned = cleaned.Replace("\"", "&quot;");
}

if ((convertFormat & ConvertHtml.BrToCrLf) == ConvertHtml.BrToCrLf)
{
cleaned = cleaned.Replace("<br>", "\r\n");
}

if ((convertFormat & ConvertHtml.CrLfToBr) == ConvertHtml.CrLfToBr)
{
cleaned = cleaned.Replace("\r\n", "<br>");
cleaned = cleaned.Replace("\r", "<br>");
cleaned = cleaned.Replace("\n", "<br>");
}

return cleaned.ToString();
}
</script>
<!doctype html public "-//w3c//dtd xhtml 1.0 transitional//en" "http://www.w3.org/tr/xhtml1/dtd/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server" id="Head1">
<title>Test ASP.NET Email</title>
<style type="text/css">
body { font: 76% verdana; }
.tbl { border-collapse: collapse; }
.tbl td { border: solid 1px #ccc; padding: 5px; }
.tb { width: 300px; }
</style>
</head>

<body>
<form id="form1" runat="server">
<div>
<h1>Test ASP.NET Email</h1>
<p><asp:label id="lblResponse" runat="server" forecolor="Red"></asp:label></p>
<table>
<tr>
<td>Mail Server*</td>
<td><asp:textbox id="txtMailServer" runat="server" CssClass="tb"></asp:textbox><asp:requiredfieldvalidator id="rfvMailServer" runat="server" errormessage="required" controltovalidate="txtMailServer" Display="Dynamic"></asp:requiredfieldvalidator></td>
</tr>
<tr>
<td>Mail Server Port</td>
<td><asp:textbox id="txtMailServerPort" runat="server" CssClass="tb"></asp:textbox></td>
</tr>
<tr>
<td>Logon Username</td>
<td><asp:TextBox runat="server" ID="txtUsername" CssClass="tb"></asp:TextBox></td>
</tr>
<tr>
<td>Logon Password</td>
<td><asp:TextBox runat="server" ID="txtPassword" TextMode="Password" CssClass="tb"></asp:TextBox></td>
</tr>
<tr>
<td></td>
<td><asp:CheckBox runat="server" ID="chkEnableSsl" Text="Enable SSL" /></td>
</tr>
<tr>
<td colspan="2">&nbsp;</td>
</tr>

<tr>
<td>Mail To*</td>
<td><asp:textbox id="txtMailTo" runat="server" CssClass="tb"></asp:textbox><asp:requiredfieldvalidator id="rfvMailTo" runat="server" errormessage="required" controltovalidate="txtMailTo" Display="Dynamic"></asp:requiredfieldvalidator></td></tr>
<tr>
<td>Mail From*</td>
<td><asp:textbox id="txtMailFrom" runat="server" CssClass="tb"></asp:textbox><asp:requiredfieldvalidator id="rfvMailFrom" runat="server" errormessage="required" controltovalidate="txtMailFrom" Display="Dynamic"></asp:requiredfieldvalidator></td></tr>
<tr>
<td>Subject*</td>
<td><asp:textbox id="txtSubject" runat="server" CssClass="tb">Test Message</asp:textbox><asp:requiredfieldvalidator id="rfvSubject" runat="server" errormessage="required" controltovalidate="txtSubject" Display="Dynamic"></asp:requiredfieldvalidator></td></tr>
<tr>
<td>Body*</td>
<td><asp:textbox id="txtBody" runat="server" textmode="MultiLine" rows="6" CssClass="tb">This is a test message.</asp:textbox><asp:requiredfieldvalidator id="rfvBody" runat="server" errormessage="required" controltovalidate="txtBody" Display="Dynamic"></asp:requiredfieldvalidator></td></tr>
<tr>
<td colspan="2">&nbsp;</td></tr>
<tr>
<td></td>
<td><asp:button id="btnSubmit" runat="server" text="Send" onclick="btnSubmit_Click"></asp:button></td></tr>
</table>
</div>
</form>
</body>
</html>

Oct 1 2009

Email Client Trials and Tribulations

I’m fed up with Outlook. We’ve moved away from using an internal MS Exchange server in lieu of using our hosting provider’s email servers. Since I need to access my email from wherever I happen to be at the time (office, home, phone, etc.), I decided that doing everything with the online mail interface would be ideal. So, I’m pretty much stuck with the interface that the hosting company provides, which happens to be SmarterMail (version 5.5).

So I gave it a genuine shot. The interface isn’t too bad, but I immediately found that something that I find very important was missing. I constantly flag messages for follow-up in my inbox so I don’t forget to get back to clients about this-and-that. Outlook has a deep flagging system that allows you to choose different colors (categories) and optionally set up reminders to popup at a specified date/time. I felt deceived, because SmarterMail had a “flag” option that even looks like the flags you see in Outlook, but when I tried it, I was presented with a menu of “Mark [as]” options that were “Read”, “Unread”, “Spam”, “Not Spam”. No option to “star” items like in Gmail, or to “flag” items like in Outlook. Webmail fail.

So, since I needed the “flag” feature and thus couldn’t use the webmail interface, I went looking for options. I like using Gmail for my personal mail and I think that has the option of downloading another email account via POP or IMAP, but I wanted to keep this completely separate from my personal mailbox. So that was out. I wanted to avoid it, but I was on to looking at mail clients again.

If I were to go back to using email clients, I needed to be sure that I kept the mailbox at the hosting provider as the single source for my email. Read or unread, moved, flagged or deleted, I needed everything to be the same no matter where I accessed it from. That meant no copies of email messages downloaded with POP. IMAP would work though, since it supports two-way communication with the server as opposed to POP’s method of simply downloading copies of messages and not reporting that anything has changed back to the main mailserver. So IMAP it is.

I started with the old mainstay, MS Outlook. I’m not willing to pay the exorbitant prices that Microsoft charges for their Office suite (at least not frequently), so I’m dealing with Outlook 2003 here. I imagine that they have made improvements by now, but Outlook 2003 hooked up to a server via IMAP sucked. Hard. deleting or moving messages resulted in them staying in their original locations, but with a strikethrough line through them, signifying that they were “marked for deletion”. Then I’d have to find and click the “Purge Messages” option in the menus to get rid of them. What a pain! Outlook fail.

Next on my list I had heard great things about: Thunderbird. It is free to download, plus Mozilla has done a great job with Firefox, a browser that surpasses the ubiquitous Internet Explorer in almost every way. Firefox is still my favorite browser even after trying Google Chrome, Opera, and Safari for Windows. Downloaded and installed Thunderbird, and – wow! I was in a timewarp; it was just like checking email in the 80s!  Well, maybe the early 90s. Despite an antiquated look, I gave it a genuine shot. I used it for at least a week or so and checked for plugins that would address some of the perceived shortcomings. Some were preference things that just make more sense in my mind; things such as replies being on the bottom of the message thread by default (even when I changed the preference for it to be on top, it would still put my signature at the bottom), and the default sorting of items showing the most-current item on the bottom (this could be fixed for when sorting by date only, but if I sort by, say, the “from” name, then the secondary sorting of date would still be backwards for me). Maybe I just got used to doing things a certain way using Outlook, but I honestly feel more like Outlook spoiled me. Not so much with features (I hardly use anything beyond basic email and flagging items), but with intuitiveness and the reading pane layout. Thunderbird fail.

It didn’t seem that I was left with many options that fit my budget of “free”. I wasn’t hopeful, but I decided to give Windows Live Mail a try. Despite it’s name, you do not have to use it with a Windows Live email account and I had tried it – with some success – with my personal Gmail account. First impressions were promising; it had the reading pane view that I’ve enjoyed since it’s inception (in 2003?) and it flags items for follow-up like a champ. It even has some handy “Quick Views” that I could use to view all my flagged items exclusively, which made it a handy replacement for the “Search folders” I had gotten used to in Outlook. I’ve currently got WLM set up at the office, on my home computer, and on my laptop. Plus, I can still get to my email on the web if I’m not near any of those systems (or my phone). It just works for me. Windows Live Mail win!

The only place where WLM falls short for me is in the calendar. I’m currently using my phone as my planner/reminder system, but I’m still on the lookout for a better option.


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!


Jul 31 2009

Why Are We Coding for IE6 Again?

Internet Explorer 6 sucks. We all know it. We all hate it. We all Most of us still have to deal with it. This is a problem I had run into before and it took me forever to figure out how to solve it this time. May as well document it here so I (and hopefully anyone else) can find it easily next time.

The problem:
Links styled with a background image are incorrectly tiling that background image. Or to put it another way; IE6 is not properly rendering the background of an inline element has a no-repeat background set. The example below shows the same link style applied in different places on the same page. As you can see, it renders the area for the background image position and dimension correctly, but it repeats the image within the confines of the actual arrow image dimensions (where it should show up). Man, that’s weird to try to explain. Hopefully the image below helps.

The problem shown:

Examples of IE6 background repeat and positioning problem

The CSS code:

.more-link {
 padding-right: 15px;
 font-weight: bold;
 text-transform: uppercase;
 background: url(../images/common/readmore-arrow.gif) 100% 1px no-repeat;
}

The HTML code:

<a href="#">View more</a>

The solution:
The solution is stupidly simple. I had figured it out before on a previous project, but of course couldn’t remember what the solution was. I just remembered it was something simple to implement. Anyway, as I emphasized above, the problem seems to be that the no-repeat background is set on an inline element. I guess that confuses IE6. Go figure. The browser is coming up on being a decade old.

The solution shown:

IE6 inline background problem - fixed

The updated CSS code:

.more-link {
 display: inline-block; 
 padding-right: 15px;
 font-weight: bold;
 text-transform: uppercase;
 background: url(../images/common/readmore-arrow.gif) 100% 1px no-repeat;
}

Voila! Tell IE to keep the element inline, but treat it like a block. Plus it shouldn’t adversely affect other browsers. Problem solved.


Jul 8 2009

WordPress Default (Kubrick) Template Reference

In modifying the default “Kubrick” theme for WordPress, I didn’t find a concise reference for all the files in the theme directory, so I put one together:

WIDE (SINGLE) COLUMN PAGES
404.php            wide-column standard "Not Found" error page
archives.php        wide-column archives listing page, by month and by category
comments-popup.php    full-page used for when the option to have comments area popup in a new window (not turned on by default) (http://codex.wordpress.org/FAQ_Working_with_WordPress#Can_I_have_popup_comments.3F)
image.php        wide-column page used for viewing a "linked-to" image resource (supercedes generic attachement.php)
links.php        wide-column links (bookmarks) listing page (ignores page content if chosed as the template to use in the admin section)
single.php        wide-column page used to display a single post with comments area

NARROW (TWO) COLUMN PAGES
archive.php        narrow-column archive view page, shows filtered archive view (ex: by month or category)
index.php        narrow-column root template file, used for everything if other page templates are not present
page.php        narrow-column page used for "pages" created in the admin section
search.php        narrow-column page used to display search results

INCLUDES
comments.php        used for inserting comments area and functionality into pages, present in image.php, index.php, single.php
footer.php        global footer (includes closing tags started in header.php)
header.php        global header (includes opening tags closed in footer.php)
searchform.php        optional include that will be used for search form template, used in sidebar and on search.php
sidebar.php        global right-side column, includes search form and list-based navigation elements

OTHER
functions.php        optional global PHP functions automatically loaded by WordPress if present

This is probably out there somewhere already, but hopefully this will come in handy for someone else as well.


Jul 6 2009

ASP.NET Membership Woes

The problem:

I kept on getting foreign key constraint errors when trying to use ASP.NET (2.0) DeleteUser method:

Membership.DeleteUser(<username>);

All my research on the internet pointed to it being a permissions problem. Like many of the posts I found, this worked fine in my local development database, but in production it would yield this error:

The DELETE statement conflicted with the REFERENCE  constraint
"FK__aspnet_Me__UserI__15502E78". The conflict occurred in  database
"dbname", table "dbo.aspnet_Membership", column 'UserId'.
The  statement has been terminated.

After hours of experimentation with user permissions in the database and research on the problem, the support team at the hosting company I was working with found the solution of setting the references foreign key constraints to cascade upon deletion. I went into SQL Server Management Studio and updated the FKs that were referenced in the errors to cascade upon deletion and voila! Problem solved. Good job CrystalTech support and especially Geoff.

Update: I looked into my local database (that has been working properly) and saw that the foreign keys are not set to cascade deletions, so it still may be some strange permissions issue. Regardless, this seems like an acceptable workaround and it did get those deletions to work (and the usernames freed up).