2011-06-24

You have failed me for the last time, Firefox

In my last post, I mentioned that Firefox was causing issues with my custom on/off control. I solved that problem, but now I have a new one.

When the on/off control renders, it renders with an onclick attribute that calls the function that moves the slider and sets the value of the control in a hidden field. If I want to add any more actions to the control (a common one is to change its color when the value is changed), I use the jQuery .click() function to attach a JavaScript function to its click event.

On a page with several on/off controls, I have bound click events to change their color and enable a save button on change. It's worked every other time I've done this, and it works in this case as well — except for Firefox. For some reason, the last group of controls (which happen to be rendered in an ASP Repeater) do not fire the jQuery click event. What's worse, even though clicking on them does in fact slide the button from one value to the other, it apparently is not setting the hidden form field to change the value — and all that code is being run from JavaScript called in the onclick attribute.

I have found numerous references to jQuery click events not being fired in Firefox, but none of the solutions will help me here. I can't move my code into the onclick event, since I need to register the click handlers from different places; and nothing is apparently wrong with the code as it works as-is not only in other browsers, but in the same browser elsewhere on the same page.

I have spent the better part of a day trying to find where this click event is being "swallowed", and why this code that works perfectly on IE, Opera, and Chrome is not functioning in Firefox. I'm done. It's an internal tool, and we have the luxury to mandate our users' browser choice to some degree.

Firefox, I don't know what your problem is, but I am tired of wasting days of development time trying to find solutions for problems that only exist in Firefox. You are the weakest link. Goodbye.

2011-06-17

Firefox, jQuery, and RegisterStartupScript

I have created a control for the web project I'm working on at work. It's an On/Off slider control, very similar in appearance to the one used by iOS, that uses jQuery to animate the sliding switch on click. Over time, I've added more features and support to it as requirements have come in — its message text is configurable, it can raise its own postback events, and it even works in AJAX postbacks. Someday, I might have to publish the code (maybe when I get rid of its dependence on images and make it size-adjustable as well).

When the control is first rendered, the slider switch is centered halfway between "On" and "Off", and I call Page.ClientScript.RegisterClientScriptBlock that calls that control's "set" function to slide the switch to the correct position. (The control's client click event sets the control's value property and calls this same function to slide the switch back and forth on demand.) It works great on IE, Opera, Chrome… but not Firefox. It would render the first few correctly, but somewhere halfway down the page (depending on how many switches were involved — my particular example page had 10), the slider switches would remain in their centered, "unset" state.

This is getting to be a theme.

I found that, if I added an alert() call before every SetSlider call, then every switch would be set, except the last one. If I reversed the calls so the alert() came second, all the switches would be set.

I was able to solve the problem by wrapping each SetSlider call in a jQuery ready function (i.e., $(function(){ … });). Because the control is self-contained and has no knowledge of other sliders on the page, that's a lot of ready functions; but jQuery seems to handle it without incident. The only other thing I had to deal with was to ensure any startup functions I had to alter the switches' states must come after those SetSlider functions — in my case, moving the code to the page's PreRenderComplete event (because, according to MSDN, the controls' PreRender events, where I registered the SetSlider methods, get called after the page's).

It works, but it still took a couple hours out of my day to find it and then fix it. Thanks again, Firefox.

2011-05-26

When it rains, it pours

You know, it would be nice if manure didn't hit the fan all at one time… for once.

A week ago today, my car was hit in an accident, which the cop blamed me for (still building my case against this for my court date in 2 months). Sunday, I tried helping my wife cook by slicing carrots on the mandoline, when my thumb must've slipped, and I cut about a quarter inch into the end of my thumb. Tuesday, I got a call from insurance telling me they're totaling the car, putting me in the difficult decision of fixing it and never expecting to resell it, or shelling out around ten grand on top of my car's value to replace it (since, with the earthquake in Japan and rising fuel costs, Priuses are getting hard to find). Wednesday, my wife called me to tell me she got a ticket for making a right turn on red where allegedly there is a sign saying not to do that. That night, at some point between picking my mother up from her house and going to a Toyota dealership to look for used Priuses before picking her truck up from the shop, my stereo bluetooth headset disappeared (a search of every place I had been since I last remember having it turned up nothing).

At least, as before, God gave me an umbrella. I was uninjured in the accident. I seem to be getting all the documentation I need to argue my case. We actually have money in savings (barely) to replace the car. The amount they offered me for the car is actually quite fair. The cut on my thumb was clean and somewhere between a quarter and a third through, shallow enough that it did not require any "reattachment". My wife's traffic ticket, if paid promptly, will only result in a single point against her and not affect insurance. I "get" to upgrade my car, not to mention change its color (I always hated the color of the one I've been driving for the past 6½ years), and I'm getting more for my car than the trade-in value the last dealer offered me.

It still would be nice if all the bad stuff would stop now.

2011-05-19

Maybe treating it as a four-way stop is best

Long ago, I wrote about my annoyance at people who get the rules about malfunctioning lights backwards. I rather wish I had one of those "ignorant" people on the road today.

At the intersection leading into the train station, the lights were completely out. Four-way stop. I was going straight. As I stopped, the car next to me turning left started to go, so I did as well. Someone coming from the cross street in the far lane (on the other side of a full lane of traffic at a stop) plowed through the intersection and hit me.

As I sat in my car (the door was jammed, so I couldn't get out right away), I looked up to verify the lights were in fact dark. They were — in my direction only. The lights in the opposite direction, however, were flashing red, and the lights on the cross street, the one the other driver "plowed through", were flashing yellow.

We both acted on information we had; the street light just gave two completely different sets of information.

I made sure to point this fact out to the officer on the scene, who noted it. I do have to appear in court in a couple months to answer a charge of "careless driving" because I failed to yield to her yellow light, so I can only hope the judge will acknowledge the screwed up lights and realize I acted appropriately.

Of course, it wasn't until I was on the train in to work that I realized I should have taken pictures and video, especially of the lights in question.

2011-05-15

Want to fly? Change your plans.

So, my mother was supposed to fly out on business today. We called to see if she had made it and if there was anything she needed. What did she say?

They downsized her plane and bumped her because she did not check in the day before the flight.

The next flight available? Tomorrow, 4pm.

So any and all plans she had for today and tomorrow, including business meetings, have to be canceled or rescheduled.

Add this to other horror stories we've heard first-hand — including friends of ours who were bumped at the last minute and forced to delay their return home for a whole weekend with their two young children (did I mention that the airline had already put their luggage on the flight, so this family of four had no clothes for that weekend?) — and is it any wonder why I'd rather spend hours in a car driving to far away destinations than giving any money to this industry?

And this is before factoring things in like insane surcharges for every little thing, and your choice of rape or pornography to get through security.…

2011-05-11

My Jury Service

A month or so ago, I received a summons in the mail to report for jury duty. I let my employer know well ahead of time, and on the designated day (Friday), I reported to the county courthouse.

I show up at 8am Friday morning. I went through a security checkpoint that reminded me of the good old days of airport security, when it didn't include taking off your shoes and submitting to a physical search that would only be appropriate in an after-school special titled "Bad Touch". In the jury room (a very large room with hundreds of chairs laid out in even rows), I picked up a questionnaire and started filling it out, while Fox News displayed on two TVs in the front.

The questionnaire included a lot of personal questions, including things like "Have you or anyone in your family had an experience with a law enforcement officer recently? Was it positive or negative?" They also included the name of the person on trial and a long list of names of potential witnesses, asking if you knew or recognized anyone. It also asked if there would be any difficulty serving the length of the trial, which was expected to last eight days.

When I was last summoned, I was very upset that, as a contractor, I was only guaranteed the low legal limit of $50/day. Now, as a full-time employee, I'd get my full pay, but only for the first three days, and then I'd be at that below-minimum-wage rate. (Colorado minimum wage is currently $7.36/hour, working out to not quite $60 per day.) I'm not much happier about this.

Time passed as more jurors reported in and started filling out their questionnaires. After about an hour, the clerk addressed us, along with a judge, to thank us for our service and tell us how important jury service is. We then watched a short video about jury service, in which people shared their thoughts about service. I noticed that the testimonials included valid concerns (like "I had small children, and I worried about what I was going to do with them", "I was really busy at work and didn't know how I'd fit this in"), but then they said how good they felt doing their civic duty — without addressing any of the concerns they initially expressed. (So, what did you do with your kids, ma'am? How did you support your family making less than minimum wage, sir?)

Our questionnaires were then gathered, at which point we were given a new randomized juror number. We were then sent home, with instructions to call a certain number after 6pm to see if we were selected to return on Monday.

This seemed like an unfair inconvenience. I was unable to tell my employer if I would be at work on Monday or not until after office hours were closed. I suppose my employer wouldn't know how long I would be out anyway, and the judge and attorneys need time to go through the 150-some questionnaires, but still, it doesn't seem like the needs of the citizens are taken into account at all.

I called, and sure enough, I was selected to return on Monday. I sent an email to my employer to give them what notice I could.

On Monday, I reported at the designated 9am time (which at least gave me plenty of time to drop my kids off at school, as usual). Reporting in were the hundred or so jurors from Friday selected to return, plus another group asked to report for the first time on Monday. The Monday jurors were processed first. The clerk announced that their trial was expected to take five days. They filled out their own questionnaires, and again we watched the video, temporarily interrupting the CNN discussion of Osama Bin Laden that was playing on the TVs today. I noticed that the video stated the average trial lasts 1-3 days, and I couldn't help but wonder how many trials must last less than a day to get that average down, considering our trial was 8 and theirs was 5.

Finally, around 10:30am, we were sent up to the courtroom. There was probably at least 20 minutes spent getting everyone seated, as we were supposed to sit in order of our juror numbers in a particular configuration around the courtroom. (The judge and attorneys had seating charts, and this would ensure they knew to whom they were speaking.) Some additional jurors had been dismissed, so there were some holes in the jury box. The first of us on the benches were called up to fill in the holes. As luck would have it, I was the first in line and ended up in the jury box to start. When I was in jury selection 6 years ago, I noticed that those people in the jury box at the beginning were among the first to be dismissed, and those called to replace them rarely were; so I had good feelings about being dismissed before hitting a substantial drop in income for a few days.

Questioning began (what is known in the court as voir dire — I guess I did learn something from that jury service video). The judge started with basic questions, such as whether or not any of us knew or recognized anyone else in the courtroom — including the defendant, whom I was honestly surprised to see present. I don't remember the defendant being present the last time I went through this, and I have to wonder if any of the potential jurors felt uncomfortable answering questions in front of him.

One of the questions the judge asked every juror was if there was any hardship in serving. I mentioned that the drop in pay would make it very difficult to support my family. He did say that, if it meant falling behind in bills, he would not keep anyone there. I answered honestly that it would be difficult, but I would at least survive. (It would take dipping into savings, but it would have been possible. I could've lied and said it would've been worse, but I didn't think that would be prudent, especially since we had a lesson in church the day before that just happened to be all about honesty.)

After the judge got through everyone, the prosecuting attorney began asking questions, probing for any biases that may make judging this particular case difficult. I was asked one question directly, but then I noticed that he didn't seem that interested in me thereafter. So, after our lunch break (during which I went to Walgreens to get some Excedrin for the headache that was building up all morning), I decided to remedy this situation. Whenever the attorney asked a general question ("Does anyone else feel like this?"), I raised my hand. Well, any time I could come up with something to say, anyway. I answered honestly, but I tried to answer a lot, so when it came time for the attorneys to dismiss jurors, I was on their mind.

When it came time for the dismissals (peremptory challenges — something I don't think was in the video but was explained at length by the judge), I ended up being dismissed by the defense attorney. I think about five people were dismissed before me, sent downstairs to collect their certificates of service (among other things, used as proof to employers of time served). They must have been discussing who they thought would remain on the jury, because when I showed up, there was a collective look of astonishment on their faces as one said, "You? No way, I thought you were a shoe-in!"

I'm not sure if I said anything in particular that got me dismissed. There was a question asked by the defense attorney along the lines of, "Could someone have a legitimate reason to lie to the police? If they do, does that mean they must be guilty of something?" I answered (honestly) that I wouldn't think they were "guilty", but it would color my perception of their testimony — if they lied to the police about one thing, what else might they lie about? While I can't know the specifics of the case, he must've had a reason for asking; I would guess that either the defendant or one of his witnesses might've lied to the police during the course of the crime/investigation, and my opinion might lead me to discount their integrity. Or maybe it was just my visibility in questioning, or a combination of both. Whatever the case, I wasn't too upset to not have to worry about supporting my family for the next week and a half.

In a way, I'm kind of disappointed I didn't get to hear the case. The charges were read to us, so I know it had to do with domestic violence and second degree murder. I would've been much more willing to serve in this very important process, too, if they didn't make it so blasted difficult to take care of your regular life in order to do this service.

2011-04-28

4096 bytes is more than enough for everybody

Ever try to make an AJAX call from JavaScript, only to find out that your data is truncated at around the 4k mark? If so, you must be using Firefox 3. Although there is a workaround described here, it means you have to write extra, special code to check for a custom property to really get all the data.

You can test this problem using this QuirksMode page. The page notes other limits on Opera and the Macintosh version of IE, but on the current (v11) version of Opera, I was unable to duplicate it. I also don't have a Mac, so I couldn't verify it there, either. Only Firefox fails to copy the whole text block.

Apparently, this has been fixed in Firefox 4, but that's small consolation when you're tasked with supporting what people are actually using.

2011-04-21

Let me url that for you

Today's programming annoyance is brought to you by the .Net Framework.

If you add a style to an HtmlControl in code — for example, the list-style-image to a list item element — you might use something like the following:

control.Style.Add("list-style-image","none");

You might expect that the style element would contain "list-style-image: none;".

Apparently, you'd be wrong. The tag generated looks like this:

<li style="list-style-image: url(none);">

There is a workaround, but it depends on you being able to specify all list-style properties at once. Using the shortcut, this code:

control.Style.Add("list-style","none none");

will generate the expected HTML, unmangled (and set both the list-style-type and list-style-image properties).

Having list-style-image: url(none) may not make your page look broken, but it will result in a browser request for the file "none" and result in some excessive 404 logs on your server.

If you had an actual image and set it with control.Style.Add("list-style-image","url(imageName.jpg)"); the style would not suddenly get "double-wrapped" with the url() function. By wrapping your value with url() "just in case" you forgot it, Microsoft tries to protect you from yourself, and ultimately causes more headaches by preventing you from setting the style to a perfectly valid value.

2011-04-01

Hollywood's War on Christianity Now Using Aliens

I recently saw the movie Paul. It's a comedy about two young adult male British nerds who are realizing a longtime dream of coming to America to visit Comic-Con, followed by taking a tour of famous UFO sites in the US (like Roswell and Area 51). As they're traveling, they witness a car suddenly veer off the desert highway and crash. When they stop to investigate, they meet Paul, a rather foul-mouthed alien who has escaped government capture and is trying to get home.

The movie is pretty funny, with lots of nerd references — from Star Wars (the cantina theme being played in a trucker bar, a guy shooting out his CB radio as he murmurs "Boring conversation anyway") to Close Encounters (a spaceship landing at Devil's Tower, a large firework that plays the five-note theme) and Aliens (a character quotes "Get away from her, you b---", a line spoken by Sigourney Weaver in Aliens, to Sigourney Weaver's character in Paul). It is also full of bathroom and locker room humor that's so popular with the kids these days.

The part where it starts to dig at religion comes when, soon after meeting Paul, the Brits decide to pull their RV into an RV park. They are greeted by a young blonde girl, Ruth, who comments on how she'd like to travel someday but has never gone anywhere in her life yet. The conversation is interrupted by a gruff voice calling her back to the office/house for "PRAYERS!"

The next day, Ruth visits the Brits' RV, and she starts to reveal herself as what I like to call a "Strict Creationist" — someone who believes in the literal word of the Bible, that the Earth was created 4,000 years ago in a process that spanned six days, and that there is no possible way there could be "alien life" elsewhere, as we were all created in God's image. Paul, somewhat put out by this, emerges from his hiding place in the bathroom. After fainting (something of a running gag in the film), she awakens and tries to convince herself it was her imagination, until she sees Paul again. She then goes into a fit of hysterics.

Up to this point, it's not so bad. Hollywood loves its stereotypes, especially taken to extremes. An openly gay character will be extremely flamboyant; a character born in the Lone Star State will almost invariably be wearing a cowboy hat and calling every female "Darlin'". It's almost a foregone conclusion that an openly Christian character will end up being extreme fundamentalist.

To stop her fit and from denying his very existence, Paul does this "alien thing" where he gives Ruth all of his knowledge and experience, including memories of his galactic travels.

Here's where it starts to turn south.

Filled with this knowledge, Ruth decides that all of what she believed is wrong, decides there is no God, and she is free to swear and fornicate. She then spends the rest of the movie trying to swear (in a manner not completely unlike Captain Kirk trying to "fit in" to contemporary Earth in Star Trek IV). Paul does make a token effort at trying to settle her down and say his experience doesn't necessarily preclude the existence of God, but his comment is ignored.

Going from one extreme to another isn't completely out of place in a low-brow Hollywood comedy. But the telling scene comes at the end, when, in a very sober moment, Paul says he's sorry for destroying Ruth's faith. Her reply? "You didn't destroy me; you freed me."

That actually made me a little angry. This was not a comedic scene; it was said with all seriousness, like it was one of the morals of the story. Faith is confining, and a foul mouth and spread legs is freedom. The disdain for religion is reinforced when Ruth's father wishes him well by saying "God be with you", and Paul scoffs, "Yeah, whatever."

I know it's just a story, and I should really just relax. And, when it comes down to it, I do see fundamentalism to be rather confining. I believe God has created this whole universe and there are many wonders and possibilities we have yet to even discover; and refusing to acknowledge wonders God has created because you cling to your narrow interpretation of what you know of Him today is akin to wearing blinders. I just object to this assertion that immorality and atheism is automatically superior to religion. These things are never "free", whether you believe in God or not — not believing does not release you from the consequences. And I'm not just talking about "fire and damnation". Promiscuity can lead to unwanted pregnancies, diseases, lack of trust in relationships; foul language can lead to loss of respect.

But, I guess, if you really believe your existence is over and done in 80 years, even these "little" consequences don't mean much, either.…

2011-03-06

Bandwidth for February

We're getting closer. February's data usage clocked in at 213.98GB (196GB according to Comcast's meter). In addition to more and more shows on Netflix being delivered in HD, I did rebuild my laptop because of issues related to attempting to install the Internet Explorer 9 beta. Since I rebuilt and tried the IE9 beta again (to determine if my issues were in fact related to the new browser — they were), I ended up rebuilding twice. This meant downloading drivers twice. In addition to the software I downloaded for installation, I also had to download the games I had purchased on Steam, Games for Windows Live, and Impulse. (They were on a good enough sale for me to purchase them this way, but one of the reasons I dislike digital downloads is I am stuck with a long download process instead of just inserting a disc.)

February's usage may have been a little high due to an unusual circumstance (rebuilding a laptop and re-downloading software), but according to the online meter, I've already used 50GB for the first six days of March. (vnstat on my server is projecting a monthly usage of 244.46GB based on use so far.)

So I burned through a lot of monthly bandwidth. And I still haven't reinstalled everything.

On Comcast's data usage page is the following text:

Your Comcast High-Speed Internet service has a monthly data usage allowance of 250 gigabytes (GB). If you are wondering whether you are at risk of exceeding this 250GB threshold, you should know that the vast majority - around 99% - of Comcast customers use significantly less than 250GB per month.

I have to wonder if that is still true (since this was implemented back in 2008), and how long it will hold. I also wonder how long it'll be before I hit that cap myself. At least I know what to expect if and when that happens, from their FAQ:

What will happen if I exceed 250 GB of data usage in a month?

The vast majority - more than 99% - of our customers will not be impacted by a 250 GB monthly data usage threshold. If you exceed more than 250 GB, you may receive a call from the Customer Security Assurance ("CSA") team to notify you of excessive use. At that time, we will tell you exactly how much data you used. When we call you, we try to help you identify the source of excessive use and ask you to moderate your usage, which the vast majority of our customers do voluntarily. If you exceed 250GB again within six months of the first contact, your service will be subject to termination and you will not be eligible for either residential or commercial internet service for twelve (12) months. We know from experience that most customers curb their usage after our first call. If your account is terminated, after the twelve (12) month period expires, you may resume service by subscribing to a service plan appropriate to your needs.

Our practice for the past several years has been to call only our heaviest data users, and this practice remains the same now that the 250GB data usage threshold is in effect. We may change our practice but will, of course, provide notice to you of any change.

Will Comcast change? Well, there is this noncommittal statement in the FAQ:

Will Comcast ever revisit the 250 GB threshold and raise it in the future as bandwidth intensive activities become more and more popular?

We recognize that the Internet is constantly changing and we are committed to continuous improvement. We will continue to evaluate our policies to ensure we provide the best online experience possible. It seems reasonable to assume that this will change over time, as the Internet and our service offerings evolve, though we have not committed to doing so.

Considering Comcast's actions against Level 3 that are suspiciously harmful to Netflix, I would be surprised if they adjust their policy when most of my data usage does happen to be Netflix.

As much as I am loathe to suggest it, it may be time to look at Qwest's offerings.…

2011-01-18

Holding back features on the web

If I were to say there's a CSS 3 feature that all major browsers support except one, which browser would you guess is lacking?

The answer for today is Mozilla Firefox.

It has come up several times on my current project where we've needed to take a variable amount of information and stuff it into a limited space, where aesthetics demand we truncate the data instead of allowing an overflow or a word wrap. The typical way to do this is with an ellipsis, but at what point one should truncate the message is usually the result of guesswork. Checking to see if a string is over, say, 35 characters and cutting it off if it is may work in most cases; but because in a proportional font, the same number of characters can be different sizes depending on which actual characters are used, any fixed number will result in some data elements appearing too short, and a few appearing too long and wrapping or overflowing anyway.

Enter the text-overflow style. In a fixed div or span, setting style="text-overflow: ellipsis;" will cause the browser to truncate the contents with an ellipsis if, when, and where it is needed.

Except for Firefox. Since CSS 3 is still technically in "draft", the coders behind Firefox have decided not to implement text-overflow, despite it being on the bug list since 2005. Mozilla's own developer forum shows that Firefox is the only browser to not implement this to date.

Oddly enough, this is the second time Firefox has failed me recently (the first being a misbehaving feature that plays havoc with AJAX queries).

I found two solutions on the web. One is to use something called XUL binding. The procedure (described here) involves creating an XML document that describes the requested behavior, and then using a Mozilla-specific CSS directive to bind it to the element. Unfortunately, not only does this require another document, but it may conflict with the text-overflow style such that only one or the other will work, but not both. Also, following the comments in the bug, XUL appears to be going away with Firefox 4, and with text-overflow still not implemented, this workaround will work no longer.

The second solution uses the JavaScript library jQuery. The function (which I found at Devon Govett's blog), when applied to a web element, takes the text, recreates it in a clone of the element, starts truncating text as necessary until it finds text that fits in the element, and replaces the text in that element. It's not terribly efficient as it iteratively tests the text on each element, and if the element can change size you either have to update it manually or tell the script to constantly check the element and recompute; but it does do the job that Mozilla won't. Fortunately, we're already using jQuery, so adding an extension was a trivial task.

I don't know if it's some higher ground they're trying to take by not implementing "draft" features, but the fact remains, as an end user of browsers, to me, they appear to be stubbornly behind the curve.

2011-01-14

ASP.Net, Dynamic Controls, and ViewState, revisited

At my current job, we are encouraged to share tips and ideas with other developers. I thought it could be useful to demonstrate the problem of dynamic controls and ViewState and my solution (posted three years ago here), since it not only is a problem that could come up in our web development, but it provides a useful opportunity to review the page life cycle.

So I grabbed my sample code and opened it in Visual Studio 2010. The good news is, it still works as advertised. However, I wanted to demonstrate the problem along with the solution; so I removed all my "extra" code. I was rather startled to find that the old problem didn't manifest itself. When I typed in data to one control and clicked a button to add another, the first control retained all its data.

It seems that the .Net Framework got some improvements over the years. The first improvement is that it seems ASP.Net is far more consistent in naming controls that are added to the page at run-time. (Part of the original problem was, when a control was loaded on page load vs. later in an event handler, the dynamically-assigned ID would be different.) The second is, if a control is loaded later in the life cycle, it does actually go back to the ViewState and re-load any applicable data. (It used to be very unreliable in this regard.)

I did find that things were not all roses. If you add a bunch of controls and start removing controls from the middle of the list, control data would get lost. Also, if you delete controls in the middle of your list and re-add controls, the controls may get added in the middle of the list instead of the end.

The solution is much easier than it used to be:

  • Create a member variable to hold the list of IDs (or whatever data is required to recreate the control and its ID) — in this example, I'm using private List<string> _childControlIds to just store the IDs, since the control type and location is always the same constant.
  • Create a Page Load event handler that looks like this:
    private void Page_Load(object sender, EventArgs e) {
     if (!IsPostBack) {
      _childControlIds = new List<string>();
      addAField(null).InitializeNewControl(); //Optional - create a new control, and initialize its data
     } else {
      if (ViewState["ControlCount"] as string[] != null) {
       _childControlIds.AddRange((ViewState["ControlCount"]) as string[]);
      }
      foreach (string controlId in _childControlIds) {
       addAField(controlId); //Create an existing control with its already-established ID
      }
     }
    }
  • The addAField method looks like this:
    private CustomChildControl addAField(string fieldId) {
     CustomChildControl cc = (CustomChildControl)LoadControl("CustomChildControl.ascx");
     if (String.IsNullOrEmpty(fieldId)) {
      cc.ID = String.Format("CUST{0}", DateTime.Now.Ticks); //new control; create a unique ID
      _childControlIds.Add(cc.ID);
     } else {
      cc.ID = fieldId; //existing control; reuse ID
     } 
     this.CustomControlsPlaceHolder.Controls.Add(cc);
     cc.DeleteControlClick += new EventHandler(DeleteCustomControl);
     return cc;
    }
    Notes:
    1. It no longer appears to be necessary to add the control before setting its ID — the ViewState manager seems to pick it up just fine either way.
    2. The custom control in my example has its own delete control and fires an event, that this page subscribes to. Your implementation may vary.
  • The DeleteCustomControl method looks like this:
    private void DeleteCustomControl(object sender, EventArgs e) {
     CustomChildControl cc = sender as CustomChildControl;
     if (cc != null) {
      _childControlIds.Remove(cc.ID);
      this.CustomControlsPlaceHolder.Controls.Remove(cc);
     }
    }
  • The method to add a control (in my case, a button on the page) is simply:
    private void AddButton_Click(object sender, EventArgs e) {
     addAField(null).InitializeNewControl(); //Create a new control, and initialize its data
    }
  • And finally, a Page PreRenderComplete event handler (because it's late enough in the page lifecycle; PreRender itself may be sufficient for your needs) that sticks the control ID list in ViewState:
    private void Page_PreRenderComplete(object sender, EventArgs e) {
     ViewState["ControlCount"] = _childControlIds.ToArray();
    }

And that's it. Surprisingly simple.

I don't know at what point this changed (or if I even over-architected the original solution — a distinct possibility). This could be an improvement in .Net 3.5, or it could be something "fixed" in a service pack along the way. The only thing I can say for certain is this much simpler method works quite well in my admittedly simple example.

2011-01-01

2010 Bandwidth

I haven't been posting monthly bandwidth numbers, mostly to distract from the fact that the majority of my posts lately were the monthly bandwidth numbers, and that's just boring. But I haven't stopped keeping track.

2010 proved out what I expected. Since we got rid of paid TV, we have been relying on Netflix for the majority of our video entertainment. The use of this has increased over the year, as we've not only become more comfortable using the service, but the number of offerings of the service has increased as well. Add to this the fact that more videos are available in HD, and it's no wonder that my monthly data usage has only been going up.

There are also other items that account for the increase. A large number of file transfers to support our website design business accounts for some of this. Also, we bought a Blu-ray player that has the capability to stream YouTube videos, of which the kids have taken advantage as a substitute for more traditional Saturday morning cartoons.

The year-over-year view is rather dramatic:

The largest-use month in 2009 was surpassed by 75% of the months in 2010, and the second-highest month of 2009 was exceeded by all of them. Comcast's measurement was consistently lower than mine, although they were only 8% off in December. It was November when, finally, we reached the halfway point of a monthly cap.

The numbers are likely to only go further up. I don't see any change in this trend. Content providers are continuing to innovate and use the bandwidth we have. Netflix's increase in HD offerings is one example. Microsoft recently updated the Xbox to use a higher quality encoding for voice communication, which, although only provides a modest increase in bandwidth, is just another example.

All this still leaves me wondering, when will general use and content innovation use up this arbitrary data cap, and turn the number of "excessive" users from what was once claimed to be "a single percent" into the majority? I also wonder if Comcast's policy of punishing those who go over their monthly number will change before or after that happens — or, more cynically, how much revenue they'll collect from fines before they consider changing their policy.

2010-10-28

Thoughts on document reader software

A document reader program should be unobtrusive. When you get a document on your computer that it is supposed to handle, you should just have to double-click on the document, and your operating system should open the associated program. It should do this quickly, without a lot of fanfare. The document is the focus of the user's attention, not the program. There should be nearly no need to open the reader program on its own, without a document; although, a link to the program in an appropriate folder on the Start Menu isn't uncalled for, should such a need arise (e.g., if the user wants to manually check for updates or change default settings for the program, he shouldn't have to find an irrelevant document to open first).

To these points, I say:

Adobe, quit installing an icon to Acrobat Reader on my desktop, without asking, every time you do an update; get rid of the bloat that causes Reader to take half a minute to open a document (running a service or pre-loading half your program into memory when I haven't even opened a document yet is not an acceptable option); and when I say "disable the splash screen", do not ignore that setting or re-enable it and think I won't notice.

Yes, I'm aware of alternatives, such as Foxit Reader, that aren't nearly as bad; unfortunately, I have to keep Acrobat around for those forms and bills that alternatives aren't able to process.

2010-10-24

IntelliMouse Explorer 1.0 in Windows 7

I have an old IntelliMouse Explorer. It's the original version, wired, but still works great. Alongside my Natural Keyboard Pro, it's an old, functional, comfortable piece of hardware that I refuse to get rid of. The replacements that have come along since often fall short in various ways. And, much like the Natural Keyboard Pro and other strong Microsoft hardware input devices from years gone by, Microsoft's software drivers have stopped supporting them.

There's no real reason for them not to work today. The keyboards haven't changed much, except to add or change the extra control keys sprinkled around the standard 121. Mice, even less so; they have the same X-Y directional input, five buttons, and a scroll wheel they've had for over a decade. But if you install the current version of IntelliType or IntelliPoint, they will refuse to detect your older keyboard and mouse; and even though the operating system will use them just fine for standard functions, all the fancy buttons and the ability to remap them (that used to work on older versions of the software) won't be available.

I came across this blog post on Blogfeld.com that describes in detail how to get a Natural Keyboard Pro to have full functionality in Vista and Windows 7. I followed these instructions earlier this month, and I can verify that they work flawlessly with the current version of IntelliType software (currently version 8). I thought maybe the same technique could be applied to get my old IntelliMouse Explorer to work with IntelliPoint 8 as well.

I won't post the details here — Blogfeld already does an excellent job at describing everything — I'll just indicate what I did to apply his technique to IntelliPoint.

I searched for an old version of IntelliPoint off of Microsoft's download site. You can still download IntelliPoint 5.2 from their site (link as of the time of this post is here, but you can search for "IntelliPoint 5" on download.microsoft.com to find it) and installed it on a Windows XP workstation in order to get the old files.

On the Windows 7 machine, I opened up the point64.inf file (IntelliPoint's version of IntelliType's type64.inf — and yes, I'm using 64-bit; the 32-bit version would naturally be point32.inf), and in the [MsMfg…] section, I added the following string to the block of IDs listed:

%HID\Vid_045E&Pid_001E.DeviceDesc%=HID_Filtr_Inst, HID\Vid_045E&Pid_001E

Further down in the [Strings] section, I added:

HID\VID_045E&PID_001E.DeviceDesc="Microsoft USB IntelliMouse Explorer (IntelliPoint)"

The next step was to modify the IPointDevices.xml file. This one required a little more thought, as IntelliPoint 5 did not have an IPointDevices.xml file to copy from. I noticed, however, that the IntelliMouse Explorer 3, which is supported in IntelliPoint 8, has the exact same configuration as the IntelliMouse Explorer 1. So, I found the <Device> section that describes the IntelliMouse 3, copied it, and pasted it to the end of IPointDevices.xml. I changed the <Name> node to read, simply, "IntelliMouse Explorer", changed the <OemAbbreviation> node to "IME", and changed the value under <HWID Type='PID'> to read "0x001E" (the last four characters of the USB ID, used in the point64.inf file above). I also had to change the ID in the <Device> node itself to something that was not used elsewhere in the file — '10' was good enough.

I followed the rest of the instructions from Blogfeld, and sure enough, it worked great. The configuration screen in IntelliPoint uses the images of the IntelliMouse Explorer 3, and it allows configuration of all five buttons and the scroll wheel, including per-application settings, like any other mouse it "officially" supports.

2010-10-20

Firefox + Ajax + Refresh = Disaster

Usually, when coding a web page that's targeting users of IE and Firefox, the browser that's going to cause the lesser amount of problems is Firefox. So I was genuinely surprised when I came across a bug reported for Firefox only that came down to what I consider the browser misbehaving.

The requirements for our app included a series of dropdown boxes, where the selection a user makes in one dropdown drives the choices that appear in the next one (what's commonly referred to as a "cascading dropdown"). For a nicer user experience, this is typically done with AJAX, so that the request/response that generates the second dropdown upon selection of the first doesn't require an entire page refresh. ASP.Net makes this really easy with the UpdatePanel control. Controls inside of an UpdatePanel can be refreshed without reloading the entire page. It's not as lean as a pure AJAX call could be, since the server reprocesses the whole page, but the coding time is greatly reduced.

Our environment includes a standard master page that includes a ScriptManager component (required for using UpdatePanels) and the following script:


<script language="javascript" type="text/javascript"> 
    function onEndRequest(sender, args) {  
        ajaxPostBackButton.disabled = false;  
        var error = args.get_error();  
        if (error != null) {  
            window.location = "../errorPage.aspx";  
        }  
        var updateProgressPanel = $get("<%=this.UpdateProgressPanel.ClientID %>");  
        updateProgressPanel.className = "HideObject";  
    }  
    function onBeginRequest(sender, args) {  
        var ajaxPostBackButtonId = args.get_postBackElement().id;  
        ajaxPostBackButton = document.getElementById(ajaxPostBackButtonId);  
        ajaxPostBackButton.disabled = true;  
        var updateProgressPanel = $get("<%=this.UpdateProgressPanel.ClientID %>");  
        updateProgressPanel.className = "DisplayProgressLayer";  
    }  
    var ajaxPostBackButton;  
    Sys.WebForms.PageRequestManager.getInstance().add_beginRequest(onBeginRequest);  
    Sys.WebForms.PageRequestManager.getInstance().add_endRequest(onEndRequest);  
</script> 

The script, in essence, binds a couple of functions to the AJAX start and stop methods that do this:

  • On start:
    • Disable the control used to trigger the AJAX call (this helps guard against double-posting)
    • Show a div that contains a "loading" animated gif to let the user know something's happening
  • On end:
    • Enable the control used to trigger the AJAX call
    • Check for an error, and if found, redirect the browser to the standard error page
    • Hide the div with the "loading" gif

Now, if the user makes a selection in the first dropdown, everything runs normally, and the second dropdown appears. If the user then presses F5 to refresh their browser, the browser reloads the page from its initial load state, i.e., with the first dropdown with the initial "Please select…" option selected, and no second dropdown.

At least, that's the way it works in IE. In Firefox, what I was seeing was, the first dropdown was getting selected to the option I had selected before I hit refresh, it was disabled, and there was no second dropdown.

Finding out why was no easy task. With the help of Firebug, I was able to show that, on refresh, neither the onBeginRequest nor the onEndRequest methods were being called, and those were the only places the dropdown's enabled state was being tinkered with. I could only conclude that Firefox itself was setting this state. But why, and how do I stop it?

A couple hours of internet searching on why a dropdown in an UpdatePanel would be disabled failed to yield any useful information. I did find one user complaining about Firefox repopulating form values with prior input on refresh; unfortunately, that user's request for how to get around it was met with a snarky response about how it was a useful feature of Firefox and how the user was mentally deficient for not appreciating it. Sorry, but when you're coding a web application that is trying to control the content of form values and states and react to changes, and the browser breaks all rules and changes those states without raising any events to react to, I'll have to go with the feature being deficient and buggy.

Coming at the problem the next day with a fresh set of search terms, I came across this blog post: Firefox refresh viewstate updatepanel bug hell!!! The post describes a more serious error that can occur with Firefox's mucking about with a form after refresh that got updated with AJAX. The solution, renaming the form's ID on every refresh, seemed a little more of a brute-force hack than I wanted, and he mentions it doesn't work well in a master page scenario anyway (which we're in).

The comments on that post, however, point to an article on developer.mozilla.org that describes the feature in more detail and, more importantly, how to turn it off. By adding the nonstandard attribute autocomplete="off" to the page's <FORM> tag, it suppresses this bothersome behavior and lets the page work as expected.

We're now determining if this action is something that should be done site-wide (add it in the master page's markup), as it could be an uncaught bug on other pages; or if it's something that should be done on a page-by-page basis, by adding this.Page.Form.Attributes["autocomplete"] = "off"; to the prerender event of any affected page.

2010-08-31

Raingutter Regatta racetrack

Regatta side viewAlthough I am not the Cubmaster anymore, I wanted to share this project. In the past, the Raingutter Regatta was a troublesome event. The kids enjoyed it well enough, but for raingutters, all we had were vinyl gutters. It was always a trick to get them elevated, so that the kids could stand and walk beside them, but keep them supported enough so they wouldn't collapse when filled with water. Also, the end caps just snapped onto the gutters, and they never formed a perfect seal; so someone had to be volunteered to keep a bucket filled so the gutters could be refilled as needed. While the kids deal with it well enough, you definitely notice a drop in enthusiasm when they have to wait for the adults to figure out how to shore up a gutter and refill it with water every couple races.

Last summer, I was the Cubmaster, and I got to the church early to start setting up the gutters. I thought I had a bit of an advantage over years past in that we had a new building that had a pavilion with picnic tables. The tables, I figured, would support the length of the gutters and keep them from folding. All I needed to do was support the sides.

As I was filling the gutters with water and trying to figure things out, however, I was blindsided by nothing short of a miracle. Our newly-called Webelos den leader had taken it upon himself to build the project you see in the picture above. (Click the picture to see a couple more images — unfortunately taken from my outdated cell phone.) He pulled up in his truck and asked me to help him unload this large boat from the back. He said he didn't want to say anything before, because he wasn't sure he could finish it in time. Indeed, he had just put some of the finishing touches on it that afternoon.

It was, to say the least, amazing. A length of PVC pipe, cut in half, formed the tracks, which were wide enough to accommodate the hulls of the boat kits we were using (which were simple styrofoam blocks — much lower tech than the Pinewood Derby cars, but much easier for the boys to cut and form on their own). The half-pipes were laid in the top of the wooden form of a large boat. (Last year, the boat did not have the sail; that was something he added for this year.) A drain hole was drilled in the bottom of one end of each track, with a standard rubber drain stopper plugging it up, so the tracks could be easily drained at the end of the night.

We had some minor problems with leaks — because he caulked it earlier that day, it didn't have time to completely dry and seal — but it wasn't anything we weren't used to. There was no worrying about gutters buckling or collapsing, and the legs were sturdy enough to keep it from going anywhere when it got bumped.

It certainly made my day. And the boys', too. That event was easily one of the most successful we had all year.

2010-08-25

I found Microsoft Phone!

Following up to this post, where I ponder what happened to a certain Microsoft software product that seemed way ahead of its time hit the market and disappeared with almost no fanfare.

At my new job, I just got my phone configured. It's an IP phone that plugs into an ethernet jack (and apparently draws power from that jack as well, as there is no other power cord), and once it connected and downloaded all its necessary updates, the IT guy walked me through some of the features.

He directed me to set up my voicemail account. He pressed the voicemail button, and a friendly-sounding, female, synthesized voice announced, "Welcome to Microsoft Exchange." He then gave me a quick overview of the features available, which included the ability to access my email from my phone.

It would appear that Microsoft Phone grew up, moved out of the house, and got a job in the corporate world.

It certainly has come a long way. One of the "cool" features he demonstrated was the voice-activated directory. He pushed "Directory", and the voice prompt asked for the name of the person to call. In his moderate southeast Asian accent, and in a relatively soft voice, he spoke the name of the coworker in a neighboring cubicle. Within a second, the computer repeated the name (which it got exactly right on the first try), asked for confirmation, and then proceeded to dial.

Later, when I set up my voicemail account (setting my own PIN, greeting message, etc.), I experimented a bit. One of the options presented to me was "Calendar". I chose that option, specified "Today" (at which point the computer told me I could just say "Calendar for today" at the main menu to get straight to this point), and the computer proceeded to read to me my appointments for the day.

The voice synthesis was very clear. While it could never compare to seeing the information on the screen where you could glance at any piece of information at will instead of waiting for it to be read to you, it was no different than, say, the difference between reading a book and listening to an audiobook version of the same. All of the commands were done by voice. At no time did I have to repeat a command that the computer didn't hear, nor did it misinterpret any command I gave it; and I didn't speak any louder or exaggerate my pronounciation when I gave my commands. Although, the whole interchange was a little slow, considering I had to wait at the end of each operation for the computer to give me a list of all the things I was "allowed" to say. (I would imagine that accuracy goes way up when the number of possible inputs is constrained.) It was still far easier than the laughable experience I had trying to use voice controls in Windows Vista.

I am a little disappointed that this product does not appear to be available for the home anymore. More than once, I've been away from home and wished I could have easy access to an email when all I had was my phone. However, I think they're probaby dead on in their target market. It seems like people are using their cell phones more than their home computers as their address books and calendars, so the need to "phone home" just isn't there. Those who do access email on the road tend to pay for a data plan for their cell phones, so they can scan through email visually instead of having it read to them linearly — a huge advantage on a home email account that may get email from hundreds of sources, most of it spam that gets through filters. On the flip side, business email accounts tend to be more business-focused (in my experience; YMMV), so hearing unread email can be less of an exercise in "sifting through junk". Also, providing a phone access path to check information means that all employees can access their personal accounts without providing or provisioning VPN accounts, company cell phones, data plans, etc.

2010-08-16

In B4 Unemployment

When I was "downsized", I got some severance pay in addition to my unused vacation time. Because of this, my unemployment benefits were due to not start until a few weeks after my actual termination date.

Today, I started my new job.

I had a few interviews that came down to a couple solid possibilities. Both were very good jobs, and I thought it would come down to a choice between them. While I was waiting to hear a final decision, I spent some time in prayer that I would be guided to the right job. Sure enough, I only got an offer from one company; the other told me my qualifications were perfect, but they decided to go with another candidate who they just "felt was a better fit".

I supposed I should be used to this by now, but I'm still impressed, and thankful, with how Heavenly Father provides a very clear path to what He has provided for me. The path may not always be the smoothest or the shortest, but it's always gotten me where I've needed to be.

2010-08-13

Friday the Thirteeneth Strikes Again

I got a notice in the mail a while back about a recall Toyota is doing on vehicles, including my Prius, for a possibility of floor mats jamming accelerator pedals. Now, I'm more inclined to believe this is more media hype than any actual problem, but as long as it's free, I have free time, and especially since they were promising a free floor vacuuming with the service, I might as well take the car in. So I made an appointment.

I left for the dealer this morning, and my wife promised to leave shortly thereafter in the minivan to pick me up. On my way in, though, I got a call from an upset wife. The van wouldn't start. The battery didn't have enough of a charge to turn it over. This was a new battery, one we bought earlier this year. It should not be dead. Unfortunately, it didn't agree.

Now, fortunately, we have a plug-in battery charger that we got many years ago, so she began charging it while I dropped off my car. Since she was going to be leaving a lot later, I walked over to our next stop, the FedEx center, to pick up a package from my former employer that had apparently already forgotten my correct address. A healthy dose of exercise and still a ten minute wait, and my wife drove by to pick me up.

We were going to get family pictures today, so we went to the shopping center. My wife wanted to stop by the shoe store to get new shoes for a couple of the kids first, so we pulled up there. The baby was still asleep, so I was going to wait in the van. She rolled down the windows and shut off the minivan. While we were unloading, though, the baby woke up. I went to start the car to roll the windows up, but all I got in response was "click click click click click". Despite the 40-minute round trip to pick me up from FedEx and come back to the shopping center, the battery had no charge. And now, we were stranded on the wrong side of the shopping center.

We hiked across the shopping center to get to our picture appointment. Fortunately, it was still morning, so it was not too warm yet; but it was still a decent hike with four kids, slightly uphill, passing a distance that included a full-size Wal-Mart and a Sam's Club (these are not small stores).

Once we got to our picture appointment (on time, even), we started trying to call the phone numbers of anyone we had in our cell phones to see who might be home and who might be able to shuttle one of us home to get jumper cables and to my mother's to pick up her truck.

Getting pictures done, we then went to get lunch at one of the nearby restaurants. While we were there, one of our church friends showed up to drive me to my house and my mother's. At my house, I got my mother's house keys, and then we went to my mother's house. Problem, the house key does not work on the storm door on her front door. It would've let me in from the garage, but I didn't have her garage door opener. We had one in the minivan, but she didn't pick me up from the minivan; she picked me up from the restaurant. (I'm not sure I would've thought to grab the garage door opener anyway.)

Fortunately, she was willing to let us try jump-starting off of her truck. We just had to go back to my house to pick up the jumper cables I forgot to grab. (I was going to pick them up after I got my mother's truck.) And, fortunately more, we weren't inconveniencing her much; through all the driving around, her daughters in the car were lulled into a nap they don't usually get, so she was getting a nice bonus.

Jump-starting the minivan was fairly uneventful, except for the usual problems of trying to get the clamps secured on the terminals and finding a good ground. Rant time: Why don't they make battery terminals longer, so that jumper cables aren't constantly in danger of slipping off the things? And, why don't they make an obvious grounding post for clamping the negative cable? The first place I clamped it was secure, but obviously not a decent ground, as it completely failed to start the car; as soon as I clamped it to a less secure but more obviously metal bolt, the minivan started right up on the first try. Rant over.

We thanked our friend profusely for the ride and vowed not to turn off the minivan for as long as we could. My wife followed me to my mother's, so we could grab her truck. (If we needed to leave the minivan behind while it got its battery replaced, we would need the extra vehicle to get home.) With the garage door opener in the minivan, this was a much easier trip.

We made a long drive to Sears, where we bought the battery, so they could check it out and replace it. Long story short, they were able to replace it while we wandered the store, so we were able to take the minivan home. They said it was in fact a bad battery, and everything else checked out ok. Unfortunately, this not only made the truck unnecessary, having the extra vehicle meant we were unable to simply stop by and pick up my car, which was already done and will now require another extra trip out to go get.

I'm not usually one to consider the 13th of a month falling on a Friday as being anything but a statistical curiosity, but it certainly seemed unlucky today.

Still, it all worked out in the end, and the worst of it was just inconvenience. Annoying, exhausting inconvenience, but just inconvenience.